UNPKG

@unito/integration-debugger

Version:

The Unito Integration Debugger

190 lines (189 loc) 8.49 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Instance = void 0; exports.create = create; const json_schema_faker_1 = require("json-schema-faker"); const integration_api_1 = require("@unito/integration-api"); const Crawler = __importStar(require("./crawler")); const Validator = __importStar(require("./validator")); async function create(crawler, options = {}) { const validator = await Validator.create(); return new Instance(validator, crawler, options); } class Instance { validator; crawler; referenceCache; constructor(validator, crawler, options = {}) { this.validator = validator; this.crawler = crawler; this.referenceCache = new Map(); // It will try to generate missing properties to fulfill the schema definition. // In our case, we don't want to generate the missing properties for additionalProperties. // Otherwise we end up in a infinite loop. json_schema_faker_1.JSONSchemaFaker.option('fillProperties', false); // TODO — Revisit in 2030-01-02 // Keep the date range close to contemporary dates. json_schema_faker_1.JSONSchemaFaker.option('minDateTime', '2024-01-01'); json_schema_faker_1.JSONSchemaFaker.option('maxDateTime', '2030-01-01'); if (options.random) { json_schema_faker_1.JSONSchemaFaker.option('random', options.random); } json_schema_faker_1.JSONSchemaFaker.format(integration_api_1.FieldValueTypes.DATE_RANGE, () => { return this.generateRange('date'); }); json_schema_faker_1.JSONSchemaFaker.format(integration_api_1.FieldValueTypes.DATETIME_RANGE, () => { return this.generateRange('datetime'); }); } async generate(initialFields, options = {}) { const fields = this.filterFields(initialFields, options); const schema = this.validator.toJsonFields(fields, false); this.prepare(schema); const item = (await json_schema_faker_1.JSONSchemaFaker.resolve(schema, this.validator.schemas)); // If reference fields are present, fetch a valid value from the integration. await this.populateReferences(item, fields); return item; } prepare(schema) { for (const [key, value] of Object.entries(schema ?? {})) { if (value.type === 'object') { this.prepare(value.properties); } // Set by references. // The REFERENCE type in the validator needs work and should be more precise about its accepted values. if (value.additionalProperties) { value.additionalProperties = false; } // Set by references. // The REFERENCE type in the validator accepts a requestSchema field, which is not supported by the generator. if (key === 'requestSchema') { delete schema.requestSchema; } } } async populateReferences(item, fields) { for (const field of fields) { if (field.type === integration_api_1.FieldValueTypes.REFERENCE) { const referenceValue = await this.getReferenceValue(field); if (referenceValue) { item[field.name] = referenceValue; } else { // No reference value can be obtained from the integration. // Leave null and let further validation catch it if needed. item[field.name] = null; } } else if (field.type === integration_api_1.FieldValueTypes.OBJECT) { // Recursively check for each fields in the object to see if we find references. if (field.isArray) { for (const object of (item[field.name] ?? [])) { await this.populateReferences(object, field.fields); } } else { // If field.name value is not set yet, attach an empty object so the recursive calls populates its fields. await this.populateReferences((item[field.name] ??= {}), field.fields); } } } } async getReferenceValue(field) { const referencePath = field.reference.path; let referenceCollection; // First try to find a corresponding reference in the cache. if (this.referenceCache.has(referencePath)) { referenceCollection = this.referenceCache.get(referencePath); } else { // Else call the integration and cache the first page returned for this referencePath. const getCollectionStep = { path: referencePath, requestSchema: undefined, schemaPath: undefined, // Not needed since no validation will be done. operation: Crawler.Operation.GetCollection, parentOperation: Crawler.Operation.GetItem, parentPath: undefined, warnings: [], errors: [], }; const getCollectionStepResult = await this.crawler.execute(getCollectionStep); referenceCollection = getCollectionStepResult.payloadOut; this.referenceCache.set(referencePath, referenceCollection); } const items = [...(referenceCollection?.data ?? [])]; const item1 = json_schema_faker_1.JSONSchemaFaker.random.pick(items); const item2 = json_schema_faker_1.JSONSchemaFaker.random.pick(items.filter(item => item !== item1)); if (field.isArray) { return item1 || item2 ? [item1, item2].filter(Boolean).map(item => ({ path: item.path })) : undefined; } else if (item1) { return { path: item1.path }; } else { return undefined; } } filterFields(fields, options) { if (!options.excludeReadOnly) { return fields; } const filteredFields = []; for (const field of fields) { if (!field.readOnly) { if (field.type === integration_api_1.FieldValueTypes.OBJECT) { filteredFields.push({ ...field, fields: this.filterFields(field.fields, options) }); } else { filteredFields.push(field); } } } return filteredFields; } generateRange(format) { const start = json_schema_faker_1.JSONSchemaFaker.random.date(); const end = new Date(start); // Set date to a future date between 0 and 365 days from the start. // NOTE: setDate will roll over to next month / year as needed end.setDate(end.getDate() + json_schema_faker_1.JSONSchemaFaker.random.number(0, 365)); return format === 'date' ? `${start.toISOString().split('T')[0]}/${end.toISOString().split('T')[0]}` : `${start.toISOString()}/${end.toISOString()}`; } } exports.Instance = Instance;