diff --git a/cli/src/commands/validate/validate.spec.ts b/cli/src/commands/validate/validate.spec.ts index 2e6eb83e..1b983ee6 100644 --- a/cli/src/commands/validate/validate.spec.ts +++ b/cli/src/commands/validate/validate.spec.ts @@ -159,6 +159,21 @@ describe('validate', () => { fetchMock.restore(); }); + it('exits with error when the pattern does not pass all the spectral validations', async () => { + + const apiGateway = readFileSync(path.resolve(__dirname, '../../../test_fixtures/api-gateway-with-no-relationships.json'), 'utf8'); + fetchMock.mock('http://exist/api-gateway.json', apiGateway); + + const apiGatewayInstantiation = readFileSync(path.resolve(__dirname, '../../../test_fixtures/api-gateway-implementation.json'), 'utf8'); + fetchMock.mock('https://exist/api-gateway-implementation.json', apiGatewayInstantiation); + + await expect(validate('https://exist/api-gateway-implementation.json', 'http://exist/api-gateway.json', metaSchemaLocation, debugDisabled)) + .rejects + .toThrow(); + + fetchMock.restore(); + }); + it('exits with error when the meta schema location is not a directory', async () => { await expect(validate('https://url/with/non/json/response', 'http://exist/api-gateway.json', 'test_fixtures/api-gateway.json', debugDisabled)) .rejects @@ -323,6 +338,14 @@ describe('formatJsonSchemaOutput', () => { }); +describe('stripRefs', () => { + const objectWithRefs = JSON.parse('{"$ref":123,"abc":{"$ref":321}}'); + const expectedString = '{"ref":123,"abc":{"ref":321}}'; + + expect(exportedForTesting.stripRefs(objectWithRefs)) + .toBe(expectedString); +}); + diff --git a/cli/src/commands/validate/validate.ts b/cli/src/commands/validate/validate.ts index dcdf0cef..a81cfbcc 100644 --- a/cli/src/commands/validate/validate.ts +++ b/cli/src/commands/validate/validate.ts @@ -28,7 +28,7 @@ export default async function validate(jsonSchemaInstantiationLocation: string, const validateSchema = ajv.compile(jsonSchema); - const spectralResult: SpectralResult = await runSpectralValidations(jsonSchemaInstantiation); + const spectralResult: SpectralResult = await runSpectralValidations(jsonSchemaInstantiation, stripRefs(jsonSchema)); errors = spectralResult.errors; validations = validations.concat(spectralResult.spectralIssues); @@ -85,14 +85,16 @@ function loadMetaSchemas(ajv: Ajv2020, metaSchemaLocation: string) { }); } -async function runSpectralValidations(jsonSchemaInstantiation: string): Promise { +async function runSpectralValidations(jsonSchemaInstantiation: string, jsonSchema: string): Promise { let errors = false; let spectralIssues: ValidationOutput[] = []; const spectral = new Spectral(); spectral.setRuleset(await getRuleset('../spectral/instantiation/validation-rules.yaml')); - const issues = await spectral.run(jsonSchemaInstantiation); + let issues = await spectral.run(jsonSchemaInstantiation); + spectral.setRuleset(await getRuleset('../spectral/pattern/validation-rules.yaml')); + issues = issues.concat(await spectral.run(jsonSchema)); if (issues && issues.length > 0) { logger.debug(`Spectral raw output: ${prettifyJson(issues)}`); @@ -182,8 +184,12 @@ function prettifyJson(json){ return JSON.stringify(json, null, 4); } +function stripRefs(obj: object) : string { + return JSON.stringify(obj).replaceAll('$ref', 'ref'); +} export const exportedForTesting = { formatSpectralOutput, - formatJsonSchemaOutput + formatJsonSchemaOutput, + stripRefs }; \ No newline at end of file diff --git a/cli/test_fixtures/api-gateway-with-no-relationships.json b/cli/test_fixtures/api-gateway-with-no-relationships.json new file mode 100644 index 00000000..8d18db04 --- /dev/null +++ b/cli/test_fixtures/api-gateway-with-no-relationships.json @@ -0,0 +1,110 @@ +{ + "$schema": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/calm.json", + "$id": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/pattern/api-gateway", + "title": "API Gateway Pattern", + "type": "object", + "properties": { + "nodes": { + "type": "array", + "minItems": 3, + "prefixItems": [ + { + "$ref": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/core.json#/defs/node", + "properties": { + "ingress-host": { + "type": "string" + }, + "ingress-port": { + "type": "integer" + }, + "well-known-endpoint": { + "type": "string" + }, + "description": { + "const": "The API Gateway used to verify authorization and access to downstream system" + }, + "type": { + "const": "system" + }, + "name": { + "const": "API Gateway" + }, + "unique-id": { + "const": "api-gateway" + } + }, + "required": [ + "ingress-host", + "ingress-port", + "well-known-endpoint" + ] + }, + { + "$ref": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/core.json#/defs/node", + "properties": { + "description": { + "const": "The API Consumer making an authenticated and authorized request" + }, + "type": { + "const": "system" + }, + "name": { + "const": "Python Based API Consumer" + }, + "unique-id": { + "const": "api-consumer" + } + } + }, + { + "$ref": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/core.json#/defs/node", + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "description": { + "const": "The API Producer serving content" + }, + "type": { + "const": "system" + }, + "name": { + "const": "Java Based API Producer" + }, + "unique-id": { + "const": "api-producer" + } + }, + "required": [ + "host", + "port" + ] + }, + { + "$ref": "https://raw.githubusercontent.com/finos-labs/architecture-as-code/main/calm/draft/2024-03/meta/core.json#/defs/node", + "properties": { + "description": { + "const": "The Identity Provider used to verify the bearer token" + }, + "type": { + "const": "system" + }, + "name": { + "const": "Identity Provider" + }, + "unique-id": { + "const": "idp" + } + } + } + ] + } + }, + "required": [ + "nodes", + "relationships" + ] +}