Skip to content

Commit

Permalink
Add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
PooyaRaki committed Oct 15, 2024
1 parent d926220 commit f7da627
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 41 deletions.
100 changes: 67 additions & 33 deletions src/datasources/db/v2/database-migrator.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { join } from 'path';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Test, type TestingModule } from '@nestjs/testing';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { postgresConfig } from '@/config/entities/postgres.config';
import { ConfigModule } from '@nestjs/config';
import { ConfigurationModule } from '@/config/configuration.module';
import configuration from '@/config/entities/__tests__/configuration';
import { TestLoggingModule } from '@/logging/__tests__/test.logging.module';
Expand All @@ -12,14 +10,18 @@ import {
LoggingService,
type ILoggingService,
} from '@/logging/logging.interface';
import { DatabaseShutdownHook } from '@/datasources/db/v2/database-shutdown.hook';
import { DatabaseInitializeHook } from '@/datasources/db/v2/database-initialize.hook';
import type { DataSource } from 'typeorm';
import { TestPostgresDatabaseModuleV2 } from '@/datasources/db/v2/test.postgres-database.module';

describe('PostgresDatabaseService', () => {
let moduleRef: TestingModule;
let postgresDatabaseService: PostgresDatabaseService;
let databaseMigratorService: DatabaseMigrator;
let loggingService: ILoggingService;
let connection: DataSource;
const NUMBER_OF_RETRIES = 2;
const truncateLockQuery = 'TRUNCATE TABLE "_lock";';
const insertLockQuery = 'INSERT INTO "_lock" (status) VALUES ($1);';

beforeAll(async () => {
// We should not require an SSL connection if using the database provided
Expand All @@ -30,6 +32,11 @@ describe('PostgresDatabaseService', () => {
...baseConfiguration,
db: {
...baseConfiguration.db,
migrator: {
numberOfRetries: NUMBER_OF_RETRIES,
migrationsExecute: false,
retryAfter: 10,
},
connection: {
postgres: {
...baseConfiguration.db.connection.postgres,
Expand All @@ -45,36 +52,12 @@ describe('PostgresDatabaseService', () => {

moduleRef = await Test.createTestingModule({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => {
const typeormConfig = configService.getOrThrow('db.orm');
const postgresConfigObject = postgresConfig(
configService.getOrThrow('db.connection.postgres'),
);

return {
...typeormConfig,
...postgresConfigObject,
...{
keepAlive: true,
migrations: ['dist/migrations/test/*.js'],
},
};
},
inject: [ConfigService],
}),
TestLoggingModule,
ConfigurationModule.register(testConfiguration),
ConfigModule,
TestPostgresDatabaseModuleV2,
],
providers: [
PostgresDatabaseService,
DatabaseMigrator,
DatabaseInitializeHook,
DatabaseShutdownHook,
// DatabaseMigrationHook,
],
providers: [DatabaseMigrator],
}).compile();

databaseMigratorService = moduleRef.get<DatabaseMigrator>(DatabaseMigrator);
Expand All @@ -83,14 +66,19 @@ describe('PostgresDatabaseService', () => {
);
loggingService = moduleRef.get<ILoggingService>(LoggingService);

await postgresDatabaseService.initializeDatabaseConnection();
connection = postgresDatabaseService.getDataSource();
});

afterAll(async () => {
await postgresDatabaseService.destroyDatabaseConnection();
await moduleRef.close();
});

beforeEach(() => {});

afterEach(() => {
jest.clearAllMocks();
});

describe('migrate()', () => {
it('Should log the start and end of the migration process', async () => {
jest.spyOn(loggingService, 'info');
Expand All @@ -101,6 +89,52 @@ describe('PostgresDatabaseService', () => {
'Migrations: Running...',
);
expect(loggingService.info).toHaveBeenCalledWith('Migrations: Finished.');
expect(loggingService.info).toHaveBeenCalledTimes(3);
});

it('Should run migration if no lock exists', async () => {
jest.spyOn(connection, 'query').mockImplementation(jest.fn());

await databaseMigratorService.migrate();

expect(connection.runMigrations).toHaveBeenCalled();
expect(connection.query).toHaveBeenCalledWith(insertLockQuery, [1]);

expect(connection.query).toHaveBeenCalledWith(truncateLockQuery);
});

it('Should throw an error if retries are exhausted', async () => {
connection.query = jest.fn().mockResolvedValue([{ id: 1, status: 1 }]);

await expect(databaseMigratorService.migrate()).rejects.toThrow(
'Migrations: Migrations are still running in another instance!',
);

expect(loggingService.info).toHaveBeenCalledWith(
'Migrations: Running in another instance...',
);
expect(loggingService.info).toHaveBeenCalledTimes(2);
});

it('Should not truncate locks if an error occurs', async () => {
connection.query = jest.fn().mockResolvedValue([{ id: 1, status: 1 }]);

await expect(databaseMigratorService.migrate()).rejects.toThrow(
'Migrations: Migrations are still running in another instance!',
);

expect(connection.query).not.toHaveBeenCalledWith(truncateLockQuery);
});

it('Should truncate locks after migrations are successful', async () => {
connection.query = jest.fn().mockResolvedValue(jest.fn());

await databaseMigratorService.migrate();

expect(loggingService.info).toHaveBeenCalledTimes(3);
expect(loggingService.info).toHaveBeenCalledWith('Migrations: Finished.');

expect(connection.query).toHaveBeenCalledWith(truncateLockQuery);
});
});
});
9 changes: 3 additions & 6 deletions src/datasources/db/v2/database-migrator.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ export class DatabaseMigrator {
public async migrate(): Promise<void> {
this.loggingService.info('Migrations: Running...');

const connection = this.databaseService.getDataSource();
const connection =
await this.databaseService.initializeDatabaseConnection();
await this.createLockTableIfNotExists(connection);

let numberOfIterations = 0;
const numberOfRetries = this.configService.getOrThrow<number>(
const numberOfRetries = await this.configService.getOrThrow(
'db.migrator.numberOfRetries',
);
while (numberOfRetries >= numberOfIterations) {
Expand Down Expand Up @@ -114,10 +115,6 @@ export class DatabaseMigrator {
/**
* Selects and retrieves locks from the database.
*
* `pg_advisory_lock` are not being used since they locks are session-based,
* For our usecase this case they are quite unreliable.
* If the session is terminated, the lock is released, which could potentially cause issues in the database.
*
* @param {DataSource} connection The database connection to select locks from.
*
* @returns {Promise<Array<LockSchema>>} A promise that resolves to an array of LockSchema objects.
Expand Down
11 changes: 9 additions & 2 deletions src/datasources/db/v2/test.postgres-database.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { Module } from '@nestjs/common';
import { PostgresDatabaseService } from '@/datasources/db/v2/postgres-database.service';

const postgresDataSourceMock = jest.fn().mockReturnValue({
query: jest.fn(),
runMigrations: jest.fn(),
});

const postgresDatabaseServiceMock = {
getDataSource: jest.fn(),
getDataSource: jest.fn().mockImplementation(postgresDataSourceMock),
isInitialized: jest.fn(),
initializeDatabaseConnection: jest.fn(),
initializeDatabaseConnection: jest
.fn()
.mockImplementation(postgresDataSourceMock),
destroyDatabaseConnection: jest.fn(),
getRepository: jest.fn(),
transaction: jest.fn(),
Expand Down

0 comments on commit f7da627

Please sign in to comment.