Skip to content

Commit a81a9de

Browse files
authored
Merge pull request #9 from Anshukumar123975/main
Add BDD Tests for Scalar and Map Validations
2 parents ca1c70f + c8db5db commit a81a9de

File tree

8 files changed

+215
-3
lines changed

8 files changed

+215
-3
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ jobs:
2424
- name: Install Dependencies
2525
run: npm install
2626

27-
- name: Run Tests
27+
- name: Run Unit Tests
2828
run: npm test
29+

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
"version": "1.0.0",
44
"description": "",
55
"main": "index.js",
6+
"type": "module",
67
"scripts": {
7-
"test": "npx vitest semantic/specifications/"
8+
"test": "npx vitest semantic/specifications/",
9+
"bdd": "cucumber-js --loader ts-node/esm --import semantic/features/support/**/*.ts semantic/features"
810
},
911
"keywords": [],
1012
"author": "",
@@ -13,6 +15,11 @@
1315
"@accordproject/concerto-core": "^3.21.0",
1416
"fs": "^0.0.1-security",
1517
"path": "^0.12.7",
16-
"vitest": "^3.2.2"
18+
"vitest": "^3.2.1"
19+
},
20+
"devDependencies": {
21+
"@cucumber/cucumber": "^11.3.0",
22+
"ts-node": "^10.9.2",
23+
"typescript": "^5.8.3"
1724
}
1825
}

semantic/features/maps.feature

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
Feature: Semantic Validation of CTO Map Specification
2+
3+
Scenario: Valid map key type should pass
4+
Given I load the following models:
5+
| model_file | alias |
6+
| maps/models/MAP_KEY_TYPE_001/map_key_type_001_valid_key_type.cto | main |
7+
When I validate the models
8+
Then no error should be thrown
9+
10+
Scenario: Invalid map key type should throw error
11+
Given I load the following models:
12+
| model_file | alias |
13+
| maps/models/MAP_KEY_TYPE_001/map_key_type_001_invalid_key_type.cto | main |
14+
Then an error should be thrown with message "Expected \"DateTime\", \"String\""
15+
16+
Scenario: Valid map value type should pass
17+
Given I load the following models:
18+
| model_file | alias |
19+
| maps/models/MAP_VALUE_TYPE_001/map_value_type_001_existing_value_type.cto | main |
20+
When I validate the models
21+
Then no error should be thrown
22+
23+
Scenario: Non-existent map value type should throw error
24+
Given I load the following models:
25+
| model_file | alias |
26+
| maps/models/MAP_VALUE_TYPE_001/map_value_type_001_type_not_exist.cto | main |
27+
When I validate the models
28+
Then an error should be thrown with message "Cannot read properties of null"
29+
30+
Scenario: Duplicate map names should throw error
31+
Given I load the following models:
32+
| model_file | alias |
33+
| maps/models/DECLARATION_001/declaration_001_duplicate_map_name.cto | main |
34+
When I validate the models
35+
Then an error should be thrown with message "Duplicate class name"
36+
37+
Scenario: Unique map names should pass
38+
Given I load the following models:
39+
| model_file | alias |
40+
| maps/models/DECLARATION_001/declaration_001_unique_map_name.cto | main |
41+
When I validate the models
42+
Then no error should be thrown

