Skip to content
Merged
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
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
# concerto-conformance
# Concerto Conformance Test Suite

## Desription
The Concerto Conformance Test Suite provides a standardized, automated testing suite to validate models and semantic behavior across Accord Project's Concerto implementation.

## Overview
This repository includes:
1. A set of semantic validation rules
2. Comprehensive valid and invalid model examples
3. Tests written using Cucumber, offering behavior-driven, human-readable test definitions
4. Support for both JavaScript and C# runtimes
The suite specifically tests core components of the Concerto ecosystem: the `ModelFile` and `ModelManager` classes from `@accordproject/concerto-core`

## Getting started
1. Install dependencies:
`npm install`
2. Run the Javascript test suite:
`npm test`
3. Run tests for C#:
`npm run test:csharp`

## Interactive CLI:
You can also use the built-in CLI for a guided setup:
`npm start`
The CLI allows you to provide custom ModelManager, Parser, or ModelFile sources for testing.

## Future Roadmap
Copy link
Collaborator

@ekarademir ekarademir Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can add those to the project board. You don't need to add them here unless you don't have time to add these to the repository before GSoC finishes. But keeping them is fine as well. Just keep in mind to have them up to date.

Enabling straightforward integration with CI/CD pipelines, so projects like Concerto itself can:
1. Automatically run conformance tests on every push
2. Detect semantic rule violations or model-breaking changes early
3. Maintain consistent validation standards across development workflows
65 changes: 65 additions & 0 deletions cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import inquirer from 'inquirer';
import fs from 'fs';
import { execSync } from 'child_process';

const defaults = {
ModelManager: '@accordproject/concerto-core',
Parser: '@accordproject/concerto-cto',
ModelFile: '@accordproject/concerto-core'
};

async function run() {
const { language } = await inquirer.prompt({
type: 'list',
name: 'language',
message: 'Choose language:',
choices: ['Javascript', 'C#']
});

const imports = {};

for (const comp of Object.keys(defaults)) {
const { include } = await inquirer.prompt({
type: 'confirm',
name: 'include',
message: `Do you want to test your own ${comp}?`
});

if (include) {
const { path: userPath } = await inquirer.prompt({
type: 'input',
name: 'path',
message: `Enter import path for ${comp} (default: ${defaults[comp]}):`
});

const finalPath = userPath.trim() || defaults[comp];
imports[comp] = `import { ${comp} } from '${finalPath}';`;

} else {
imports[comp] = `import { ${comp} } from '${defaults[comp]}';`;
}
}

const templatePath = `semantic/features/support/templates/${language}`;
const outputPath = `semantic/features/support/${language}`;

const worldTemplate = fs.readFileSync(`${templatePath}/world.template.ts`, 'utf-8');
const worldOutput = worldTemplate.replace('{{MODELMANAGER_IMPORT}}', imports.ModelManager);
fs.writeFileSync(`${outputPath}/world.ts`, worldOutput);

const stepsTemplate = fs.readFileSync(`${templatePath}/steps.template.ts`, 'utf-8');
const stepsOutput = stepsTemplate
.replace('{{PARSER_IMPORT}}', imports.Parser)
.replace('{{MODELFILE_IMPORT}}', imports.ModelFile);
fs.writeFileSync(`${outputPath}/steps.ts`, stepsOutput);

console.log("Setup ready. Running tests...");

if (language === 'Javascript') {
execSync('npm test', { stdio: 'inherit' });
} else {
execSync('npm run test:csharp', { stdio: 'inherit' });
}
}

