@unito/integration-debugger
Version:
The Unito Integration Debugger
190 lines (189 loc) • 8.49 kB
JavaScript
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;
;