semantic/features/scalars.feature

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
Feature: Semantic Validation of CTO Scalars specification
2+
3+
Scenario: should pass for valid number bounds
4+
Given I load the following models:
5+
| model_file |alias|
6+
| scalars/models/NUMBER_VALIDATOR_001/number_validator_001_valid_bounds.cto |main|
7+
Then no error should be thrown
8+
9+
Scenario: should throw for no number bounds
10+
Given I load the following models:
11+
| model_file |alias|
12+
| scalars/models/NUMBER_VALIDATOR_001/number_validator_001_no_bounds.cto |main|
13+
Then an error should be thrown with message "/Expected \"/"
14+
15+
Scenario: should pass for valid number range
16+
Given I load the following models:
17+
| model_file |alias|
18+
| scalars/models/NUMBER_VALIDATOR_002/number_validator_002_valid_range.cto |main|
19+
Then no error should be thrown
20+
21+
Scenario: should throw when lower > upper in number bounds
22+
Given I load the following models:
23+
| model_file |alias|
24+
| scalars/models/NUMBER_VALIDATOR_002/number_validator_002_lower_greater_than_upper.cto |main|
25+
Then an error should be thrown with message "/Lower bound must be less than or equal to upper bound/"
26+
27+
Scenario: should pass for valid string length bounds
28+
Given I load the following models:
29+
| model_file |alias|
30+
| scalars/models/STRING_VALIDATOR_001/string_validator_001_valid_bounds.cto |main|
31+
Then no error should be thrown
32+
33+
Scenario: should throw for empty string length bounds
34+
Given I load the following models:
35+
| model_file |alias|
36+
| scalars/models/STRING_VALIDATOR_001/string_validator_001_no_bounds.cto |main|
37+
Then an error should be thrown with message "/Expected \"/"
38+
39+
Scenario: should pass for positive string length bounds
40+
Given I load the following models:
41+
| model_file |alias|
42+
| scalars/models/STRING_VALIDATOR_002/string_validator_002_positive_bounds.cto |main|
43+
Then no error should be thrown
44+
45+
Scenario: should throw for negative bounds in string length
46+
Given I load the following models:
47+
| model_file |alias|
48+
| scalars/models/STRING_VALIDATOR_002/string_validator_002_negative_bounds.cto |main|
49+
Then an error should be thrown with message "/minLength and-or maxLength must be positive integers/"
50+
51+
Scenario: should pass when lower < upper in string length
52+
Given I load the following models:
53+
| model_file |alias|
54+
| scalars/models/STRING_VALIDATOR_003/string_validator_003_valid_order.cto |main|
55+
Then no error should be thrown
56+
57+
Scenario: should throw when lower > upper in string length
58+
Given I load the following models:
59+
| model_file |alias|
60+
| scalars/models/STRING_VALIDATOR_003/string_validator_003_lower_greater_than_upper.cto |main|
61+
Then an error should be thrown with message "/minLength must be less than or equal to maxLength/"
62+
63+
Scenario: should throw for invalid regex
64+
Given I load the following models:
65+
| model_file |alias|
66+
| scalars/models/STRING_VALIDATOR_004/string_validator_004_valid_regex.cto |main|
67+
Then no error should be thrown
68+
69+
Scenario: should pass for valid regex pattern
70+
Given I load the following models:
71+
| model_file |alias|
72+
| scalars/models/STRING_VALIDATOR_004/string_validator_004_invalid_regex.cto |main|
73+
Then an error should be thrown with message "/Expected comment/"

semantic/features/support/steps.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Given, When, Then } from '@cucumber/cucumber';
2+
import { loadCTO } from './utils/loadCTO.ts';
3+
import assert from 'assert';
4+
5+
Given('I load the following models:', function (dataTable) {
6+
for (const row of dataTable.hashes()) {
7+
const modelContent = loadCTO(row.model_file);
8+
try {
9+
this.modelManager.addCTOModel(modelContent, row.model_file, true);
10+
} catch (err) {
11+
this.error = err as Error;
12+
break;
13+
}
14+
}
15+
});
16+
17+
When('I validate the models', function () {
18+
try {
19+
this.modelManager.validateModelFiles();
20+
this.error = null;
21+
} catch (err) {
22+
this.error = err as Error;
23+
}
24+
});
25+
26+
Then('an error should be thrown with message {string}', function (expected: string) {
27+
assert(this.error, 'Expected an error to be thrown, but none was');
28+
29+
let isMatch: boolean;
30+
const match = expected.match(/^\/(.+)\/([gimsuy]*)?$/);
31+
32+
if (match) {
33+
const pattern = new RegExp(match[1], match[2]);
34+
isMatch = pattern.test(this.error.message);
35+
assert(
36+
isMatch,
37+
`Expected error to match regex ${pattern}, but got: "${this.error.message}"`
38+
);
39+
} else {
40+
isMatch = this.error.message.includes(expected);
41+
assert(
42+
isMatch,
43+
`Expected error to include "${expected}", but got: "${this.error.message}"`
44+
);
45+
}
46+
});
47+
48+
49+
Then('no error should be thrown', function () {
50+
assert.strictEqual(this.error, null, `Expected no error, but got: ${this.error?.message}`);
51+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
4+
export const loadCTO = (relativePath: string): string => {
5+
const basePath = path.resolve('semantic/specifications/');
6+
return fs.readFileSync(path.join(basePath, relativePath), 'utf8');
7+
};

semantic/features/support/world.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { setWorldConstructor, IWorldOptions } from '@cucumber/cucumber';
2+
import { ModelManager } from '@accordproject/concerto-core';
3+
4+
export class CustomWorld {
5+
public modelManager: ModelManager;
6+
public error: Error | null;
7+
8+
constructor(options: IWorldOptions) {
9+
this.modelManager = new ModelManager({ enableMapType: true });
10+
this.error = null;
11+
}
12+
}
13+
14+
setWorldConstructor(CustomWorld);

tsconfig.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"module": "Node16",
5+
"moduleResolution": "Node16",
6+
"allowImportingTsExtensions": true,
7+
"noEmit": true,
8+
"strict": true,
9+
"esModuleInterop": true,
10+
"skipLibCheck": true,
11+
"forceConsistentCasingInFileNames": true,
12+
"resolveJsonModule": true,
13+
"isolatedModules": true
14+
},
15+
"include": ["semantic/features/**/*.ts"],
16+
"exclude": ["node_modules"]
17+
}

0 commit comments

Comments
 (0)