diff --git a/README.md b/README.md index 767006ed..006483fa 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ export class DatabaseModule {} ## Configuration -| Name | Type | Required | Default | -| -------- | -------- | -------- | ------------------- | -| `Model` | `Object` | `false` | `objection.Model` | -| `config` | `Object` | `false` | Knex default config | +| Name | Type | Required | Default | +| -------- | -------- | -------- | ----------------- | +| `Model` | `Object` | `false` | `objection.Model` | +| `config` | `Object` | `true` | | diff --git a/jest.config.js b/jest.config.js index 7324049a..4f9f7ba0 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,7 @@ module.exports = { clearMocks: true, collectCoverage: true, + collectCoverageFrom: ["<rootDir>/lib/**/*.ts", "!**/node_modules/**"], coverageDirectory: "coverage", coverageReporters: ["json", "text", "lcov"], testEnvironment: "node", diff --git a/lib/core.ts b/lib/core.ts index 2ca1a21b..83ef0a2d 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -14,10 +14,9 @@ import { } from "./interfaces"; export class ObjectionCoreModule { - public static forRoot(options: ObjectionModuleOptions = {}): DynamicModule { - const knexConfig = options.config || {}; + public static forRoot(options: ObjectionModuleOptions): DynamicModule { const Base = options.Model || Model; - const connection = knex(knexConfig); + const connection = knex(options.config); Base.knex(connection); @@ -54,9 +53,7 @@ export class ObjectionCoreModule { provide: KNEX_CONNECTION, inject: [OBJECTION_MODULE_OPTIONS], useFactory(objectionModuleOptions: ObjectionModuleOptions) { - const config = objectionModuleOptions.config || {}; - - return knex(config); + return knex(objectionModuleOptions.config); } }; @@ -93,9 +90,7 @@ export class ObjectionCoreModule { ): Provider[] { if (options.useExisting || options.useFactory) { return [this.createAsyncOptionsProvider(options)]; - } - - if (!options.useClass) { + } else if (!options.useClass) { throw new Error("Invalid configuration"); } diff --git a/lib/interfaces.ts b/lib/interfaces.ts index 74230ff7..d4f9c38f 100644 --- a/lib/interfaces.ts +++ b/lib/interfaces.ts @@ -5,7 +5,7 @@ import { Model } from "objection"; export interface ObjectionModuleOptions { Model?: typeof Model; - config?: Knex.Config; + config: Knex.Config; } export interface ObjectionModuleOptionsFactory { diff --git a/lib/module.ts b/lib/module.ts index 4a5ff694..3b27650c 100644 --- a/lib/module.ts +++ b/lib/module.ts @@ -8,7 +8,7 @@ import { // eslint-disable-next-line new-cap @Module({}) export class ObjectionModule { - public static forRoot(options?: ObjectionModuleOptions): DynamicModule { + public static forRoot(options: ObjectionModuleOptions): DynamicModule { return { module: ObjectionModule, imports: [ObjectionCoreModule.forRoot(options)] diff --git a/tests/core.spec.ts b/tests/core.spec.ts new file mode 100644 index 00000000..49d9e61d --- /dev/null +++ b/tests/core.spec.ts @@ -0,0 +1,198 @@ +import { + KNEX_CONNECTION, + OBJECTION_BASE_MODEL, + OBJECTION_MODULE_OPTIONS +} from "@/constants"; +import { ObjectionCoreModule } from "@/core"; +import { + ObjectionModuleOptions, + ObjectionModuleOptionsFactory +} from "@/interfaces"; +import { Injectable } from "@nestjs/common"; +import { Test, TestingModule } from "@nestjs/testing"; +import knex from "knex"; +import { Model } from "objection"; + +describe("ObjectionCoreModule", () => { + let testingModule: TestingModule; + const config: knex.Config = { + client: "sqlite3", + useNullAsDefault: true, + connection: { + filename: "./testing.sqlite" + } + }; + + describe("#forRoot", () => { + beforeEach(async () => { + testingModule = await Test.createTestingModule({ + imports: [ + ObjectionCoreModule.forRoot({ + config + }) + ] + }).compile(); + }); + + test("provides a connection", () => { + const connection = testingModule.get<knex>("KnexConnection"); + + expect(connection).toBeDefined(); + }); + + test("provides a base model", () => { + const model = testingModule.get<Model>("ObjectionBaseModel"); + + expect(model).toBeDefined(); + }); + }); + + describe("#forRootAsync", () => { + beforeEach(async () => { + testingModule = await Test.createTestingModule({ + imports: [ + ObjectionCoreModule.forRootAsync({ + useFactory() { + return { + config + }; + } + }) + ] + }).compile(); + }); + + test("provides a connection", () => { + const connection = testingModule.get<knex>(KNEX_CONNECTION); + + expect(connection).toBeDefined(); + }); + + test("provides a base model", () => { + const model = testingModule.get<Model>(OBJECTION_BASE_MODEL); + + expect(model).toBeDefined(); + }); + }); + + describe("#createAsyncProviders", () => { + class ModuleOptionsFactory implements ObjectionModuleOptionsFactory { + public createObjectionModuleOptions(): ObjectionModuleOptions { + return { + config + }; + } + } + + test("throws an error if options.useClass, useExisting, useFactory are not provided", () => { + expect(() => { + ObjectionCoreModule.createAsyncProviders({}); + }).toThrowError("Invalid configuration"); + }); + + test("leverages useClass if provided", () => { + const providers = ObjectionCoreModule.createAsyncProviders({ + useClass: ModuleOptionsFactory + }); + + expect(providers).toEqual([ + { + inject: [ModuleOptionsFactory], + useFactory: expect.any(Function), + provide: OBJECTION_MODULE_OPTIONS + }, + { + useClass: ModuleOptionsFactory, + provide: ModuleOptionsFactory + } + ]); + }); + + test("returns an array of providers when useExisting is passed", () => { + const providers = ObjectionCoreModule.createAsyncProviders({ + useExisting: ModuleOptionsFactory + }); + + expect(providers).toEqual([ + { + inject: [ModuleOptionsFactory], + useFactory: expect.any(Function), + provide: OBJECTION_MODULE_OPTIONS + } + ]); + }); + + test("returns an array of providers when useFactory is passed", () => { + const providers = ObjectionCoreModule.createAsyncProviders({ + useFactory: () => ({ + config + }) + }); + + expect(providers).toEqual([ + { + inject: [], + useFactory: expect.any(Function), + provide: OBJECTION_MODULE_OPTIONS + } + ]); + }); + }); + + describe("#createAsyncOptionsProvider", () => { + // eslint-disable-next-line new-cap + @Injectable() + class ModuleOptionsFactory implements ObjectionModuleOptionsFactory { + public createObjectionModuleOptions(): ObjectionModuleOptions { + return { + config + }; + } + } + + test("returns the appropriate provider when useFactory is passed", () => { + const provider = ObjectionCoreModule.createAsyncOptionsProvider({ + useFactory: () => ({ + config + }) + }); + + expect(provider).toEqual({ + inject: [], + provide: OBJECTION_MODULE_OPTIONS, + useFactory: expect.any(Function) + }); + }); + + test("returns the appropriate provider when useExisting is passed", () => { + const provider = ObjectionCoreModule.createAsyncOptionsProvider({ + useExisting: ModuleOptionsFactory + }); + + expect(provider).toEqual({ + inject: [ModuleOptionsFactory], + provide: OBJECTION_MODULE_OPTIONS, + useFactory: expect.any(Function) + }); + }); + + test("returns an async factory function that calls createObjectionModuleOptions", async () => { + jest.spyOn( + ModuleOptionsFactory.prototype, + "createObjectionModuleOptions" + ); + + await Test.createTestingModule({ + imports: [ + ObjectionCoreModule.forRootAsync({ + useClass: ModuleOptionsFactory + }) + ] + }).compile(); + + expect( + ModuleOptionsFactory.prototype.createObjectionModuleOptions + ).toHaveBeenCalled(); + }); + }); +}); diff --git a/tests/module.spec.ts b/tests/module.spec.ts index 6454e3ca..4a939f0b 100644 --- a/tests/module.spec.ts +++ b/tests/module.spec.ts @@ -1,3 +1,4 @@ +import { KNEX_CONNECTION, OBJECTION_BASE_MODEL } from "@/constants"; import { ObjectionModule } from "@/module"; import { Test, TestingModule } from "@nestjs/testing"; import knex from "knex"; @@ -25,13 +26,13 @@ describe("ObjectionModule", () => { }); test("provides a connection", () => { - const connection = testingModule.get<knex>("KnexConnection"); + const connection = testingModule.get<knex>(KNEX_CONNECTION); expect(connection).toBeDefined(); }); test("provides a base model", () => { - const model = testingModule.get<Model>("ObjectionBaseModel"); + const model = testingModule.get<Model>(OBJECTION_BASE_MODEL); expect(model).toBeDefined(); });