mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Docs for testing the API (#10275)
* mock-knex * test on the migrations run started. * test passing for run.up() * reorganize /tests/ to allow integration tests * e2e setup changes * e2e jest.config moved * e2e paths fixed, integration config * add nonadmin role and user seed+migration * auth/login w/ documentation (docs will be moved) * update user seed * add postgres10 to the ci? * argon2 saves the day * items tests passing with postgres10 support * removed comments * move generateHash out of directus_users Co-authored-by: Jay Cammarano <jaycammarano@gmail.com>
This commit is contained in:
@@ -1,44 +0,0 @@
|
||||
import { Knex } from 'knex';
|
||||
import axios from 'axios';
|
||||
|
||||
export async function awaitDatabaseConnection(
|
||||
database: Knex,
|
||||
checkSQL: string,
|
||||
currentAttempt = 0
|
||||
): Promise<void | null> {
|
||||
try {
|
||||
await database.raw(checkSQL);
|
||||
} catch {
|
||||
if (currentAttempt === 10) {
|
||||
throw new Error(`Couldn't connect to DB`);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(async () => {
|
||||
await awaitDatabaseConnection(database, checkSQL, currentAttempt + 1);
|
||||
resolve(null);
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function awaitDirectusConnection(port = 6100, currentAttempt = 0): Promise<void | null> {
|
||||
try {
|
||||
await axios.get(`http://localhost:${port}/server/ping`);
|
||||
} catch {
|
||||
if (currentAttempt === 10) {
|
||||
throw new Error(`Couldn't connect to Directus`);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await awaitDirectusConnection(port, currentAttempt + 1);
|
||||
resolve(null);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
import {
|
||||
createArtist,
|
||||
createEvent,
|
||||
createTour,
|
||||
createGuest,
|
||||
seedTable,
|
||||
createOrganizer,
|
||||
createMany,
|
||||
} from './factories';
|
||||
import knex, { Knex } from 'knex';
|
||||
import config from '../../config';
|
||||
import { getDBsToTest } from '../../get-dbs-to-test';
|
||||
|
||||
describe('Item factories', () => {
|
||||
describe('createArtist', () => {
|
||||
it('returns an artist object of column names and values', () => {
|
||||
expect(createArtist()).toMatchObject({
|
||||
id: expect.any(String),
|
||||
name: expect.any(String),
|
||||
members: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createEvent', () => {
|
||||
it('returns an event object of column names and values', () => {
|
||||
expect(createEvent()).toMatchObject({
|
||||
id: expect.any(String),
|
||||
cost: expect.any(Number),
|
||||
created_at: expect.any(Date),
|
||||
description: expect.any(String),
|
||||
tags: expect.any(String),
|
||||
time: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGuest', () => {
|
||||
it('returns an object of column names and values', () => {
|
||||
expect(createGuest()).toMatchObject({
|
||||
id: expect.any(String),
|
||||
birthday: expect.any(Date),
|
||||
shows_attended: expect.any(Number),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('createOrganizer', () => {
|
||||
it('returns an object of column names and values', () => {
|
||||
expect(createOrganizer()).toMatchObject({
|
||||
id: expect.any(String),
|
||||
company_name: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('createTour', () => {
|
||||
it('revenue to be the correct type', () => {
|
||||
expect(typeof createTour().revenue).toBe('bigint');
|
||||
});
|
||||
});
|
||||
describe('createMany', () => {
|
||||
it('returns an array of Artists', () => {
|
||||
const artists = createMany(createArtist, 4);
|
||||
expect(artists[0]).toMatchObject({ name: expect.any(String) });
|
||||
expect(artists.length).toBe(4);
|
||||
});
|
||||
it('returns an array of Guests with a favorite_artist when passed in options', () => {
|
||||
const artist = createArtist();
|
||||
const guests = createMany(createGuest, 4, { favorite_artist: artist.id });
|
||||
expect(guests[0]).toMatchObject({ name: expect.any(String), favorite_artist: expect.any(String) });
|
||||
expect(guests.length).toBe(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('seeding databases', () => {
|
||||
const databases = new Map<string, Knex>();
|
||||
beforeAll(async () => {
|
||||
const vendors = getDBsToTest();
|
||||
|
||||
for (const vendor of vendors) {
|
||||
databases.set(vendor, knex(config.knexConfig[vendor]!));
|
||||
}
|
||||
});
|
||||
afterAll(async () => {
|
||||
const vendors = getDBsToTest();
|
||||
|
||||
for (const vendor of vendors) {
|
||||
databases.set(vendor, knex(config.knexConfig[vendor]!));
|
||||
await databases.get(vendor)!.destroy();
|
||||
}
|
||||
});
|
||||
describe('seedTable', () => {
|
||||
it.each(getDBsToTest())('%p returns void when there is no options', async (vendor) => {
|
||||
const database = databases.get(vendor);
|
||||
expect(await seedTable(database!, 5, 'artists', createArtist)).toBe(undefined);
|
||||
});
|
||||
it.each(getDBsToTest())('%p to insert the correct number of artists', async (vendor) => {
|
||||
const database = databases.get(vendor);
|
||||
|
||||
expect(await seedTable(database!, 1606, 'artists', createArtist)).toBe(undefined);
|
||||
|
||||
const count = await database!('artists').count('*', { as: 'artists' });
|
||||
if (typeof count[0]?.artists === 'string') expect(parseInt(count[0]?.artists)).toBeGreaterThanOrEqual(1606);
|
||||
});
|
||||
it.each(getDBsToTest())('%p has a response based on passed in options select and where', async (vendor) => {
|
||||
const database = databases.get(vendor);
|
||||
const artist = createArtist();
|
||||
const options = { select: ['name', 'id'], where: ['name', artist.name] };
|
||||
const insert: any = await seedTable(database!, 1, 'artists', artist, options);
|
||||
expect(insert[0]).toMatchObject({
|
||||
id: expect.any(String),
|
||||
name: artist.name,
|
||||
});
|
||||
});
|
||||
it.each(getDBsToTest())('%p has a response based on passed in options raw', async (vendor) => {
|
||||
const database = databases.get(vendor);
|
||||
const artist = createArtist();
|
||||
const options = { raw: `SELECT name from artists WHERE name='${artist.name}';` };
|
||||
const response: any = await seedTable(database!, 1, 'artists', artist, options);
|
||||
if (vendor === 'postgres') {
|
||||
expect(response.rows[0]).toStrictEqual({
|
||||
name: artist.name,
|
||||
});
|
||||
}
|
||||
if (vendor === 'mssql') {
|
||||
expect(response[0]).toStrictEqual({
|
||||
name: artist.name,
|
||||
});
|
||||
}
|
||||
if (vendor === 'mysql' || vendor === 'maria') {
|
||||
expect(response[0][0]).toMatchObject({
|
||||
name: artist.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('inserting factories', () => {
|
||||
describe('createArtist', () => {
|
||||
it.each(getDBsToTest())('%p returns an artist object of column names and values', async (vendor) => {
|
||||
const database = databases.get(vendor);
|
||||
const artist = createArtist();
|
||||
if (vendor === 'postgres' && typeof artist.members === 'string') {
|
||||
const options = { select: ['*'], where: ['name', artist.name] };
|
||||
artist.members = JSON.parse(artist.members);
|
||||
|
||||
expect(await seedTable(database!, 1, 'artists', artist, options)).toMatchObject([
|
||||
{ id: artist.id, name: artist.name, members: artist.members },
|
||||
]);
|
||||
} else if (vendor === 'mysql') {
|
||||
const artist: any = createArtist();
|
||||
const options = { select: ['*'], where: ['name', artist.name] };
|
||||
expect(await seedTable(database!, 1, 'artists', artist, options)).toMatchObject([
|
||||
{ id: artist.id, name: artist.name, members: `{"guitar": "${JSON.parse(artist.members).guitar}"}` },
|
||||
]);
|
||||
} else {
|
||||
const artist = createArtist();
|
||||
const options = { select: ['*'], where: ['name', artist.name] };
|
||||
expect(await seedTable(database!, 1, 'artists', artist, options)).toMatchObject([
|
||||
{ name: artist.name, members: artist.members },
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('createEvent', () => {
|
||||
it.each(getDBsToTest())('%p returns an event object of column names and values', async (vendor) => {
|
||||
const database = databases.get(vendor)!;
|
||||
const event = createEvent();
|
||||
const options = { select: ['*'], where: ['id', event.id] };
|
||||
if (vendor === 'mssql') {
|
||||
expect(await seedTable(database, 1, 'events', event, options)).toMatchObject([
|
||||
{
|
||||
id: event.id?.toUpperCase(),
|
||||
cost: event.cost,
|
||||
created_at: expect.any(Date),
|
||||
description: event.description,
|
||||
tags: event.tags,
|
||||
time: expect.any(Date),
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
expect(await seedTable(database, 1, 'events', event, options)).toMatchObject([
|
||||
{
|
||||
id: event.id,
|
||||
cost: event.cost,
|
||||
created_at: expect.any(Date),
|
||||
description: event.description,
|
||||
tags: event.tags,
|
||||
time: event.time,
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGuest', () => {
|
||||
it.each(getDBsToTest())('%p returns an guest object of column names and values', async (vendor) => {
|
||||
const database = databases.get(vendor)!;
|
||||
const guest = createGuest();
|
||||
const options = { select: ['*'], where: ['name', guest.name] };
|
||||
const insertedGuest = await seedTable(database, 1, 'guests', guest, options);
|
||||
if (vendor === 'mssql') {
|
||||
expect(insertedGuest).toMatchObject([
|
||||
{
|
||||
name: guest.name,
|
||||
birthday: expect.any(Date),
|
||||
earliest_events_to_show: expect.any(Date),
|
||||
latest_events_to_show: expect.any(Date),
|
||||
password: guest.password,
|
||||
shows_attended: guest.shows_attended,
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
expect(insertedGuest).toMatchObject([
|
||||
{
|
||||
earliest_events_to_show: expect.any(String),
|
||||
latest_events_to_show: expect.any(String),
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTour', () => {
|
||||
it.each(getDBsToTest())('%p returns an tour object of column names and values', async (vendor) => {
|
||||
const database = databases.get(vendor)!;
|
||||
const tour = createTour();
|
||||
const options = { select: ['*'], where: ['revenue', tour.revenue] };
|
||||
const insertedTour = await seedTable(database, 1, 'tours', tour, options);
|
||||
|
||||
if (vendor === 'mysql' || vendor === 'maria') {
|
||||
expect(insertedTour[0].revenue.toString()).toBe(tour.revenue.toString());
|
||||
} else {
|
||||
expect(insertedTour).toMatchObject([
|
||||
{
|
||||
revenue: tour.revenue.toString(),
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('createOrganizer', () => {
|
||||
it.each(getDBsToTest())('%p returns an organizer object of column names and values', async (vendor) => {
|
||||
const database = databases.get(vendor)!;
|
||||
const organizer = createOrganizer();
|
||||
const options = { select: ['*'], where: ['id', organizer.id] };
|
||||
expect(await seedTable(database, 1, 'organizers', organizer, options)).toMatchObject([
|
||||
{
|
||||
company_name: expect.any(String),
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createMany', () => {
|
||||
it.each(getDBsToTest())('%p returns array of guests', async (vendor) => {
|
||||
const database = databases.get(vendor)!;
|
||||
const artist = createArtist();
|
||||
await seedTable(database, 1, 'artists', artist, { select: ['id'] });
|
||||
const options = { select: ['*'], where: ['favorite_artist', artist.id] };
|
||||
const response = await seedTable(
|
||||
database,
|
||||
1,
|
||||
'guests',
|
||||
createMany(createGuest, 5, { favorite_artist: artist.id }),
|
||||
options
|
||||
);
|
||||
|
||||
expect(response[0]).toMatchObject({ name: expect.any(String), favorite_artist: expect.any(String) });
|
||||
expect(response.length).toBe(5);
|
||||
});
|
||||
it.each(getDBsToTest())('%p returns array of guests', async (vendor) => {
|
||||
const database = databases.get(vendor)!;
|
||||
const artist = createArtist();
|
||||
await seedTable(database, 1, 'artists', artist, { select: ['id'] });
|
||||
const options = { select: ['*'], where: ['favorite_artist', artist.id] };
|
||||
const response = await seedTable(
|
||||
database,
|
||||
1,
|
||||
'guests',
|
||||
createMany(createGuest, 5, { favorite_artist: artist.id }),
|
||||
options
|
||||
);
|
||||
|
||||
expect(response[0]).toMatchObject({ name: expect.any(String), favorite_artist: expect.any(String) });
|
||||
expect(response.length).toBe(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,230 +0,0 @@
|
||||
import { music, internet, name, datatype, lorem } from 'faker';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
type Guest = {
|
||||
id: string;
|
||||
name: string;
|
||||
birthday: Date;
|
||||
earliest_events_to_show: Date | string;
|
||||
latest_events_to_show: Date | string;
|
||||
password: string;
|
||||
shows_attended: number;
|
||||
favorite_artist?: string | Artist;
|
||||
};
|
||||
|
||||
type Artist = {
|
||||
id: string;
|
||||
name: string;
|
||||
members: string | Record<string, any>;
|
||||
};
|
||||
|
||||
type Tour = {
|
||||
id: string;
|
||||
revenue: bigint;
|
||||
};
|
||||
|
||||
type Organizer = {
|
||||
id: string;
|
||||
company_name: string;
|
||||
};
|
||||
|
||||
type Event = {
|
||||
id: string;
|
||||
time: string;
|
||||
description: string;
|
||||
cost: number;
|
||||
created_at: Date;
|
||||
tags: string;
|
||||
};
|
||||
|
||||
type JoinTable = {
|
||||
[column: string]: number | string;
|
||||
};
|
||||
|
||||
export type Item = Guest | Artist | Tour | Organizer | Event | JoinTable;
|
||||
|
||||
/*
|
||||
* Options Example: Artist
|
||||
* Select: ['name', 'members']
|
||||
* Where: ['id', uuid]
|
||||
*/
|
||||
|
||||
type SeedOptions = {
|
||||
select?: string[];
|
||||
where?: any[];
|
||||
raw?: string;
|
||||
};
|
||||
|
||||
/* CreateManyOptions:
|
||||
* {column: countOfRelation}
|
||||
* EX: {favorites_artists: 45}
|
||||
* used for tying together relations randomly
|
||||
*/
|
||||
|
||||
type CreateManyOptions = {
|
||||
[column: string]: string | (() => unknown);
|
||||
};
|
||||
|
||||
export const seedTable = async function (
|
||||
database: Knex<any, unknown>,
|
||||
count: number,
|
||||
table: string,
|
||||
factory: Item | (() => Item) | Item[],
|
||||
options?: SeedOptions
|
||||
): Promise<void | any[] | any> {
|
||||
const row: Record<string, number> = {};
|
||||
if (Array.isArray(factory)) {
|
||||
await database(table).insert(factory);
|
||||
} else if (typeof factory === 'object') {
|
||||
if (count > 1) {
|
||||
try {
|
||||
const fakeRows = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
fakeRows.push(factory);
|
||||
row[table] = row[table]! + 1;
|
||||
}
|
||||
await database(table).insert(fakeRows);
|
||||
} catch (error: any) {
|
||||
throw new Error(error);
|
||||
}
|
||||
} else {
|
||||
await database(table).insert(factory);
|
||||
row[table] = row[table]! + 1;
|
||||
}
|
||||
} else if (count >= 200) {
|
||||
try {
|
||||
let fakeRows: any[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
fakeRows.push(factory());
|
||||
if (i % 200 === 0) {
|
||||
await database.batchInsert(table, fakeRows, 200);
|
||||
fakeRows = [];
|
||||
}
|
||||
row[table] = row[table]! + 1;
|
||||
}
|
||||
if (count - row[table]! < 200) {
|
||||
await database.batchInsert(table, fakeRows, 200);
|
||||
fakeRows = [];
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw new Error(error);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const fakeRows = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
fakeRows.push(factory());
|
||||
row[table] = row[table]! + 1;
|
||||
}
|
||||
await database(table).insert(fakeRows);
|
||||
} catch (error: any) {
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
if (options) {
|
||||
const { select, where, raw } = options;
|
||||
if (raw) return await database.schema.raw(raw!);
|
||||
else if (where && select) return await database(table).select(select).where(where[0], where[1]);
|
||||
else return await database(table).select(select!);
|
||||
}
|
||||
};
|
||||
|
||||
export const createArtist = (): Artist => ({
|
||||
id: uuid(),
|
||||
name: internet.userName(),
|
||||
members: JSON.stringify({ guitar: internet.userName() }),
|
||||
});
|
||||
|
||||
export const createEvent = (): Event => ({
|
||||
id: uuid(),
|
||||
cost: datatype.float(),
|
||||
description: lorem.paragraphs(2),
|
||||
created_at: randomDateTime(new Date(1030436120350), new Date(1633466120350)),
|
||||
time: randomTime(),
|
||||
tags: `tags
|
||||
${music.genre()}
|
||||
${music.genre()}
|
||||
${music.genre()}
|
||||
`,
|
||||
});
|
||||
|
||||
export const createTour = (): Tour => ({
|
||||
id: uuid(),
|
||||
revenue: BigInt(getRandomInt(Number.MAX_SAFE_INTEGER)),
|
||||
});
|
||||
|
||||
export const createGuest = (): Guest => ({
|
||||
id: uuid(),
|
||||
birthday: randomDateTime(new Date(1030436120350), new Date(1633466120350)),
|
||||
name: `${name.firstName()} ${name.lastName()}`,
|
||||
earliest_events_to_show: randomTime(),
|
||||
latest_events_to_show: randomTime(),
|
||||
password: getRandomString(32),
|
||||
shows_attended: datatype.number(),
|
||||
});
|
||||
|
||||
export const createOrganizer = (): Organizer => ({
|
||||
id: uuid(),
|
||||
company_name: `${name.firstName()} ${name.lastName()}`,
|
||||
});
|
||||
|
||||
export const createMany = (factory: (() => Item) | Record<string, any>, count: number, options?: CreateManyOptions) => {
|
||||
const items: Item[] = [];
|
||||
if (options && typeof factory !== 'object') {
|
||||
for (let rows = 0; rows < count; rows++) {
|
||||
const item: any = factory();
|
||||
for (const [column, value] of Object.entries(options)) {
|
||||
if (typeof value === 'string') {
|
||||
item[column] = value;
|
||||
} else {
|
||||
item[column] = value();
|
||||
}
|
||||
}
|
||||
items.push(item);
|
||||
}
|
||||
} else if (typeof factory !== 'object') {
|
||||
for (let rows = 0; rows < count; rows++) {
|
||||
items.push(factory());
|
||||
}
|
||||
} else {
|
||||
for (let rows = 0; rows < count; rows++) {
|
||||
const item: any = factory;
|
||||
for (const [column, value] of Object.entries(options!)) {
|
||||
if (typeof value === 'string') {
|
||||
item[column] = value;
|
||||
} else {
|
||||
item[column] = value();
|
||||
}
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
};
|
||||
|
||||
function getRandomInt(max: number) {
|
||||
let int = 0;
|
||||
while (int === 0) {
|
||||
int = Math.floor(Math.random() * max);
|
||||
}
|
||||
return int;
|
||||
}
|
||||
|
||||
function randomDateTime(start: Date, end: Date) {
|
||||
return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
|
||||
}
|
||||
|
||||
function randomTime() {
|
||||
const dateTime = randomDateTime(new Date(1030436120350), new Date(1633466120350)).toUTCString();
|
||||
return dateTime.substring(17, 25);
|
||||
}
|
||||
|
||||
function getRandomString(length: number) {
|
||||
const randomChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += randomChars.charAt(Math.floor(Math.random() * randomChars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user