@tstdl/base 0.93.145 → 0.93.146
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/authentication/tests/authentication.client-service.test.js +15 -19
- package/authentication/tests/authentication.service.test.js +92 -119
- package/notification/tests/notification-client.test.js +39 -50
- package/notification/tests/notification-flow.test.js +204 -238
- package/notification/tests/notification-sse.service.test.js +20 -27
- package/notification/tests/notification-type.service.test.js +17 -20
- package/orm/tests/query-complex.test.js +80 -111
- package/orm/tests/repository-advanced.test.js +100 -143
- package/orm/tests/repository-attributes.test.js +30 -39
- package/orm/tests/repository-compound-primary-key.test.js +67 -75
- package/orm/tests/repository-comprehensive.test.js +76 -101
- package/orm/tests/repository-coverage.test.d.ts +1 -0
- package/orm/tests/repository-coverage.test.js +88 -149
- package/orm/tests/repository-cti-extensive.test.d.ts +1 -0
- package/orm/tests/repository-cti-extensive.test.js +118 -147
- package/orm/tests/repository-cti-mapping.test.d.ts +1 -0
- package/orm/tests/repository-cti-mapping.test.js +29 -42
- package/orm/tests/repository-cti-soft-delete.test.d.ts +1 -0
- package/orm/tests/repository-cti-soft-delete.test.js +25 -37
- package/orm/tests/repository-cti-transactions.test.js +19 -33
- package/orm/tests/repository-cti-upsert-many.test.d.ts +1 -0
- package/orm/tests/repository-cti-upsert-many.test.js +38 -50
- package/orm/tests/repository-cti.test.d.ts +1 -0
- package/orm/tests/repository-cti.test.js +195 -247
- package/orm/tests/repository-expiration.test.d.ts +1 -0
- package/orm/tests/repository-expiration.test.js +46 -59
- package/orm/tests/repository-extra-coverage.test.d.ts +1 -0
- package/orm/tests/repository-extra-coverage.test.js +195 -337
- package/orm/tests/repository-mapping.test.d.ts +1 -0
- package/orm/tests/repository-mapping.test.js +20 -20
- package/orm/tests/repository-regression.test.js +124 -163
- package/orm/tests/repository-search.test.js +30 -44
- package/orm/tests/repository-soft-delete.test.js +54 -79
- package/orm/tests/repository-types.test.js +77 -111
- package/package.json +1 -1
- package/task-queue/tests/worker.test.js +5 -5
- package/testing/README.md +38 -16
- package/testing/integration-setup.d.ts +11 -0
- package/testing/integration-setup.js +57 -30
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
|
|
1
2
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
3
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
4
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -7,17 +8,20 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
10
|
};
|
|
10
|
-
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
11
|
-
import { NumberProperty, StringProperty } from '../../schema/index.js';
|
|
12
11
|
import { sql } from 'drizzle-orm';
|
|
13
12
|
import { beforeAll, describe, expect, test } from 'vitest';
|
|
13
|
+
import { NumberProperty, StringProperty } from '../../schema/index.js';
|
|
14
|
+
import { setupIntegrationTest } from '../../testing/index.js';
|
|
14
15
|
import { ChildEntity, Column, Inheritance, Table } from '../decorators.js';
|
|
15
16
|
import { Entity } from '../entity.js';
|
|
16
|
-
import {
|
|
17
|
-
import { injectRepository } from '../server/repository.js';
|
|
17
|
+
import { getRepository } from '../server/index.js';
|
|
18
18
|
describe('ORM Repository CTI Extensive (Integration)', () => {
|
|
19
19
|
let injector;
|
|
20
20
|
let db;
|
|
21
|
+
let personRepo;
|
|
22
|
+
let empRepo;
|
|
23
|
+
let mgrRepo;
|
|
24
|
+
let cntRepo;
|
|
21
25
|
const schema = 'test_orm_cti_extensive';
|
|
22
26
|
// --- Entities ---
|
|
23
27
|
let Person = class Person extends Entity {
|
|
@@ -78,18 +82,13 @@ describe('ORM Repository CTI Extensive (Integration)', () => {
|
|
|
78
82
|
], Contractor);
|
|
79
83
|
// --- Setup ---
|
|
80
84
|
beforeAll(async () => {
|
|
81
|
-
injector =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
password: 'wf7rq6glrk5jykne',
|
|
89
|
-
database: 'tstdl',
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
db = injector.resolve(Database);
|
|
85
|
+
({ injector, database: db } = await setupIntegrationTest({
|
|
86
|
+
orm: { schema },
|
|
87
|
+
}));
|
|
88
|
+
personRepo = injector.resolve(getRepository(Person));
|
|
89
|
+
empRepo = injector.resolve(getRepository(Employee));
|
|
90
|
+
mgrRepo = injector.resolve(getRepository(Manager));
|
|
91
|
+
cntRepo = injector.resolve(getRepository(Contractor));
|
|
93
92
|
await db.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
|
|
94
93
|
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('managers')} CASCADE`);
|
|
95
94
|
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('employees')} CASCADE`);
|
|
@@ -137,102 +136,85 @@ describe('ORM Repository CTI Extensive (Integration)', () => {
|
|
|
137
136
|
// --- Tests ---
|
|
138
137
|
test('should insert different subtypes via specific repositories', async () => {
|
|
139
138
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('persons')} CASCADE`);
|
|
140
|
-
await
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const c1 = await cntRepo.insert(Object.assign(new Contractor(), { name: 'C1', contractEnd: '2025-12-31' }));
|
|
147
|
-
expect(e1.type).toBe('employee');
|
|
148
|
-
expect(m1.type).toBe('manager');
|
|
149
|
-
expect(c1.type).toBe('contractor');
|
|
150
|
-
});
|
|
139
|
+
const e1 = await empRepo.insert(Object.assign(new Employee(), { name: 'E1', salary: 50000 }));
|
|
140
|
+
const m1 = await mgrRepo.insert(Object.assign(new Manager(), { name: 'M1', salary: 80000, department: 'IT' }));
|
|
141
|
+
const c1 = await cntRepo.insert(Object.assign(new Contractor(), { name: 'C1', contractEnd: '2025-12-31' }));
|
|
142
|
+
expect(e1.type).toBe('employee');
|
|
143
|
+
expect(m1.type).toBe('manager');
|
|
144
|
+
expect(c1.type).toBe('contractor');
|
|
151
145
|
});
|
|
152
146
|
test('should filter by mixed parent and child properties', async () => {
|
|
153
147
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('persons')} CASCADE`);
|
|
154
|
-
await
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
salary: 90000,
|
|
162
|
-
department: 'IT',
|
|
163
|
-
});
|
|
164
|
-
expect(richITManagers).toHaveLength(1);
|
|
165
|
-
expect(richITManagers[0].name).toBe('Alice');
|
|
148
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'Alice', salary: 90000, department: 'IT' }));
|
|
149
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'Bob', salary: 90000, department: 'HR' }));
|
|
150
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'Charlie', salary: 60000, department: 'IT' }));
|
|
151
|
+
// Filter by parent (salary) AND child (department)
|
|
152
|
+
const richITManagers = await mgrRepo.loadManyByQuery({
|
|
153
|
+
salary: 90000,
|
|
154
|
+
department: 'IT',
|
|
166
155
|
});
|
|
156
|
+
expect(richITManagers).toHaveLength(1);
|
|
157
|
+
expect(richITManagers[0].name).toBe('Alice');
|
|
167
158
|
});
|
|
168
159
|
test('should sort by parent property then child property', async () => {
|
|
169
160
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('persons')} CASCADE`);
|
|
170
|
-
await
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
['department', 'asc'],
|
|
180
|
-
],
|
|
181
|
-
});
|
|
182
|
-
expect(sorted).toHaveLength(3);
|
|
183
|
-
expect(sorted[0].name).toBe('C'); // 90000
|
|
184
|
-
expect(sorted[1].name).toBe('B'); // 50000, A
|
|
185
|
-
expect(sorted[2].name).toBe('A'); // 50000, Z
|
|
161
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'A', salary: 50000, department: 'Z' }));
|
|
162
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'B', salary: 50000, department: 'A' }));
|
|
163
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'C', salary: 90000, department: 'A' }));
|
|
164
|
+
// Sort by salary (parent) DESC, then department (child) ASC
|
|
165
|
+
const sorted = await mgrRepo.loadManyByQuery({}, {
|
|
166
|
+
order: [
|
|
167
|
+
['salary', 'desc'],
|
|
168
|
+
['department', 'asc'],
|
|
169
|
+
],
|
|
186
170
|
});
|
|
171
|
+
expect(sorted).toHaveLength(3);
|
|
172
|
+
expect(sorted[0].name).toBe('C'); // 90000
|
|
173
|
+
expect(sorted[1].name).toBe('B'); // 50000, A
|
|
174
|
+
expect(sorted[2].name).toBe('A'); // 50000, Z
|
|
187
175
|
});
|
|
188
176
|
test('should paginate polymorphic results correctly', async () => {
|
|
189
177
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('persons')} CASCADE`);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const page2 = await personRepo.loadManyByQuery({}, {
|
|
216
|
-
order: [['name', 'asc']],
|
|
217
|
-
limit: 2,
|
|
218
|
-
offset: 2,
|
|
219
|
-
includeSubclasses: [Employee, Manager, Contractor],
|
|
220
|
-
});
|
|
221
|
-
expect(page2).toHaveLength(2);
|
|
222
|
-
expect(page2[0].name).toBe('C_Cnt');
|
|
223
|
-
expect(page2[0]).toBeInstanceOf(Contractor);
|
|
224
|
-
expect(page2[1].name).toBe('D_Emp');
|
|
225
|
-
expect(page2[1]).toBeInstanceOf(Employee);
|
|
226
|
-
// Load page 3 (size 2) - should have 1
|
|
227
|
-
const page3 = await personRepo.loadManyByQuery({}, {
|
|
228
|
-
order: [['name', 'asc']],
|
|
229
|
-
limit: 2,
|
|
230
|
-
offset: 4,
|
|
231
|
-
includeSubclasses: [Employee, Manager, Contractor],
|
|
232
|
-
});
|
|
233
|
-
expect(page3).toHaveLength(1);
|
|
234
|
-
expect(page3[0].name).toBe('E_Mgr');
|
|
178
|
+
// Insert 5 entities: 2 employees, 2 managers, 1 contractor
|
|
179
|
+
// Names ordered: A, B, C, D, E
|
|
180
|
+
await empRepo.insert(Object.assign(new Employee(), { name: 'A_Emp', salary: 1000 }));
|
|
181
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'B_Mgr', salary: 2000, department: 'D1' }));
|
|
182
|
+
await cntRepo.insert(Object.assign(new Contractor(), { name: 'C_Cnt', contractEnd: '2025' }));
|
|
183
|
+
await empRepo.insert(Object.assign(new Employee(), { name: 'D_Emp', salary: 1000 }));
|
|
184
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'E_Mgr', salary: 2000, department: 'D2' }));
|
|
185
|
+
// Load page 1 (size 2) sorted by name
|
|
186
|
+
const page1 = await personRepo.loadManyByQuery({}, {
|
|
187
|
+
order: [['name', 'asc']],
|
|
188
|
+
limit: 2,
|
|
189
|
+
offset: 0,
|
|
190
|
+
includeSubclasses: [Employee, Manager, Contractor],
|
|
191
|
+
});
|
|
192
|
+
expect(page1).toHaveLength(2);
|
|
193
|
+
expect(page1[0].name).toBe('A_Emp');
|
|
194
|
+
expect(page1[0]).toBeInstanceOf(Employee);
|
|
195
|
+
expect(page1[1].name).toBe('B_Mgr');
|
|
196
|
+
expect(page1[1]).toBeInstanceOf(Manager);
|
|
197
|
+
// Load page 2 (size 2)
|
|
198
|
+
const page2 = await personRepo.loadManyByQuery({}, {
|
|
199
|
+
order: [['name', 'asc']],
|
|
200
|
+
limit: 2,
|
|
201
|
+
offset: 2,
|
|
202
|
+
includeSubclasses: [Employee, Manager, Contractor],
|
|
235
203
|
});
|
|
204
|
+
expect(page2).toHaveLength(2);
|
|
205
|
+
expect(page2[0].name).toBe('C_Cnt');
|
|
206
|
+
expect(page2[0]).toBeInstanceOf(Contractor);
|
|
207
|
+
expect(page2[1].name).toBe('D_Emp');
|
|
208
|
+
expect(page2[1]).toBeInstanceOf(Employee);
|
|
209
|
+
// Load page 3 (size 2) - should have 1
|
|
210
|
+
const page3 = await personRepo.loadManyByQuery({}, {
|
|
211
|
+
order: [['name', 'asc']],
|
|
212
|
+
limit: 2,
|
|
213
|
+
offset: 4,
|
|
214
|
+
includeSubclasses: [Employee, Manager, Contractor],
|
|
215
|
+
});
|
|
216
|
+
expect(page3).toHaveLength(1);
|
|
217
|
+
expect(page3[0].name).toBe('E_Mgr');
|
|
236
218
|
});
|
|
237
219
|
test('should rollback whole operation if one child insert fails in bulk insert', async () => {
|
|
238
220
|
// NOTE: Repositories typically don't support heterogeneous bulk insert via `insertMany` directly if they are typed to one Entity.
|
|
@@ -240,22 +222,19 @@ describe('ORM Repository CTI Extensive (Integration)', () => {
|
|
|
240
222
|
// Here we will test doing multiple operations in a transaction and failing one.
|
|
241
223
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('persons')} CASCADE`);
|
|
242
224
|
try {
|
|
243
|
-
await
|
|
244
|
-
const
|
|
245
|
-
await
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// Missing department
|
|
257
|
-
await txRepo.insert(invalidMgr);
|
|
258
|
-
});
|
|
225
|
+
await mgrRepo.transaction(async (tx) => {
|
|
226
|
+
const txRepo = mgrRepo.withTransaction(tx);
|
|
227
|
+
await txRepo.insert(Object.assign(new Manager(), { name: 'TxMgr1', salary: 1000, department: 'D1' }));
|
|
228
|
+
// This one should fail due to constraint violation (missing department is not nullable in DB but maybe optional in class?
|
|
229
|
+
// In our setup 'department' is NOT NULL.
|
|
230
|
+
// Let's force a failure by passing a raw invalid object or similar, or just throwing.
|
|
231
|
+
// A better DB constraint failure: Duplicate ID? Or invalid type?
|
|
232
|
+
// Let's try inserting a manager with a missing required field using 'any' to bypass TS check.
|
|
233
|
+
const invalidMgr = new Manager();
|
|
234
|
+
invalidMgr.name = 'TxMgr2';
|
|
235
|
+
invalidMgr.salary = 2000;
|
|
236
|
+
// Missing department
|
|
237
|
+
await txRepo.insert(invalidMgr);
|
|
259
238
|
});
|
|
260
239
|
}
|
|
261
240
|
catch (e) {
|
|
@@ -269,40 +248,32 @@ describe('ORM Repository CTI Extensive (Integration)', () => {
|
|
|
269
248
|
// In our current schema we avoided it, but let's see if we can query based on aliasing if it were to happen?
|
|
270
249
|
// Actually, let's test querying with 'OR' condition across parent and child fields.
|
|
271
250
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('persons')} CASCADE`);
|
|
272
|
-
await
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const results = await mgrRepo.loadManyByQuery({
|
|
284
|
-
department: 'MatchDept',
|
|
285
|
-
});
|
|
286
|
-
expect(results).toHaveLength(1);
|
|
287
|
-
expect(results[0].name).toBe('OtherName');
|
|
251
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'MatchName', salary: 1000, department: 'Other' }));
|
|
252
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'OtherName', salary: 1000, department: 'MatchDept' }));
|
|
253
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'NoMatch', salary: 1000, department: 'Other' }));
|
|
254
|
+
// Logic: name = 'MatchName' OR department = 'MatchDept'
|
|
255
|
+
// ORM's `loadManyByQuery` usually takes an object for AND.
|
|
256
|
+
// We might need to use `loadMany` with a custom query builder or check if `loadManyByQuery` supports arrays for OR?
|
|
257
|
+
// If not supported easily, we skip this specific complex query for now, or use `loadMany` with simple filters.
|
|
258
|
+
// Let's stick to simple filters that we know are supported: simple object matches (AND).
|
|
259
|
+
// Let's test a deeply nested query filter if supported.
|
|
260
|
+
const results = await mgrRepo.loadManyByQuery({
|
|
261
|
+
department: 'MatchDept',
|
|
288
262
|
});
|
|
263
|
+
expect(results).toHaveLength(1);
|
|
264
|
+
expect(results[0].name).toBe('OtherName');
|
|
289
265
|
});
|
|
290
266
|
test('should correctly count with subclass inclusion', async () => {
|
|
291
267
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('persons')} CASCADE`);
|
|
292
|
-
await
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
// Standard CTI usually implies querying the 'employees' table which includes Managers' rows.
|
|
303
|
-
expect(await empRepo.count()).toBe(2);
|
|
304
|
-
// Count on Manager should see 1
|
|
305
|
-
expect(await mgrRepo.count()).toBe(1);
|
|
306
|
-
});
|
|
268
|
+
await empRepo.insert(Object.assign(new Employee(), { name: 'E1', salary: 100 }));
|
|
269
|
+
await mgrRepo.insert(Object.assign(new Manager(), { name: 'M1', salary: 200, department: 'D1' }));
|
|
270
|
+
// Count on Person should see 2
|
|
271
|
+
expect(await personRepo.count()).toBe(2);
|
|
272
|
+
// Count on Employee should see 2 (Manager IS-A Employee)
|
|
273
|
+
// Wait, does 'count()' on Employee include Managers by default?
|
|
274
|
+
// Standard CTI usually implies querying the 'employees' table which includes Managers' rows.
|
|
275
|
+
expect(await empRepo.count()).toBe(2);
|
|
276
|
+
// Count on Manager should see 1
|
|
277
|
+
expect(await mgrRepo.count()).toBe(1);
|
|
307
278
|
});
|
|
308
279
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
|
|
1
2
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
3
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
4
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -7,17 +8,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
10
|
};
|
|
10
|
-
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
11
|
-
import { StringProperty } from '../../schema/index.js';
|
|
12
11
|
import { sql } from 'drizzle-orm';
|
|
13
12
|
import { beforeAll, describe, expect, test } from 'vitest';
|
|
13
|
+
import { StringProperty } from '../../schema/index.js';
|
|
14
|
+
import { setupIntegrationTest } from '../../testing/index.js';
|
|
14
15
|
import { ChildEntity, Column, Inheritance, Table } from '../decorators.js';
|
|
15
16
|
import { Entity } from '../entity.js';
|
|
16
|
-
import {
|
|
17
|
-
import { injectRepository } from '../server/repository.js';
|
|
17
|
+
import { getRepository } from '../server/index.js';
|
|
18
18
|
describe('ORM Repository CTI Mapping (Integration)', () => {
|
|
19
19
|
let injector;
|
|
20
20
|
let db;
|
|
21
|
+
let repo;
|
|
21
22
|
const schema = 'test_orm_cti_mapping';
|
|
22
23
|
let MappedParent = class MappedParent extends Entity {
|
|
23
24
|
discriminator;
|
|
@@ -50,18 +51,10 @@ describe('ORM Repository CTI Mapping (Integration)', () => {
|
|
|
50
51
|
ChildEntity('child')
|
|
51
52
|
], MappedChild);
|
|
52
53
|
beforeAll(async () => {
|
|
53
|
-
injector =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
host: '127.0.0.1',
|
|
58
|
-
port: 5432,
|
|
59
|
-
user: 'tstdl',
|
|
60
|
-
password: 'wf7rq6glrk5jykne',
|
|
61
|
-
database: 'tstdl',
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
db = injector.resolve(Database);
|
|
54
|
+
({ injector, database: db } = await setupIntegrationTest({
|
|
55
|
+
orm: { schema },
|
|
56
|
+
}));
|
|
57
|
+
repo = injector.resolve(getRepository(MappedChild));
|
|
65
58
|
await db.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
|
|
66
59
|
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('mapped_children')} CASCADE`);
|
|
67
60
|
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('mapped_parents')} CASCADE`);
|
|
@@ -88,34 +81,28 @@ describe('ORM Repository CTI Mapping (Integration)', () => {
|
|
|
88
81
|
`);
|
|
89
82
|
});
|
|
90
83
|
test('should respect @Column renaming in CTI', async () => {
|
|
91
|
-
await
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
expect(loaded.displayName).toBe('Visible');
|
|
105
|
-
expect(loaded.secret).toBe('Hidden');
|
|
106
|
-
});
|
|
84
|
+
const child = await repo.insert(Object.assign(new MappedChild(), { displayName: 'Visible', secret: 'Hidden' }));
|
|
85
|
+
expect(child.discriminator).toBe('child');
|
|
86
|
+
expect(child.displayName).toBe('Visible');
|
|
87
|
+
expect(child.secret).toBe('Hidden');
|
|
88
|
+
// Verify DB column names
|
|
89
|
+
const { rows: parentRows } = await db.execute(sql `SELECT display_name FROM ${sql.identifier(schema)}.${sql.identifier('mapped_parents')} WHERE id = ${child.id}`);
|
|
90
|
+
expect(parentRows[0]['display_name']).toBe('Visible');
|
|
91
|
+
const { rows: childRows } = await db.execute(sql `SELECT internal_secret FROM ${sql.identifier(schema)}.${sql.identifier('mapped_children')} WHERE id = ${child.id}`);
|
|
92
|
+
expect(childRows[0]['internal_secret']).toBe('Hidden');
|
|
93
|
+
// Load back
|
|
94
|
+
const loaded = await repo.load(child.id);
|
|
95
|
+
expect(loaded.displayName).toBe('Visible');
|
|
96
|
+
expect(loaded.secret).toBe('Hidden');
|
|
107
97
|
});
|
|
108
98
|
test('should filter by renamed columns in both tables', async () => {
|
|
109
|
-
await
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
displayName: 'B',
|
|
115
|
-
secret: 'S2'
|
|
116
|
-
});
|
|
117
|
-
expect(results).toHaveLength(1);
|
|
118
|
-
expect(results[0].displayName).toBe('B');
|
|
99
|
+
await repo.insert(Object.assign(new MappedChild(), { displayName: 'A', secret: 'S1' }));
|
|
100
|
+
await repo.insert(Object.assign(new MappedChild(), { displayName: 'B', secret: 'S2' }));
|
|
101
|
+
const results = await repo.loadManyByQuery({
|
|
102
|
+
displayName: 'B',
|
|
103
|
+
secret: 'S2'
|
|
119
104
|
});
|
|
105
|
+
expect(results).toHaveLength(1);
|
|
106
|
+
expect(results[0].displayName).toBe('B');
|
|
120
107
|
});
|
|
121
108
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
|
|
1
2
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
3
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
4
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -7,17 +8,18 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
10
|
};
|
|
10
|
-
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
11
|
-
import { StringProperty } from '../../schema/index.js';
|
|
12
11
|
import { sql } from 'drizzle-orm';
|
|
13
12
|
import { beforeAll, describe, expect, test } from 'vitest';
|
|
13
|
+
import { StringProperty } from '../../schema/index.js';
|
|
14
|
+
import { setupIntegrationTest } from '../../testing/index.js';
|
|
14
15
|
import { ChildEntity, Column, Inheritance, Table } from '../decorators.js';
|
|
15
16
|
import { Entity } from '../entity.js';
|
|
16
|
-
import {
|
|
17
|
-
import { injectRepository } from '../server/repository.js';
|
|
17
|
+
import { getRepository } from '../server/index.js';
|
|
18
18
|
describe('ORM Repository CTI Soft Delete (Integration)', () => {
|
|
19
19
|
let injector;
|
|
20
20
|
let db;
|
|
21
|
+
let baseRepo;
|
|
22
|
+
let subRepo;
|
|
21
23
|
const schema = 'test_orm_cti_soft_delete';
|
|
22
24
|
let Base = class Base extends Entity {
|
|
23
25
|
type;
|
|
@@ -48,18 +50,11 @@ describe('ORM Repository CTI Soft Delete (Integration)', () => {
|
|
|
48
50
|
ChildEntity('subtype')
|
|
49
51
|
], Subtype);
|
|
50
52
|
beforeAll(async () => {
|
|
51
|
-
injector =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
port: 5432,
|
|
57
|
-
user: 'tstdl',
|
|
58
|
-
password: 'wf7rq6glrk5jykne',
|
|
59
|
-
database: 'tstdl',
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
db = injector.resolve(Database);
|
|
53
|
+
({ injector, database: db } = await setupIntegrationTest({
|
|
54
|
+
orm: { schema },
|
|
55
|
+
}));
|
|
56
|
+
baseRepo = injector.resolve(getRepository(Base));
|
|
57
|
+
subRepo = injector.resolve(getRepository(Subtype));
|
|
63
58
|
await db.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
|
|
64
59
|
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('subtypes')} CASCADE`);
|
|
65
60
|
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('bases')} CASCADE`);
|
|
@@ -87,29 +82,22 @@ describe('ORM Repository CTI Soft Delete (Integration)', () => {
|
|
|
87
82
|
});
|
|
88
83
|
test('should soft delete from subtype repository', async () => {
|
|
89
84
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('bases')} CASCADE`);
|
|
90
|
-
await
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
expect(await repo.has(entity.id)).toBe(false);
|
|
97
|
-
expect(await repo.loadManyByQuery({ id: entity.id }).then((r) => r[0])).toBeUndefined();
|
|
98
|
-
});
|
|
85
|
+
const entity = await subRepo.insert(Object.assign(new Subtype(), { baseName: 'B1', subData: 'S1' }));
|
|
86
|
+
await subRepo.delete(entity.id);
|
|
87
|
+
const { rows } = await db.execute(sql `SELECT delete_timestamp FROM ${sql.identifier(schema)}.${sql.identifier('bases')} WHERE id = ${entity.id}`);
|
|
88
|
+
expect(rows[0]['delete_timestamp']).not.toBeNull();
|
|
89
|
+
expect(await subRepo.has(entity.id)).toBe(false);
|
|
90
|
+
expect(await subRepo.loadManyByQuery({ id: entity.id }).then((r) => r[0])).toBeUndefined();
|
|
99
91
|
});
|
|
100
92
|
test('should polymorphically filter soft deleted entities', async () => {
|
|
101
93
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('bases')} CASCADE`);
|
|
102
|
-
await
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
expect(allActive[0].id).toBe(s1.id);
|
|
111
|
-
const allIncludingDeleted = await baseRepo.loadAll({ includeSubclasses: true, withDeleted: true });
|
|
112
|
-
expect(allIncludingDeleted).toHaveLength(2);
|
|
113
|
-
});
|
|
94
|
+
const s1 = await subRepo.insert(Object.assign(new Subtype(), { baseName: 'Active', subData: 'S1' }));
|
|
95
|
+
const s2 = await subRepo.insert(Object.assign(new Subtype(), { baseName: 'Deleted', subData: 'S2' }));
|
|
96
|
+
await subRepo.delete(s2.id);
|
|
97
|
+
const allActive = await baseRepo.loadAll({ includeSubclasses: true });
|
|
98
|
+
expect(allActive).toHaveLength(1);
|
|
99
|
+
expect(allActive[0].id).toBe(s1.id);
|
|
100
|
+
const allIncludingDeleted = await baseRepo.loadAll({ includeSubclasses: true, withDeleted: true });
|
|
101
|
+
expect(allIncludingDeleted).toHaveLength(2);
|
|
114
102
|
});
|
|
115
103
|
});
|