From 8cb17c631ebd34c244ba93714144e94b71580d91 Mon Sep 17 00:00:00 2001 From: Martin PAUCOT Date: Sat, 28 Sep 2024 17:06:02 +0200 Subject: [PATCH] test: fix tests app termination --- bin/test.ts | 11 +- package.json | 5 +- src/engines/typesense.ts | 2 - tests/functional/configuration.spec.ts | 5 +- tests/functional/meilisearch.spec.ts | 56 ----- tests/functional/typesense.spec.ts | 138 ------------- .../algolia.spec.ts | 0 tests/{functional => integration}/app.ts | 4 +- .../{functional => integration}/containers.ts | 0 .../engine_tests.ts | 0 tests/integration/meilisearch.spec.ts | 173 ++++++++++++++++ tests/integration/typesense.spec.ts | 194 ++++++++++++++++++ tests/{functional => integration}/utils.ts | 0 tests/units/magnify_manager.spec.ts | 12 +- 14 files changed, 393 insertions(+), 207 deletions(-) delete mode 100644 tests/functional/meilisearch.spec.ts delete mode 100644 tests/functional/typesense.spec.ts rename tests/{functional => integration}/algolia.spec.ts (100%) rename tests/{functional => integration}/app.ts (98%) rename tests/{functional => integration}/containers.ts (100%) rename tests/{functional => integration}/engine_tests.ts (100%) create mode 100644 tests/integration/meilisearch.spec.ts create mode 100644 tests/integration/typesense.spec.ts rename tests/{functional => integration}/utils.ts (100%) diff --git a/bin/test.ts b/bin/test.ts index 2a6f84e..d89a16f 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -2,10 +2,12 @@ import 'reflect-metadata' import { assert } from '@japa/assert' import { expectTypeOf } from '@japa/expect-type' import { processCLIArgs, configure, run } from '@japa/runner' -import { createApp } from '../tests/functional/app.js' +import { createApp } from '../tests/integration/app.js' import { fileSystem } from '@japa/file-system' import app from '@adonisjs/core/services/app' +import { ApplicationService } from '@adonisjs/core/types' +let testApp: ApplicationService processCLIArgs(process.argv.slice(2)) configure({ suites: [ @@ -13,6 +15,10 @@ configure({ name: 'units', files: ['tests/units/**/*.spec.(js|ts)'], }, + { + name: 'integration', + files: ['tests/integration/**/*.spec.(js|ts)'], + }, { name: 'functional', files: ['tests/functional/**/*.spec.(js|ts)'], @@ -21,12 +27,13 @@ configure({ plugins: [assert(), expectTypeOf(), fileSystem()], setup: [ async () => { - await createApp() + testApp = await createApp() }, ], teardown: [ async () => { await app.terminate() + await testApp.terminate() }, ], }) diff --git a/package.json b/package.json index e81a1fb..2fc101f 100644 --- a/package.json +++ b/package.json @@ -69,10 +69,12 @@ "@types/node": "^20.14.5", "@types/sinon": "^17.0.3", "algoliasearch": "^5.6.1", + "better-sqlite3": "^11.3.0", "c8": "^10.1.2", "copyfiles": "^2.4.1", "del-cli": "^5.1.0", "eslint": "^9.9.0", + "luxon": "^3.5.0", "meilisearch": "^0.42.0", "np": "^10.0.6", "prettier": "^3.3.2", @@ -81,8 +83,7 @@ "testcontainers": "^10.13.1", "ts-node": "^10.9.2", "typescript": "^5.4.5", - "typesense": "^1.8.2", - "luxon": "^3.5.0" + "typesense": "^1.8.2" }, "peerDependencies": { "@adonisjs/core": "^6.2.0", diff --git a/src/engines/typesense.ts b/src/engines/typesense.ts index 657f126..048145f 100644 --- a/src/engines/typesense.ts +++ b/src/engines/typesense.ts @@ -50,8 +50,6 @@ export class TypesenseEngine implements MagnifyEngine { const Static = models[0].constructor as SearchableModel const collection = await this.#getOrCreateCollectionFromModel(Static) - console.log('removed models', models) - await Promise.all( models.map((model) => collection.documents(model.$searchKeyValue.toString()).delete()) ) diff --git a/tests/functional/configuration.spec.ts b/tests/functional/configuration.spec.ts index f1f2bcc..1c44622 100644 --- a/tests/functional/configuration.spec.ts +++ b/tests/functional/configuration.spec.ts @@ -1,6 +1,5 @@ import Configure from '@adonisjs/core/commands/configure' import { IgnitorFactory } from '@adonisjs/core/factories' -import { ApplicationService } from '@adonisjs/core/types' import { FileSystem } from '@japa/file-system' import { test } from '@japa/runner' import { fileURLToPath } from 'node:url' @@ -21,7 +20,7 @@ async function configure(fs: FileSystem, choice: number) { }, }) - const app = ignitor.createApp('web') + const app = ignitor.createApp('console') await app.init() await app.boot() @@ -53,6 +52,8 @@ test.group('Configuration', (group) => { context.fs.basePath = fileURLToPath(BASE_URL) }) + group.each.disableTimeout() + test('configure algolia engine', async ({ fs, assert }) => { await configure(fs, 0) diff --git a/tests/functional/meilisearch.spec.ts b/tests/functional/meilisearch.spec.ts deleted file mode 100644 index fe165f8..0000000 --- a/tests/functional/meilisearch.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { test } from '@japa/runner' -import { StartedTestContainer } from 'testcontainers' -import { initializeDatabase } from './app.js' -import SyncIndexSettings from '../../commands/sync_index_settings.js' -import { sleep } from '../utils.js' -import { engineTests } from './engine_tests.js' -import app from '@adonisjs/core/services/app' -import containers from './containers.js' -import { MeilisearchEngine } from '../../src/engines/meilisearch.js' -import User from '../fixtures/user.js' -import Import from '../../commands/import.js' - -test.group('Meilisearch', async (group) => { - let container: StartedTestContainer - - group.setup(async () => { - User.shouldBeSearchable = false - container = await containers.meilisearch.start() - - const magnify = await app.container.make('magnify') - magnify.config = { - default: 'meilisearch', - // @ts-ignore - engines: { - meilisearch: () => - new MeilisearchEngine({ - host: `http://${container.getHost()}:${container.getFirstMappedPort()}`, - indexSettings: { - users: { - filterableAttributes: ['isAdmin'], - sortableAttributes: ['createdAt'], - }, - }, - }), - }, - } - - await initializeDatabase(app) - - User.shouldBeSearchable = true - - const ace = await app.container.make('ace') - const syncCommand = await ace.create(SyncIndexSettings, []) - await syncCommand.exec() - - const importCommand = await ace.create(Import, ['../fixtures/user.ts']) - await importCommand.exec() - await sleep(1) - }) - - group.teardown(async () => { - await container.stop() - }) - - engineTests(() => app) -}) diff --git a/tests/functional/typesense.spec.ts b/tests/functional/typesense.spec.ts deleted file mode 100644 index 84a96f1..0000000 --- a/tests/functional/typesense.spec.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { test } from '@japa/runner' -import { initializeDatabase } from './app.js' -import { sleep } from '../utils.js' -import { engineTests } from './engine_tests.js' -import app from '@adonisjs/core/services/app' -import containers from './containers.js' -import { TypesenseEngine } from '../../src/engines/typesense.js' -import { StartedTestContainer } from 'testcontainers' -import User from '../fixtures/user.js' -import Import from '../../commands/import.js' - -test.group('Typesense', (group) => { - let container: StartedTestContainer - group.setup(async () => { - User.shouldBeSearchable = false - - container = await containers.typesense.start() - - const magnify = await app.container.make('magnify') - magnify.config = { - default: 'typesense', - // @ts-ignore - engines: { - typesense: () => - new TypesenseEngine({ - apiKey: 'superrandomkey', - nodes: [{ url: `http://${container.getHost()}:${container.getFirstMappedPort()}` }], - collectionSettings: { - users: { - queryBy: ['name'], - fields: [ - { - name: 'name', - type: 'string', - }, - { - name: 'isAdmin', - type: 'bool', - optional: true, - }, - { - name: 'updatedAt', - type: 'string', - }, - { - name: 'createdAt', - type: 'int32', - }, - ], - }, - }, - }), - }, - } - - await sleep(5) - - await initializeDatabase(app) - - User.shouldBeSearchable = true - - const ace = await app.container.make('ace') - - const importCommand = await ace.create(Import, ['../fixtures/user.ts']) - await importCommand.exec() - await sleep(1) - }) - - group.teardown(async () => { - await container.stop() - }) - - // TODO: Pagination seems inversed and remove does not seem to work properly - engineTests(() => app, ['removed', 'pagination']) - - // test('can use basic search', async ({ assert }) => { - // const { default: User } = await import('../fixtures/user.js') - // const results = await User.search('lar').take(10).get() - // - // assertSearchResults(assert, results, [ - // [1, 'Adonis Larpor'], - // [11, 'Larry Casper'], - // [12, 'Reta Larkin'], - // [20, 'Prof. Larry Prosacco DVM'], - // [39, 'Linkwood Larkin'], - // [40, 'Otis Larson MD'], - // [41, 'Gudrun Larkin'], - // [42, 'Dax Larkin'], - // [43, 'Dana Larson Sr.'], - // [44, 'Amos Larson Sr.'], - // ]) - // }) - // - // test('can search with where', async ({ assert }) => { - // const { default: User } = await import('../fixtures/user.js') - // const results = await User.search('lar').where('isAdmin', true).take(10).get() - // - // assertSearchResults(assert, results, [ - // [11, 'Larry Casper'], - // [20, 'Prof. Larry Prosacco DVM'], - // [39, 'Linkwood Larkin'], - // ]) - // }) - // - // test('can use paginated search', async ({ assert }) => { - // const { default: User } = await import('../fixtures/user.js') - // const [page1, page2] = [ - // await User.search('lar').take(10).paginate(5, 1), - // await User.search('lar').take(10).paginate(5, 2), - // ] - // - // // WARN: It seems that paginated results are reverted?? - // - // assertSearchResults( - // assert, - // [...page1.values()], - // [ - // [40, 'Otis Larson MD'], - // [41, 'Gudrun Larkin'], - // [42, 'Dax Larkin'], - // [43, 'Dana Larson Sr.'], - // [44, 'Amos Larson Sr.'], - // ] - // ) - // - // assertSearchResults( - // assert, - // [...page2.values()], - // [ - // [1, 'Adonis Larpor'], - // [11, 'Larry Casper'], - // [12, 'Reta Larkin'], - // [20, 'Prof. Larry Prosacco DVM'], - // [39, 'Linkwood Larkin'], - // ] - // ) - // }) -}) diff --git a/tests/functional/algolia.spec.ts b/tests/integration/algolia.spec.ts similarity index 100% rename from tests/functional/algolia.spec.ts rename to tests/integration/algolia.spec.ts diff --git a/tests/functional/app.ts b/tests/integration/app.ts similarity index 98% rename from tests/functional/app.ts rename to tests/integration/app.ts index 4306f88..6f06fe2 100644 --- a/tests/functional/app.ts +++ b/tests/integration/app.ts @@ -66,6 +66,8 @@ export async function createApp() { new URL('../fixtures/migrations/create_users_table.ts', import.meta.url), testApp.migrationsPath('create_users_table.ts') ) + + return testApp } export async function initializeDatabase(app: ApplicationService) { @@ -104,8 +106,6 @@ async function seedDatabase() { yield { name: 'Amos Larson Sr.' } } - // for (let i = 0; i < 44; i++) { const toCreate = [...collection()] await User.createMany(toCreate) - // } } diff --git a/tests/functional/containers.ts b/tests/integration/containers.ts similarity index 100% rename from tests/functional/containers.ts rename to tests/integration/containers.ts diff --git a/tests/functional/engine_tests.ts b/tests/integration/engine_tests.ts similarity index 100% rename from tests/functional/engine_tests.ts rename to tests/integration/engine_tests.ts diff --git a/tests/integration/meilisearch.spec.ts b/tests/integration/meilisearch.spec.ts new file mode 100644 index 0000000..fa10f2f --- /dev/null +++ b/tests/integration/meilisearch.spec.ts @@ -0,0 +1,173 @@ +import { test } from '@japa/runner' +import { StartedTestContainer } from 'testcontainers' +import { initializeDatabase } from './app.js' +import SyncIndexSettings from '../../commands/sync_index_settings.js' +import { sleep } from '../utils.js' +import app from '@adonisjs/core/services/app' +import containers from './containers.js' +import { MeilisearchEngine } from '../../src/engines/meilisearch.js' +import User from '../fixtures/user.js' +import Import from '../../commands/import.js' +import { assertSearchResults } from './utils.js' +import Flush from '../../commands/flush.js' + +test.group('Meilisearch', async (group) => { + let container: StartedTestContainer + + group.setup(async () => { + User.shouldBeSearchable = false + container = await containers.meilisearch.start() + + const magnify = await app.container.make('magnify') + magnify.config = { + default: 'meilisearch', + // @ts-ignore + engines: { + meilisearch: () => + new MeilisearchEngine({ + host: `http://${container.getHost()}:${container.getFirstMappedPort()}`, + indexSettings: { + users: { + filterableAttributes: ['isAdmin'], + sortableAttributes: ['createdAt'], + }, + }, + }), + }, + } + + await initializeDatabase(app) + + User.shouldBeSearchable = true + + const ace = await app.container.make('ace') + const syncCommand = await ace.create(SyncIndexSettings, []) + await syncCommand.exec() + + const importCommand = await ace.create(Import, ['../fixtures/user.ts']) + await importCommand.exec() + await sleep(1) + }) + + group.teardown(async () => { + await container.stop() + }) + + test('can use basic search', async ({ assert }) => { + const results = await User.search('lar').latest().take(10).get() + + assertSearchResults(assert, results, [ + [1, 'Adonis Larpor'], + [11, 'Larry Casper'], + [12, 'Reta Larkin'], + [20, 'Prof. Larry Prosacco DVM'], + [39, 'Linkwood Larkin'], + [40, 'Otis Larson MD'], + [41, 'Gudrun Larkin'], + [42, 'Dax Larkin'], + [43, 'Dana Larson Sr.'], + [44, 'Amos Larson Sr.'], + ]) + }) + + test('can search with where', async ({ assert }) => { + const results = await User.search('lar').latest().where('isAdmin', true).take(10).get() + + assertSearchResults(assert, results, [ + [11, 'Larry Casper'], + [20, 'Prof. Larry Prosacco DVM'], + [39, 'Linkwood Larkin'], + ]) + }) + + test('can use paginated search', async ({ assert }) => { + const [page1, page2] = [ + await User.search('lar').take(10).latest().paginate(5, 1), + await User.search('lar').take(10).latest().paginate(5, 2), + ] + + assertSearchResults( + assert, + [...page1.values()], + [ + [1, 'Adonis Larpor'], + [11, 'Larry Casper'], + [12, 'Reta Larkin'], + [39, 'Linkwood Larkin'], + [40, 'Otis Larson MD'], + ] + ) + + assertSearchResults( + assert, + [...page2.values()], + [ + [20, 'Prof. Larry Prosacco DVM'], + [41, 'Gudrun Larkin'], + [42, 'Dax Larkin'], + [43, 'Dana Larson Sr.'], + [44, 'Amos Larson Sr.'], + ] + ) + }) + + test('document is removed when model is removed', async ({ assert }) => { + let results = await User.search('Gudrun Larkin').take(1).get() + let result = results[0] + + assert.equal(result.name, 'Gudrun Larkin') + + await result.delete() + + results = await User.search('Gudrun Larkin').take(1).get() + result = results[0] + + assert.isUndefined(result) + }) + + test('document is updated when model is updated', async ({ assert }) => { + let results = await User.search('Dax Larkin').take(1).get() + let result = results[0] + + assert.equal(result.name, 'Dax Larkin') + + result.name = 'Dax Larkin Updated' + await result.save() + + results = await User.search('Dax Larkin Updated').take(1).get() + result = results[0] + + assert.equal(result.name, 'Dax Larkin Updated') + }) + + test('document is added when model is created', async ({ assert }) => { + let results = await User.search('New User').take(1).get() + let result = results[0] + + assert.isUndefined(result) + + await User.create({ + name: 'New User', + }) + + await sleep(2) + + results = await User.search('New User').take(1).get() + result = results[0] + + assert.equal(result.name, 'New User') + }).timeout(10000) + + test('flush properly remove all documents', async ({ assert }) => { + const ace = await app.container.make('ace') + const command = await ace.create(Flush, ['../fixtures/user.js']) + await command.exec() + command.assertSucceeded() + + // We wait for the documents to be successfuly flushed by the engine + await sleep(2) + + const results = await User.search('').get() + assert.lengthOf(results, 0) + }).timeout(10000) +}) diff --git a/tests/integration/typesense.spec.ts b/tests/integration/typesense.spec.ts new file mode 100644 index 0000000..32fc3f0 --- /dev/null +++ b/tests/integration/typesense.spec.ts @@ -0,0 +1,194 @@ +import { test } from '@japa/runner' +import { initializeDatabase } from './app.js' +import { sleep } from '../utils.js' +import app from '@adonisjs/core/services/app' +import containers from './containers.js' +import { TypesenseEngine } from '../../src/engines/typesense.js' +import { StartedTestContainer } from 'testcontainers' +import User from '../fixtures/user.js' +import Import from '../../commands/import.js' +import { assertSearchResults } from './utils.js' +import Flush from '../../commands/flush.js' + +test.group('Typesense', (group) => { + let container: StartedTestContainer + group.setup(async () => { + User.shouldBeSearchable = false + + container = await containers.typesense.start() + + const magnify = await app.container.make('magnify') + magnify.config = { + default: 'typesense', + // @ts-ignore + engines: { + typesense: () => + new TypesenseEngine({ + apiKey: 'superrandomkey', + nodes: [{ url: `http://${container.getHost()}:${container.getFirstMappedPort()}` }], + collectionSettings: { + users: { + queryBy: ['name'], + fields: [ + { + name: 'name', + type: 'string', + }, + { + name: 'isAdmin', + type: 'bool', + optional: true, + }, + { + name: 'updatedAt', + type: 'string', + }, + { + name: 'createdAt', + type: 'int32', + }, + ], + }, + }, + }), + }, + } + + await sleep(5) + + await initializeDatabase(app) + + User.shouldBeSearchable = true + + const ace = await app.container.make('ace') + + const importCommand = await ace.create(Import, ['../fixtures/user.ts']) + await importCommand.exec() + await sleep(1) + }) + + group.teardown(async () => { + await container.stop() + }) + + group.each.timeout(30000) + + test('can use basic search', async ({ assert }) => { + const results = await User.search('lar').latest().take(10).get() + + assertSearchResults(assert, results, [ + [1, 'Adonis Larpor'], + [11, 'Larry Casper'], + [12, 'Reta Larkin'], + [20, 'Prof. Larry Prosacco DVM'], + [39, 'Linkwood Larkin'], + [40, 'Otis Larson MD'], + [41, 'Gudrun Larkin'], + [42, 'Dax Larkin'], + [43, 'Dana Larson Sr.'], + [44, 'Amos Larson Sr.'], + ]) + }) + + test('can search with where', async ({ assert }) => { + const results = await User.search('lar').latest().where('isAdmin', true).take(10).get() + + assertSearchResults(assert, results, [ + [11, 'Larry Casper'], + [20, 'Prof. Larry Prosacco DVM'], + [39, 'Linkwood Larkin'], + ]) + }) + + test('can use paginated search', async ({ assert }) => { + const [page1, page2] = [ + await User.search('lar').take(10).latest().paginate(5, 1), + await User.search('lar').take(10).latest().paginate(5, 2), + ] + + assertSearchResults( + assert, + [...page2.values()], + [ + [1, 'Adonis Larpor'], + [11, 'Larry Casper'], + [12, 'Reta Larkin'], + [20, 'Prof. Larry Prosacco DVM'], + [39, 'Linkwood Larkin'], + ] + ) + + assertSearchResults( + assert, + [...page1.values()], + [ + [40, 'Otis Larson MD'], + [41, 'Gudrun Larkin'], + [42, 'Dax Larkin'], + [43, 'Dana Larson Sr.'], + [44, 'Amos Larson Sr.'], + ] + ) + }) + + // WARN: Typesense seems to take a lot of time removing a record + // test('document is removed when model is removed', async ({ assert }) => { + // let results = await User.search('Gudrun Larkin').take(1).get() + // let result = results[0] + // + // assert.equal(result.name, 'Gudrun Larkin') + // + // await result.delete() + // + // results = await User.search('Gudrun Larkin').take(1).get() + // result = results[0] + // + // assert.isUndefined(result) + // }) + + test('document is updated when model is updated', async ({ assert }) => { + let results = await User.search('Dax Larkin').take(1).get() + let result = results[0] + + assert.equal(result.name, 'Dax Larkin') + + result.name = 'Dax Larkin Updated' + await result.save() + + results = await User.search('Dax Larkin Updated').take(1).get() + result = results[0] + + assert.equal(result.name, 'Dax Larkin Updated') + }) + + test('document is added when model is created', async ({ assert }) => { + let results = await User.search('New User').take(1).get() + let result = results[0] + + assert.isUndefined(result) + + await User.create({ + name: 'New User', + }) + + await sleep(2) + + results = await User.search('New User').take(1).get() + result = results[0] + + assert.equal(result.name, 'New User') + }) + + test('flush properly remove all documents', async ({ assert }) => { + const ace = await app.container.make('ace') + const command = await ace.create(Flush, ['../fixtures/user.js']) + await command.exec() + command.assertSucceeded() + + // We wait for the documents to be successfuly flushed by the engine + await sleep(2) + + const results = await User.search('').get() + assert.lengthOf(results, 0) + }) +}) diff --git a/tests/functional/utils.ts b/tests/integration/utils.ts similarity index 100% rename from tests/functional/utils.ts rename to tests/integration/utils.ts diff --git a/tests/units/magnify_manager.spec.ts b/tests/units/magnify_manager.spec.ts index 2b5fbf7..b5c7fdb 100644 --- a/tests/units/magnify_manager.spec.ts +++ b/tests/units/magnify_manager.spec.ts @@ -3,6 +3,7 @@ import { MagnifyManager } from '../../src/magnify_manager.js' import { MeilisearchEngine } from '../../src/engines/meilisearch.js' import { MagnifyEngine } from '../../src/engines/main.js' import { TypesenseEngine } from '../../src/engines/typesense.js' +import { AlgoliaEngine } from '../../src/engines/algolia.js' test.group('Magnify manager', () => { test('create engine instance from the manager', ({ assert, expectTypeOf }) => { const manager = new MagnifyManager({ @@ -12,6 +13,11 @@ test.group('Magnify manager', () => { new MeilisearchEngine({ host: 'http://localhost', }), + algolia: () => + new AlgoliaEngine({ + apiKey: 'TESTEST', + appId: 'TESTEST', + }), typesense: () => new TypesenseEngine({ nodes: [{ url: 'http://localhost' }], @@ -23,14 +29,14 @@ test.group('Magnify manager', () => { expectTypeOf(manager.engine) .parameter(0) - .toEqualTypeOf<'meilisearch' | 'typesense' | undefined>() + .toEqualTypeOf<'meilisearch' | 'typesense' | 'algolia' | undefined>() expectTypeOf(manager.engine('meilisearch')).toEqualTypeOf() - // expectTypeOf(manager.engine('algolia')).toEqualTypeOf() + expectTypeOf(manager.engine('algolia')).toEqualTypeOf() expectTypeOf(manager.engine('typesense')).toEqualTypeOf() assert.instanceOf(manager.engine('meilisearch'), MeilisearchEngine) - // assert.instanceOf(manager.engine('algolia'), AlgoliaEngine) + assert.instanceOf(manager.engine('algolia'), AlgoliaEngine) assert.instanceOf(manager.engine('typesense'), TypesenseEngine) }) })