mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
* Add black box tests * Revert docker compose file * Update workflow * Try use workflow from dev repo * Increase seedDB() timeout * Disable other checks for now * Change DB sequence * Update jest moduleNameMapper * Update workflow's docker-compose.yml path * Slice array first * Remove differentiation of status code * Delete field only after foreign key constraints are removed * Add checks for different types of primary key * Test global query filter for all field types * Increase timeout for m2o seeding * Add case insensitive string operators * Update filter check to run on relational fields * Enable time field checks * Add seeded random and fix relational seeding * Add casting for integer and bigInteger * Minor fixes * Reduce bigInt values * Separate seeding of DB structure from values * Add primaryKey seeding function * Use automatic IDs except for string pk * Try fix ci * Update package-lock.json * Update common.test for concealed user tokens * Use dynamic field type for m2o.test relational fields * Temporary disable missing nicontains for string type * Add support for alias type filtering * Fix relational filter operator checks * Add initial o2m test * Remove integer pk limit * Add empty checks for string and uuid null * Limit generated integer value to 4 bytes * Patch timezone tests for MSSQL * Remove sample query filter test * Fix timezone test for sqlite * Fix MSSQL uuids * Fix MSSQL timestamp inaccuracy * Cast datetime schema to milliseconds for comparison * Fix MySQL / Maria timestamp inaccuracy * Fix MySQL / Maria between operator inconsistency for float type * Fix missing time datatype in Oracle * Skip filter testing on Oracle * Enable o2m filter tests for other collections * Run tests only on SQLite for PRs unless the Full Tests label exists * Try fix actions * Refactor github actions * Update tests flow setup to use getURL() * Start postgres docker * Reinstate package-lock * Fix geometry test * Remove .gitkeep files * Add todo.md * Rename black box to blackbox Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
359 lines
9.6 KiB
TypeScript
359 lines
9.6 KiB
TypeScript
import { v5 as uuid } from 'uuid';
|
|
import * as seedrandom from 'seedrandom';
|
|
import { PrimaryKeyType } from '@common/types';
|
|
|
|
const SEED_UUID_NAMESPACE = 'e81a0012-568b-415c-96fa-66508f594067';
|
|
const FIVE_YEARS_IN_MILLISECONDS = 5 * 365 * 24 * 60 * 60 * 1000;
|
|
|
|
type OptionsSeedGenerateBase = {
|
|
quantity: number;
|
|
seed?: string | undefined;
|
|
};
|
|
|
|
export type OptionsSeedGeneratePrimaryKeys = OptionsSeedGenerateBase & OptionsSeedGenerateInteger;
|
|
|
|
export type OptionsSeedGenerateInteger = OptionsSeedGenerateBase & {
|
|
startsAt?: number;
|
|
incremental?: boolean;
|
|
};
|
|
|
|
export type OptionsSeedGenerateBigInteger = OptionsSeedGenerateBase;
|
|
|
|
export type OptionsSeedGenerateString = OptionsSeedGenerateBase & {
|
|
startsWith?: string;
|
|
endsWith?: string;
|
|
};
|
|
|
|
export type OptionsSeedGenerateUUID = OptionsSeedGenerateBase;
|
|
|
|
export type OptionsSeedGenerateBoolean = OptionsSeedGenerateBase;
|
|
|
|
export type OptionsSeedGenerateCSV = OptionsSeedGenerateBase;
|
|
|
|
export type OptionsSeedGenerateDate = OptionsSeedGenerateBase & {
|
|
startsFrom?: Date | string;
|
|
endsOn?: Date | string;
|
|
};
|
|
|
|
export type OptionsSeedGenerateDateTime = OptionsSeedGenerateTimestamp;
|
|
|
|
export type OptionsSeedGenerateDecimal = OptionsSeedGenerateFloat;
|
|
|
|
export type OptionsSeedGenerateFloat = OptionsSeedGenerateBase & {
|
|
startsAt?: number;
|
|
endsAt?: number;
|
|
amount?: number;
|
|
precision?: number;
|
|
};
|
|
|
|
export type OptionsSeedGenerateGeometry = OptionsSeedGenerateBase;
|
|
|
|
export type OptionsSeedGenerateHash = OptionsSeedGenerateBase;
|
|
|
|
export type OptionsSeedGenerateJSON = OptionsSeedGenerateBase;
|
|
|
|
export type OptionsSeedGenerateTime = OptionsSeedGenerateBase & {
|
|
startsFrom?: Date | string;
|
|
endsOn?: Date | string;
|
|
};
|
|
|
|
export type OptionsSeedGenerateTimestamp = OptionsSeedGenerateBase & {
|
|
startsFrom?: Date;
|
|
endsOn?: Date;
|
|
};
|
|
|
|
export const SeedFunctions = {
|
|
generatePrimaryKeys,
|
|
generateValues: {
|
|
integer: generateInteger,
|
|
bigInteger: generateBigInteger,
|
|
uuid: generateUUID,
|
|
string: generateString,
|
|
text: generateString,
|
|
boolean: generateBoolean,
|
|
csv: generateCSV,
|
|
date: generateDate,
|
|
dateTime: generateDateTime,
|
|
decimal: generateDecimal,
|
|
float: generateFloat,
|
|
// geometry: null,
|
|
// hash: null,
|
|
// json: null,
|
|
time: generateTime,
|
|
timestamp: generateTimestamp,
|
|
},
|
|
};
|
|
|
|
function generatePrimaryKeys(pkType: PrimaryKeyType, options: OptionsSeedGeneratePrimaryKeys) {
|
|
switch (pkType) {
|
|
case 'integer':
|
|
return generateInteger(options);
|
|
case 'uuid':
|
|
return generateUUID(options);
|
|
case 'string':
|
|
return generateString(options);
|
|
default:
|
|
throw new Error(`Unsupported primary key type: ${pkType}`);
|
|
}
|
|
}
|
|
|
|
function generateInteger(options: OptionsSeedGenerateInteger) {
|
|
const random = seedrandom.xor128(options.seed ?? 'default');
|
|
const values = [];
|
|
|
|
if (options.incremental) {
|
|
const randomStartValue = random.int32();
|
|
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
if (options.startsAt) {
|
|
values.push(options.startsAt + i);
|
|
} else {
|
|
values.push(randomStartValue + i);
|
|
}
|
|
}
|
|
} else {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
if (options.startsAt) {
|
|
values.push(random.int32() + options.startsAt + i);
|
|
} else {
|
|
values.push(random.int32() + i);
|
|
}
|
|
}
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
function generateBigInteger(options: OptionsSeedGenerateBigInteger) {
|
|
const random = seedrandom.xor128(options.seed ?? 'default');
|
|
const values = [];
|
|
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
// TODO: Maximise array length back to 15, fix filter parsing which reduces this value
|
|
const hexString = Array(12)
|
|
.fill(0)
|
|
.map(() => Math.round(random() * 0xf).toString(16))
|
|
.join('');
|
|
|
|
values.push(BigInt(`0x7${hexString}`));
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
function generateFloat(options: OptionsSeedGenerateFloat) {
|
|
const random = seedrandom.xor128(options.seed ?? 'default');
|
|
const values = [];
|
|
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
if (options.startsAt && options.endsAt) {
|
|
values.push(
|
|
Number((options.startsAt + random() * (options.endsAt - options.startsAt + 1)).toFixed(options.precision ?? 3))
|
|
);
|
|
} else if (options.startsAt) {
|
|
values.push(Number((options.startsAt + random() * (options.amount ?? 100)).toFixed(options.precision ?? 3)));
|
|
} else if (options.endsAt) {
|
|
values.push(Number((options.endsAt - random() * (options.amount ?? 100)).toFixed(options.precision ?? 3)));
|
|
} else {
|
|
values.push(Number((random() * (options.amount ?? 100)).toFixed(options.precision ?? 3)));
|
|
}
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
function generateDecimal(options: OptionsSeedGenerateDecimal) {
|
|
return generateFloat(options);
|
|
}
|
|
|
|
function generateString(options: OptionsSeedGenerateString) {
|
|
const values = [];
|
|
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
let value = uuid(String((options.seed ?? 'default') + i), SEED_UUID_NAMESPACE).replace(/-/g, '');
|
|
if (options.startsWith) {
|
|
value = options.startsWith + value;
|
|
}
|
|
if (options.endsWith) {
|
|
value = value + options.endsWith;
|
|
}
|
|
values.push(value);
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
function generateUUID(options: OptionsSeedGenerateUUID) {
|
|
const values = [];
|
|
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(uuid(String((options.seed ?? 'default') + i), SEED_UUID_NAMESPACE));
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
function generateBoolean(options: OptionsSeedGenerateBoolean) {
|
|
const random = seedrandom.xor128(options.seed ?? 'default');
|
|
const values = [];
|
|
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(random() >= 0.5);
|
|
}
|
|
|
|
// Ensure that there is always true and false values to test on
|
|
if (options.quantity >= 2) {
|
|
values[0] = true;
|
|
values[1] = false;
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
function generateCSV(options: OptionsSeedGenerateCSV) {
|
|
const values = [];
|
|
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(uuid(String((options.seed ?? 'default') + i), SEED_UUID_NAMESPACE));
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
function generateDate(options: OptionsSeedGenerateDate) {
|
|
const random = seedrandom.xor128(options.seed ?? 'default');
|
|
const values = [];
|
|
|
|
if (typeof options.startsFrom === 'string') {
|
|
options.startsFrom = new Date(Date.parse(options.startsFrom));
|
|
}
|
|
|
|
if (typeof options.endsOn === 'string') {
|
|
options.endsOn = new Date(Date.parse(options.endsOn));
|
|
}
|
|
|
|
if (options.startsFrom && options.endsOn) {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(
|
|
new Date(
|
|
Math.floor(
|
|
random() * (options.endsOn.getTime() - options.startsFrom.getTime() + 1) + options.startsFrom.getTime()
|
|
)
|
|
)
|
|
.toISOString()
|
|
.substring(0, 10)
|
|
);
|
|
}
|
|
} else if (options.startsFrom) {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(
|
|
new Date(Math.floor(random() * FIVE_YEARS_IN_MILLISECONDS + options.startsFrom.getTime()))
|
|
.toISOString()
|
|
.substring(0, 10)
|
|
);
|
|
}
|
|
} else if (options.endsOn) {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(new Date(Math.floor(random() * options.endsOn.getTime())).toISOString().substring(0, 10));
|
|
}
|
|
} else {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(new Date(Math.floor(random() * FIVE_YEARS_IN_MILLISECONDS)).toISOString().substring(0, 10));
|
|
}
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
function generateDateTime(options: OptionsSeedGenerateDateTime) {
|
|
const values = generateTimestamp(options);
|
|
|
|
for (let i = 0; i < values.length; i++) {
|
|
values[i] = values[i].slice(0, -5);
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
function generateTime(options: OptionsSeedGenerateTime) {
|
|
const random = seedrandom.xor128(options.seed ?? 'default');
|
|
const values = [];
|
|
let timeStart = options.startsFrom;
|
|
let timeEnd = options.endsOn;
|
|
|
|
if (typeof timeStart === 'string') {
|
|
timeStart = new Date(`1970-01-01T${timeStart}`);
|
|
}
|
|
|
|
if (typeof timeEnd === 'string') {
|
|
timeEnd = new Date(`1970-01-01T${timeEnd}`);
|
|
}
|
|
|
|
if (timeStart && timeEnd) {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(
|
|
new Date(Math.floor(random() * (timeEnd.getTime() - timeStart.getTime() + 1) + timeStart.getTime()))
|
|
.toISOString()
|
|
.slice(11, 19)
|
|
);
|
|
}
|
|
} else if (timeStart) {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(
|
|
new Date(Math.floor(random() * (86400000 /* 24h */ - timeStart.getTime()) + timeStart.getTime()))
|
|
.toISOString()
|
|
.slice(11, 19)
|
|
);
|
|
}
|
|
} else if (timeEnd) {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(new Date(Math.floor(random() * timeEnd.getTime())).toISOString().slice(11, 19));
|
|
}
|
|
} else {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(new Date(Math.floor(random() * FIVE_YEARS_IN_MILLISECONDS)).toISOString().slice(11, 19));
|
|
}
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
function generateTimestamp(options: OptionsSeedGenerateTimestamp) {
|
|
const random = seedrandom.xor128(options.seed ?? 'default');
|
|
const values = [];
|
|
|
|
if (options.startsFrom && options.endsOn) {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(
|
|
new Date(
|
|
Math.floor(
|
|
random() * (options.endsOn.getTime() - options.startsFrom.getTime() + 1) + options.startsFrom.getTime()
|
|
)
|
|
).toISOString()
|
|
);
|
|
}
|
|
} else if (options.startsFrom) {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(
|
|
new Date(Math.floor(random() * FIVE_YEARS_IN_MILLISECONDS + options.startsFrom.getTime())).toISOString()
|
|
);
|
|
}
|
|
} else if (options.endsOn) {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(new Date(Math.floor(random() * options.endsOn.getTime())).toISOString());
|
|
}
|
|
} else {
|
|
for (let i = 0; i < options.quantity; i++) {
|
|
values.push(new Date(Math.floor(random() * FIVE_YEARS_IN_MILLISECONDS)).toISOString());
|
|
}
|
|
}
|
|
|
|
// Overcome MySQL / Maria created without decimal accuracy
|
|
// Overcome MSSQL specific accuracy up to 1/300th of a second
|
|
for (let index = 0; index < values.length; index++) {
|
|
values[index] = values[index].slice(0, 20) + '000Z';
|
|
}
|
|
|
|
return values;
|
|
}
|