Skip to content

Commit

Permalink
Enable end-to-end tests for Oracle and SQLite (#11094)
Browse files Browse the repository at this point in the history
* Enable end-to-end tests for Oracle and SQLite

* Add Oracle back to allVendors

* Use yum

* Use dnf

* Actually use yum

* Actually use dnf

* Actually use yum

* Try again

* Try manual install

* Fix env declaration

* Bump knex version (because knex/knex#4844)

* Set max pool size for Oracle

* Add awaitDatabaseConnection

* Cache install and build step

* Run different tests sequentially

* Fix workflow name

* Show test results

* Fix names

* Fix success check

* Fix outputs

* Add oracle driver install

* Fix env

* Revert to previous structure to benchmark performance

* Only build specs and drive packages for unit tests

* Don't install everything to run linters

* Use this branch

* Fix missing lint dep

* Revert "Don't install everything to run linters", also build shared package

* Skip app build for tests

* Don't serve app for e2e tests

* Change time fields to timestamp becaues of inconsistencies between vendors

* Make npm ci faster

Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
  • Loading branch information
Oreilles and rijkvanzanten committed Jan 19, 2022
1 parent 9498410 commit 9dda9ca
Show file tree
Hide file tree
Showing 14 changed files with 73 additions and 78 deletions.
12 changes: 7 additions & 5 deletions .github/workflows/ci.yml
Expand Up @@ -53,27 +53,29 @@ jobs:
lint:
name: Lint
needs: pre_check
uses: directus/directus/.github/workflows/lint.yml@main
uses: directus/directus/.github/workflows/lint.yml@add-e2e-tests-vendors
with:
should_skip: ${{ needs.pre_check.outputs.should_skip }}

codeql_analysis:
name: CodeQL Analysis
needs: pre_check
uses: directus/directus/.github/workflows/codeql-analysis.yml@main
uses: directus/directus/.github/workflows/codeql-analysis.yml@add-e2e-tests-vendors
with:
should_skip: ${{ needs.pre_check.outputs.should_skip }}

unit_tests:
name: Unit Tests
needs: pre_check
uses: directus/directus/.github/workflows/unit-tests.yml@main
uses: directus/directus/.github/workflows/unit-tests.yml@add-e2e-tests-vendors
with:
should_skip: ${{ needs.pre_check.outputs.should_skip }}

e2e_tests:
name: End-to-End Tests
needs: pre_check
uses: directus/directus/.github/workflows/e2e-tests.yml@main
uses: directus/directus/.github/workflows/e2e-tests.yml@add-e2e-tests-vendors
with:
should_skip: ${{ needs.pre_check.outputs.should_skip == 'true' || fromJSON(needs.pre_check.outputs.paths_result).e2e_tests.should_skip }}
should_skip:
${{ needs.pre_check.outputs.should_skip == 'true' ||
fromJSON(needs.pre_check.outputs.paths_result).e2e_tests.should_skip }}
21 changes: 13 additions & 8 deletions .github/workflows/e2e-tests.yml
Expand Up @@ -22,23 +22,28 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v2

- name: Start databases containers
run: docker compose -f tests/docker-compose.yml up -d --quiet-pull

- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: npm

- name: Install dependencies
run: npm ci
run: npm ci --prefer-offline

- name: Build packages
run: npm run build

- name: Wait for databases to be ready
run: docker compose -f tests/docker-compose.yml up -d --quiet-pull --no-recreate --wait
run: npm run build:api

- name: Install oracle client
run: |
sudo apt update -y && sudo apt install -y alien libaio1 && \
wget https://download.oracle.com/otn_software/linux/instantclient/214000/$ORACLE_DL && \
sudo alien -i $ORACLE_DL
env:
ORACLE_DL: oracle-instantclient-basic-21.4.0.0.0-1.el8.x86_64.rpm

- name: Start databases
run: docker compose -f tests/docker-compose.yml up -d --quiet-pull --wait

- name: Run tests
run: npm run test:e2e
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Expand Up @@ -23,7 +23,7 @@ jobs:
cache: npm

- name: Install dependencies
run: npm ci --workspaces=false
run: npm ci --workspaces=false --prefer-offline

- name: Run linters
run: npm run lint
4 changes: 2 additions & 2 deletions .github/workflows/unit-tests.yml
Expand Up @@ -27,10 +27,10 @@ jobs:
cache: npm

- name: Install dependencies
run: npm ci
run: npm ci --prefer-offline

- name: Build packages
run: npm run build
run: npm run build:api

- name: Run tests
run: npm run test
Expand Down
11 changes: 7 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -14,6 +14,7 @@
"format": "prettier --write \"**/*.{js,ts,vue,md,yaml}\"",
"dev": "lerna run dev --stream --parallel",
"build": "lerna run build",
"build:api": "lerna run build --ignore @directus/app",
"pack": "node docker/pack",
"release": "lerna version --force-publish --exact",
"test": "lerna run test",
Expand Down Expand Up @@ -45,7 +46,7 @@
"faker": "5.5.3",
"globby": "11.0.4",
"jest": "27.3.1",
"knex": "0.95.14",
"knex": "0.95.15",
"lerna": "4.0.0",
"lint-staged": "11.2.6",
"listr": "0.14.3",
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/api/items/many-to-many.test.ts
Expand Up @@ -117,7 +117,7 @@ describe('/items', () => {
});

expect(response.data.data).toBe(undefined);
expect(await databases.get(vendor)!('artists_events').select('*').where('id', item[0].id)).toStrictEqual([]);
expect(await databases.get(vendor)!('artists_events').select('*').where('id', item[0].id)).toMatchObject([]);
expect(await databases.get(vendor)!('artists').select('name').where('id', artist.id)).toMatchObject([
{ name: artist.name },
]);
Expand Down Expand Up @@ -297,7 +297,7 @@ describe('/items', () => {

expect(response.data.data).toBe(undefined);
for (let row = 0; row < items.length; row++) {
expect(await databases.get(vendor)!('artists_events').select('*').where('id', items[row].id)).toStrictEqual([]);
expect(await databases.get(vendor)!('artists_events').select('*').where('id', items[row].id)).toMatchObject([]);
}
expect((await databases.get(vendor)!('artists').select('name').where('id', artist.id))[0].name).toBe(artist.name);

Expand Down
42 changes: 8 additions & 34 deletions tests/e2e/api/items/no-relations.test.ts
Expand Up @@ -37,16 +37,11 @@ describe('/items', () => {
});
it.each(vendors)(`%p retrieves a guest's favorite artist`, async (vendor) => {
const url = `http://localhost:${config.envs[vendor]!.PORT!}`;
const guest = createGuest();
const artist = createArtist();
await seedTable(databases.get(vendor)!, 1, 'artists', artist, {
select: ['id'],
});
const guest = createGuest();
guest.favorite_artist = artist.id;
await seedTable(databases.get(vendor)!, 1, 'guests', guest, {
select: ['id'],
where: ['name', guest.name],
});
await seedTable(databases.get(vendor)!, 1, 'artists', artist);
await seedTable(databases.get(vendor)!, 1, 'guests', guest);

const response = await request(url)
.get(`/items/artists/${artist.id}`)
Expand Down Expand Up @@ -164,7 +159,7 @@ describe('/items', () => {
});

expect(response.data.data).toBe(undefined);
expect(await databases.get(vendor)!('artists').select('*').where('id', artist.id)).toStrictEqual([]);
expect(await databases.get(vendor)!('artists').select('*').where('id', artist.id)).toMatchObject([]);
});
});
describe('/:collection GET', () => {
Expand Down Expand Up @@ -256,31 +251,10 @@ describe('/items', () => {
it.each(vendors)(`%p updates many artists to a different name`, async (vendor) => {
const url = `http://localhost:${config.envs[vendor]!.PORT!}`;

let items;
const keys: any[] = [];
const artists = createMany(createArtist, 5, { id: uuid });
if (vendor === 'mssql') {
items = await seedTable(databases.get(vendor)!, 1, 'artists', artists, {
raw: 'SELECT TOP(10) id FROM artists ORDER BY id DESC;',
});
Object.values(items).forEach((item: any) => {
keys.push(item.id);
});
} else if (vendor !== 'postgres' && vendor !== 'postgres10') {
items = await seedTable(databases.get(vendor)!, 1, 'artists', artists, {
raw: 'select id from artists order by id desc limit 10;',
});
Object.values(items[0]).forEach((item: any) => {
keys.push(item.id);
});
} else {
items = await seedTable(databases.get(vendor)!, 1, 'artists', artists, {
raw: 'select id from artists order by id desc limit 10;',
});
Object.values(items.rows).forEach((item: any) => {
keys.push(item.id);
});
}
await seedTable(databases.get(vendor)!, 1, 'artists', artists);
const items = await databases.get(vendor)?.select('id').from('artists').limit(10);
const keys = Object.values(items ?? []).map((item: any) => item.id);

const body = {
keys: keys,
Expand Down Expand Up @@ -319,7 +293,7 @@ describe('/items', () => {

expect(response.data.data).toBe(undefined);
for (let row = 0; row < body.length; row++) {
expect(await databases.get(vendor)!('artists').select('*').where('id', body[row])).toStrictEqual([]);
expect(await databases.get(vendor)!('artists').select('*').where('id', body[row])).toMatchObject([]);
}
});
});
Expand Down
14 changes: 12 additions & 2 deletions tests/e2e/config.ts
@@ -1,4 +1,5 @@
import { Knex } from 'knex';
import { promisify } from 'util';
import { allVendors } from './get-dbs-to-test';

type Vendor = typeof allVendors[number];
Expand Down Expand Up @@ -33,6 +34,7 @@ const directusConfig = {
CACHE_ENABLED: 'false',
RATE_LIMITER_ENABLED: 'false',
LOG_LEVEL: 'error',
SERVE_APP: 'false',
};

const config: Config = {
Expand Down Expand Up @@ -105,7 +107,15 @@ const config: Config = {
sqlite3: {
client: 'sqlite3',
connection: {
filename: './data.db',
filename: './test.db',
},
useNullAsDefault: true,
pool: {
afterCreate: async (conn: any, callback: any) => {
const run = promisify(conn.run.bind(conn));
await run('PRAGMA foreign_keys = ON');
callback(null, conn);
},
},
...knexConfig,
},
Expand Down Expand Up @@ -181,7 +191,7 @@ const config: Config = {
sqlite3: {
...directusConfig,
DB_CLIENT: 'sqlite3',
DB_FILENAME: './data.db',
DB_FILENAME: './test.db',
PORT: '59158',
},
},
Expand Down
3 changes: 1 addition & 2 deletions tests/e2e/get-dbs-to-test.ts
@@ -1,5 +1,4 @@
/** @TODO once Oracle is officially supported, enable it here */
export const allVendors = ['mssql', 'mysql', 'postgres', /* 'oracle', */ 'maria' /*, 'sqlite3'*/, 'postgres10'];
export const allVendors = ['mssql', 'mysql', 'postgres', 'maria', 'oracle', 'sqlite3', 'postgres10'];

const vendors = process.env.TEST_DB?.split(',').map((v) => v.trim()) ?? allVendors;

Expand Down
Expand Up @@ -9,8 +9,8 @@ exports.up = async function (knex) {
table.string('name');
table.date('birthday');
table.string('search_radius');
table.time('earliest_events_to_show');
table.time('latest_events_to_show');
table.timestamp('earliest_events_to_show');
table.timestamp('latest_events_to_show');
table.string('password');
table.integer('shows_attended');
table.uuid('favorite_artist').references('id').inTable('artists');
Expand All @@ -21,7 +21,7 @@ exports.up = async function (knex) {
table.float('cost');
table.text('description');
table.text('tags');
table.time('time');
table.timestamp('time');
});
await knex.schema.createTable('tours', (table) => {
table.uuid('id').primary();
Expand Down
6 changes: 6 additions & 0 deletions tests/e2e/setup/setup.ts
Expand Up @@ -7,6 +7,8 @@ import config from '../config';
import global from './global';
import { spawn, spawnSync } from 'child_process';
import { sleep } from './utils/sleep';
import { writeFileSync } from 'fs';
import { awaitDatabaseConnection } from './utils/await-connection';

let started = false;

Expand All @@ -28,6 +30,10 @@ export default async (): Promise<void> => {
title: config.names[vendor]!,
task: async () => {
const database = knex(config.knexConfig[vendor]!);
await awaitDatabaseConnection(database, config.knexConfig[vendor]!.waitTestSQL);
if (vendor === 'sqlite3') {
writeFileSync('test.db', '');
}
const bootstrap = spawnSync('node', ['api/cli', 'bootstrap'], { env: config.envs[vendor] });
if (bootstrap.stderr.length > 0) {
throw new Error(`Directus-${vendor} bootstrap failed: \n ${bootstrap.stderr.toString()}`);
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/setup/utils/await-connection.ts
Expand Up @@ -3,7 +3,7 @@ import axios from 'axios';
import { sleep } from './sleep';

export async function awaitDatabaseConnection(database: Knex, checkSQL: string): Promise<void | null> {
for (let attempt = 0; attempt <= 10; attempt++) {
for (let attempt = 0; attempt <= 20; attempt++) {
try {
await database.raw(checkSQL);
return null; // success
Expand All @@ -16,7 +16,7 @@ export async function awaitDatabaseConnection(database: Knex, checkSQL: string):
}

export async function awaitDirectusConnection(port: number): Promise<void | null> {
for (let attempt = 0; attempt <= 10; attempt++) {
for (let attempt = 0; attempt <= 20; attempt++) {
try {
await axios.get(`http://localhost:${port}/server/ping`);
return null; // success
Expand Down
19 changes: 7 additions & 12 deletions tests/e2e/setup/utils/factories.ts
Expand Up @@ -31,7 +31,7 @@ type Organizer = {

type Event = {
id: string;
time: string;
time: Date;
description: string;
cost: number;
created_at: Date;
Expand Down Expand Up @@ -140,8 +140,8 @@ export const createEvent = (): Event => ({
id: uuid(),
cost: 1504.04,
description: lorem.paragraphs(2),
created_at: randomDateTime(new Date(1030436120350), new Date(1633466120350)),
time: randomTime(),
created_at: randomDateTime(),
time: randomDateTime(),
tags: `tags
${music.genre()}
${music.genre()}
Expand All @@ -156,10 +156,10 @@ export const createTour = (): Tour => ({

export const createGuest = (): Guest => ({
id: uuid(),
birthday: randomDateTime(new Date(1030436120350), new Date(1633466120350)),
birthday: randomDateTime(),
name: `${name.firstName()} ${name.lastName()}`,
earliest_events_to_show: randomTime(),
latest_events_to_show: randomTime(),
earliest_events_to_show: randomDateTime(),
latest_events_to_show: randomDateTime(),
password: getRandomString(32),
shows_attended: datatype.number(),
});
Expand Down Expand Up @@ -211,15 +211,10 @@ function getRandomInt(max: number) {
return int;
}

function randomDateTime(start: Date, end: Date) {
function randomDateTime(start = new Date(1030436120350), end = new Date(1633466120350)) {
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 = '';
Expand Down

0 comments on commit 9dda9ca

Please sign in to comment.