diff --git a/src/packages/external-source/external-source.ts b/src/packages/external-source/external-source.ts index d796dc2..a041461 100644 --- a/src/packages/external-source/external-source.ts +++ b/src/packages/external-source/external-source.ts @@ -33,11 +33,9 @@ const refreshLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes }); -export function updateSchemaWithDefs(defs: { event_types: any, source_type: any }): Ajv.ValidateFunction { +export function updateSchemaWithDefs(defs: { event_types: any; source_type: any }): Ajv.ValidateFunction { // build if statement - const ifThenElse: { [key: string]: any } = { - - }; + const ifThenElse: { [key: string]: any } = {}; let ifThenElsePointer = ifThenElse; const keys = Object.keys(defs.event_types); @@ -51,18 +49,18 @@ export function updateSchemaWithDefs(defs: { event_types: any, source_type: any ...defs.event_types[event_type_name], // additionally, restrict extra properties - additionalProperties: false + additionalProperties: false, }; const source_type_name = Object.keys(defs.source_type)[0]; const source_type_schema = { ...defs.source_type[source_type_name], // additionally, restrict extra properties - additionalProperties: false + additionalProperties: false, }; localSchemaCopy.properties.events.items.properties.attributes = event_type_schema; - localSchemaCopy.properties.events.items.properties.event_type_name = { "const": event_type_name }; + localSchemaCopy.properties.events.items.properties.event_type_name = { const: event_type_name }; // insert def for "source" attributes localSchemaCopy.properties.source.properties.attributes = source_type_schema; @@ -74,44 +72,42 @@ export function updateSchemaWithDefs(defs: { event_types: any, source_type: any // handle n event types for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; - console.log("NOW ON:", key); - ifThenElsePointer["if"] = { + console.log('NOW ON:', key); + ifThenElsePointer['if'] = { properties: { event_type_name: { - const: key - } - } + const: key, + }, + }, }; - ifThenElsePointer["then"] = { + ifThenElsePointer['then'] = { properties: { attributes: { - $ref: `#/$defs/event_types/${key}` - } - } - }; - ifThenElsePointer["else"] = { - + $ref: `#/$defs/event_types/${key}`, + }, + }, }; - ifThenElsePointer = ifThenElsePointer["else"]; + ifThenElsePointer['else'] = {}; + ifThenElsePointer = ifThenElsePointer['else']; } // fill in the final else with the last element const key = keys[keys.length - 1]; - ifThenElsePointer["properties"] = { + ifThenElsePointer['properties'] = { attributes: { - $ref: `#/$defs/event_types/${key}` - } - } + $ref: `#/$defs/event_types/${key}`, + }, + }; // insert if statement into local copy of baseExternalSourceSchema const localSchemaCopy = structuredClone(baseExternalSourceSchema); - localSchemaCopy.properties.events.items["if"] = ifThenElse["if"]; - localSchemaCopy.properties.events.items["then"] = ifThenElse["then"]; - localSchemaCopy.properties.events.items["else"] = ifThenElse["else"]; + localSchemaCopy.properties.events.items['if'] = ifThenElse['if']; + localSchemaCopy.properties.events.items['then'] = ifThenElse['then']; + localSchemaCopy.properties.events.items['else'] = ifThenElse['else']; // insert def for "source" attributes const sourceTypeKey = Object.keys(defs.source_type)[0]; - localSchemaCopy.properties.source.properties.attributes = { $ref: `#/$defs/source_type/${sourceTypeKey}` } + localSchemaCopy.properties.source.properties.attributes = { $ref: `#/$defs/source_type/${sourceTypeKey}` }; // add defs localSchemaCopy.$defs = { @@ -121,16 +117,16 @@ export function updateSchemaWithDefs(defs: { event_types: any, source_type: any ...defs.source_type[sourceTypeKey], // additionally, restrict extra properties - additionalProperties: false - } - } + additionalProperties: false, + }, + }, }; for (const event_type of keys) { localSchemaCopy.$defs.event_types[event_type] = { ...defs.event_types[event_type], // additionally, restrict extra properties - additionalProperties: false + additionalProperties: false, }; } @@ -161,11 +157,12 @@ async function uploadExternalSourceEventTypes(req: Request, res: Response) { }; // Validate uploaded attribute schemas are formatted validly - const schemasAreValid: boolean = await compiledAttributeMetaschema({ event_types: parsedEventTypes, source_types: parsedSourceTypes }); + const schemasAreValid: boolean = await compiledAttributeMetaschema({ + event_types: parsedEventTypes, + source_types: parsedSourceTypes, + }); if (!schemasAreValid) { - logger.error( - `POST /uploadExternalSourceEventTypes: Schema validation failed for uploaded source and event types.`, - ); + logger.error(`POST /uploadExternalSourceEventTypes: Schema validation failed for uploaded source and event types.`); compiledAttributeMetaschema.errors?.forEach(error => logger.error(error)); res.status(500).send({ message: compiledAttributeMetaschema.errors }); return; @@ -181,16 +178,16 @@ async function uploadExternalSourceEventTypes(req: Request, res: Response) { for (const external_event_type of event_type_keys) { externalEventTypeInput.push({ attribute_schema: parsedEventTypes[external_event_type], - name: external_event_type - }) + name: external_event_type, + }); } const source_type_keys = Object.keys(parsedSourceTypes); for (const external_source_type of source_type_keys) { externalSourceTypeInput.push({ attribute_schema: parsedSourceTypes[external_source_type], - name: external_source_type - }) + name: external_source_type, + }); } // Run the Hasura migration for creating all types, in one go @@ -218,11 +215,13 @@ async function uploadExternalSource(req: Request, res: Response) { } = req; const { body } = req; - if (typeof (body) !== "object") { + if (typeof body !== 'object') { logger.error( `POST /uploadExternalSourceEventTypes: Body of request must be a JSON, with two stringified properties: "source" and "events".`, ); - res.status(500).send({ message: `Body of request must be a JSON, with two stringified properties: "source" and "events".` }); + res + .status(500) + .send({ message: `Body of request must be a JSON, with two stringified properties: "source" and "events".` }); return; } @@ -232,12 +231,19 @@ async function uploadExternalSource(req: Request, res: Response) { const { source, events } = body; parsedSource = JSON.parse(source); parsedExternalEvents = JSON.parse(events); - } - catch (e) { + } catch (e) { logger.error( - `POST /uploadExternalSourceEventTypes: Body of request must be a JSON, with two stringified properties: "source" and "events". Alternatively, parsing may have failed:\n${e as Error}`, + `POST /uploadExternalSourceEventTypes: Body of request must be a JSON, with two stringified properties: "source" and "events". Alternatively, parsing may have failed:\n${ + e as Error + }`, ); - res.status(500).send({ message: `Body of request must be a JSON, with two stringified properties: "source" and "events". Alternatively, parsing may have failed:\n${e as Error}` }); + res + .status(500) + .send({ + message: `Body of request must be a JSON, with two stringified properties: "source" and "events". Alternatively, parsing may have failed:\n${ + e as Error + }`, + }); return; } const { attributes, derivation_group_name, key, period, source_type_name, valid_at } = parsedSource; @@ -272,7 +278,7 @@ async function uploadExternalSource(req: Request, res: Response) { query: gql.GET_SOURCE_EVENT_TYPE_ATTRIBUTE_SCHEMAS, variables: { externalEventTypes: eventTypeNames, - externalSourceType: source_type_name + externalSourceType: source_type_name, }, }), headers, @@ -280,7 +286,8 @@ async function uploadExternalSource(req: Request, res: Response) { }); const attributeSchemaJson = await attributeSchemas.json(); - const { external_event_type, external_source_type } = attributeSchemaJson.data as GetSourceEventTypeAttributeSchemasResponse; + const { external_event_type, external_source_type } = + attributeSchemaJson.data as GetSourceEventTypeAttributeSchemasResponse; if (external_event_type.length === 0 || external_source_type.length === 0) { logger.error( @@ -290,17 +297,26 @@ async function uploadExternalSource(req: Request, res: Response) { return; } - const eventTypeNamesMappedToSchemas = external_event_type.reduce((acc: Record, eventType: ExternalEventTypeInsertInput) => { - acc[eventType.name] = eventType.attribute_schema; - return acc; - }, {}); - const sourceTypeNamesMappedToSchemas = external_source_type.reduce((acc: Record, sourceType: ExternalSourceTypeInsertInput) => { - acc[sourceType.name] = sourceType.attribute_schema; - return acc; - }, {}); + const eventTypeNamesMappedToSchemas = external_event_type.reduce( + (acc: Record, eventType: ExternalEventTypeInsertInput) => { + acc[eventType.name] = eventType.attribute_schema; + return acc; + }, + {}, + ); + const sourceTypeNamesMappedToSchemas = external_source_type.reduce( + (acc: Record, sourceType: ExternalSourceTypeInsertInput) => { + acc[sourceType.name] = sourceType.attribute_schema; + return acc; + }, + {}, + ); // Assemble megaschema from attribute schemas - const compiledExternalSourceMegaschema: Ajv.ValidateFunction = updateSchemaWithDefs({ event_types: eventTypeNamesMappedToSchemas, source_type: sourceTypeNamesMappedToSchemas }); + const compiledExternalSourceMegaschema: Ajv.ValidateFunction = updateSchemaWithDefs({ + event_types: eventTypeNamesMappedToSchemas, + source_type: sourceTypeNamesMappedToSchemas, + }); // Verify that this is a valid external source let sourceIsValid: boolean = false; @@ -308,8 +324,18 @@ async function uploadExternalSource(req: Request, res: Response) { if (sourceIsValid) { logger.info(`POST /uploadExternalSource: External Source ${key}'s formatting is valid`); } else { - logger.error(`POST /uploadExternalSource: External Source ${key}'s formatting is invalid:\n${JSON.stringify(compiledExternalSourceMegaschema.errors)}`); - res.status(500).send({ message: `External Source ${key}'s formatting is invalid:\n${JSON.stringify(compiledExternalSourceMegaschema.errors)}` }); + logger.error( + `POST /uploadExternalSource: External Source ${key}'s formatting is invalid:\n${JSON.stringify( + compiledExternalSourceMegaschema.errors, + )}`, + ); + res + .status(500) + .send({ + message: `External Source ${key}'s formatting is invalid:\n${JSON.stringify( + compiledExternalSourceMegaschema.errors, + )}`, + }); return; } @@ -405,13 +431,7 @@ export default (app: Express) => { * tags: * - Hasura */ - app.post( - '/uploadExternalSourceEventTypes', - upload.any(), - refreshLimiter, - auth, - uploadExternalSourceEventTypes, - ); + app.post('/uploadExternalSourceEventTypes', upload.any(), refreshLimiter, auth, uploadExternalSourceEventTypes); /** * @swagger diff --git a/src/packages/external-source/gql.ts b/src/packages/external-source/gql.ts index 7b030c2..a328b1f 100644 --- a/src/packages/external-source/gql.ts +++ b/src/packages/external-source/gql.ts @@ -73,5 +73,5 @@ export default { attribute_schema } } - ` + `, }; diff --git a/src/packages/schemas/external-event-validation-schemata.ts b/src/packages/schemas/external-event-validation-schemata.ts index f12502b..7c52046 100644 --- a/src/packages/schemas/external-event-validation-schemata.ts +++ b/src/packages/schemas/external-event-validation-schemata.ts @@ -1,102 +1,106 @@ // a schema that describes the format for the attribute files (which are, themselves, JSON Schema-like) export const attributeSchemaMetaschema = { - "$defs": { - "AttributeSchema": { - "additionalProperties": false, - "patternProperties": { - "^.*$": { - "properties": { - "attributes": { - "additionalProperties": true, - "type": "object" - }, - "required": { - "items": { "type": "string" }, - "type": "array" - }, - "type": { "type": "string" } - }, - "required": ["required", "properties", "type"], - "type": "object" - } + $defs: { + AttributeSchema: { + additionalProperties: false, + patternProperties: { + '^.*$': { + properties: { + attributes: { + additionalProperties: true, + type: 'object', }, - "type": "object" - } - }, - "$schema": "http://json-schema.org/draft-07/schema", - "additionalProperties": false, - "description": "Schema for the attributes of uploaded source types and/or event types.", - "properties": { - "event_types": { - "$ref": "#/$defs/AttributeSchema" + required: { + items: { type: 'string' }, + type: 'array', + }, + type: { type: 'string' }, + }, + required: ['required', 'properties', 'type'], + type: 'object', }, - "source_types": { - "$ref": "#/$defs/AttributeSchema" - } + }, + type: 'object', + }, + }, + $schema: 'http://json-schema.org/draft-07/schema', + additionalProperties: false, + description: 'Schema for the attributes of uploaded source types and/or event types.', + properties: { + event_types: { + $ref: '#/$defs/AttributeSchema', }, - "required": ["source_types", "event_types"], - "title": "TypeSpecificationSchema", - "type": "object" -} + source_types: { + $ref: '#/$defs/AttributeSchema', + }, + }, + required: ['source_types', 'event_types'], + title: 'TypeSpecificationSchema', + type: 'object', +}; // the schema that schemas for specific types are integrated with, after pulling them from the database export const baseExternalSourceSchema: { [key: string]: any } = { - $id: "source_schema", - $schema: "http://json-schema.org/draft-07/schema", - additionalProperties: false, - description: "The base schema for external sources. Defs and ifs, for specific source/event type attributes, are integrated later.", - properties: { - events: { - items: { - additionalProperties: false, - properties: { - attributes: { - type: "object" - }, - duration: { "type": "string" }, - event_type_name: { "type": "string" }, - key: { "type": "string" }, - start_time: { "type": "string" } - }, - required: ["duration", "event_type_name", "key", "attributes", "start_time"], - type: "object" - }, - type: "array" + $id: 'source_schema', + $schema: 'http://json-schema.org/draft-07/schema', + additionalProperties: false, + description: + 'The base schema for external sources. Defs and ifs, for specific source/event type attributes, are integrated later.', + properties: { + events: { + items: { + additionalProperties: false, + properties: { + attributes: { + type: 'object', + }, + duration: { type: 'string' }, + event_type_name: { type: 'string' }, + key: { type: 'string' }, + start_time: { type: 'string' }, }, - source: { - additionalProperties: false, - properties: { - attributes: { - type: "object" // WILL BE REPLACED WITH A $ref - }, - derivation_group_name: { "type": "string" }, - key: { "type": "string" }, - period: { - additionalProperties: false, - properties: { - end_time: { - pattern: "^(\\d){4}-([0-3][0-9])-([0-9][0-9])T([0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\\+|-)([0-1][0-9]):([0-5][0-9])$", - type: "string" - }, - start_time: { - pattern: "^(\\d){4}-([0-3][0-9])-([0-9][0-9])T([0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\\+|-)([0-1][0-9]):([0-5][0-9])$", - type: "string" - } - }, - required: ["start_time", "end_time"], - type: "object" - }, - source_type_name: { "type": "string" }, - valid_at: { - pattern: "^(\\d){4}-([0-3][0-9])-([0-9][0-9])T([0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\\+|-)([0-1][0-9]):([0-5][0-9])$", - type: "string" - } + required: ['duration', 'event_type_name', 'key', 'attributes', 'start_time'], + type: 'object', + }, + type: 'array', + }, + source: { + additionalProperties: false, + properties: { + attributes: { + type: 'object', // WILL BE REPLACED WITH A $ref + }, + derivation_group_name: { type: 'string' }, + key: { type: 'string' }, + period: { + additionalProperties: false, + properties: { + end_time: { + pattern: + '^(\\d){4}-([0-3][0-9])-([0-9][0-9])T([0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\\+|-)([0-1][0-9]):([0-5][0-9])$', + type: 'string', + }, + start_time: { + pattern: + '^(\\d){4}-([0-3][0-9])-([0-9][0-9])T([0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\\+|-)([0-1][0-9]):([0-5][0-9])$', + type: 'string', }, - required: ["key", "source_type_name", "valid_at", "period", "attributes"], - type: "object" - } + }, + required: ['start_time', 'end_time'], + type: 'object', + }, + source_type_name: { type: 'string' }, + valid_at: { + pattern: + '^(\\d){4}-([0-3][0-9])-([0-9][0-9])T([0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\\+|-)([0-1][0-9]):([0-5][0-9])$', + type: 'string', + }, + }, + required: ['key', 'source_type_name', 'valid_at', 'period', 'attributes'], + type: 'object', }, - required: ["source", "events"], - title: "SourceTypeA", - type: "object" -}; \ No newline at end of file + }, + required: ['source', 'events'], + title: 'SourceTypeA', + type: 'object', +}; diff --git a/src/types/external-source.ts b/src/types/external-source.ts index 16887d9..2f20c34 100644 --- a/src/types/external-source.ts +++ b/src/types/external-source.ts @@ -8,7 +8,6 @@ export type ExternalSourceTypeInsertInput = { attribute_schema: object; }; - export type ExternalEventTypeInsertInput = { name: string; attribute_schema: object; @@ -51,8 +50,8 @@ export type CreateExternalSourceResponse = { }; export type CreateExternalSourceEventTypeResponse = { - createExternalEventTypes: { returning: string[] }, - createExternalSourceTypes: { returning: string[] } + createExternalEventTypes: { returning: string[] }; + createExternalSourceTypes: { returning: string[] }; }; export type ExternalEventInsertInput = { @@ -84,6 +83,6 @@ export type AttributeSchema = { }; export type GetSourceEventTypeAttributeSchemasResponse = { - external_event_type: ExternalEventTypeInsertInput[], - external_source_type: ExternalSourceTypeInsertInput[], -} + external_event_type: ExternalEventTypeInsertInput[]; + external_source_type: ExternalSourceTypeInsertInput[]; +}; diff --git a/test/external_source.validation.test.ts b/test/external_source.validation.test.ts index 65a0408..0c1ce9b 100644 --- a/test/external_source.validation.test.ts +++ b/test/external_source.validation.test.ts @@ -6,312 +6,310 @@ import { updateSchemaWithDefs } from '../src/packages/external-source/external-s const ajv = Ajv(); const attributeDefs = { - "event_types": { - "EventTypeA": { - "properties": { - "series": { - "properties": { - "iteration": { "type": "number" }, - "make": { "type": "string" }, - "type": { "type": "string" }, + event_types: { + EventTypeA: { + properties: { + series: { + properties: { + iteration: { type: 'number' }, + make: { type: 'string' }, + type: { type: 'string' }, }, - "required": ["type", "make", "iteration"], - "type": "object", - } + required: ['type', 'make', 'iteration'], + type: 'object', + }, }, - "required": ["series"], - "type": "object", + required: ['series'], + type: 'object', }, - "EventTypeB": { - "properties": { - "projectUser": { - "type": "string" + EventTypeB: { + properties: { + projectUser: { + type: 'string', + }, + tick: { + type: 'number', }, - "tick": { - "type": "number" - } }, - "required": ["projectUser", "tick"], - "type": "object" + required: ['projectUser', 'tick'], + type: 'object', }, - "EventTypeC": { - "properties": { - "aperture": { - "type": "string" + EventTypeC: { + properties: { + aperture: { + type: 'string', + }, + subduration: { + pattern: '^P(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?T(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?$', + type: 'string', }, - "subduration": { - "pattern": "^P(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?T(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?$", - "type": "string" - } }, - "required": ["aperture", "subduration"], - "type": "object" - } + required: ['aperture', 'subduration'], + type: 'object', + }, }, - "source_types": { - "SourceTypeA": { - "properties": { - "version": { - "type": "number" + source_types: { + SourceTypeA: { + properties: { + version: { + type: 'number', + }, + wrkcat: { + type: 'string', }, - "wrkcat": { - "type": "string" - } }, - "required": ["version", "wrkcat"], - "type": "object" + required: ['version', 'wrkcat'], + type: 'object', }, - "SourceTypeB": { - "properties": { - "version": { - "type": "number" + SourceTypeB: { + properties: { + version: { + type: 'number', + }, + wrkcat: { + type: 'string', }, - "wrkcat": { - "type": "string" - } }, - "required": ["version", "wrkcat"], - "type": "object" - } - } + required: ['version', 'wrkcat'], + type: 'object', + }, + }, }; const incorrectAttributeDefs = { - "event_types": { - "EventTypeA": { - "properties": { - "series": { - "properties": { - "iteration": { "type": "number" }, - "make": { "type": "string" }, - "type": { "type": "string" }, + event_types: { + EventTypeA: { + properties: { + series: { + properties: { + iteration: { type: 'number' }, + make: { type: 'string' }, + type: { type: 'string' }, }, // "required": ["type", "make", "iteration"], // missing required field (not an issue) - "type": "object", - } + type: 'object', + }, }, // "required": ["series"], // missing required field (the issue, only at level patternProperties/sdfdsf/required) - "type": "object", + type: 'object', }, - "EventTypeB": { - "properties": { - "projectUser": { - "type": "string" + EventTypeB: { + properties: { + projectUser: { + type: 'string', + }, + tick: { + type: 'number', }, - "tick": { - "type": "number" - } }, - "required": ["projectUser", "tick"], - "type": "object" + required: ['projectUser', 'tick'], + type: 'object', }, - "EventTypeC": { - "properties": { - "aperture": { - "type": "string" + EventTypeC: { + properties: { + aperture: { + type: 'string', + }, + subduration: { + pattern: '^P(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?T(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?$', + type: 'string', }, - "subduration": { - "pattern": "^P(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?T(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?$", - "type": "string" - } }, - "required": ["aperture", "subduration"], - "type": "object" - } + required: ['aperture', 'subduration'], + type: 'object', + }, }, - "source_types": { - "SourceTypeA": { - "properties": { - "version": { - "type": "number" + source_types: { + SourceTypeA: { + properties: { + version: { + type: 'number', + }, + wrkcat: { + type: 'string', }, - "wrkcat": { - "type": "string" - } }, - "required": ["version", "wrkcat"], - "type": "object" + required: ['version', 'wrkcat'], + type: 'object', }, - "SourceTypeB": { - "properties": { - "version": { - "type": "number" + SourceTypeB: { + properties: { + version: { + type: 'number', + }, + wrkcat: { + type: 'string', }, - "wrkcat": { - "type": "string" - } }, - "required": ["version", "wrkcat"], - "type": "object" - } - } + required: ['version', 'wrkcat'], + type: 'object', + }, + }, }; const correctExternalSource = { - "events": [ + events: [ { - "attributes": { - "series": { - "iteration": 17, - "make": "alpha", - "type": "A", - } + attributes: { + series: { + iteration: 17, + make: 'alpha', + type: 'A', + }, }, - "duration": "02:00:00", - "event_type_name": "EventTypeA", - "key": "EventTypeA:1/1", - "start_time": "2024-01-01T01:35:00+00:00", + duration: '02:00:00', + event_type_name: 'EventTypeA', + key: 'EventTypeA:1/1', + start_time: '2024-01-01T01:35:00+00:00', }, { - "attributes": { - "series": { - "iteration": 21, - "make": "beta", - "type": "B" - } + attributes: { + series: { + iteration: 21, + make: 'beta', + type: 'B', + }, }, - "duration": "02:00:00", - "event_type_name": "EventTypeA", - "key": "EventTypeA:1/2", - "start_time": "2024-01-02T11:50:00+00:00", + duration: '02:00:00', + event_type_name: 'EventTypeA', + key: 'EventTypeA:1/2', + start_time: '2024-01-02T11:50:00+00:00', }, { - "attributes": { - "projectUser": "Jerry", - "tick": 18 + attributes: { + projectUser: 'Jerry', + tick: 18, }, - "duration": "03:40:00", - "event_type_name": "EventTypeB", - "key": "EventTypeB:1/3", - "start_time": "2024-01-03T15:20:00+00:00" - } + duration: '03:40:00', + event_type_name: 'EventTypeB', + key: 'EventTypeB:1/3', + start_time: '2024-01-03T15:20:00+00:00', + }, ], - "source": { - "attributes": { - "version": 1, - "wrkcat": "234" + source: { + attributes: { + version: 1, + wrkcat: '234', }, - "key": "SourceTypeA:valid_source_A.json", - "period": { - "end_time": "2024-01-07T00:00:00+00:00", - "start_time": "2024-01-01T00:00:00+00:00" + key: 'SourceTypeA:valid_source_A.json', + period: { + end_time: '2024-01-07T00:00:00+00:00', + start_time: '2024-01-01T00:00:00+00:00', }, - "source_type_name": "SourceTypeA", - "valid_at": "2024-01-01T00:00:00+00:00", - } + source_type_name: 'SourceTypeA', + valid_at: '2024-01-01T00:00:00+00:00', + }, }; const incorrectExternalSourceAttributes = { - "events": [ + events: [ { - "attributes": { - "series": { - "iteration": 17, - "make": "alpha", - "type": "A", - } + attributes: { + series: { + iteration: 17, + make: 'alpha', + type: 'A', + }, }, - "duration": "02:00:00", - "event_type_name": "EventTypeA", - "key": "EventTypeA:1/1", - "start_time": "2024-01-01T01:35:00+00:00", + duration: '02:00:00', + event_type_name: 'EventTypeA', + key: 'EventTypeA:1/1', + start_time: '2024-01-01T01:35:00+00:00', }, { - "attributes": { - "series": { - "iteration": 21, - "make": "beta", - "type": "B" - } + attributes: { + series: { + iteration: 21, + make: 'beta', + type: 'B', + }, }, - "duration": "02:00:00", - "event_type_name": "EventTypeA", - "key": "EventTypeA:1/2", - "start_time": "2024-01-02T11:50:00+00:00", + duration: '02:00:00', + event_type_name: 'EventTypeA', + key: 'EventTypeA:1/2', + start_time: '2024-01-02T11:50:00+00:00', }, { - "attributes": { - "projectUser": "Jerry", - "tick": 18 + attributes: { + projectUser: 'Jerry', + tick: 18, }, - "duration": "03:40:00", - "event_type_name": "EventTypeB", - "key": "EventTypeB:1/3", - "start_time": "2024-01-03T15:20:00+00:00" - } + duration: '03:40:00', + event_type_name: 'EventTypeB', + key: 'EventTypeB:1/3', + start_time: '2024-01-03T15:20:00+00:00', + }, ], - "source": { - "attributes": { - "version": 1, - "wrkcat": 234 // <-- wrong type. expecting string. + source: { + attributes: { + version: 1, + wrkcat: 234, // <-- wrong type. expecting string. }, - "key": "SourceTypeA:valid_source_A.json", - "period": { - "end_time": "2024-01-07T00:00:00+00:00", - "start_time": "2024-01-01T00:00:00+00:00" + key: 'SourceTypeA:valid_source_A.json', + period: { + end_time: '2024-01-07T00:00:00+00:00', + start_time: '2024-01-01T00:00:00+00:00', }, - "source_type_name": "SourceTypeA", - "valid_at": "2024-01-01T00:00:00+00:00", - } + source_type_name: 'SourceTypeA', + valid_at: '2024-01-01T00:00:00+00:00', + }, }; const incorrectExternalEventAttributes = { - "events": [ + events: [ { - "attributes": { - "series": { - "iteration": 17, - "make": "alpha", + attributes: { + series: { + iteration: 17, + make: 'alpha', // "type": "A", <-- missing. - } + }, }, - "duration": "02:00:00", - "event_type_name": "EventTypeA", - "key": "EventTypeA:1/1", - "start_time": "2024-01-01T01:35:00+00:00", + duration: '02:00:00', + event_type_name: 'EventTypeA', + key: 'EventTypeA:1/1', + start_time: '2024-01-01T01:35:00+00:00', }, { - "attributes": { - "series": { - "iteration": 21, - "make": "beta", - "type": "B" - } + attributes: { + series: { + iteration: 21, + make: 'beta', + type: 'B', + }, }, - "duration": "02:00:00", - "event_type_name": "EventTypeA", - "key": "EventTypeA:1/2", - "start_time": "2024-01-02T11:50:00+00:00", + duration: '02:00:00', + event_type_name: 'EventTypeA', + key: 'EventTypeA:1/2', + start_time: '2024-01-02T11:50:00+00:00', }, { - "attributes": { - "projectUser": "Jerry", - "tick": 18 + attributes: { + projectUser: 'Jerry', + tick: 18, }, - "duration": "03:40:00", - "event_type_name": "EventTypeB", - "key": "EventTypeB:1/3", - "start_time": "2024-01-03T15:20:00+00:00" - } + duration: '03:40:00', + event_type_name: 'EventTypeB', + key: 'EventTypeB:1/3', + start_time: '2024-01-03T15:20:00+00:00', + }, ], - "source": { - "attributes": { - "version": 1, - "wrkcat": "234" + source: { + attributes: { + version: 1, + wrkcat: '234', }, - "key": "SourceTypeA:valid_source_A.json", - "period": { - "end_time": "2024-01-07T00:00:00+00:00", - "start_time": "2024-01-01T00:00:00+00:00" + key: 'SourceTypeA:valid_source_A.json', + period: { + end_time: '2024-01-07T00:00:00+00:00', + start_time: '2024-01-01T00:00:00+00:00', }, - "source_type_name": "SourceTypeA", - "valid_at": "2024-01-01T00:00:00+00:00", - } + source_type_name: 'SourceTypeA', + valid_at: '2024-01-01T00:00:00+00:00', + }, }; - describe('validation tests', () => { - // test to verify source/event type file is correctly formatted test('verify source/event type file is correctly formatted', () => { // get the validator @@ -334,16 +332,16 @@ describe('validation tests', () => { const errors = attributeValidator.errors; expect(errors?.length).toBe(1); - expect(errors?.at(0)?.schemaPath).toBe("#/$defs/AttributeSchema/patternProperties/%5E.*%24/required"); + expect(errors?.at(0)?.schemaPath).toBe('#/$defs/AttributeSchema/patternProperties/%5E.*%24/required'); expect(errors?.at(0)?.message).toMatch("should have required property 'required'"); }); // test to verify that composition of a base schema with attribute schemas work test('verify validation functionality of updateSchemaWithDefs', () => { // transform attributeDefs to match something that might come from hasura (just ONE source type, as we will be constructing a schema for a specific source) - const attributeSchema: { event_types: any, source_type: any } = { + const attributeSchema: { event_types: any; source_type: any } = { event_types: [], - source_type: {} + source_type: {}, }; attributeSchema.event_types = attributeDefs.event_types; attributeSchema.source_type['SourceTypeA'] = attributeDefs.source_types.SourceTypeA; @@ -355,19 +353,20 @@ describe('validation tests', () => { if (schema) { // verify it is formatted correctly - expect(Object.keys(schema.$defs.event_types)).toMatchObject(["EventTypeA", "EventTypeB", "EventTypeC"]); - expect(Object.keys(schema.$defs.source_type)).toMatchObject(["SourceTypeA"]); - expect(schema.properties.events.items.else.else.properties.attributes.$ref).toEqual("#/$defs/event_types/EventTypeC"); + expect(Object.keys(schema.$defs.event_types)).toMatchObject(['EventTypeA', 'EventTypeB', 'EventTypeC']); + expect(Object.keys(schema.$defs.source_type)).toMatchObject(['SourceTypeA']); + expect(schema.properties.events.items.else.else.properties.attributes.$ref).toEqual( + '#/$defs/event_types/EventTypeC', + ); } }); - // source testing describe('validating (and failing) sources', () => { // transform attributeDefs to match something that might come from hasura (just ONE source type, as we will be constructing a schema for a specific source) - const attributeSchema: { event_types: any, source_type: any } = { + const attributeSchema: { event_types: any; source_type: any } = { event_types: [], - source_type: {} + source_type: {}, }; attributeSchema.event_types = attributeDefs.event_types; attributeSchema.source_type['SourceTypeA'] = attributeDefs.source_types.SourceTypeA; @@ -389,8 +388,8 @@ describe('validation tests', () => { const errors = schemaFunctionWithDefs.errors; expect(errors?.length).toBe(1); - expect(errors?.at(0)?.schemaPath).toBe("#/$defs/source_type/SourceTypeA/properties/wrkcat/type"); - expect(errors?.at(0)?.message).toMatch("should be string"); + expect(errors?.at(0)?.schemaPath).toBe('#/$defs/source_type/SourceTypeA/properties/wrkcat/type'); + expect(errors?.at(0)?.message).toMatch('should be string'); }); // test to verify an event's attributes are incorrectly formatted @@ -400,7 +399,7 @@ describe('validation tests', () => { const errors = schemaFunctionWithDefs.errors; expect(errors?.length).toBe(1); - expect(errors?.at(0)?.schemaPath).toBe("#/$defs/event_types/EventTypeA/properties/series/required"); + expect(errors?.at(0)?.schemaPath).toBe('#/$defs/event_types/EventTypeA/properties/series/required'); expect(errors?.at(0)?.message).toMatch("should have required property 'type'"); }); });