From 9dda9caddba01d1ebf07273b1d73d873331953dd Mon Sep 17 00:00:00 2001 From: Oreille <33065839+Oreilles@users.noreply.github.com> Date: Wed, 19 Jan 2022 19:00:44 +0100 Subject: [PATCH] Enable end-to-end tests for Oracle and SQLite (#11094) * 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 https://github.com/knex/knex/issues/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 --- .github/workflows/ci.yml | 12 +++--- .github/workflows/e2e-tests.yml | 21 ++++++---- .github/workflows/lint.yml | 2 +- .github/workflows/unit-tests.yml | 4 +- package-lock.json | 11 +++-- package.json | 3 +- tests/e2e/api/items/many-to-many.test.ts | 4 +- tests/e2e/api/items/no-relations.test.ts | 42 ++++--------------- tests/e2e/config.ts | 14 ++++++- tests/e2e/get-dbs-to-test.ts | 3 +- .../20211016054403_create_base_tables.js | 6 +-- tests/e2e/setup/setup.ts | 6 +++ tests/e2e/setup/utils/await-connection.ts | 4 +- tests/e2e/setup/utils/factories.ts | 19 ++++----- 14 files changed, 73 insertions(+), 78 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cea3de94914e6..d35eb348b0a19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 }} diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9dfae6be5a0ae..cdd1427bac91d 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -22,9 +22,6 @@ 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: @@ -32,13 +29,21 @@ jobs: 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 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 18e25029fd8de..a3b2ccd0ec548 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -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 diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index eda603b923087..409c5511a860c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -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 diff --git a/package-lock.json b/package-lock.json index c3205fa3c4de9..08a8135b1994f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,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", @@ -25972,8 +25972,9 @@ } }, "node_modules/knex": { - "version": "0.95.14", - "license": "MIT", + "version": "0.95.15", + "resolved": "https://registry.npmjs.org/knex/-/knex-0.95.15.tgz", + "integrity": "sha512-Loq6WgHaWlmL2bfZGWPsy4l8xw4pOE+tmLGkPG0auBppxpI0UcK+GYCycJcqz9W54f2LiGewkCVLBm3Wq4ur/w==", "dependencies": { "colorette": "2.0.16", "commander": "^7.1.0", @@ -62430,7 +62431,9 @@ "peer": true }, "knex": { - "version": "0.95.14", + "version": "0.95.15", + "resolved": "https://registry.npmjs.org/knex/-/knex-0.95.15.tgz", + "integrity": "sha512-Loq6WgHaWlmL2bfZGWPsy4l8xw4pOE+tmLGkPG0auBppxpI0UcK+GYCycJcqz9W54f2LiGewkCVLBm3Wq4ur/w==", "requires": { "colorette": "2.0.16", "commander": "^7.1.0", diff --git a/package.json b/package.json index afd45b75e6678..47a4196f7771c 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/tests/e2e/api/items/many-to-many.test.ts b/tests/e2e/api/items/many-to-many.test.ts index ccb63118e8492..7a8771e0dca06 100644 --- a/tests/e2e/api/items/many-to-many.test.ts +++ b/tests/e2e/api/items/many-to-many.test.ts @@ -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 }, ]); @@ -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); diff --git a/tests/e2e/api/items/no-relations.test.ts b/tests/e2e/api/items/no-relations.test.ts index 96588afc422a4..79bc7495e5f8b 100644 --- a/tests/e2e/api/items/no-relations.test.ts +++ b/tests/e2e/api/items/no-relations.test.ts @@ -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}`) @@ -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', () => { @@ -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, @@ -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([]); } }); }); diff --git a/tests/e2e/config.ts b/tests/e2e/config.ts index f6c44a2acb749..a78f60c30eecc 100644 --- a/tests/e2e/config.ts +++ b/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]; @@ -33,6 +34,7 @@ const directusConfig = { CACHE_ENABLED: 'false', RATE_LIMITER_ENABLED: 'false', LOG_LEVEL: 'error', + SERVE_APP: 'false', }; const config: Config = { @@ -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, }, @@ -181,7 +191,7 @@ const config: Config = { sqlite3: { ...directusConfig, DB_CLIENT: 'sqlite3', - DB_FILENAME: './data.db', + DB_FILENAME: './test.db', PORT: '59158', }, }, diff --git a/tests/e2e/get-dbs-to-test.ts b/tests/e2e/get-dbs-to-test.ts index 04bd68182bfaa..488239049badc 100644 --- a/tests/e2e/get-dbs-to-test.ts +++ b/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; diff --git a/tests/e2e/setup/migrations/20211016054403_create_base_tables.js b/tests/e2e/setup/migrations/20211016054403_create_base_tables.js index 1172b5ddda9f9..6dc94062823d1 100644 --- a/tests/e2e/setup/migrations/20211016054403_create_base_tables.js +++ b/tests/e2e/setup/migrations/20211016054403_create_base_tables.js @@ -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'); @@ -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(); diff --git a/tests/e2e/setup/setup.ts b/tests/e2e/setup/setup.ts index 267b756812678..7b0cda119252e 100644 --- a/tests/e2e/setup/setup.ts +++ b/tests/e2e/setup/setup.ts @@ -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; @@ -28,6 +30,10 @@ export default async (): Promise => { 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()}`); diff --git a/tests/e2e/setup/utils/await-connection.ts b/tests/e2e/setup/utils/await-connection.ts index 64618fd9ad025..04e4000a7d562 100644 --- a/tests/e2e/setup/utils/await-connection.ts +++ b/tests/e2e/setup/utils/await-connection.ts @@ -3,7 +3,7 @@ import axios from 'axios'; import { sleep } from './sleep'; export async function awaitDatabaseConnection(database: Knex, checkSQL: string): Promise { - for (let attempt = 0; attempt <= 10; attempt++) { + for (let attempt = 0; attempt <= 20; attempt++) { try { await database.raw(checkSQL); return null; // success @@ -16,7 +16,7 @@ export async function awaitDatabaseConnection(database: Knex, checkSQL: string): } export async function awaitDirectusConnection(port: number): Promise { - 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 diff --git a/tests/e2e/setup/utils/factories.ts b/tests/e2e/setup/utils/factories.ts index 75cdc8131d7dd..56355a715d022 100644 --- a/tests/e2e/setup/utils/factories.ts +++ b/tests/e2e/setup/utils/factories.ts @@ -31,7 +31,7 @@ type Organizer = { type Event = { id: string; - time: string; + time: Date; description: string; cost: number; created_at: Date; @@ -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()} @@ -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(), }); @@ -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 = '';