Skip to content

Commit 8b1ecd5

Browse files
authored
BC-8909 - add migration creation functionality and related tests (#5499)
1 parent 2931b74 commit 8b1ecd5

File tree

8 files changed

+86
-32
lines changed

8 files changed

+86
-32
lines changed

apps/server/src/modules/management/console/database-management.console.spec.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createMock, DeepMocked } from '@golevelup/ts-jest';
2-
import { Test, TestingModule } from '@nestjs/testing';
32
import { ConsoleWriterService } from '@infra/console';
3+
import { Test, TestingModule } from '@nestjs/testing';
44
import { DatabaseManagementUc } from '../uc/database-management.uc';
55
import { DatabaseManagementConsole } from './database-management.console';
66

@@ -100,16 +100,25 @@ describe('DatabaseManagementConsole', () => {
100100
expect(consoleInfoSpy).toHaveBeenCalledWith('migration up is completed');
101101
expect(databaseManagementUc.migrationUp).toHaveBeenCalled();
102102
});
103+
103104
it('should migrate down', async () => {
104105
await service.migration({ down: true });
105106
expect(consoleInfoSpy).toHaveBeenCalledWith('migration down is completed');
106107
expect(databaseManagementUc.migrationDown).toHaveBeenCalled();
107108
});
109+
108110
it('should check pending migrations', async () => {
109111
await service.migration({ pending: true });
110112
expect(consoleInfoSpy).toHaveBeenCalledWith(expect.stringContaining('Pending:'));
111113
expect(databaseManagementUc.migrationPending).toHaveBeenCalled();
112114
});
115+
116+
it('should check create migrations', async () => {
117+
await service.migration({ create: true });
118+
expect(consoleInfoSpy).toHaveBeenCalledWith('migration created');
119+
expect(databaseManagementUc.migrationCreate).toHaveBeenCalled();
120+
});
121+
113122
it('should no migrate if no param specified', async () => {
114123
await service.migration({});
115124
expect(consoleErrorSpy).toHaveBeenCalledWith('no migration option was given');

apps/server/src/modules/management/console/database-management.console.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ interface MigrationOptions {
1515
to?: string;
1616
only?: string;
1717
pending?: boolean;
18+
create?: boolean;
1819
}
1920

2021
@Console({ command: 'database', description: 'database setup console' })
@@ -118,12 +119,17 @@ export class DatabaseManagementConsole {
118119
required: false,
119120
description: 'list pending migrations',
120121
},
122+
{
123+
flags: '--create',
124+
required: false,
125+
description: 'create a new migration',
126+
},
121127
],
122128
description: 'Execute MikroOrm migration up/down',
123129
})
124130
public async migration(migrationOptions: MigrationOptions): Promise<string> {
125131
let report = 'no migration option was given';
126-
if (!migrationOptions.up && !migrationOptions.down && !migrationOptions.pending) {
132+
if (!migrationOptions.up && !migrationOptions.down && !migrationOptions.pending && !migrationOptions.create) {
127133
this.consoleWriter.error(report);
128134
return report;
129135
}
@@ -139,6 +145,10 @@ export class DatabaseManagementConsole {
139145
const pendingMigrations = await this.databaseManagementUc.migrationPending();
140146
report = `Pending: ${JSON.stringify(pendingMigrations.map((migration) => migration.name))}`;
141147
}
148+
if (migrationOptions.create) {
149+
await this.databaseManagementUc.migrationCreate();
150+
report = 'migration created';
151+
}
142152

143153
this.consoleWriter.info(report);
144154
return report;

apps/server/src/modules/management/mikro-orm-cli.config.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,22 @@ import { MikroOrmModuleSyncOptions } from '@mikro-orm/nestjs';
33
import path from 'path';
44
import { ENTITIES } from './management.entity.imports';
55

6-
export const migrationsPath = path.resolve(__dirname, '..', '..', 'migrations', 'mikro-orm');
6+
const migrationsDistPath = path.resolve(__dirname, '..', '..', 'migrations', 'mikro-orm');
7+
const migrationsSourcePath = path.resolve(
8+
__dirname,
9+
'..',
10+
'..',
11+
'..',
12+
'..',
13+
'..',
14+
'apps',
15+
'server',
16+
'src',
17+
'migrations',
18+
'mikro-orm'
19+
);
720

821
const mikroOrmCliConfig: MikroOrmModuleSyncOptions = {
9-
// TODO repeats server module definitions
1022
type: 'mongo',
1123
clientUrl: DB_URL,
1224
password: DB_PASSWORD,
@@ -15,8 +27,8 @@ const mikroOrmCliConfig: MikroOrmModuleSyncOptions = {
1527
allowGlobalContext: true,
1628
migrations: {
1729
tableName: 'migrations', // name of database table with log of executed transactions
18-
path: migrationsPath, // path to the folder with migrations
19-
pathTs: migrationsPath, // path to the folder with TS migrations (if used, we should put path to compiled files in `path`)
30+
path: migrationsDistPath, // path to the folder with migrations
31+
pathTs: migrationsSourcePath, // path to the folder with TS migrations
2032
glob: '!(*.d).{js,ts}', // how to match migration files (all .js and .ts files, but not .d.ts)
2133
transactional: false, // wrap each migration in a transaction
2234
disableForeignKeys: true, // wrap statements with `set foreign_key_checks = 0` or equivalent

apps/server/src/modules/management/service/database-management.service.spec.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { MikroORM } from '@mikro-orm/core';
1+
import { createMock, DeepMocked } from '@golevelup/ts-jest';
2+
import { IMigrator, MikroORM } from '@mikro-orm/core';
23
import { EntityManager, ObjectId } from '@mikro-orm/mongodb';
34
import { Test, TestingModule } from '@nestjs/testing';
45
import { createCollections } from '@testing/create-collections';
@@ -169,4 +170,15 @@ describe('DatabaseManagementService', () => {
169170
expect(spy).toHaveBeenCalled();
170171
});
171172
});
173+
174+
describe('When call migrationCreate()', () => {
175+
it('should call migrator.createInitialMigration()', async () => {
176+
const spyGetMigrator = jest.spyOn(orm, 'getMigrator');
177+
const spyMigrator: DeepMocked<IMigrator> = createMock<IMigrator>();
178+
spyGetMigrator.mockReturnValue(spyMigrator);
179+
180+
await service.migrationCreate();
181+
expect(spyMigrator.createInitialMigration).toHaveBeenCalled();
182+
});
183+
});
172184
});

apps/server/src/modules/management/service/database-management.service.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class DatabaseManagementService {
6666
}
6767

6868
public async syncIndexes(): Promise<void> {
69-
return this.orm.getSchemaGenerator().ensureIndexes();
69+
await this.orm.getSchemaGenerator().ensureIndexes();
7070
}
7171

7272
public async migrationUp(from?: string, to?: string, only?: string): Promise<void> {
@@ -88,7 +88,13 @@ export class DatabaseManagementService {
8888
return pendingMigrations;
8989
}
9090

91-
private migrationParams(only?: string, from?: string, to?: string) {
91+
public async migrationCreate(): Promise<void> {
92+
const migrator = this.orm.getMigrator();
93+
94+
await migrator.createInitialMigration();
95+
}
96+
97+
private migrationParams(only?: string, from?: string, to?: string): MigrateOptions {
9298
const params: MigrateOptions = {};
9399
if (only) {
94100
params.migrations = [only];

apps/server/src/modules/management/uc/database-management.uc.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,5 +701,10 @@ describe('DatabaseManagementService', () => {
701701
await uc.migrationPending();
702702
expect(dbService.migrationPending).toHaveBeenCalled();
703703
});
704+
it('should call migrationCreate', async () => {
705+
jest.spyOn(dbService, 'migrationDown').mockImplementation();
706+
await uc.migrationCreate();
707+
expect(dbService.migrationCreate).toHaveBeenCalled();
708+
});
704709
});
705710
});

apps/server/src/modules/management/uc/database-management.uc.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,15 @@ export class DatabaseManagementUc {
5757
/**
5858
* setup dir with json files
5959
*/
60-
private getSeedFolder() {
60+
private getSeedFolder(): string {
6161
return this.fileSystemAdapter.joinPath(this.baseDir, 'setup');
6262
}
6363

6464
/**
6565
* export folder name based on current date
6666
* @returns
6767
*/
68-
private getTargetFolder(toSeedFolder?: boolean) {
68+
private getTargetFolder(toSeedFolder?: boolean): string {
6969
if (toSeedFolder === true) {
7070
const targetFolder = this.getSeedFolder();
7171
return targetFolder;
@@ -118,7 +118,7 @@ export class DatabaseManagementUc {
118118
source: 'files' | 'database',
119119
folder: string,
120120
collectionNameFilter?: string[]
121-
) {
121+
): Promise<CollectionFilePath[]> {
122122
let allCollectionsWithFilePaths: CollectionFilePath[] = [];
123123

124124
// load all available collections from source
@@ -151,7 +151,7 @@ export class DatabaseManagementUc {
151151
return allCollectionsWithFilePaths;
152152
}
153153

154-
private async dropCollectionIfExists(collectionName: string) {
154+
private async dropCollectionIfExists(collectionName: string): Promise<void> {
155155
const collectionExists = await this.databaseManagementService.collectionExists(collectionName);
156156
if (collectionExists) {
157157
// clear existing documents, if collection exists
@@ -162,7 +162,7 @@ export class DatabaseManagementUc {
162162
}
163163
}
164164

165-
async seedDatabaseCollectionsFromFactories(collections?: string[]): Promise<string[]> {
165+
public async seedDatabaseCollectionsFromFactories(collections?: string[]): Promise<string[]> {
166166
const promises = generateSeedData((s: string) => this.injectEnvVars(s))
167167
.filter((data) => {
168168
if (collections && collections.length > 0) {
@@ -266,7 +266,7 @@ export class DatabaseManagementUc {
266266
* @param toSeedFolder optional override existing seed data files
267267
* @returns the list of collection names exported
268268
*/
269-
async exportCollectionsToFileSystem(collections?: string[], toSeedFolder?: boolean): Promise<string[]> {
269+
public async exportCollectionsToFileSystem(collections?: string[], toSeedFolder?: boolean): Promise<string[]> {
270270
const targetFolder = this.getTargetFolder(toSeedFolder);
271271
await this.fileSystemAdapter.createDir(targetFolder);
272272
// detect collections to export based on database collections
@@ -301,7 +301,7 @@ export class DatabaseManagementUc {
301301
/**
302302
* Updates the indexes in the database based on definitions in entities
303303
*/
304-
async syncIndexes(): Promise<void> {
304+
public async syncIndexes(): Promise<void> {
305305
await this.createUserSearchIndex();
306306
return this.databaseManagementService.syncIndexes();
307307
}
@@ -357,7 +357,7 @@ export class DatabaseManagementUc {
357357
return json;
358358
}
359359

360-
private resolvePlaceholder(placeholder: string) {
360+
private resolvePlaceholder(placeholder: string): string {
361361
if (Configuration.has(placeholder)) {
362362
return Configuration.get(placeholder) as string;
363363
}
@@ -369,13 +369,13 @@ export class DatabaseManagementUc {
369369
return '';
370370
}
371371

372-
private encryptSecrets(collectionName: string, jsonDocuments: unknown[]) {
372+
private encryptSecrets(collectionName: string, jsonDocuments: unknown[]): void {
373373
if (collectionName === systemsCollectionName) {
374374
this.encryptSecretsInSystems(jsonDocuments as SystemEntity[]);
375375
}
376376
}
377377

378-
private encryptSecretsInSystems(systems: SystemEntity[]) {
378+
private encryptSecretsInSystems(systems: SystemEntity[]): SystemEntity[] {
379379
systems.forEach((system) => {
380380
if (system.oauthConfig) {
381381
system.oauthConfig.clientSecret = this.defaultEncryptionService.encrypt(system.oauthConfig.clientSecret);
@@ -397,7 +397,7 @@ export class DatabaseManagementUc {
397397
* Manual replacement with the intend placeholders or value is mandatory.
398398
* Currently, this affects system and storageproviders collections.
399399
*/
400-
private removeSecrets(collectionName: string, jsonDocuments: unknown[]) {
400+
private removeSecrets(collectionName: string, jsonDocuments: unknown[]): void {
401401
if (collectionName === systemsCollectionName) {
402402
this.removeSecretsFromSystems(jsonDocuments as SystemEntity[]);
403403
}
@@ -406,14 +406,14 @@ export class DatabaseManagementUc {
406406
}
407407
}
408408

409-
private removeSecretsFromStorageproviders(storageProviders: StorageProviderEntity[]) {
409+
private removeSecretsFromStorageproviders(storageProviders: StorageProviderEntity[]): void {
410410
storageProviders.forEach((storageProvider) => {
411411
storageProvider.accessKeyId = defaultSecretReplacementHintText;
412412
storageProvider.secretAccessKey = defaultSecretReplacementHintText;
413413
});
414414
}
415415

416-
private removeSecretsFromSystems(systems: SystemEntity[]) {
416+
private removeSecretsFromSystems(systems: SystemEntity[]): SystemEntity[] {
417417
systems.forEach((system) => {
418418
if (system.oauthConfig) {
419419
system.oauthConfig.clientSecret = defaultSecretReplacementHintText;
@@ -428,16 +428,22 @@ export class DatabaseManagementUc {
428428
return systems;
429429
}
430430

431+
public async migrationCreate(): Promise<void> {
432+
await this.databaseManagementService.migrationCreate();
433+
}
434+
431435
public async migrationUp(from?: string, to?: string, only?: string): Promise<void> {
432-
return this.databaseManagementService.migrationUp(from, to, only);
436+
await this.databaseManagementService.migrationUp(from, to, only);
433437
}
434438

435439
public async migrationDown(from?: string, to?: string, only?: string): Promise<void> {
436-
return this.databaseManagementService.migrationDown(from, to, only);
440+
await this.databaseManagementService.migrationDown(from, to, only);
437441
}
438442

439443
public async migrationPending(): Promise<UmzugMigration[]> {
440-
return this.databaseManagementService.migrationPending();
444+
const result = await this.databaseManagementService.migrationPending();
445+
446+
return result;
441447
}
442448

443449
public encryptPlainText(plainText: string): string {

package.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,6 @@
2020
"node": "22",
2121
"npm": ">=9"
2222
},
23-
"mikro-orm": {
24-
"useTsNode": true,
25-
"configPaths": [
26-
"./apps/server/src/modules/management/mikro-orm-cli.config.ts",
27-
"./dist/server/modules/management/mikro-orm-cli.config.js"
28-
]
29-
},
3023
"scripts": {
3124
"lint": "eslint . --ignore-path .gitignore",
3225
"test": "npm run nest:test && npm run feathers:test",
@@ -53,6 +46,7 @@
5346
"migration:down": "npm run nest:start:console -- database migration --down",
5447
"migration:pending": "npm run nest:start:console -- database migration --pending",
5548
"migration:persisted": "npm run nest:start:console -- database export --collection migrations --override",
49+
"migration:create": "npm run nest:start:console -- database migration --create",
5650
"nest:prebuild": "rimraf dist",
5751
"nest:build": "nest build",
5852
"nest:build:all": "npm run nest:build",

0 commit comments

Comments
 (0)