@koalarx/nest 4.0.5 → 4.0.6
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/commands/new/configure-test-runner.js +13 -2
- package/cli/commands/new/create-ddd-structure.js +2 -4
- package/cli/constants/cli-project-checklist.js +22 -3
- package/cli/utils/install-module.js +5 -0
- package/cli/utils/install-workspace-config.js +2 -0
- package/cli/utils/patch-app-test-module.js +29 -0
- package/cli/utils/remove-sample-parts.js +10 -3
- package/koala-nest/.vscode/tasks.json +9 -0
- package/koala-nest/src/application/common/request-validator.base.ts +36 -1
- package/koala-nest/src/core/tools/mapping/auto-mapper.ts +5 -1
- package/koala-nest/src/core/tools/mapping/mapping-store.ts +22 -14
- package/koala-nest/src/test/application/read-many-person.handler.spec.ts +42 -2
- package/koala-nest/src/test/core/mapping.spec.ts +61 -0
- package/koala-nest/src/test/host/controllers/app/app.e2e.spec.ts +24 -0
- package/koala-nest/src/test/utils/create-e2e-database.ts +17 -1
- package/koala-nest/vitest.config.e2e.ts +13 -0
- package/koala-nest/vitest.config.ts +12 -0
- package/package.json +1 -1
|
@@ -6,8 +6,19 @@ export function configureTestRunner(packageJson, packageManager) {
|
|
|
6
6
|
scripts["test:watch"] = "bun test --watch";
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
|
-
scripts.test = "vitest run";
|
|
10
|
-
scripts["test:watch"] = "vitest";
|
|
9
|
+
scripts.test = "vitest run --config vitest.config.ts";
|
|
10
|
+
scripts["test:watch"] = "vitest --config vitest.config.ts";
|
|
11
11
|
devDependencies.vitest = "^4.1.8";
|
|
12
12
|
devDependencies["vite-tsconfig-paths"] = "^5.1.4";
|
|
13
13
|
}
|
|
14
|
+
export function configureE2ETestRunner(packageJson, packageManager) {
|
|
15
|
+
const scripts = packageJson.scripts;
|
|
16
|
+
const devDependencies = packageJson.devDependencies;
|
|
17
|
+
devDependencies.supertest ??= "^7.1.0";
|
|
18
|
+
devDependencies["@types/supertest"] ??= "^6.0.2";
|
|
19
|
+
if (packageManager === "bun") {
|
|
20
|
+
scripts["test:e2e"] = "bun test --preload ./src/test/setup-e2e.ts src/test/host/controllers/";
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
scripts["test:e2e"] = "vitest run --config vitest.config.e2e.ts";
|
|
24
|
+
}
|
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { DDD_LAYER_FOLDERS } from "../../constants/domain.js";
|
|
4
4
|
import { patchGeneratedProjectConfig } from "../../utils/patch-generated-project.js";
|
|
5
5
|
import { runCommand } from "../../utils/run-command.js";
|
|
6
|
-
import { configureTestRunner } from "./configure-test-runner.js";
|
|
6
|
+
import { configureE2ETestRunner, configureTestRunner } from "./configure-test-runner.js";
|
|
7
7
|
export async function createDDDStructure(projectName, packageManager) {
|
|
8
8
|
const folders = [...DDD_LAYER_FOLDERS];
|
|
9
9
|
for (const folder of folders) {
|
|
@@ -38,14 +38,12 @@ export async function createDDDStructure(projectName, packageManager) {
|
|
|
38
38
|
packageJson.scripts["migration:revert"] = `${migrationRunner} ${typeormCli} migration:revert ${migrationDatasource}`;
|
|
39
39
|
packageJson.devDependencies ??= {};
|
|
40
40
|
configureTestRunner(packageJson, packageManager);
|
|
41
|
+
configureE2ETestRunner(packageJson, packageManager);
|
|
41
42
|
delete packageJson.scripts["test:cov"];
|
|
42
43
|
delete packageJson.scripts["test:debug"];
|
|
43
|
-
delete packageJson.scripts["test:e2e"];
|
|
44
44
|
delete packageJson.jest;
|
|
45
45
|
delete packageJson.devDependencies["@types/jest"];
|
|
46
|
-
delete packageJson.devDependencies["@types/supertest"];
|
|
47
46
|
delete packageJson.devDependencies["ts-jest"];
|
|
48
|
-
delete packageJson.devDependencies["supertest"];
|
|
49
47
|
writeFileSync(path.join(process.cwd(), projectName, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
50
48
|
patchGeneratedProjectConfig(path.join(process.cwd(), projectName));
|
|
51
49
|
await runCommand([packageManager, "install"], path.join(process.cwd(), projectName));
|
|
@@ -5,6 +5,21 @@ import {
|
|
|
5
5
|
resolveNewProjectOptions
|
|
6
6
|
} from "./domain.js";
|
|
7
7
|
import { resolveProjectFeatures } from "../utils/install-module.js";
|
|
8
|
+
export const E2E_INFRA_PATHS = [
|
|
9
|
+
"src/test/setup-e2e.ts",
|
|
10
|
+
"src/test/create-e2e-test-app.ts",
|
|
11
|
+
"src/test/e2e-context.ts",
|
|
12
|
+
"src/test/utils/create-e2e-database.ts",
|
|
13
|
+
"src/test/utils/e2e-database-client.ts",
|
|
14
|
+
"src/test/host/controllers/app/app.e2e.spec.ts"
|
|
15
|
+
];
|
|
16
|
+
export const CRUD_E2E_EXAMPLE_PATHS = [
|
|
17
|
+
"src/test/host/controllers/person/person.controller.e2e.spec.ts",
|
|
18
|
+
"src/test/host/controllers/person/lazy-loading.e2e.spec.ts",
|
|
19
|
+
"src/test/host/controllers/auth/auth.controller.e2e.spec.ts",
|
|
20
|
+
"src/test/app-auth-test.module.ts",
|
|
21
|
+
"src/test/create-auth-e2e-test-app.ts"
|
|
22
|
+
];
|
|
8
23
|
export const WORKSPACE_SETUP_PATHS = [
|
|
9
24
|
".vscode/launch.json",
|
|
10
25
|
".vscode/settings.json",
|
|
@@ -172,9 +187,13 @@ export function buildProjectExpectation(template, auth, features) {
|
|
|
172
187
|
};
|
|
173
188
|
}
|
|
174
189
|
export function requiredPathsForExpectation(expectation) {
|
|
175
|
-
const paths = [
|
|
190
|
+
const paths = [
|
|
191
|
+
...CORE_REQUIRED_PATHS,
|
|
192
|
+
...WORKSPACE_SETUP_PATHS,
|
|
193
|
+
...E2E_INFRA_PATHS
|
|
194
|
+
];
|
|
176
195
|
if (expectation.template === Template.CRUD_SAMPLE) {
|
|
177
|
-
paths.push(...CRUD_TEMPLATE_REQUIRED_PATHS);
|
|
196
|
+
paths.push(...CRUD_TEMPLATE_REQUIRED_PATHS, ...CRUD_E2E_EXAMPLE_PATHS);
|
|
178
197
|
}
|
|
179
198
|
if (expectation.cache === "redis") {
|
|
180
199
|
paths.push(...CACHE_REDIS_REQUIRED_PATHS);
|
|
@@ -196,7 +215,7 @@ export function requiredPathsForExpectation(expectation) {
|
|
|
196
215
|
export function forbiddenPathsForExpectation(expectation) {
|
|
197
216
|
const paths = [];
|
|
198
217
|
if (expectation.template === Template.DEFAULT) {
|
|
199
|
-
paths.push(...DEFAULT_TEMPLATE_FORBIDDEN_PATHS);
|
|
218
|
+
paths.push(...DEFAULT_TEMPLATE_FORBIDDEN_PATHS, ...CRUD_E2E_EXAMPLE_PATHS);
|
|
200
219
|
} else {
|
|
201
220
|
paths.push(...CRUD_TEMPLATE_FORBIDDEN_PATHS);
|
|
202
221
|
}
|
|
@@ -153,6 +153,11 @@ export async function installModule(module, template, projectName = "", options
|
|
|
153
153
|
for (const configFile of ["tsconfig.build.json", ".env.example"]) {
|
|
154
154
|
cpSync(path.join(getSourceCodePath(), configFile), path.join(projectPath, configFile));
|
|
155
155
|
}
|
|
156
|
+
if (packageManager !== "bun") {
|
|
157
|
+
for (const configFile of ["vitest.config.ts", "vitest.config.e2e.ts"]) {
|
|
158
|
+
cpSync(path.join(getSourceCodePath(), configFile), path.join(projectPath, configFile));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
156
161
|
patchInfraModuleFile(projectName, false);
|
|
157
162
|
patchMainFile(projectName, stripMainOptionalFeatures);
|
|
158
163
|
if (!options.skipPackages) {
|
|
@@ -45,6 +45,8 @@ export function installWorkspaceConfig(projectName, packageManager) {
|
|
|
45
45
|
task.command = runScriptCommand(packageManager, "start:dev");
|
|
46
46
|
} else if (task.command.includes("test")) {
|
|
47
47
|
task.command = runScriptCommand(packageManager, "test");
|
|
48
|
+
} else if (task.command.includes("test:e2e")) {
|
|
49
|
+
task.command = runScriptCommand(packageManager, "test:e2e");
|
|
48
50
|
} else if (task.command.includes("migration:run")) {
|
|
49
51
|
task.command = runScriptCommand(packageManager, "migration:run");
|
|
50
52
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveProjectPath } from "./resolve-project-path.js";
|
|
4
|
+
const defaultAppTestModule = `import { envSchema } from '@/core/env';
|
|
5
|
+
import { Module } from '@nestjs/common';
|
|
6
|
+
import { ConfigModule } from '@nestjs/config';
|
|
7
|
+
import { InfraModule } from '@/infra/infra.module';
|
|
8
|
+
import { e2eDatabaseUrl } from '@/test/e2e-context';
|
|
9
|
+
|
|
10
|
+
@Module({
|
|
11
|
+
imports: [
|
|
12
|
+
ConfigModule.forRoot({
|
|
13
|
+
isGlobal: true,
|
|
14
|
+
ignoreEnvFile: true,
|
|
15
|
+
validate: () =>
|
|
16
|
+
envSchema.parse({
|
|
17
|
+
PORT: 3000,
|
|
18
|
+
NODE_ENV: 'test',
|
|
19
|
+
DATABASE_URL: e2eDatabaseUrl,
|
|
20
|
+
}),
|
|
21
|
+
}),
|
|
22
|
+
InfraModule,
|
|
23
|
+
],
|
|
24
|
+
})
|
|
25
|
+
export class AppTestModule {}
|
|
26
|
+
`;
|
|
27
|
+
export function patchAppTestModuleForDefault(projectName) {
|
|
28
|
+
writeFileSync(path.join(resolveProjectPath(projectName), "src/test/app-test.module.ts"), defaultAppTestModule);
|
|
29
|
+
}
|
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { removeImportLines } from "./project-files.js";
|
|
4
4
|
import { patchAppModuleJobs } from "./patch-jobs-module.js";
|
|
5
|
+
import { patchAppTestModuleForDefault } from "./patch-app-test-module.js";
|
|
5
6
|
import { resolveProjectPath } from "./resolve-project-path.js";
|
|
6
7
|
import { stripMainOptionalFeatures } from "./patch-main.js";
|
|
7
8
|
import { stripDefineDocumentationAuth } from "./patch-define-documentation.js";
|
|
@@ -85,11 +86,13 @@ const partsToRemove = [
|
|
|
85
86
|
replace: [{ from: "PersonMapper.createMap();", to: "" }]
|
|
86
87
|
}
|
|
87
88
|
];
|
|
88
|
-
const
|
|
89
|
+
const defaultSampleTestPathsToRemove = [
|
|
89
90
|
"src/test/application",
|
|
90
91
|
"src/test/mockup/person",
|
|
91
92
|
"src/test/host/controllers/person/person.controller.e2e.spec.ts",
|
|
92
|
-
"src/test/host/controllers/person/lazy-loading.e2e.spec.ts"
|
|
93
|
+
"src/test/host/controllers/person/lazy-loading.e2e.spec.ts"
|
|
94
|
+
];
|
|
95
|
+
const defaultAuthE2ePathsToRemove = [
|
|
93
96
|
"src/test/host/controllers/auth/auth.controller.e2e.spec.ts",
|
|
94
97
|
"src/test/app-auth-test.module.ts",
|
|
95
98
|
"src/test/create-auth-e2e-test-app.ts"
|
|
@@ -154,7 +157,11 @@ export async function removeSampleParts(projectName) {
|
|
|
154
157
|
writeFileSync(partPath, content, "utf8");
|
|
155
158
|
}
|
|
156
159
|
patchAppModuleJobs(projectName, { eventHandlers: [], cronJobs: [] });
|
|
157
|
-
|
|
160
|
+
patchAppTestModuleForDefault(projectName);
|
|
161
|
+
removePaths(projectName, [
|
|
162
|
+
...defaultSampleTestPathsToRemove,
|
|
163
|
+
...defaultAuthE2ePathsToRemove
|
|
164
|
+
]);
|
|
158
165
|
}
|
|
159
166
|
export async function cleanDefaultTemplateWithoutAuth(projectName) {
|
|
160
167
|
stripDefaultProjectAuth(projectName);
|
|
@@ -25,6 +25,15 @@
|
|
|
25
25
|
"group": "test",
|
|
26
26
|
"label": "Run tests"
|
|
27
27
|
},
|
|
28
|
+
{
|
|
29
|
+
"type": "shell",
|
|
30
|
+
"command": "bun run test:e2e",
|
|
31
|
+
"options": {
|
|
32
|
+
"cwd": "${workspaceFolder}"
|
|
33
|
+
},
|
|
34
|
+
"group": "test",
|
|
35
|
+
"label": "Run E2E tests"
|
|
36
|
+
},
|
|
28
37
|
{
|
|
29
38
|
"type": "shell",
|
|
30
39
|
"command": "bun run migration:run",
|
|
@@ -6,7 +6,7 @@ export abstract class RequestValidatorBase<
|
|
|
6
6
|
protected _request: Record<string, any>;
|
|
7
7
|
|
|
8
8
|
constructor(request: TRequest) {
|
|
9
|
-
this._request =
|
|
9
|
+
this._request = RequestValidatorBase.toValidationInput(request);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
validate(): TRequest {
|
|
@@ -30,4 +30,39 @@ export abstract class RequestValidatorBase<
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
protected abstract get schema(): ZodType;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Objetos plain (query HTTP) passam direto. Instâncias de classe carregam
|
|
36
|
+
* defaults nos fields — removemos os que não foram alterados para o Zod
|
|
37
|
+
* aplicar defaults/transforms sem sobrescrever query params explícitos.
|
|
38
|
+
*/
|
|
39
|
+
private static toValidationInput<T extends Record<string, unknown>>(
|
|
40
|
+
request: T,
|
|
41
|
+
): Record<string, unknown> {
|
|
42
|
+
if (request === null || typeof request !== 'object') {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const prototype = Object.getPrototypeOf(request);
|
|
47
|
+
const isClassInstance =
|
|
48
|
+
prototype !== null &&
|
|
49
|
+
prototype !== Object.prototype &&
|
|
50
|
+
typeof prototype.constructor === 'function';
|
|
51
|
+
|
|
52
|
+
if (!isClassInstance) {
|
|
53
|
+
return { ...request };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const Constructor = prototype.constructor as new () => T;
|
|
57
|
+
const defaultValues = new Constructor() as Record<string, unknown>;
|
|
58
|
+
const input: Record<string, unknown> = { ...request };
|
|
59
|
+
|
|
60
|
+
for (const key of Object.keys(defaultValues)) {
|
|
61
|
+
if (Object.is(input[key], defaultValues[key])) {
|
|
62
|
+
delete input[key];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return input;
|
|
67
|
+
}
|
|
33
68
|
}
|
|
@@ -94,7 +94,11 @@ export class AutoMapper {
|
|
|
94
94
|
continue;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
const sourceValue = data[sourceProp.name];
|
|
98
|
+
|
|
99
|
+
if (sourceValue !== undefined) {
|
|
100
|
+
targetInstance[targetPropName] = sourceValue;
|
|
101
|
+
}
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
return targetInstance;
|
|
@@ -57,32 +57,40 @@ export class MappingStore {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
const parentPrototype = Object.getPrototypeOf(current.prototype);
|
|
61
|
+
current = (parentPrototype?.constructor as Type<any> | undefined) ?? null;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
return props;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
static getPropType(entity: Type<any>, propName: string) {
|
|
67
|
-
|
|
68
|
+
let current: Type<any> | null = entity;
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
`Property ${propName} not found in entity ${entity.name}`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
70
|
+
while (current?.prototype) {
|
|
71
|
+
const prop = this._entities.get(current)?.find((p) => p.name === propName);
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
if (prop) {
|
|
74
|
+
const { type, isArray } = prop;
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
if (!type) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
if (isArray && type instanceof Function) {
|
|
81
|
+
return (type as () => Type<any>)();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return type;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const parentPrototype = Object.getPrototypeOf(current.prototype);
|
|
88
|
+
current = (parentPrototype?.constructor as Type<any> | undefined) ?? null;
|
|
83
89
|
}
|
|
84
90
|
|
|
85
|
-
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Property ${propName} not found in entity ${entity.name}`,
|
|
93
|
+
);
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
static add(
|
|
@@ -21,18 +21,58 @@ describe('ReadManyPersonHandler', () => {
|
|
|
21
21
|
contacts: [],
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
let capturedPage: number | undefined;
|
|
24
25
|
const repository = {
|
|
25
|
-
findMany: async (
|
|
26
|
+
findMany: async (query: { page?: number; skip: () => number }) => {
|
|
27
|
+
capturedPage = query.page;
|
|
28
|
+
return { items: [person], count: 1 };
|
|
29
|
+
},
|
|
26
30
|
} as unknown as IPersonRepository;
|
|
27
31
|
|
|
28
32
|
const handler = new ReadManyPersonHandler(repository, new CacheStub());
|
|
29
|
-
const result = await handler.handle(
|
|
33
|
+
const result = await handler.handle({ page: '2', limit: '5' } as never);
|
|
30
34
|
|
|
35
|
+
expect(capturedPage).toBe(1);
|
|
31
36
|
expect(result.count).toBe(1);
|
|
32
37
|
expect(result.items).toHaveLength(1);
|
|
33
38
|
expect(result.items[0].name).toBe('Jane');
|
|
34
39
|
});
|
|
35
40
|
|
|
41
|
+
it('preserva paginação ao receber instância de ReadManyPersonRequest', async () => {
|
|
42
|
+
let capturedPage: number | undefined;
|
|
43
|
+
const repository = {
|
|
44
|
+
findMany: async (query: { page?: number }) => {
|
|
45
|
+
capturedPage = query.page;
|
|
46
|
+
return { items: [], count: 0 };
|
|
47
|
+
},
|
|
48
|
+
} as unknown as IPersonRepository;
|
|
49
|
+
|
|
50
|
+
const handler = new ReadManyPersonHandler(repository, new CacheStub());
|
|
51
|
+
const request = Object.assign(new ReadManyPersonRequest(), {
|
|
52
|
+
page: '2',
|
|
53
|
+
limit: '5',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await handler.handle(request);
|
|
57
|
+
|
|
58
|
+
expect(capturedPage).toBe(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('aplica defaults de paginação quando a request não informa page', async () => {
|
|
62
|
+
let capturedPage: number | undefined;
|
|
63
|
+
const repository = {
|
|
64
|
+
findMany: async (query: { page?: number }) => {
|
|
65
|
+
capturedPage = query.page;
|
|
66
|
+
return { items: [], count: 0 };
|
|
67
|
+
},
|
|
68
|
+
} as unknown as IPersonRepository;
|
|
69
|
+
|
|
70
|
+
const handler = new ReadManyPersonHandler(repository, new CacheStub());
|
|
71
|
+
await handler.handle(new ReadManyPersonRequest());
|
|
72
|
+
|
|
73
|
+
expect(capturedPage).toBe(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
36
76
|
it('reutiliza cache para a mesma consulta de listagem', async () => {
|
|
37
77
|
const person = Person.from({
|
|
38
78
|
id: 1,
|
|
@@ -137,6 +137,67 @@ describe('AutoMapper', () => {
|
|
|
137
137
|
expect(target.fullName).toBe('John Doe');
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
+
it('should map inherited properties from parent classes', () => {
|
|
141
|
+
class BaseRequest {
|
|
142
|
+
@AutoMap()
|
|
143
|
+
page?: number;
|
|
144
|
+
|
|
145
|
+
@AutoMap()
|
|
146
|
+
limit?: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
class ChildRequest extends BaseRequest {
|
|
150
|
+
@AutoMap()
|
|
151
|
+
name?: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
class BaseDto {
|
|
155
|
+
@AutoMap()
|
|
156
|
+
page?: number = 0;
|
|
157
|
+
|
|
158
|
+
@AutoMap()
|
|
159
|
+
limit?: number = 10;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
class ChildDto extends BaseDto {
|
|
163
|
+
@AutoMap()
|
|
164
|
+
name?: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
createMap(ChildRequest, ChildDto);
|
|
168
|
+
|
|
169
|
+
const source = Object.assign(new ChildRequest(), {
|
|
170
|
+
page: 2,
|
|
171
|
+
limit: 25,
|
|
172
|
+
name: 'John',
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const target = AutoMapper.map(source, ChildRequest, ChildDto);
|
|
176
|
+
|
|
177
|
+
expect(target.page).toBe(2);
|
|
178
|
+
expect(target.limit).toBe(25);
|
|
179
|
+
expect(target.name).toBe('John');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should resolve inherited property types via getProps and getPropType', () => {
|
|
183
|
+
class BaseRequest {
|
|
184
|
+
@AutoMap()
|
|
185
|
+
page?: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
class ChildRequest extends BaseRequest {
|
|
189
|
+
@AutoMap()
|
|
190
|
+
name?: string;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const props = MappingStore.getProps(ChildRequest).map((p) => p.name);
|
|
194
|
+
|
|
195
|
+
expect(props).toContain('page');
|
|
196
|
+
expect(props).toContain('name');
|
|
197
|
+
expect(MappingStore.getPropType(ChildRequest, 'page')).toBe(Number);
|
|
198
|
+
expect(MappingStore.getPropType(ChildRequest, 'name')).toBe(String);
|
|
199
|
+
});
|
|
200
|
+
|
|
140
201
|
it('should map nested objects using the target property name', () => {
|
|
141
202
|
class SourceChild {
|
|
142
203
|
@AutoMap()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="bun-types/test-globals" />
|
|
2
|
+
|
|
3
|
+
import { createE2ETestApp } from '@/test/create-e2e-test-app';
|
|
4
|
+
import type { INestApplication } from '@nestjs/common';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Estrutura mínima de E2E — use como ponto de partida no template Padrão.
|
|
8
|
+
* No exemplo CRUD, veja `person.controller.e2e.spec.ts` e `auth.controller.e2e.spec.ts`.
|
|
9
|
+
*/
|
|
10
|
+
describe('App (E2E)', () => {
|
|
11
|
+
let app: INestApplication;
|
|
12
|
+
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
app = await createE2ETestApp();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterAll(async () => {
|
|
18
|
+
await app.close();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should bootstrap the application', () => {
|
|
22
|
+
expect(app.getHttpServer()).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { Type } from '@nestjs/common';
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
4
5
|
import { setE2EDatabaseUrl } from '../e2e-context';
|
|
5
6
|
import { E2EDatabaseClient } from './e2e-database-client';
|
|
6
7
|
|
|
8
|
+
function resolveMigrationRunner(): string {
|
|
9
|
+
const packageJson = JSON.parse(readFileSync('package.json', 'utf8')) as {
|
|
10
|
+
packageManager?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const packageManager = packageJson.packageManager ?? 'bun';
|
|
14
|
+
|
|
15
|
+
if (packageManager === 'bun') {
|
|
16
|
+
return 'bun';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return 'node --import ts-node/register/transpile-only';
|
|
20
|
+
}
|
|
21
|
+
|
|
7
22
|
function generateUniqueDatabaseURL() {
|
|
8
23
|
const schemaId = randomUUID();
|
|
9
24
|
|
|
@@ -33,8 +48,9 @@ export async function createE2EDatabase<T extends E2EDatabaseClient>(
|
|
|
33
48
|
await client.createDatabase(schemaId);
|
|
34
49
|
|
|
35
50
|
const env = { ...process.env, DATABASE_URL: url, NODE_ENV: 'test' };
|
|
51
|
+
const migrationRunner = resolveMigrationRunner();
|
|
36
52
|
execSync(
|
|
37
|
-
|
|
53
|
+
`${migrationRunner} ./node_modules/typeorm/cli.js migration:run -d ./src/infra/database/migrations/migration-datasource.ts`,
|
|
38
54
|
{
|
|
39
55
|
cwd: process.cwd(),
|
|
40
56
|
env,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [tsconfigPaths()],
|
|
6
|
+
test: {
|
|
7
|
+
include: ['src/test/**/*.e2e.spec.ts'],
|
|
8
|
+
setupFiles: ['src/test/setup-e2e.ts'],
|
|
9
|
+
environment: 'node',
|
|
10
|
+
testTimeout: 60_000,
|
|
11
|
+
hookTimeout: 60_000,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [tsconfigPaths()],
|
|
6
|
+
test: {
|
|
7
|
+
include: ['src/test/**/*.spec.ts'],
|
|
8
|
+
exclude: ['src/test/**/*.e2e.spec.ts'],
|
|
9
|
+
setupFiles: ['src/test/setup.ts'],
|
|
10
|
+
environment: 'node',
|
|
11
|
+
},
|
|
12
|
+
});
|