Skip to content

Commit

Permalink
added server testing app
Browse files Browse the repository at this point in the history
  • Loading branch information
nattadex committed Mar 15, 2024
1 parent 00870cd commit 2548072
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 75 deletions.
2 changes: 2 additions & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
"prettier": "@waveshq/standard-prettier",
"dependencies": {
"@prisma/client": "^5.6.0",
"@stickyjs/testcontainers": "^1.3.10",
"@waveshq/standard-api-fastify": "^3.0.1",
"class-validator": "^0.14.1",
"joi": "^17.12.2",
"light-my-request": "^5.12.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0"
},
Expand Down
19 changes: 16 additions & 3 deletions apps/server/src/MarbleFiLsdServerApp.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Request, Response, NextFunction } from "express";
import { INestApplication } from "@nestjs/common";
import { NextFunction, Request, Response } from "express";
import { INestApplication, NestApplicationOptions } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { NestFastifyApplication } from "@nestjs/platform-fastify";
import {
FastifyAdapter,
NestFastifyApplication,
} from "@nestjs/platform-fastify";

import { AppModule } from "./AppModule";

Expand All @@ -15,6 +18,16 @@ export class MarbleFiLsdServerApp<

constructor(protected readonly module: any) {}

get nestApplicationOptions(): NestApplicationOptions {
return {
bufferLogs: true,
};
}

get fastifyAdapter(): FastifyAdapter {
return new FastifyAdapter();
}

