Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Run Conformance Tests

on:
push:
branches:
- '**'
pull_request:
branches:
- '**'

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'

- name: Install Dependencies
run: npm install

- name: Run Tests
run: npm test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
package-lock.json
18 changes: 18 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "concerto-conformance",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"No tests defined. Skipping...\" && exit 0"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@accordproject/concerto-core": "^3.21.0",
"fs": "^0.0.1-security",
"path": "^0.12.7",
"vitest": "^3.2.2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace org.example.invalid

concept SampleConcept {
o String name
o String $class
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace org.example.valid

concept SampleConcept {
o String name
o String category
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace org.example.invalid

concept SampleConcept {
o Integer $class
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace org.example.valid

concept SampleConcept {
o String name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace org.example.invalid

concept SampleConcept {
o String name
}

concept SampleConcept {
o String description
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace org.example.valid

concept SampleConceptA {
o String name
}

concept SampleConceptB {
o String description
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace org.example.valid

concept BaseConcept {
o String baseProperty
}

concept DerivedConcept extends BaseConcept {
o String detail
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace org.example.invalid

concept DerivedConcept extends NonExistentConcept {
o String detail
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace org.example.invalid

concept Entity identified by id {
o Integer id
o String name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace org.example.scalarid

scalar CustomString extends String

concept Book identified by isbn {
o CustomString isbn
o String title
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace org.example.valid

concept Entity identified by id {
o String id
o String name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace org.example.invalid

concept Product identified by productId {
o String productId optional
o String name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace org.example.valid

concept Product identified by productId {
o String productId
o String name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace org.example.invalid

concept BaseConcept {
o String id
}

concept SubConcept extends BaseConcept identified by subId {
o String subId
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace org.example.valid

concept BaseConcept identified by id {
o String id
}

concept SubConcept extends BaseConcept {
o String subId
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace org.example.invalid

concept BaseConcept {
o String name
}

concept SubConcept extends BaseConcept {
o String name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace org.example.valid

concept BaseConcept {
o String name
}

concept SubConcept extends BaseConcept {
o String description
}
172 changes: 172 additions & 0 deletions semantic/specifications/concepts/tests/concepts.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { describe, it, expect } from 'vitest';
import fs from 'fs';
import path from 'path';
import { ModelManager } from '@accordproject/concerto-core';

const loadCTO = (relativePath) => {
return fs.readFileSync(path.resolve(relativePath), 'utf8');
};

describe('Semantic Validation - CLASS_DECLARATION_001 - system property name', () => {
it('should throw semantic error for property name using system-reserved name', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_001/class_declaration_001_system_property_name.cto');
const modelManager = new ModelManager();
expect(() =>
modelManager.addCTOModel(cto, 'class_declaration_001_system_property_name.cto', true)
).toThrow(/Invalid field name '\$class'/);
});

it('should pass for valid property names not using system-reserved names', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_001/class_declaration_001_valid_property_name.cto');
const modelManager = new ModelManager();
expect(() =>
modelManager.addCTOModel(cto, 'class_declaration_001_valid_property_name.cto', true)
).not.toThrow();
});
});

describe('Semantic Validation - CLASS_DECLARATION_002 - invalid $class type', () => {
it('should throw semantic error for $class property with invalid type', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_002/class_declaration_002_invalid_$class_type.cto');
const modelManager = new ModelManager();
expect(() =>
modelManager.addCTOModel(cto, 'class_declaration_002_invalid_$class_type.cto', true)
).toThrow(/Invalid field name '\$class'/);
});

it('should pass for valid models without $class declared explicitly', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_002/class_declaration_002_valid_$class_type.cto');
const modelManager = new ModelManager();
expect(() =>
modelManager.addCTOModel(cto, 'class_declaration_002_valid_$class_type.cto', true)
).not.toThrow();
});
});

describe('Semantic Validation - CLASS_DECLARATION_003 - duplicate class names', () => {
it('should throw semantic error for duplicate concept declarations in the same file', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_003/class_declaration_003_duplicate_class_name.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_003_duplicate_class_name.cto', true)
expect(() =>
modelManager.validateModelFiles()
).toThrow(/Duplicate class name/);
});

it('should pass for uniquely named concept declarations', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_003/class_declaration_003_unique_class_name.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_003_unique_class_name.cto', true)
expect(() =>
modelManager.validateModelFiles()
).not.toThrow();
});
});

describe('Semantic Validation - CLASS_DECLARATION_004 - super type existence', () => {
it('should throw semantic error when the super type does not exist', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_004/class_declaration_004_supertype_not_exist.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_004_supertype_not_exist.cto', true);
expect(() =>
modelManager.validateModelFiles()
).toThrow(/Could not find super type/);
});

it('should pass when the super type is correctly declared', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_004/class_declaration_004_supertype_exist.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_004_supertype_exist.cto', true);
expect(() =>
modelManager.validateModelFiles()
).not.toThrow();
});
});

describe('Semantic Validation - CLASS_DECLARATION_005 - identifying field must be a String or String scalar', () => {
it('should throw semantic error when identifier field is not of type String or String scalar', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_005/class_declaration_005_invalid_identifier_type.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_005_invalid_identifier_type.cto', true);
expect(() =>
modelManager.validateModelFiles()
).toThrow(/Class "Entity" is identified by field "id", but the type of the field is not "String"/);
});

it('should pass when identifier is of type String', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_005/class_declaration_005_valid_identifier_type_string.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_005_valid_identifier_type_string.cto', true);
expect(() =>
modelManager.validateModelFiles()
).not.toThrow();
});

it('should pass when identifier is a String-based scalar', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_005/class_declaration_005_valid_identifier_type_scalar.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_005_valid_identifier_type_scalar.cto', true);
expect(() =>
modelManager.validateModelFiles()
).not.toThrow();
});
});

describe('Semantic Validation - CLASS_DECLARATION_006 - identifying fields cannot be optional', () => {
it('should throw semantic error when identifier is optional', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_006/class_declaration_006_optional_identifier.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_006_optional_identifier.cto', true);
expect(() =>
modelManager.validateModelFiles()
).toThrow(/Identifying fields cannot be optional/);
});

it('should pass when identifier is required', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_006/class_declaration_006_required_identifier.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_006_required_identifier.cto', true);
expect(() =>
modelManager.validateModelFiles()
).not.toThrow();
});
});

describe('Semantic Validation - CLASS_DECLARATION_007 - supertype must also be system identified', () => {
it('should throw semantic error when supertype is not system identified', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_007/class_declaration_007_supertype_not_system_identified.cto');
const modelManager = new ModelManager();
expect(() =>
modelManager.addCTOModel(cto, 'class_declaration_007_supertype_not_system_identified.cto', true)
).toThrow(/Expected "\{", comment, end of line, or whitespace but "i" found/);
});

it('should pass when both declaration and supertype are system identified', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_007/class_declaration_007_supertype_system_identified.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_007_supertype_system_identified.cto', true);
expect(() =>
modelManager.validateModelFiles()
).not.toThrow();
});
});

describe('Semantic Validation - CLASS_DECLARATION_008 - property must not duplicate super type', () => {
it('should throw semantic error when a property is duplicated from supertype', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_008/class_declaration_008_duplicate_property_from_super.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_008_duplicate_property_from_super.cto', true);
expect(() =>
modelManager.validateModelFiles()
).toThrow(/Class .* has more than one field named .*/);
});

it('should pass when property names are unique across inheritance hierarchy', () => {
const cto = loadCTO('semantic/specifications/concepts/models/CLASS_DECLARATION_008/class_declaration_008_unique_property_from_super.cto');
const modelManager = new ModelManager();
modelManager.addCTOModel(cto, 'class_declaration_008_unique_property_from_super.cto', true);
expect(() =>
modelManager.validateModelFiles()
).not.toThrow();
});
});
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.