run();
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
"main": "index.js",
"type": "module",
"scripts": {
"test": "cucumber-js \"semantic/features/**/*.feature\" --loader ts-node/esm --import \"semantic/features/support/**/*.ts\" --tags \"not @skip\"",
"test:csharp": "dotnet test semantic/features/support/C#/Semantic.Support.CSharp/Semantic.Support.CSharp.csproj"
"test": "cucumber-js \"semantic/features/**/*.feature\" --loader ts-node/esm --import \"semantic/features/support/Javascript/*.ts\" --tags \"not @skip\"",
"test:csharp": "dotnet test semantic/features/support/C#/Semantic.Support.CSharp/Semantic.Support.CSharp.csproj",
"start": "node cli.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@accordproject/concerto-core": "^3.21.0",
"@accordproject/concerto-cto": "^3.22.0",
"child_process": "^1.0.2",
"fs": "^0.0.1-security",
"inquirer": "^12.6.3",
"path": "^0.12.7",
"vitest": "^3.2.1"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
[assembly: System.Reflection.AssemblyCompanyAttribute("Semantic.Support.CSharp")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+63a012d9040a0114719eb56b8d735e07fff7bb4a")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+069d3994476d298ecb67f9eee824952d7af16260")]
[assembly: System.Reflection.AssemblyProductAttribute("Semantic.Support.CSharp")]
[assembly: System.Reflection.AssemblyTitleAttribute("Semantic.Support.CSharp")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ac751e616cdb4178e8e1b0c3c1233dc4b2b6a691fe113800adc728d7d8146b29
728d0b1b0d333a3b88a57dc69ea86f43dc0b627052d6c700db51c82470e30ee7
7 changes: 2 additions & 5 deletions semantic/features/support/Javascript/steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Given('I load the following models:', function (dataTable) {
for (const row of dataTable.hashes()) {
const modelContent = loadCTO(row.model_file);
try {
// this.modelManager.addCTOModel(modelContent, row.model_file, true);
const ast = Parser.parse(modelContent, row.model_file);
const modelFile = new ModelFile(this.modelManager, ast, modelContent, row.model_file);
this.modelManager.addModelFile(modelFile, null, modelFile.getName(), true);
Expand All @@ -19,7 +18,6 @@ Given('I load the following models:', function (dataTable) {
}
});


When('I validate the models', function () {
try {
this.modelManager.validateModelFiles();
Expand All @@ -31,7 +29,7 @@ When('I validate the models', function () {

Then('an error should be thrown with message {string}', function (expected: string) {
assert(this.error, 'Expected an error to be thrown, but none was');

let isMatch: boolean;
const match = expected.match(/^\/(.+)\/([gimsuy]*)?$/);

Expand All @@ -51,7 +49,6 @@ Then('an error should be thrown with message {string}', function (expected: stri
}
});


Then('no error should be thrown', function () {
assert.strictEqual(this.error, null, `Expected no error, but got: ${this.error?.message}`);
});
});
5 changes: 3 additions & 2 deletions semantic/features/support/Javascript/world.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { setWorldConstructor, IWorldOptions } from '@cucumber/cucumber';
import { ModelManager } from '@accordproject/concerto-core';

import { setWorldConstructor, IWorldOptions } from '@cucumber/cucumber';

export class CustomWorld {
public modelManager: ModelManager;
public error: Error | null;
Expand All @@ -11,4 +12,4 @@ export class CustomWorld {
}
}

setWorldConstructor(CustomWorld);
setWorldConstructor(CustomWorld);
54 changes: 54 additions & 0 deletions semantic/features/support/templates/Javascript/steps.template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Given, When, Then } from '@cucumber/cucumber';
import { loadCTO } from './utils/loadCTO.ts';
{{PARSER_IMPORT}}
{{MODELFILE_IMPORT}}
import assert from 'assert';

Given('I load the following models:', function (dataTable) {
for (const row of dataTable.hashes()) {
const modelContent = loadCTO(row.model_file);
try {
const ast = Parser.parse(modelContent, row.model_file);
const modelFile = new ModelFile(this.modelManager, ast, modelContent, row.model_file);
this.modelManager.addModelFile(modelFile, null, modelFile.getName(), true);
} catch (err) {
this.error = err as Error;
break;
}
}
});

When('I validate the models', function () {
try {
this.modelManager.validateModelFiles();
this.error = null;
} catch (err) {
this.error = err as Error;
}
});

Then('an error should be thrown with message {string}', function (expected: string) {
assert(this.error, 'Expected an error to be thrown, but none was');

let isMatch: boolean;
const match = expected.match(/^\/(.+)\/([gimsuy]*)?$/);

if (match) {
const pattern = new RegExp(match[1], match[2]);
isMatch = pattern.test(this.error.message);
assert(
isMatch,
`Expected error to match regex ${pattern}, but got: "${this.error.message}"`
);
} else {
isMatch = this.error.message.includes(expected);
assert(
isMatch,
`Expected error to include "${expected}", but got: "${this.error.message}"`
);
}
});

Then('no error should be thrown', function () {
assert.strictEqual(this.error, null, `Expected no error, but got: ${this.error?.message}`);
});
15 changes: 15 additions & 0 deletions semantic/features/support/templates/Javascript/world.template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{{MODELMANAGER_IMPORT}}

import { setWorldConstructor, IWorldOptions } from '@cucumber/cucumber';

export class CustomWorld {
public modelManager: ModelManager;
public error: Error | null;

constructor(options: IWorldOptions) {
this.modelManager = new ModelManager({ enableMapType: true });
this.error = null;
}
}

setWorldConstructor(CustomWorld);
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"resolveJsonModule": true,
"isolatedModules": true
},
"include": ["semantic/features/**/*.ts"],
"exclude": ["node_modules"]
"exclude": ["node_modules","semantic/features/support/Javascript/**/*.template.ts"],
"include": ["semantic/features/support/Javascript/*.ts", "semantic/features/support/templates/Javascript/world.template.ts", "semantic/features/support/templates/Javascript/steps.template.ts"]
}