From 0a438c95c41381e212f5cf2efd96e6d88a38398d Mon Sep 17 00:00:00 2001 From: Dan Selman Date: Sat, 2 Sep 2023 09:16:52 +0100 Subject: [PATCH 1/2] feat(analysis) scalar change analysis Signed-off-by: Dan Selman --- .../concerto-analysis/src/compare-config.ts | 6 +- .../concerto-analysis/src/compare-utils.ts | 7 +- packages/concerto-analysis/src/compare.ts | 29 ++- packages/concerto-analysis/src/comparer.ts | 11 +- .../concerto-analysis/src/comparers/index.ts | 4 +- .../src/comparers/scalar-declarations.ts | 72 +++++++ .../test/fixtures/scalar-added.cto | 3 + .../test/fixtures/scalar-changed-extends.cto | 3 + .../scalar-number-validator-added.cto | 3 + ...r-number-validator-changed-lowercompat.cto | 3 + ...r-number-validator-changed-uppercompat.cto | 3 + ...number-validator-changed-upperincompat.cto | 3 + .../fixtures/scalar-number-validators.cto | 3 + .../scalar-string-validator-added.cto | 3 + .../scalar-string-validator-changed.cto | 3 + .../scalar-string-validator-length-added.cto | 3 + ...g-validator-length-changed-lowercompat.cto | 3 + ...validator-length-changed-lowerincompat.cto | 3 + ...g-validator-length-changed-uppercompat.cto | 3 + ...validator-length-changed-upperincompat.cto | 3 + .../fixtures/scalar-string-validators.cto | 3 + .../test/unit/compare.test.ts | 179 +++++++++++++++++- 22 files changed, 345 insertions(+), 8 deletions(-) create mode 100644 packages/concerto-analysis/src/comparers/scalar-declarations.ts create mode 100644 packages/concerto-analysis/test/fixtures/scalar-added.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-changed-extends.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-number-validator-added.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-lowercompat.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-uppercompat.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-upperincompat.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-number-validators.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-string-validator-added.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-string-validator-changed.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-string-validator-length-added.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-lowercompat.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-lowerincompat.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-uppercompat.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-upperincompat.cto create mode 100644 packages/concerto-analysis/test/fixtures/scalar-string-validators.cto diff --git a/packages/concerto-analysis/src/compare-config.ts b/packages/concerto-analysis/src/compare-config.ts index 4ee56dbb8..cb36687e6 100644 --- a/packages/concerto-analysis/src/compare-config.ts +++ b/packages/concerto-analysis/src/compare-config.ts @@ -61,6 +61,10 @@ export const defaultCompareConfig: CompareConfig = { 'property-validator-removed': CompareResult.PATCH, 'property-validator-changed': CompareResult.MAJOR, 'map-key-type-changed': CompareResult.MAJOR, - 'map-value-type-changed': CompareResult.MAJOR + 'map-value-type-changed': CompareResult.MAJOR, + 'scalar-extends-changed': CompareResult.MAJOR, + 'scalar-validator-added' : CompareResult.MAJOR, + 'scalar-validator-removed' : CompareResult.PATCH, + 'scalar-validator-changed' : CompareResult.MAJOR, } }; diff --git a/packages/concerto-analysis/src/compare-utils.ts b/packages/concerto-analysis/src/compare-utils.ts index f0d8b926e..f07f2bd17 100644 --- a/packages/concerto-analysis/src/compare-utils.ts +++ b/packages/concerto-analysis/src/compare-utils.ts @@ -12,7 +12,7 @@ * limitations under the License. */ -import { ClassDeclaration, EnumValueDeclaration, Field, MapDeclaration, NumberValidator, Property, RelationshipDeclaration, StringValidator, Validator } from '@accordproject/concerto-core'; +import { ClassDeclaration, EnumValueDeclaration, Field, MapDeclaration, ScalarDeclaration, NumberValidator, Property, RelationshipDeclaration, StringValidator, Validator } from '@accordproject/concerto-core'; import Declaration from '@accordproject/concerto-core/types/lib/introspect/declaration'; export function getDeclarationType(declaration: Declaration) { @@ -34,6 +34,11 @@ export function getDeclarationType(declaration: Declaration) { } } else if (declaration instanceof MapDeclaration) { return 'map'; + } else if (declaration instanceof ScalarDeclaration) { + return 'scalar'; + } + else { + throw new Error(`unknown class declaration type "${declaration}"`); } } diff --git a/packages/concerto-analysis/src/compare.ts b/packages/concerto-analysis/src/compare.ts index 9d1fe21a4..76ff8228b 100644 --- a/packages/concerto-analysis/src/compare.ts +++ b/packages/concerto-analysis/src/compare.ts @@ -12,7 +12,7 @@ * limitations under the License. */ -import { ClassDeclaration, MapDeclaration, ModelFile, Property } from '@accordproject/concerto-core'; +import { ClassDeclaration, MapDeclaration, ModelFile, Property, ScalarDeclaration } from '@accordproject/concerto-core'; import { CompareConfig, CompareResult, defaultCompareConfig } from './compare-config'; import { CompareFinding } from './compare-message'; import { CompareResults } from './compare-results'; @@ -69,14 +69,26 @@ export class Compare { return b.filter(bItem => !a.some(aItem => bItem.getName() === aItem.getName())); } + private getAddedScalarDeclarations(a: ScalarDeclaration[], b: ScalarDeclaration[]): ScalarDeclaration[] { + return b.filter(bItem => !a.some(aItem => bItem.getName() === aItem.getName())); + } + private getMatchingMapDeclarations(a: MapDeclaration[], b: MapDeclaration[]): [a: MapDeclaration, b: MapDeclaration][] { return a.map(aItem => [aItem, b.find(bItem => aItem.getName() === bItem.getName())]).filter(([, b]) => !!b) as [MapDeclaration, MapDeclaration][]; } + private getMatchingScalarDeclarations(a: ScalarDeclaration[], b: ScalarDeclaration[]): [a: ScalarDeclaration, b: ScalarDeclaration][] { + return a.map(aItem => [aItem, b.find(bItem => aItem.getName() === bItem.getName())]).filter(([, b]) => !!b) as [ScalarDeclaration, ScalarDeclaration][]; + } + private getRemovedMapDeclarations(a: MapDeclaration[], b: MapDeclaration[]): MapDeclaration[] { return a.filter(aItem => !b.some(bItem => aItem.getName() === bItem.getName())); } + private getRemovedScalarDeclarations(a: ScalarDeclaration[], b: ScalarDeclaration[]): ScalarDeclaration[] { + return a.filter(aItem => !b.some(bItem => aItem.getName() === bItem.getName())); + } + private getAddedClassDeclarations(a: ClassDeclaration[], b: ClassDeclaration[]): ClassDeclaration[] { return b.filter(bItem => !a.some(aItem => bItem.getName() === aItem.getName())); } @@ -98,12 +110,26 @@ export class Compare { removed.forEach(a => comparers.forEach(comparer => comparer.compareMapDeclaration?.(a, undefined))); } + private compareScalarDeclarations(comparers: Comparer[], a: ScalarDeclaration[], b: ScalarDeclaration[]) { + const added = this.getAddedScalarDeclarations(a, b); + const matching = this.getMatchingScalarDeclarations(a, b); + const removed = this.getRemovedScalarDeclarations(a, b); + added.forEach(b => comparers.forEach(comparer => comparer.compareScalarDeclaration?.(undefined, b))); + matching.forEach(([a, b]) => comparers.forEach(comparer => comparer.compareScalarDeclaration?.(a, b))); + removed.forEach(a => comparers.forEach(comparer => comparer.compareScalarDeclaration?.(a, undefined))); + } + + private compareClassDeclaration(comparers: Comparer[], a: ClassDeclaration, b: ClassDeclaration) { comparers.forEach(comparer => comparer.compareClassDeclaration?.(a, b)); // MapDeclarations do not contain properties, nothing to compare. if(a instanceof MapDeclaration || b instanceof MapDeclaration) { return; } + // ScalarDeclarations do not contain properties, nothing to compare. + if(a instanceof ScalarDeclaration || b instanceof ScalarDeclaration) { + return; + } this.compareProperties(comparers, a.getOwnProperties(), b.getOwnProperties()); } @@ -120,6 +146,7 @@ export class Compare { comparers.forEach(comparer => comparer.compareModelFiles?.(a, b)); this.compareClassDeclarations(comparers, a.getAllDeclarations(), b.getAllDeclarations()); this.compareMapDeclarations(comparers, a.getMapDeclarations(), b.getMapDeclarations()); + this.compareScalarDeclarations(comparers, a.getScalarDeclarations(), b.getScalarDeclarations()); } private buildResults(findings: CompareFinding[]) { diff --git a/packages/concerto-analysis/src/comparer.ts b/packages/concerto-analysis/src/comparer.ts index 65c55d6e4..125f02848 100644 --- a/packages/concerto-analysis/src/comparer.ts +++ b/packages/concerto-analysis/src/comparer.ts @@ -12,7 +12,7 @@ * limitations under the License. */ -import { ClassDeclaration, MapDeclaration, ModelFile, Property } from '@accordproject/concerto-core'; +import { ClassDeclaration, MapDeclaration, ModelFile, Property, ScalarDeclaration } from '@accordproject/concerto-core'; import { CompareContext } from './compare-context'; /** @@ -42,6 +42,15 @@ export type Comparer = { */ compareMapDeclaration?: (a: MapDeclaration | undefined, b: MapDeclaration | undefined) => void; + /** + * Called to compare two scalar declarations. If a is undefined, but b is defined, then this scalar declaration was + * created in the second model. If a is defined, but b is undefined, then this map declaration was removed in the + * second model. + * @param a The first scalar declaration for comparision, or undefined if it is undefined in the first model. + * @param b The second scalar declaration for comparision, or undefined if it is undefined in the second model. + */ + compareScalarDeclaration?: (a: ScalarDeclaration | undefined, b: ScalarDeclaration | undefined) => void; + /** * Called to compare two properties. If a is undefined, but b is definecd, then this property was * created in the second model. If a is defined, but b is undefined, then this property was removed in the diff --git a/packages/concerto-analysis/src/comparers/index.ts b/packages/concerto-analysis/src/comparers/index.ts index 53fee5de5..cb0a8b004 100644 --- a/packages/concerto-analysis/src/comparers/index.ts +++ b/packages/concerto-analysis/src/comparers/index.ts @@ -16,10 +16,12 @@ import { classDeclarationComparerFactories } from './class-declarations'; import { modelFileComparerFactories } from './model-files'; import { propertyComparerFactories } from './properties'; import { mapDeclarationComparerFactories } from './map-declarations'; +import { scalarDeclarationComparerFactories } from './scalar-declarations'; export const comparerFactories = [ ...classDeclarationComparerFactories, ...propertyComparerFactories, ...modelFileComparerFactories, - ...mapDeclarationComparerFactories + ...mapDeclarationComparerFactories, + ...scalarDeclarationComparerFactories ]; diff --git a/packages/concerto-analysis/src/comparers/scalar-declarations.ts b/packages/concerto-analysis/src/comparers/scalar-declarations.ts new file mode 100644 index 000000000..a66eaa0b9 --- /dev/null +++ b/packages/concerto-analysis/src/comparers/scalar-declarations.ts @@ -0,0 +1,72 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComparerFactory } from '../comparer'; +import { getValidatorType } from '../compare-utils'; + +const scalarDeclarationExtendsChanged: ComparerFactory = (context) => ({ + compareScalarDeclaration: (a, b) => { + + if (!a || !b) { + return; + } + + if(a.getType() !== b.getType()) { + context.report({ + key: 'scalar-extends-changed', + message: `The scalar extends was changed from "${a.getType()}" to "${b.getType()}"`, + element: b.getName() + }); + } + }, +}); + +const scalarValidatorChanged: ComparerFactory = (context) => ({ + compareScalarDeclaration: (a, b) => { + if (!a || !b) { + return; + } + const aValidator = a.getValidator(); + const bValidator = b.getValidator(); + if (!aValidator && !bValidator) { + return; + } else if (!aValidator && bValidator) { + const bValidatorType = getValidatorType(bValidator); + context.report({ + key: 'scalar-validator-added', + message: `A ${bValidatorType} validator was added to the scalar "${a.getName()}"`, + element: a.getName() + }); + return; + } else if (aValidator && !bValidator) { + const aValidatorType = getValidatorType(aValidator); + context.report({ + key: 'scalar-validator-removed', + message: `A ${aValidatorType} validator was removed from the scalar "${a.getName()}"`, + element: a.getName() + }); + return; + } else if (!aValidator.compatibleWith(bValidator)) { + const aValidatorType = getValidatorType(aValidator); + context.report({ + key: 'scalar-validator-changed', + message: `A ${aValidatorType} validator for the scalar "${a.getName()}" was changed and is no longer compatible`, + element: a.getName() + }); + return; + } + } +}); + +export const scalarDeclarationComparerFactories = [scalarDeclarationExtendsChanged, scalarValidatorChanged]; diff --git a/packages/concerto-analysis/test/fixtures/scalar-added.cto b/packages/concerto-analysis/test/fixtures/scalar-added.cto new file mode 100644 index 000000000..e5c18d692 --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-added.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends String \ No newline at end of file diff --git a/packages/concerto-analysis/test/fixtures/scalar-changed-extends.cto b/packages/concerto-analysis/test/fixtures/scalar-changed-extends.cto new file mode 100644 index 000000000..b97f3c24a --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-changed-extends.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends Integer \ No newline at end of file diff --git a/packages/concerto-analysis/test/fixtures/scalar-number-validator-added.cto b/packages/concerto-analysis/test/fixtures/scalar-number-validator-added.cto new file mode 100644 index 000000000..6fdfb8590 --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-number-validator-added.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends Integer range=[-1,1] \ No newline at end of file diff --git a/packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-lowercompat.cto b/packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-lowercompat.cto new file mode 100644 index 000000000..9852f8bd8 --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-lowercompat.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends Integer range=[-2,1] \ No newline at end of file diff --git a/packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-uppercompat.cto b/packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-uppercompat.cto new file mode 100644 index 000000000..ff0cb8ef3 --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-uppercompat.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends Integer range=[-1,2] \ No newline at end of file diff --git a/packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-upperincompat.cto b/packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-upperincompat.cto new file mode 100644 index 000000000..97563e93f --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-number-validator-changed-upperincompat.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends Integer range=[-1,0] \ No newline at end of file diff --git a/packages/concerto-analysis/test/fixtures/scalar-number-validators.cto b/packages/concerto-analysis/test/fixtures/scalar-number-validators.cto new file mode 100644 index 000000000..b97f3c24a --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-number-validators.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends Integer \ No newline at end of file diff --git a/packages/concerto-analysis/test/fixtures/scalar-string-validator-added.cto b/packages/concerto-analysis/test/fixtures/scalar-string-validator-added.cto new file mode 100644 index 000000000..6fca5d28b --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-string-validator-added.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends String regex=/foo/ \ No newline at end of file diff --git a/packages/concerto-analysis/test/fixtures/scalar-string-validator-changed.cto b/packages/concerto-analysis/test/fixtures/scalar-string-validator-changed.cto new file mode 100644 index 000000000..8e145609f --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-string-validator-changed.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends String regex=/bar/ diff --git a/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-added.cto b/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-added.cto new file mode 100644 index 000000000..8f58fb18f --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-added.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends String length=[2,10] diff --git a/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-lowercompat.cto b/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-lowercompat.cto new file mode 100644 index 000000000..9f9e467dc --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-lowercompat.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends String length=[1,10] \ No newline at end of file diff --git a/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-lowerincompat.cto b/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-lowerincompat.cto new file mode 100644 index 000000000..516170813 --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-lowerincompat.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends String length=[3,10] \ No newline at end of file diff --git a/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-uppercompat.cto b/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-uppercompat.cto new file mode 100644 index 000000000..6361234f8 --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-uppercompat.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends String length=[2,100] \ No newline at end of file diff --git a/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-upperincompat.cto b/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-upperincompat.cto new file mode 100644 index 000000000..245d7982a --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-string-validator-length-changed-upperincompat.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends String length=[2,8] diff --git a/packages/concerto-analysis/test/fixtures/scalar-string-validators.cto b/packages/concerto-analysis/test/fixtures/scalar-string-validators.cto new file mode 100644 index 000000000..e5c18d692 --- /dev/null +++ b/packages/concerto-analysis/test/fixtures/scalar-string-validators.cto @@ -0,0 +1,3 @@ +namespace org.accordproject.concerto.test@1.2.3 + +scalar Thing extends String \ No newline at end of file diff --git a/packages/concerto-analysis/test/unit/compare.test.ts b/packages/concerto-analysis/test/unit/compare.test.ts index fb8a38204..f192623e7 100644 --- a/packages/concerto-analysis/test/unit/compare.test.ts +++ b/packages/concerto-analysis/test/unit/compare.test.ts @@ -69,7 +69,7 @@ test('should detect a change of namespace', async () => { expect(results.result).toBe(CompareResult.ERROR); }); -['asset', 'concept', 'enum', 'event', 'participant', 'transaction', 'map'].forEach(type => { +['asset', 'concept', 'enum', 'event', 'participant', 'transaction', 'map', 'scalar'].forEach(type => { test(`should detect a ${type} being added`, async () => { process.env.ENABLE_MAP_TYPE = 'true'; // TODO Remove on release of MapType const [a, b] = await getModelFiles('empty.cto', `${type}-added.cto`); @@ -207,7 +207,7 @@ test('should detect a relationship changing to a field', async () => { expect(results.result).toBe(CompareResult.MAJOR); }); -test('should detect a scalar changing to an array', async () => { +test('should detect a property changing to an array', async () => { const [a, b] = await getModelFiles('scalar-to-array-a.cto', 'scalar-to-array-b.cto'); const results = new Compare().compare(a, b); expect(results.findings).toEqual(expect.arrayContaining([ @@ -219,7 +219,7 @@ test('should detect a scalar changing to an array', async () => { expect(results.result).toBe(CompareResult.MAJOR); }); -test('should detect an array changing to a scalar', async () => { +test('should detect an array changing to a property', async () => { const [a, b] = await getModelFiles('scalar-to-array-b.cto', 'scalar-to-array-a.cto'); const results = new Compare().compare(a, b); expect(results.findings).toEqual(expect.arrayContaining([ @@ -259,6 +259,19 @@ test('should detect a map value type changing from x to y', async () => { expect(results.result).toBe(CompareResult.MAJOR); }); +test('should detect a scalar extends changing from x to y', async () => { + const [a, b] = await getModelFiles('scalar-added.cto', 'scalar-changed-extends.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'scalar-extends-changed', + message: 'The scalar extends was changed from "String" to "Integer"' + }) + ])); + expect(results.findings[0].message).toBe('The scalar extends was changed from "String" to "Integer"'); + expect(results.result).toBe(CompareResult.MAJOR); +}); + test('should detect a primitive typed field changing to a declaration typed field', async () => { const [a, b] = await getModelFiles('primitive-to-declaration-a.cto', 'primitive-to-declaration-b.cto'); const results = new Compare().compare(a, b); @@ -481,6 +494,166 @@ test('should detect a string validator being changed on a property (incompatible expect(results.result).toBe(CompareResult.MAJOR); }); +test('should detect a number validator being added to a scalar', async () => { + const [a, b] = await getModelFiles('scalar-number-validators.cto', 'scalar-number-validator-added.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'scalar-validator-added', + message: 'A number validator was added to the scalar "Thing"' + }) + ])); + expect(results.result).toBe(CompareResult.MAJOR); +}); + +test('should detect a number validator being removed from a scalar', async () => { + const [a, b] = await getModelFiles('scalar-number-validator-added.cto', 'scalar-number-validators.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'scalar-validator-removed', + message: 'A number validator was removed from the scalar "Thing"' + }) + ])); + expect(results.result).toBe(CompareResult.PATCH); +}); + +test('should not detect a number validator being changed on a scalar (compatible lower bound)', async () => { + const [a, b] = await getModelFiles('scalar-number-validator-added.cto', 'scalar-number-validator-changed-lowercompat.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual([]); + expect(results.result).toBe(CompareResult.NONE); +}); + +test('should detect a number validator being changed on a property (incompatible lower bound)', async () => { + const [a, b] = await getModelFiles('number-validator-added.cto', 'number-validator-changed-lowerincompat.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'property-validator-changed', + message: 'A number validator for the field "number" in the concept "Thing" was changed and is no longer compatible' + }) + ])); + expect(results.result).toBe(CompareResult.MAJOR); +}); + +test('should not detect a number validator being changed on a scalar (compatible upper bound)', async () => { + const [a, b] = await getModelFiles('scalar-number-validator-added.cto', 'scalar-number-validator-changed-uppercompat.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual([]); + expect(results.result).toBe(CompareResult.NONE); +}); + +test('should detect a number validator being changed on a property (incompatible upper bound)', async () => { + const [a, b] = await getModelFiles('scalar-number-validator-added.cto', 'scalar-number-validator-changed-upperincompat.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'scalar-validator-changed', + message: 'A number validator for the scalar "Thing" was changed and is no longer compatible' + }) + ])); + expect(results.result).toBe(CompareResult.MAJOR); +}); + +test('should detect a string validator being added to a scalar', async () => { + const [a, b] = await getModelFiles('scalar-string-validators.cto', 'scalar-string-validator-added.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'scalar-validator-added', + message: 'A string validator was added to the scalar "Thing"' + }) + ])); + expect(results.result).toBe(CompareResult.MAJOR); +}); + +test('should detect a string validator being removed from a scalar', async () => { + const [a, b] = await getModelFiles('scalar-string-validator-added.cto', 'scalar-string-validators.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'scalar-validator-removed', + message: 'A string validator was removed from the scalar "Thing"' + }) + ])); + expect(results.result).toBe(CompareResult.PATCH); +}); + +test('should detect a string validator being changed on a scalar', async () => { + const [a, b] = await getModelFiles('scalar-string-validator-added.cto', 'scalar-string-validator-changed.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'scalar-validator-changed', + message: 'A string validator for the scalar "Thing" was changed and is no longer compatible' + }) + ])); + expect(results.result).toBe(CompareResult.MAJOR); +}); + +test('should detect a string length validator being added to a scalar', async () => { + const [a, b] = await getModelFiles('scalar-string-validators.cto', 'scalar-string-validator-length-added.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'scalar-validator-added', + message: 'A string validator was added to the scalar "Thing"' + }) + ])); + expect(results.result).toBe(CompareResult.MAJOR); +}); + +test('should detect a string length validator being removed from a scalar', async () => { + const [a, b] = await getModelFiles('scalar-string-validator-length-added.cto', 'scalar-string-validators.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'scalar-validator-removed', + message: 'A string validator was removed from the scalar "Thing"' + }) + ])); + expect(results.result).toBe(CompareResult.PATCH); +}); + +test('should not detect a string length validator being changed on a property (compatible minLength bound)', async () => { + const [a, b] = await getModelFiles('string-validator-length-added.cto', 'string-validator-length-changed-lowercompat.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual([]); + expect(results.result).toBe(CompareResult.NONE); +}); + +test('should detect a string length validator being changed on a scalar (incompatible minLength bound)', async () => { + const [a, b] = await getModelFiles('scalar-string-validator-length-added.cto', 'scalar-string-validator-length-changed-lowerincompat.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'scalar-validator-changed', + message: 'A string validator for the scalar "Thing" was changed and is no longer compatible' + }) + ])); + expect(results.result).toBe(CompareResult.MAJOR); +}); + +test('should not detect a string validator being changed on a scalar (compatible maxLength bound)', async () => { + const [a, b] = await getModelFiles('scalar-string-validator-length-added.cto', 'scalar-string-validator-length-changed-uppercompat.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual([]); + expect(results.result).toBe(CompareResult.NONE); +}); + +test('should detect a string validator being changed on a scalar (incompatible maxLength bound)', async () => { + const [a, b] = await getModelFiles('scalar-string-validator-length-added.cto', 'scalar-string-validator-length-changed-upperincompat.cto'); + const results = new Compare().compare(a, b); + expect(results.findings).toEqual(expect.arrayContaining([ + expect.objectContaining({ + key: 'scalar-validator-changed', + message: 'A string validator for the scalar "Thing" was changed and is no longer compatible' + }) + ])); + expect(results.result).toBe(CompareResult.MAJOR); +}); + test('should give a MAJOR CompareResult for Map Type compare config rules)', async () => { expect(defaultCompareConfig.rules['map-key-type-changed']).toBe(CompareResult.MAJOR); expect(defaultCompareConfig.rules['map-value-type-changed']).toBe(CompareResult.MAJOR); From e45607f2c8583473c6708766664fc20f691298a9 Mon Sep 17 00:00:00 2001 From: Dan Selman Date: Sat, 2 Sep 2023 09:38:10 +0100 Subject: [PATCH 2/2] test(coverage) Signed-off-by: Dan Selman --- packages/concerto-analysis/src/compare-utils.ts | 2 +- packages/concerto-analysis/test/unit/compare-utils.test.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/concerto-analysis/src/compare-utils.ts b/packages/concerto-analysis/src/compare-utils.ts index f07f2bd17..180d51aa0 100644 --- a/packages/concerto-analysis/src/compare-utils.ts +++ b/packages/concerto-analysis/src/compare-utils.ts @@ -38,7 +38,7 @@ export function getDeclarationType(declaration: Declaration) { return 'scalar'; } else { - throw new Error(`unknown class declaration type "${declaration}"`); + throw new Error(`unknown declaration type "${declaration}"`); } } diff --git a/packages/concerto-analysis/test/unit/compare-utils.test.ts b/packages/concerto-analysis/test/unit/compare-utils.test.ts index 58dae5241..3eb27680d 100644 --- a/packages/concerto-analysis/test/unit/compare-utils.test.ts +++ b/packages/concerto-analysis/test/unit/compare-utils.test.ts @@ -22,6 +22,11 @@ test('should throw for unknown class declaration type', () => { expect(() => getDeclarationType(classDeclaration)).toThrow('unknown class declaration type "ClassDeclaration {id=foo@1.0.0.undefined super=Concept enum=false abstract=false}"'); }); +test('should throw for unknown thing', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(() => getDeclarationType('thing' as any)).toThrow('unknown declaration type "thing"'); +}); + test('should throw for unknown class property type', () => { expect(() => getPropertyType(property)).toThrow('unknown property type "[object Object]'); });