async createNestApp(): Promise<App> {
const app = await NestFactory.create(AppModule);
await this.configureApp(app);
Expand Down
32 changes: 32 additions & 0 deletions apps/server/src/modules/BaseModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { DynamicModule, Global, Module, ModuleMetadata } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { LoggerModule } from "nestjs-pino";

/**
* Baseline module for any Bridge nest applications.
*
* - `@nestjs/config`, nestjs ConfigModule
* - `nestjs-pino`, the Pino logger for NestJS
* - `joi`, for validation of environment variables
*/
@Global()
@Module({
imports: [
LoggerModule.forRoot({
exclude: ["/health", "/version", "/settings"],
}),
ConfigModule.forRoot({
isGlobal: true,
cache: true,
}),
],
})
export class BaseModule {
static with(metadata: ModuleMetadata): DynamicModule {
return {
module: BaseModule,
global: true,
...metadata,
};
}
}
112 changes: 65 additions & 47 deletions apps/server/src/user/UserController.spec.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,75 @@
import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@stickyjs/testcontainers';
import { UserController} from "./UserController";
import { UserService} from "./UserService";
import {PrismaService} from "../PrismaService";

import {buildTestConfig, TestingModule } from "../../test/TestingModule";
import {MarbleFiLsdServerTestingApp} from "../../dist/test-i9n/MarbleFiLsdServerTestingApp";

describe('UserController', () => {
let testing: MarbleFiLsdServerTestingApp
let userController: UserController;
let userService: UserService;
let prismaService: PrismaService
let startedPostgresContainer: StartedPostgreSqlContainer;

beforeAll(async () => {
startedPostgresContainer = await new PostgreSqlContainer().start();
testing = new MarbleFiLsdServerTestingApp(
TestingModule.register(
buildTestConfig({startedPostgresContainer}),
),
);
const app = await testing.start();
import { HttpStatus } from "@nestjs/common";
import {
PostgreSqlContainer,
StartedPostgreSqlContainer,
} from "@stickyjs/testcontainers";
import { UserController } from "./UserController";
import { UserService } from "./UserService";
import { PrismaService } from "../PrismaService";

// init postgres database
prismaService = app.get<PrismaService>(PrismaService);
userService = new UserService(prismaService);
userController = new UserController(userService);
});
import { buildTestConfig, TestingModule } from "../../test/TestingModule";
import { MarbleFiLsdServerTestingApp } from "../../test/MarbleFiLsdServerTestingApp";

describe("UserController", () => {
let testing: MarbleFiLsdServerTestingApp;
let userController: UserController;
let userService: UserService;
let prismaService: PrismaService;
let startedPostgresContainer: StartedPostgreSqlContainer;

beforeAll(async () => {
startedPostgresContainer = await new PostgreSqlContainer().start();
testing = new MarbleFiLsdServerTestingApp(
TestingModule.register(buildTestConfig({ startedPostgresContainer })),
);
const app = await testing.start();

describe('create user', () => {
it('should create an active user in db', async () => {
const res = await userController.create("[email protected]", 'ACTIVE');
// init postgres database
prismaService = app.get<PrismaService>(PrismaService);
userService = new UserService(prismaService);
userController = new UserController(userService);
});

expect(res).toEqual({ id: 1, email: '[email protected]', status: 'ACTIVE' });
});
describe("create user", () => {
it("should create an active user in db", async () => {
const res = await userController.create("[email protected]", "ACTIVE");

it('should create an inactive user in db', async () => {
const res = await userController.create("[email protected]", 'INACTIVE');
expect(res).toEqual({
id: 1,
email: "[email protected]",
status: "ACTIVE",
});
});

expect(res).toEqual({ id: 2, email: '[email protected]', status: 'INACTIVE' });
});
it("should create an inactive user in db", async () => {
const res = await userController.create("[email protected]", "INACTIVE");

it('should create an active user by default', async () => {
const res = await userController.create("[email protected]");
expect(res).toEqual({
id: 2,
email: "[email protected]",
status: "INACTIVE",
});
});

expect(res).toEqual({ id: 3, email: '[email protected]', status: 'ACTIVE' });
});
it("should create an active user by default", async () => {
const res = await userController.create("[email protected]");

it('should not create a user with same email', async () => {
const res = await userController.create("[email protected]");
expect(res).toEqual({
id: 3,
email: "[email protected]",
status: "ACTIVE",
});
});

console.log(res)
// expect(res).toEqual({ id: 3, email: 'test@example.com', status: 'ACTIVE' });
});
it("should not create a user with same email", async () => {
try {
await userController.create("[email protected]");
} catch (e) {
expect(e.response.statusCode).toStrictEqual(HttpStatus.BAD_REQUEST);
expect(e.response.message).toStrictEqual(
`Duplicate email '[email protected]' found in database`,
);
}
});
});
});
});
52 changes: 52 additions & 0 deletions apps/server/test/MarbleFiLsdServerTestingApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { NestFastifyApplication } from "@nestjs/platform-fastify";
import { Test, TestingModule } from "@nestjs/testing";
import {
Chain as LightMyRequestChain,
InjectOptions,
Response as LightMyRequestResponse,
} from "light-my-request";

import { MarbleFiLsdServerApp } from "../src/MarbleFiLsdServerApp";
import { BaseModule } from "../src/modules/BaseModule";

/**
* Testing app used for testing MarbleFi Server App behaviour through integration tests
*/
export class MarbleFiLsdServerTestingApp extends MarbleFiLsdServerApp<NestFastifyApplication> {
async createTestingModule(): Promise<TestingModule> {
return Test.createTestingModule({
imports: [
BaseModule.with({
imports: [this.module],
}),
],
}).compile();
}

override async createNestApp(): Promise<NestFastifyApplication> {
const module = await this.createTestingModule();
return module.createNestApplication<NestFastifyApplication>(
this.fastifyAdapter,
this.nestApplicationOptions,
);
}

async start(): Promise<NestFastifyApplication> {
return this.init();
}

/**
* A wrapper function around native `fastify.inject()` method.
* @returns {void}
*/
inject(): LightMyRequestChain;
inject(opts: InjectOptions | string): Promise<LightMyRequestResponse>;
inject(
opts?: InjectOptions | string,
): LightMyRequestChain | Promise<LightMyRequestResponse> {
if (opts === undefined) {
return this.app!.inject();
}
return this.app!.inject(opts);
}
}
50 changes: 26 additions & 24 deletions apps/server/test/TestingModule.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
import * as child_process from 'node:child_process';
import * as child_process from "node:child_process";

import { DynamicModule, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { StartedPostgreSqlContainer } from '@stickyjs/testcontainers';
import { DynamicModule, Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { StartedPostgreSqlContainer } from "@stickyjs/testcontainers";

import { AppConfig, DeepPartial} from "../src/AppConfig";
import { AppModule} from "../src/AppModule";
import { AppConfig, DeepPartial } from "../src/AppConfig";
import { AppModule } from "../src/AppModule";

@Module({})
export class TestingModule {
static register(config: AppConfig): DynamicModule {
return {
module: TestingModule,
imports: [AppModule, ConfigModule.forFeature(() => config)],
};
}
static register(config: AppConfig): DynamicModule {
return {
module: TestingModule,
imports: [AppModule, ConfigModule.forFeature(() => config)],
};
}
}

export function buildTestConfig({
startedPostgresContainer,
}: BuildTestConfigParams) {
if (startedPostgresContainer === undefined) {
throw Error('Must pass in StartedPostgresContainer');
}
const dbUrl = `postgres://${startedPostgresContainer.getUsername()}:${startedPostgresContainer.getPassword()}@${startedPostgresContainer.getHost()}:${startedPostgresContainer.getPort()}`;
child_process.execSync(`export DATABASE_URL=${dbUrl} && pnpm prisma migrate deploy`);
return {
dbUrl: dbUrl ?? ''
};
startedPostgresContainer,
}: BuildTestConfigParams) {
if (startedPostgresContainer === undefined) {
throw Error("Must pass in StartedPostgresContainer");
}
const dbUrl = `postgres://${startedPostgresContainer.getUsername()}:${startedPostgresContainer.getPassword()}@${startedPostgresContainer.getHost()}:${startedPostgresContainer.getPort()}`;
child_process.execSync(
`export DATABASE_URL=${dbUrl} && pnpm prisma migrate deploy`,
);
return {
dbUrl: dbUrl ?? "",
};
}

type BuildTestConfigParams = DeepPartial<OptionalBuildTestConfigParams> & {
startedPostgresContainer: StartedPostgreSqlContainer;
startedPostgresContainer: StartedPostgreSqlContainer;
};

type OptionalBuildTestConfigParams = {
dbUrl: string;
dbUrl: string;
};
30 changes: 29 additions & 1 deletion pnpm-lock.yaml

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

0 comments on commit 2548072

Please sign in to comment.