@koalarx/nest 4.0.8 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/utils/install-module.js +14 -17
- package/cli/utils/patch-app-test-module.js +2 -1
- package/cli/utils/patch-env.js +2 -0
- package/koala-nest/src/core/database/db-context.ts +3 -0
- package/koala-nest/src/core/database/entity.ts +9 -0
- package/koala-nest/src/core/env.ts +1 -0
- package/koala-nest/src/domain/entities/person/person-address.ts +2 -1
- package/koala-nest/src/domain/entities/person/person-contact.ts +2 -2
- package/koala-nest/src/domain/entities/person/person.ts +2 -2
- package/koala-nest/src/domain/entities/user/user.ts +1 -1
- package/koala-nest/src/infra/database/data-source-factory.ts +4 -6
- package/koala-nest/src/infra/database/migrations/migration-datasource.ts +7 -0
- package/koala-nest/src/test/app-auth-test.module.ts +2 -1
- package/koala-nest/src/test/app-test.module.ts +2 -1
- package/koala-nest/src/test/e2e-context.ts +4 -1
- package/koala-nest/src/test/host/controllers/person/lazy-loading.e2e.spec.ts +50 -14
- package/koala-nest/src/test/setup-e2e.ts +6 -43
- package/koala-nest/src/test/utils/create-e2e-database.ts +24 -16
- package/koala-nest/src/test/utils/e2e-database-client.ts +2 -2
- package/package.json +1 -1
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cpSync,
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
rmSync,
|
|
7
|
-
writeFileSync
|
|
8
|
-
} from "node:fs";
|
|
9
|
-
import path from "node:path";
|
|
10
1
|
import {
|
|
11
2
|
AUTH_DEV_PACKAGES,
|
|
12
3
|
AUTH_PACKAGES,
|
|
@@ -18,13 +9,24 @@ import {
|
|
|
18
9
|
HEALTH_PACKAGES
|
|
19
10
|
} from "../constants/core-packages.js";
|
|
20
11
|
import {
|
|
21
|
-
AuthChoice,
|
|
22
12
|
AuthStrategy,
|
|
23
13
|
ExtraFeature,
|
|
24
14
|
InstallModule as Modules,
|
|
25
15
|
Template
|
|
26
16
|
} from "../constants/domain.js";
|
|
17
|
+
import {
|
|
18
|
+
cpSync,
|
|
19
|
+
existsSync,
|
|
20
|
+
mkdirSync,
|
|
21
|
+
readFileSync,
|
|
22
|
+
rmSync,
|
|
23
|
+
writeFileSync
|
|
24
|
+
} from "node:fs";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import { getPackageManager } from "./get-package-manager.js";
|
|
27
27
|
import { getSourceCodePath } from "./get-source-code-path.js";
|
|
28
|
+
import { patchAuthInstall } from "./patch-auth-install.js";
|
|
29
|
+
import { restoreDefineDocumentationWithAuth } from "./patch-define-documentation.js";
|
|
28
30
|
import {
|
|
29
31
|
patchAppModuleForHealth,
|
|
30
32
|
patchHealthCheckWithoutRedis
|
|
@@ -34,24 +36,19 @@ import {
|
|
|
34
36
|
patchInfraModuleForCache,
|
|
35
37
|
stripInfraModuleCache
|
|
36
38
|
} from "./patch-infra-module.js";
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
patchMainForAuth
|
|
40
|
-
} from "./patch-main.js";
|
|
41
|
-
import { restoreDefineDocumentationWithAuth } from "./patch-define-documentation.js";
|
|
39
|
+
import { patchMainForAuth } from "./patch-main.js";
|
|
42
40
|
import { pruneCoreAuthForSlimTemplate } from "./prune-core-auth.js";
|
|
43
41
|
import { removeSampleParts } from "./remove-sample-parts.js";
|
|
44
42
|
import { resolveProjectPath } from "./resolve-project-path.js";
|
|
45
43
|
import { runCommand } from "./run-command.js";
|
|
46
|
-
import { getPackageManager } from "./get-package-manager.js";
|
|
47
44
|
export {
|
|
48
45
|
AuthChoice,
|
|
49
46
|
AuthStrategy,
|
|
50
47
|
CRUD_BUNDLED_FEATURES,
|
|
51
48
|
ExtraFeature,
|
|
52
|
-
InstallModule as Modules,
|
|
53
49
|
mapExtraFeatureToModule,
|
|
54
50
|
mergeCrudSampleFeatures,
|
|
51
|
+
InstallModule as Modules,
|
|
55
52
|
resolveNewProjectOptions,
|
|
56
53
|
Template
|
|
57
54
|
} from "../constants/domain.js";
|
|
@@ -5,7 +5,7 @@ const defaultAppTestModule = `import { envSchema } from '@/core/env';
|
|
|
5
5
|
import { Module } from '@nestjs/common';
|
|
6
6
|
import { ConfigModule } from '@nestjs/config';
|
|
7
7
|
import { InfraModule } from '@/infra/infra.module';
|
|
8
|
-
import { e2eDatabaseUrl } from '@/test/e2e-context';
|
|
8
|
+
import { e2eDatabaseUrl, e2eSchemaName } from '@/test/e2e-context';
|
|
9
9
|
|
|
10
10
|
@Module({
|
|
11
11
|
imports: [
|
|
@@ -17,6 +17,7 @@ import { e2eDatabaseUrl } from '@/test/e2e-context';
|
|
|
17
17
|
PORT: 3000,
|
|
18
18
|
NODE_ENV: 'test',
|
|
19
19
|
DATABASE_URL: e2eDatabaseUrl,
|
|
20
|
+
DATABASE_SCHEMA: e2eSchemaName,
|
|
20
21
|
}),
|
|
21
22
|
}),
|
|
22
23
|
InfraModule,
|
package/cli/utils/patch-env.js
CHANGED
|
@@ -11,6 +11,7 @@ export const envSchema = z.object({
|
|
|
11
11
|
HOST: z.string().default('0.0.0.0'),
|
|
12
12
|
NODE_ENV: z.enum(['test', 'develop', 'staging', 'production']),
|
|
13
13
|
DATABASE_URL: z.string(),
|
|
14
|
+
DATABASE_SCHEMA: z.string().optional(),
|
|
14
15
|
REDIS_CONNECTION_STRING: z.string().optional(),
|
|
15
16
|
CACHE_KEY_PREFIX: z.string().optional(),
|
|
16
17
|
CRON_JOBS_ENABLED: envBooleanSchema(false),
|
|
@@ -73,6 +74,7 @@ export const envSchema = z.object({
|
|
|
73
74
|
HOST: z.string().default('0.0.0.0'),
|
|
74
75
|
NODE_ENV: z.enum(['test', 'develop', 'staging', 'production']),
|
|
75
76
|
DATABASE_URL: z.string(),
|
|
77
|
+
DATABASE_SCHEMA: z.string().optional(),
|
|
76
78
|
REDIS_CONNECTION_STRING: z.string().optional(),
|
|
77
79
|
CACHE_KEY_PREFIX: z.string().optional(),
|
|
78
80
|
CRON_JOBS_ENABLED: envBooleanSchema(false),
|
|
@@ -11,6 +11,7 @@ export const envSchema = z.object({
|
|
|
11
11
|
HOST: z.string().default('0.0.0.0'),
|
|
12
12
|
NODE_ENV: z.enum(['test', 'develop', 'staging', 'production']),
|
|
13
13
|
DATABASE_URL: z.string(),
|
|
14
|
+
DATABASE_SCHEMA: z.string().optional(),
|
|
14
15
|
REDIS_CONNECTION_STRING: z.string().optional(),
|
|
15
16
|
CACHE_KEY_PREFIX: z.string().optional(),
|
|
16
17
|
CRON_JOBS_ENABLED: envBooleanSchema(false),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EntityBase } from '@/core/base/entity.base';
|
|
2
|
+
import { Entity } from '@/core/database/entity';
|
|
2
3
|
import { AutoMap } from '@/core/tools/mapping';
|
|
3
|
-
import { Column,
|
|
4
|
+
import { Column, PrimaryGeneratedColumn } from 'typeorm';
|
|
4
5
|
|
|
5
6
|
@Entity('person_address')
|
|
6
7
|
export class PersonAddress extends EntityBase<PersonAddress> {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { EntityBase } from '@/core/base/entity.base';
|
|
2
|
+
import { Entity } from '@/core/database/entity';
|
|
1
3
|
import { AutoMap } from '@/core/tools/mapping';
|
|
2
4
|
import {
|
|
3
5
|
Column,
|
|
4
|
-
Entity,
|
|
5
6
|
ManyToOne,
|
|
6
7
|
PrimaryGeneratedColumn,
|
|
7
8
|
type Relation,
|
|
8
9
|
} from 'typeorm';
|
|
9
10
|
import type { Person } from './person';
|
|
10
|
-
import { EntityBase } from '@/core/base/entity.base';
|
|
11
11
|
|
|
12
12
|
@Entity('person_contact')
|
|
13
13
|
export class PersonContact extends EntityBase<PersonContact> {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { EntityBase } from '@/core/base/entity.base';
|
|
2
|
+
import { Entity } from '@/core/database/entity';
|
|
1
3
|
import { AutoMap } from '@/core/tools/mapping';
|
|
2
4
|
import {
|
|
3
5
|
Column,
|
|
4
|
-
Entity,
|
|
5
6
|
JoinColumn,
|
|
6
7
|
OneToMany,
|
|
7
8
|
OneToOne,
|
|
@@ -9,7 +10,6 @@ import {
|
|
|
9
10
|
} from 'typeorm';
|
|
10
11
|
import { PersonAddress } from './person-address';
|
|
11
12
|
import { PersonContact } from './person-contact';
|
|
12
|
-
import { EntityBase } from '@/core/base/entity.base';
|
|
13
13
|
|
|
14
14
|
@Entity('person')
|
|
15
15
|
export class Person extends EntityBase<Person> {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { AuthProfile } from '@/core/auth/auth-profile.enum';
|
|
2
2
|
import { EntityBase } from '@/core/base/entity.base';
|
|
3
|
+
import { Entity } from '@/core/database/entity';
|
|
3
4
|
import { AutoMap } from '@/core/tools/mapping';
|
|
4
5
|
import { UserStatus } from '@/domain/entities/user/enums/user-status.enum';
|
|
5
6
|
import {
|
|
6
7
|
Column,
|
|
7
8
|
CreateDateColumn,
|
|
8
|
-
Entity,
|
|
9
9
|
PrimaryGeneratedColumn,
|
|
10
10
|
UpdateDateColumn,
|
|
11
11
|
} from 'typeorm';
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PersonAddress } from '@/domain/entities/person/person-address';
|
|
3
|
-
import { PersonContact } from '@/domain/entities/person/person-contact';
|
|
4
|
-
import { User } from '@/domain/entities/user/user';
|
|
5
|
-
import { DataSource } from 'typeorm';
|
|
1
|
+
import { DbContext } from '@/core/database/db-context';
|
|
6
2
|
import { EnvService } from '@/infra/common/env.service';
|
|
3
|
+
import { DataSource } from 'typeorm';
|
|
7
4
|
|
|
8
5
|
export const DATA_SOURCE_PROVIDER_TOKEN = 'DATA_SOURCE';
|
|
9
6
|
|
|
@@ -11,7 +8,8 @@ export async function dataSourceFactory(env: EnvService) {
|
|
|
11
8
|
const dataSource = new DataSource({
|
|
12
9
|
type: 'postgres',
|
|
13
10
|
url: env.get('DATABASE_URL'),
|
|
14
|
-
|
|
11
|
+
schema: env.get('DATABASE_SCHEMA'),
|
|
12
|
+
entities: Array.from(DbContext.entities.values()),
|
|
15
13
|
invalidWhereValuesBehavior: {
|
|
16
14
|
undefined: 'ignore',
|
|
17
15
|
},
|
|
@@ -3,10 +3,17 @@ import path from 'node:path';
|
|
|
3
3
|
import { DataSource } from 'typeorm';
|
|
4
4
|
|
|
5
5
|
const root = process.cwd();
|
|
6
|
+
const schema = process.env.DATABASE_SCHEMA;
|
|
6
7
|
|
|
7
8
|
export default new DataSource({
|
|
8
9
|
type: 'postgres',
|
|
9
10
|
url: process.env.DATABASE_URL,
|
|
11
|
+
...(schema
|
|
12
|
+
? {
|
|
13
|
+
schema,
|
|
14
|
+
extra: { options: `-c search_path=${schema},public` },
|
|
15
|
+
}
|
|
16
|
+
: {}),
|
|
10
17
|
entities: [path.join(root, 'src/domain/entities/**/*.{js,ts}')],
|
|
11
18
|
migrations: [path.join(root, 'src/infra/database/migrations/[0-9]*.{js,ts}')],
|
|
12
19
|
migrationsTableName: 'migrations',
|
|
@@ -3,7 +3,7 @@ import { AuthModule } from '@/host/controllers/auth/auth.module';
|
|
|
3
3
|
import { Module } from '@nestjs/common';
|
|
4
4
|
import { ConfigModule } from '@nestjs/config';
|
|
5
5
|
import { PersonModule } from '@/host/controllers/person/person.module';
|
|
6
|
-
import { e2eDatabaseUrl } from '@/test/e2e-context';
|
|
6
|
+
import { e2eDatabaseUrl, e2eSchemaName } from '@/test/e2e-context';
|
|
7
7
|
import { getJwtTestKeys } from '@/test/utils/jwt-test-keys';
|
|
8
8
|
|
|
9
9
|
const jwtKeys = getJwtTestKeys();
|
|
@@ -18,6 +18,7 @@ const jwtKeys = getJwtTestKeys();
|
|
|
18
18
|
PORT: 3000,
|
|
19
19
|
NODE_ENV: 'test',
|
|
20
20
|
DATABASE_URL: e2eDatabaseUrl,
|
|
21
|
+
DATABASE_SCHEMA: e2eSchemaName,
|
|
21
22
|
JWT_PRIVATE_KEY: jwtKeys.privateKey,
|
|
22
23
|
JWT_PUBLIC_KEY: jwtKeys.publicKey,
|
|
23
24
|
JWT_ACCESS_TOKEN_EXPIRES_IN: '15m',
|
|
@@ -2,7 +2,7 @@ import { envSchema } from '@/core/env';
|
|
|
2
2
|
import { Module } from '@nestjs/common';
|
|
3
3
|
import { ConfigModule } from '@nestjs/config';
|
|
4
4
|
import { PersonModule } from '@/host/controllers/person/person.module';
|
|
5
|
-
import { e2eDatabaseUrl } from '@/test/e2e-context';
|
|
5
|
+
import { e2eDatabaseUrl, e2eSchemaName } from '@/test/e2e-context';
|
|
6
6
|
|
|
7
7
|
@Module({
|
|
8
8
|
imports: [
|
|
@@ -14,6 +14,7 @@ import { e2eDatabaseUrl } from '@/test/e2e-context';
|
|
|
14
14
|
PORT: 3000,
|
|
15
15
|
NODE_ENV: 'test',
|
|
16
16
|
DATABASE_URL: e2eDatabaseUrl,
|
|
17
|
+
DATABASE_SCHEMA: e2eSchemaName,
|
|
17
18
|
}),
|
|
18
19
|
}),
|
|
19
20
|
PersonModule,
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export let e2eDatabaseUrl: string | undefined;
|
|
2
|
+
export let e2eSchemaName: string | undefined;
|
|
2
3
|
|
|
3
|
-
export function
|
|
4
|
+
export function setE2EDatabaseContext(url: string, schemaName: string) {
|
|
4
5
|
e2eDatabaseUrl = url;
|
|
6
|
+
e2eSchemaName = schemaName;
|
|
5
7
|
process.env.DATABASE_URL = url;
|
|
8
|
+
process.env.DATABASE_SCHEMA = schemaName;
|
|
6
9
|
process.env.NODE_ENV = 'test';
|
|
7
10
|
}
|
|
@@ -10,11 +10,13 @@ import { IPersonRepository } from '@/domain/repositories/iperson.repository';
|
|
|
10
10
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Testes E2E do RepositoryBase - Lazy Loading
|
|
14
|
+
*
|
|
15
|
+
* Valida o carregamento de relacionamentos por cenário:
|
|
16
|
+
* - findById: carrega address e contacts (detalhe da entidade)
|
|
17
|
+
* - findMany: retorna apenas dados escalares (performance em listagens)
|
|
16
18
|
*/
|
|
17
|
-
describe('
|
|
19
|
+
describe('RepositoryBase - Lazy Loading (E2E)', () => {
|
|
18
20
|
let moduleRef: TestingModule;
|
|
19
21
|
|
|
20
22
|
beforeAll(async () => {
|
|
@@ -23,8 +25,8 @@ describe('PersonRepository - Relations (E2E)', () => {
|
|
|
23
25
|
}).compile();
|
|
24
26
|
});
|
|
25
27
|
|
|
26
|
-
describe('findById -
|
|
27
|
-
it('should load person with
|
|
28
|
+
describe('findById - Carregamento de Relacionamentos', () => {
|
|
29
|
+
it('should load person with all relationships using findById', async () => {
|
|
28
30
|
const createHandler = moduleRef.get(CreatePersonHandler);
|
|
29
31
|
const created = await createHandler.handle({
|
|
30
32
|
name: 'João Silva',
|
|
@@ -51,10 +53,30 @@ describe('PersonRepository - Relations (E2E)', () => {
|
|
|
51
53
|
'joao@example.com',
|
|
52
54
|
]);
|
|
53
55
|
});
|
|
56
|
+
|
|
57
|
+
it('should have all relationships loaded and accessible', async () => {
|
|
58
|
+
const createHandler = moduleRef.get(CreatePersonHandler);
|
|
59
|
+
const created = await createHandler.handle({
|
|
60
|
+
name: 'Test Person',
|
|
61
|
+
contacts: [{ contact: 'test@example.com' }],
|
|
62
|
+
address: {
|
|
63
|
+
address: 'Test Address',
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const readHandler = moduleRef.get(ReadPersonHandler);
|
|
68
|
+
const person = await readHandler.handle(created.id);
|
|
69
|
+
|
|
70
|
+
expect(person.id).toBe(created.id);
|
|
71
|
+
expect(person.address).toBeDefined();
|
|
72
|
+
expect(person.address.address).toBeDefined();
|
|
73
|
+
expect(person.contacts).toBeDefined();
|
|
74
|
+
expect(person.contacts.length).toBeGreaterThan(0);
|
|
75
|
+
});
|
|
54
76
|
});
|
|
55
77
|
|
|
56
|
-
describe('findMany -
|
|
57
|
-
it('should return persons without loading
|
|
78
|
+
describe('findMany - Listagem sem Relacionamentos', () => {
|
|
79
|
+
it('should return persons without loading relationships in findMany', async () => {
|
|
58
80
|
const createHandler = moduleRef.get(CreatePersonHandler);
|
|
59
81
|
|
|
60
82
|
for (let i = 0; i < 2; i++) {
|
|
@@ -76,11 +98,12 @@ describe('PersonRepository - Relations (E2E)', () => {
|
|
|
76
98
|
const person = items[0];
|
|
77
99
|
expect(person.id).toBeDefined();
|
|
78
100
|
expect(person.name).toBeDefined();
|
|
101
|
+
expect(person.active).toBeDefined();
|
|
79
102
|
expect(person.address).toBeUndefined();
|
|
80
|
-
expect(person.contacts
|
|
103
|
+
expect(person.contacts).toBeUndefined();
|
|
81
104
|
});
|
|
82
105
|
|
|
83
|
-
it('should
|
|
106
|
+
it('should load multiple persons efficiently', async () => {
|
|
84
107
|
const createHandler = moduleRef.get(CreatePersonHandler);
|
|
85
108
|
|
|
86
109
|
const createdIds: number[] = [];
|
|
@@ -95,6 +118,8 @@ describe('PersonRepository - Relations (E2E)', () => {
|
|
|
95
118
|
createdIds.push(result.id);
|
|
96
119
|
}
|
|
97
120
|
|
|
121
|
+
expect(createdIds.length).toBe(3);
|
|
122
|
+
|
|
98
123
|
const readManyHandler = moduleRef.get(ReadManyPersonHandler);
|
|
99
124
|
const result = await readManyHandler.handle(new ReadManyPersonRequest());
|
|
100
125
|
|
|
@@ -105,15 +130,24 @@ describe('PersonRepository - Relations (E2E)', () => {
|
|
|
105
130
|
createdIds.forEach((id) => {
|
|
106
131
|
expect(returnedIds).toContain(id);
|
|
107
132
|
});
|
|
133
|
+
|
|
134
|
+
result.items.forEach((person) => {
|
|
135
|
+
expect(person.id).toBeDefined();
|
|
136
|
+
expect(person.name).toBeDefined();
|
|
137
|
+
expect(person.active).toBeDefined();
|
|
138
|
+
});
|
|
108
139
|
});
|
|
109
140
|
});
|
|
110
141
|
|
|
111
|
-
describe('findById
|
|
112
|
-
it('
|
|
142
|
+
describe('Comparação entre findById e findMany', () => {
|
|
143
|
+
it('should load relationships only in findById', async () => {
|
|
113
144
|
const createHandler = moduleRef.get(CreatePersonHandler);
|
|
114
145
|
const created = await createHandler.handle({
|
|
115
146
|
name: 'Comparação Test',
|
|
116
|
-
contacts: [
|
|
147
|
+
contacts: [
|
|
148
|
+
{ contact: 'first@example.com' },
|
|
149
|
+
{ contact: 'second@example.com' },
|
|
150
|
+
],
|
|
117
151
|
address: {
|
|
118
152
|
address: 'Rua Comparação, 789',
|
|
119
153
|
},
|
|
@@ -131,11 +165,13 @@ describe('PersonRepository - Relations (E2E)', () => {
|
|
|
131
165
|
expect(personFromFindMany).toBeDefined();
|
|
132
166
|
expect(personFromFindById.id).toBe(personFromFindMany!.id);
|
|
133
167
|
expect(personFromFindById.name).toBe(personFromFindMany!.name);
|
|
168
|
+
|
|
134
169
|
expect(personFromFindById.address).toBeDefined();
|
|
135
170
|
expect(personFromFindById.contacts).toBeDefined();
|
|
136
171
|
expect(personFromFindById.contacts.length).toBeGreaterThan(0);
|
|
172
|
+
|
|
137
173
|
expect(personFromFindMany?.address).toBeUndefined();
|
|
138
|
-
expect(personFromFindMany?.contacts
|
|
174
|
+
expect(personFromFindMany?.contacts).toBeUndefined();
|
|
139
175
|
});
|
|
140
176
|
});
|
|
141
177
|
});
|
|
@@ -1,60 +1,23 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
import { delay } from '@koalarx/utils/KlDelay';
|
|
3
2
|
import { createE2EDatabase } from '@/test/utils/create-e2e-database';
|
|
4
3
|
import { E2EDatabaseClient } from '@/test/utils/e2e-database-client';
|
|
5
4
|
import { Pool } from 'pg';
|
|
6
5
|
|
|
7
6
|
class E2EPostgresClient extends E2EDatabaseClient {
|
|
8
|
-
private baseUrl: URL;
|
|
9
|
-
|
|
10
7
|
public pool: Pool;
|
|
11
8
|
|
|
12
9
|
constructor(url: string, schemaName: string) {
|
|
13
10
|
super(url, schemaName);
|
|
14
|
-
|
|
15
|
-
this.baseUrl = new URL(this.url);
|
|
16
|
-
this.baseUrl.pathname = `/${this.schemaName}`;
|
|
17
|
-
|
|
18
|
-
this.pool = this.createSession();
|
|
11
|
+
this.pool = new Pool({ connectionString: url });
|
|
19
12
|
}
|
|
20
13
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
connectionString: this.baseUrl.toString(),
|
|
24
|
-
...(idleTimeout ? { idleTimeoutMillis: idleTimeout } : {}),
|
|
25
|
-
});
|
|
14
|
+
async createSchema(schemaName: string): Promise<void> {
|
|
15
|
+
await this.pool.query(`CREATE SCHEMA "${schemaName}"`);
|
|
26
16
|
}
|
|
27
17
|
|
|
28
|
-
async
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
const pool = this.createSession();
|
|
32
|
-
|
|
33
|
-
await pool.query(`CREATE DATABASE "${schemaName}"`);
|
|
34
|
-
await pool.end();
|
|
35
|
-
|
|
36
|
-
this.baseUrl.pathname = `/${schemaName}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async dropDatabase(): Promise<void> {
|
|
18
|
+
async dropSchema(): Promise<void> {
|
|
19
|
+
await this.pool.query(`DROP SCHEMA IF EXISTS "${this.schemaName}" CASCADE`);
|
|
40
20
|
await this.pool.end();
|
|
41
|
-
|
|
42
|
-
await delay(1000);
|
|
43
|
-
|
|
44
|
-
this.baseUrl.pathname = '/postgres';
|
|
45
|
-
const pool = this.createSession(100);
|
|
46
|
-
|
|
47
|
-
await pool.query(`
|
|
48
|
-
SELECT pg_terminate_backend(pg_stat_activity.pid)
|
|
49
|
-
FROM pg_stat_activity
|
|
50
|
-
WHERE pg_stat_activity.datname = '${this.schemaName}'
|
|
51
|
-
AND pid <> pg_backend_pid()
|
|
52
|
-
`);
|
|
53
|
-
|
|
54
|
-
await delay(500);
|
|
55
|
-
|
|
56
|
-
await pool.query(`DROP DATABASE IF EXISTS "${this.schemaName}"`);
|
|
57
|
-
await pool.end();
|
|
58
21
|
}
|
|
59
22
|
}
|
|
60
23
|
|
|
@@ -62,5 +25,5 @@ const { client } = await createE2EDatabase(E2EPostgresClient);
|
|
|
62
25
|
export const pgClient = client;
|
|
63
26
|
|
|
64
27
|
afterAll(async () => {
|
|
65
|
-
await pgClient.
|
|
28
|
+
await pgClient.dropSchema();
|
|
66
29
|
});
|
|
@@ -2,7 +2,7 @@ import { Type } from '@nestjs/common';
|
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { readFileSync } from 'node:fs';
|
|
5
|
-
import {
|
|
5
|
+
import { setE2EDatabaseContext } from '../e2e-context';
|
|
6
6
|
import { E2EDatabaseClient } from './e2e-database-client';
|
|
7
7
|
|
|
8
8
|
function resolveMigrationRunner(): string {
|
|
@@ -19,35 +19,37 @@ function resolveMigrationRunner(): string {
|
|
|
19
19
|
return 'node --import ts-node/register/transpile-only';
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function
|
|
23
|
-
const
|
|
22
|
+
function generateUniqueSchemaConfig() {
|
|
23
|
+
const schemaName = `e2e_${randomUUID().replace(/-/g, '_')}`;
|
|
24
24
|
|
|
25
25
|
if (!process.env.DATABASE_URL) {
|
|
26
26
|
throw new Error('Please provide a DATABASE_URL environment variable');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const url = new URL(process.env.DATABASE_URL);
|
|
30
|
-
url.pathname = `/${schemaId}`;
|
|
31
|
-
|
|
32
29
|
return {
|
|
33
|
-
url:
|
|
34
|
-
|
|
30
|
+
url: process.env.DATABASE_URL,
|
|
31
|
+
schemaName,
|
|
35
32
|
};
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
export async function createE2EDatabase<T extends E2EDatabaseClient>(
|
|
39
36
|
clientInstance: Type<T>,
|
|
40
37
|
) {
|
|
41
|
-
const { url,
|
|
38
|
+
const { url, schemaName } = generateUniqueSchemaConfig();
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
setE2EDatabaseContext(url, schemaName);
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
const client = new clientInstance(url, schemaId);
|
|
42
|
+
const client = new clientInstance(url, schemaName);
|
|
47
43
|
|
|
48
|
-
|
|
44
|
+
try {
|
|
45
|
+
await client.createSchema(schemaName);
|
|
49
46
|
|
|
50
|
-
const env = {
|
|
47
|
+
const env = {
|
|
48
|
+
...process.env,
|
|
49
|
+
DATABASE_URL: url,
|
|
50
|
+
DATABASE_SCHEMA: schemaName,
|
|
51
|
+
NODE_ENV: 'test',
|
|
52
|
+
};
|
|
51
53
|
const migrationRunner = resolveMigrationRunner();
|
|
52
54
|
execSync(
|
|
53
55
|
`${migrationRunner} ./node_modules/typeorm/cli.js migration:run -d ./src/infra/database/migrations/migration-datasource.ts`,
|
|
@@ -58,9 +60,15 @@ export async function createE2EDatabase<T extends E2EDatabaseClient>(
|
|
|
58
60
|
},
|
|
59
61
|
);
|
|
60
62
|
|
|
61
|
-
return { client,
|
|
63
|
+
return { client, schemaName };
|
|
62
64
|
} catch (error) {
|
|
63
|
-
|
|
65
|
+
try {
|
|
66
|
+
await client.dropSchema();
|
|
67
|
+
} catch {
|
|
68
|
+
// schema pode não ter sido criado
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.error('Erro ao preparar schema e2e:', error);
|
|
64
72
|
throw error;
|
|
65
73
|
}
|
|
66
74
|
}
|
|
@@ -4,6 +4,6 @@ export abstract class E2EDatabaseClient {
|
|
|
4
4
|
protected readonly schemaName: string,
|
|
5
5
|
) {}
|
|
6
6
|
|
|
7
|
-
abstract
|
|
8
|
-
abstract
|
|
7
|
+
abstract createSchema(schemaName: string): Promise<void>;
|
|
8
|
+
abstract dropSchema(): Promise<void>;
|
|
9
9
|
}
|