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
23 changes: 23 additions & 0 deletions shared/src/commands/validate/validate.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,29 @@ describe('validate E2E', () => {
await schemaDirectory.loadSchemas();
});

it('rejects pattern with invalid JSON Schema', async () => {
// AJV2020 ignores references to the JSON 2020-12 draft, but will
// attempt to load any other JSON schema draft but not register it,
// leading to an infinite loop.
const badPattern = { '$schema': 'https://json-schema.org/draft/2019-09/schema' };
const response = await validate(undefined, badPattern, schemaDirectory, false);

expect(response).not.toBeNull();
expect(response).not.toBeUndefined();
expect(response.hasErrors).toBeTruthy();
expect(response.jsonSchemaValidationOutputs).toHaveLength(1);
expect(response.jsonSchemaValidationOutputs[0].message).toContain('reading \'$schema\'');
});

it('accepts pattern with valid JSON Schema', async () => {
const validPattern = { '$schema': 'https://json-schema.org/draft/2020-12/schema' };
const response = await validate(undefined, validPattern, schemaDirectory, false);

expect(response).not.toBeNull();
expect(response).not.toBeUndefined();
expect(response.hasErrors).toBeFalsy();
});

it('validates architecture against pattern with options', async () => {
const inputPattern = JSON.parse(readFileSync(inputPatternPath, 'utf-8'));
const inputArch = JSON.parse(readFileSync(inputArchPath, 'utf-8'));
Expand Down
52 changes: 31 additions & 21 deletions shared/src/schema-directory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ vi.mock('./logger', () => {
return {
initLogger: () => {
return {
info: () => {},
debug: () => {},
warn: () => {},
error: () => {}
info: () => { },
debug: () => { },
warn: () => { },
error: () => { }
};
}
};
Expand All @@ -32,18 +32,18 @@ describe('SchemaDirectory', () => {

it('calls documentloader initialise', async () => {
const schemaDir = new SchemaDirectory(mockDocLoader);

await schemaDir.loadSchemas();
expect(mockDocLoader.initialise).toHaveBeenCalled();
});


it('calls loadMissingDocument method when trying to resolve a spec not loaded at startup', async () => {
const schemaDir = new SchemaDirectory(mockDocLoader);

await schemaDir.loadSchemas();

const expectedValue = {'$id': 'abcd'};
const expectedValue = { '$id': 'abcd' };
mockDocLoader.loadMissingDocument.mockResolvedValueOnce(expectedValue);

const returnedSchema = await schemaDir.getSchema('mock id');
Expand All @@ -53,10 +53,10 @@ describe('SchemaDirectory', () => {

it('resolves a reference from a stored schema', async () => {
const schemaDir = new SchemaDirectory(mockDocLoader);

const nodeJson = loadSchema(path.join(__dirname, '../../calm/release/1.1/meta/core.json'));
const nodeRef = 'https://calm.finos.org/release/1.1/meta/core.json#/defs/node';

mockDocLoader.loadMissingDocument.mockReturnValueOnce(new Promise(resolve => resolve(nodeJson)));

const nodeDef = await schemaDir.getDefinition(nodeRef);
Expand All @@ -67,24 +67,24 @@ describe('SchemaDirectory', () => {

it('recursively resolve references from a loaded schema', async () => {
const schemaDir = new SchemaDirectory(mockDocLoader);

mockDocLoader.loadMissingDocument.mockResolvedValueOnce(
loadSchema('test_fixtures/schema-directory/references.json')
);

const ref = 'https://calm.com/references.json#/defs/top-level';
const definition = await schemaDir.getDefinition(ref);

// this should include top-level, but also recursively pull in inner-prop
expect(definition['properties']).toHaveProperty('top-level');
expect(definition['properties']).toHaveProperty('inner-prop');
});

it('qualify relative references within same file to absolute IDs', async () => {
const schemaDir = new SchemaDirectory(mockDocLoader);

mockDocLoader.loadMissingDocument.mockResolvedValueOnce(loadSchema('test_fixtures/schema-directory/relative-ref.json'));

const ref = 'https://calm.com/relative.json#/defs/top-level';
const definition = await schemaDir.getDefinition(ref);

Expand All @@ -93,7 +93,7 @@ describe('SchemaDirectory', () => {

it('throw error if doc loader fails to return a requested schema', async () => {
const schemaDir = new SchemaDirectory(mockDocLoader);

mockDocLoader.loadMissingDocument.mockRejectedValue(new Error('test error'));

const ref = 'https://calm.com/missing-inner-ref.json#/defs/top-level';
Expand All @@ -103,11 +103,11 @@ describe('SchemaDirectory', () => {

it('throw error if returned schema does not contain given def', async () => {
const schemaDir = new SchemaDirectory(mockDocLoader);

mockDocLoader.loadMissingDocument.mockResolvedValueOnce(
loadSchema('test_fixtures/schema-directory/missing-inner-ref.json')
);

const ref = 'https://calm.com/missing-inner-ref.json#/defs/top-level';
const missingRef = '/defs/not-found'; // see missing-inner-ref.json
const definition = await schemaDir.getDefinition(ref);
Expand All @@ -118,11 +118,11 @@ describe('SchemaDirectory', () => {

it('terminate early in the case of a circular reference', async () => {
const schemaDir = new SchemaDirectory(mockDocLoader);

mockDocLoader.loadMissingDocument.mockResolvedValueOnce(
loadSchema('test_fixtures/schema-directory/recursive.json')
);

const ref = 'https://calm.com/recursive.json#/defs/top-level';
const definition = await schemaDir.getDefinition(ref);

Expand All @@ -133,18 +133,28 @@ describe('SchemaDirectory', () => {

it('look up self-definitions without schema ID at top level from the pattern itself', async () => {
const schemaDir = new SchemaDirectory(mockDocLoader);

mockDocLoader.loadMissingDocument.mockResolvedValueOnce(
loadSchema('test_fixtures/schema-directory/relative-ref.json')
);

const ref = 'https://calm.com/relative.json#/defs/top-level';
const definition = await schemaDir.getDefinition(ref);

// this should include top-level, but also recursively pull in inner-prop
expect(definition['properties']).toHaveProperty('top-level');
expect(definition['properties']).toHaveProperty('inner-prop');
});

it('reject attempts to load standard JSON Schemas - HTTPS protocol', async () => {
const schemaDir = new SchemaDirectory(mockDocLoader);
await expect(schemaDir.getSchema('https://json-schema.org/draft/2019-09/schema')).rejects.toThrow();
});

it('reject attempts to load standard JSON Schemas - HTTP protocol', async () => {
const schemaDir = new SchemaDirectory(mockDocLoader);
await expect(schemaDir.getSchema('http://json-schema.org/draft/2019-09/schema')).rejects.toThrow();
});
});

function loadSchema(path: string): object {
Expand Down
4 changes: 4 additions & 0 deletions shared/src/schema-directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ export class SchemaDirectory {
public async getSchema(schemaId: string): Promise<object> {
if (!this.schemas.has(schemaId)) {
try {
if (/^https?:\/\/json-schema\.org/.test(schemaId)) {
throw new Error(`Attempted to load standard JSON Schema with ID ${schemaId}. This is not supported.`);
}

const document = await this.documentLoader.loadMissingDocument(schemaId, 'schema');
this.storeDocument(schemaId, 'schema', document);

Expand Down
Loading