303 lines
11 KiB
JavaScript
303 lines
11 KiB
JavaScript
/**
|
|
* Mock Data Generation for Cypress Testing
|
|
*
|
|
* This module provides functions to generate realistic test data for Koha objects
|
|
* based on OpenAPI schema definitions. It uses Faker.js to generate random data
|
|
* that conforms to the API specifications.
|
|
*
|
|
* @module mockData
|
|
*/
|
|
|
|
const { faker } = require("@faker-js/faker");
|
|
const { readYamlFile } = require("./../plugins/readYamlFile.js");
|
|
const { query } = require("./db.js");
|
|
const fs = require("fs");
|
|
|
|
/**
|
|
* Cache to store generated ID values to prevent duplicates
|
|
* @type {Set<string>}
|
|
*/
|
|
const generatedDataCache = new Set();
|
|
|
|
/**
|
|
* Generates mock data for a specific data type based on OpenAPI schema properties.
|
|
*
|
|
* @function generateMockData
|
|
* @param {string} type - The data type (string, integer, boolean, array, number, date, date-time)
|
|
* @param {Object} properties - OpenAPI schema properties for the field
|
|
* @param {Array} [properties.enum] - Enumerated values to choose from
|
|
* @param {number} [properties.maxLength] - Maximum length for strings
|
|
* @param {number} [properties.minLength] - Minimum length for strings
|
|
* @returns {*} Generated mock data appropriate for the type
|
|
* @private
|
|
* @example
|
|
* // Generate a string with max length 50
|
|
* const name = generateMockData('string', { maxLength: 50 });
|
|
*
|
|
* // Generate from enum values
|
|
* const status = generateMockData('string', { enum: ['active', 'inactive'] });
|
|
*/
|
|
const generateMockData = (type, properties) => {
|
|
if (properties.hasOwnProperty("enum")) {
|
|
let values = properties.enum;
|
|
return values[Math.floor(Math.random() * values.length)];
|
|
}
|
|
|
|
switch (type) {
|
|
case "string":
|
|
if (properties?.maxLength) {
|
|
// The probability to have a string with length=1 is the same as length=10
|
|
// We have very limited pool of possible values for length=1 which will result in a "Duplicate ID" error from the server
|
|
// Setting minLength to 3 to prevent this kind of failures
|
|
let minLength =
|
|
properties.minLength === 1 ||
|
|
properties.minLength === undefined
|
|
? 3
|
|
: properties.minLength;
|
|
|
|
if (
|
|
properties.maxLength !== undefined &&
|
|
properties.maxLength < minLength
|
|
) {
|
|
minLength = properties.maxLength;
|
|
}
|
|
return (value = faker.string.alpha({
|
|
length: {
|
|
min: minLength,
|
|
max: properties.maxLength,
|
|
},
|
|
}));
|
|
}
|
|
return (value = faker.lorem.words(3));
|
|
case "integer":
|
|
// Do not return more than int(11);
|
|
return faker.number.int(2 ** 31 - 1);
|
|
case "boolean":
|
|
return faker.datatype.boolean();
|
|
case "array":
|
|
return [faker.lorem.word(), faker.lorem.word()];
|
|
case "number":
|
|
return faker.number.float();
|
|
case "date":
|
|
return new Date().toISOString().split("T")[0];
|
|
case "date-time":
|
|
return new Date().toISOString();
|
|
default:
|
|
return faker.lorem.word();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generates mock data for an entire object based on OpenAPI schema properties.
|
|
*
|
|
* @function generateDataFromSchema
|
|
* @param {Object} properties - OpenAPI schema properties object
|
|
* @param {Object} [values={}] - Override values for specific fields
|
|
* @returns {Object} Generated mock object with all required fields
|
|
* @private
|
|
* @description This function:
|
|
* - Iterates through all properties in the schema
|
|
* - Generates appropriate mock data for each field
|
|
* - Handles object relationships (libraries, items, etc.)
|
|
* - Ensures unique values for ID fields
|
|
* - Applies any override values provided
|
|
*
|
|
* Special handling for object relationships:
|
|
* - home_library/holding_library -> generates library object
|
|
* - item_type -> generates item_type object
|
|
* - Automatically sets corresponding _id fields
|
|
*/
|
|
const generateDataFromSchema = (properties, values = {}) => {
|
|
const mockData = {};
|
|
const ids = {};
|
|
Object.entries(properties).forEach(([key, value]) => {
|
|
if (values.hasOwnProperty(key)) {
|
|
mockData[key] = values[key];
|
|
} else {
|
|
let data;
|
|
let type = value.type;
|
|
if (Array.isArray(type)) {
|
|
type = type.filter(t => t != '"null"')[0];
|
|
}
|
|
|
|
type =
|
|
value?.format == "date" || value?.format == "date-time"
|
|
? value.format
|
|
: type;
|
|
let fk_name;
|
|
if (type == "object") {
|
|
switch (key) {
|
|
case "home_library":
|
|
case "holding_library":
|
|
data = buildSampleObject({ object: "library" });
|
|
fk_name = "library_id";
|
|
break;
|
|
case "pickup_library":
|
|
data = buildSampleObject({ object: "library" });
|
|
fk_name = "pickup_library_id";
|
|
break;
|
|
case "library":
|
|
data = buildSampleObject({ object: "library" });
|
|
fk_name = "library_id";
|
|
break;
|
|
case "item_type":
|
|
data = buildSampleObject({ object: "item_type" });
|
|
fk_name = "item_type_id";
|
|
break;
|
|
case "item":
|
|
data = buildSampleObject({ object: "item" });
|
|
fk_name = "item_id";
|
|
break;
|
|
case "_strings":
|
|
break;
|
|
default:
|
|
try {
|
|
data = generateMockData(type, value);
|
|
} catch (e) {
|
|
throw new Error(
|
|
`Failed to generate data for (${key}): ${e}`
|
|
);
|
|
}
|
|
}
|
|
if (typeof data === "object") {
|
|
ids[key] = data[fk_name];
|
|
}
|
|
} else {
|
|
try {
|
|
if (key.match(/_id$/)) {
|
|
let attempts = 0;
|
|
|
|
do {
|
|
data = generateMockData(type, value);
|
|
attempts++;
|
|
if (attempts > 10) {
|
|
throw new Error(
|
|
"Could not generate unique string after 10 attempts"
|
|
);
|
|
}
|
|
} while (generatedDataCache.has(data));
|
|
|
|
generatedDataCache.add(data);
|
|
} else {
|
|
data = generateMockData(type, value);
|
|
}
|
|
} catch (e) {
|
|
throw new Error(
|
|
`Failed to generate data for ${key} (${type}): ${e}`
|
|
);
|
|
}
|
|
}
|
|
mockData[key] = data;
|
|
}
|
|
});
|
|
|
|
Object.keys(ids).forEach(k => {
|
|
if (
|
|
mockData.hasOwnProperty(k + "_id") &&
|
|
!values.hasOwnProperty(k + "_id")
|
|
) {
|
|
mockData[k + "_id"] = ids[k];
|
|
}
|
|
});
|
|
|
|
return mockData;
|
|
};
|
|
|
|
/**
|
|
* Builds an array of sample objects based on OpenAPI schema definitions.
|
|
*
|
|
* @function buildSampleObjects
|
|
* @param {Object} params - Configuration parameters
|
|
* @param {string} params.object - Object type to generate (must match YAML file name)
|
|
* @param {Object} [params.values] - Override values for specific fields
|
|
* @param {number} [params.count=1] - Number of objects to generate
|
|
* @returns {Array<Object>} Array of generated objects
|
|
* @throws {Error} When object type is not supported or generation fails
|
|
* @description This function:
|
|
* - Reads the OpenAPI schema from api/v1/swagger/definitions/{object}.yaml
|
|
* - Generates the specified number of objects
|
|
* - Applies any override values to all generated objects
|
|
* - Ensures all objects conform to the API schema
|
|
*
|
|
* @example
|
|
* // Generate 3 patron objects
|
|
* const patrons = buildSampleObjects({
|
|
* object: 'patron',
|
|
* count: 3
|
|
* });
|
|
*
|
|
* @example
|
|
* // Generate 2 items with specific library
|
|
* const items = buildSampleObjects({
|
|
* object: 'item',
|
|
* values: { library_id: 'CPL' },
|
|
* count: 2
|
|
* });
|
|
*/
|
|
const buildSampleObjects = ({ object, values, count = 1 }) => {
|
|
const yamlPath = `api/v1/swagger/definitions/${object}.yaml`;
|
|
if (!fs.existsSync(yamlPath)) {
|
|
throw new Error(
|
|
`Object type not supported: '${object}'. No spec file.`
|
|
);
|
|
}
|
|
const schema = readYamlFile(yamlPath);
|
|
let generatedObject;
|
|
try {
|
|
generatedObject = Array.from({ length: count }, () =>
|
|
generateDataFromSchema(schema.properties, values)
|
|
);
|
|
} catch (e) {
|
|
throw new Error(`Failed to generate data for object '${object}': ${e}`);
|
|
}
|
|
return generatedObject;
|
|
};
|
|
|
|
/**
|
|
* Builds a single sample object based on OpenAPI schema definitions.
|
|
*
|
|
* @function buildSampleObject
|
|
* @param {Object} params - Configuration parameters
|
|
* @param {string} params.object - Object type to generate (must match YAML file name)
|
|
* @param {Object} [params.values={}] - Override values for specific fields
|
|
* @returns {Object} Generated object conforming to API schema
|
|
* @throws {Error} When object type is not supported or generation fails
|
|
* @description This is a convenience function that generates a single object
|
|
* by calling buildSampleObjects with count=1 and returning the first result.
|
|
*
|
|
* Supported object types include:
|
|
* - patron: Library patron/borrower
|
|
* - item: Bibliographic item
|
|
* - biblio: Bibliographic record
|
|
* - library: Library/branch
|
|
* - hold: Hold/reservation request
|
|
* - checkout: Circulation checkout
|
|
* - vendor: Acquisitions vendor
|
|
* - basket: Acquisitions basket
|
|
* - And others as defined in api/v1/swagger/definitions/
|
|
*
|
|
* @example
|
|
* // Generate a single patron
|
|
* const patron = buildSampleObject({ object: 'patron' });
|
|
*
|
|
* @example
|
|
* // Generate an item with specific values
|
|
* const item = buildSampleObject({
|
|
* object: 'item',
|
|
* values: {
|
|
* barcode: '12345678',
|
|
* home_library_id: 'CPL'
|
|
* }
|
|
* });
|
|
*/
|
|
const buildSampleObject = ({ object, values = {} }) => {
|
|
return buildSampleObjects({ object, values })[0];
|
|
};
|
|
|
|
module.exports = {
|
|
generateMockData,
|
|
generateDataFromSchema,
|
|
buildSampleObject,
|
|
buildSampleObjects,
|
|
};
|