@tstdl/base 0.93.145 → 0.93.147

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.
Files changed (44) hide show
  1. package/authentication/tests/authentication.client-service.test.js +15 -19
  2. package/authentication/tests/authentication.service.test.js +92 -119
  3. package/notification/tests/notification-client.test.js +39 -50
  4. package/notification/tests/notification-flow.test.js +204 -238
  5. package/notification/tests/notification-sse.service.test.js +20 -27
  6. package/notification/tests/notification-type.service.test.js +17 -20
  7. package/orm/repository.types.d.ts +13 -2
  8. package/orm/server/repository.d.ts +60 -4
  9. package/orm/server/repository.js +126 -25
  10. package/orm/tests/query-complex.test.js +80 -111
  11. package/orm/tests/repository-advanced.test.js +100 -143
  12. package/orm/tests/repository-attributes.test.js +30 -39
  13. package/orm/tests/repository-compound-primary-key.test.js +67 -75
  14. package/orm/tests/repository-comprehensive.test.js +76 -101
  15. package/orm/tests/repository-coverage.test.d.ts +1 -0
  16. package/orm/tests/repository-coverage.test.js +88 -149
  17. package/orm/tests/repository-cti-extensive.test.d.ts +1 -0
  18. package/orm/tests/repository-cti-extensive.test.js +118 -147
  19. package/orm/tests/repository-cti-mapping.test.d.ts +1 -0
  20. package/orm/tests/repository-cti-mapping.test.js +29 -42
  21. package/orm/tests/repository-cti-soft-delete.test.d.ts +1 -0
  22. package/orm/tests/repository-cti-soft-delete.test.js +25 -37
  23. package/orm/tests/repository-cti-transactions.test.js +19 -33
  24. package/orm/tests/repository-cti-upsert-many.test.d.ts +1 -0
  25. package/orm/tests/repository-cti-upsert-many.test.js +38 -50
  26. package/orm/tests/repository-cti.test.d.ts +1 -0
  27. package/orm/tests/repository-cti.test.js +195 -247
  28. package/orm/tests/repository-expiration.test.d.ts +1 -0
  29. package/orm/tests/repository-expiration.test.js +46 -59
  30. package/orm/tests/repository-extra-coverage.test.d.ts +1 -0
  31. package/orm/tests/repository-extra-coverage.test.js +195 -337
  32. package/orm/tests/repository-mapping.test.d.ts +1 -0
  33. package/orm/tests/repository-mapping.test.js +20 -20
  34. package/orm/tests/repository-regression.test.js +124 -163
  35. package/orm/tests/repository-search.test.js +30 -44
  36. package/orm/tests/repository-soft-delete.test.js +54 -79
  37. package/orm/tests/repository-types.test.js +77 -111
  38. package/orm/tests/repository-undelete.test.d.ts +2 -0
  39. package/orm/tests/repository-undelete.test.js +201 -0
  40. package/package.json +3 -3
  41. package/task-queue/tests/worker.test.js +5 -5
  42. package/testing/README.md +38 -16
  43. package/testing/integration-setup.d.ts +11 -0
  44. 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 { configureOrm, Database } from '../server/index.js';
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 = new Injector('Test');
82
- configureOrm({
83
- repositoryConfig: { schema },
84
- connection: {
85
- host: '127.0.0.1',
86
- port: 5432,
87
- user: 'tstdl',
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 runInInjectionContext(injector, async () => {
141
- const empRepo = injectRepository(Employee);
142
- const mgrRepo = injectRepository(Manager);
143
- const cntRepo = injectRepository(Contractor);
144
- const e1 = await empRepo.insert(Object.assign(new Employee(), { name: 'E1', salary: 50000 }));
145
- const m1 = await mgrRepo.insert(Object.assign(new Manager(), { name: 'M1', salary: 80000, department: 'IT' }));
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 runInInjectionContext(injector, async () => {
155
- const mgrRepo = injectRepository(Manager);
156
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'Alice', salary: 90000, department: 'IT' }));
157
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'Bob', salary: 90000, department: 'HR' }));
158
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'Charlie', salary: 60000, department: 'IT' }));
159
- // Filter by parent (salary) AND child (department)
160
- const richITManagers = await mgrRepo.loadManyByQuery({
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 runInInjectionContext(injector, async () => {
171
- const mgrRepo = injectRepository(Manager);
172
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'A', salary: 50000, department: 'Z' }));
173
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'B', salary: 50000, department: 'A' }));
174
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'C', salary: 90000, department: 'A' }));
175
- // Sort by salary (parent) DESC, then department (child) ASC
176
- const sorted = await mgrRepo.loadManyByQuery({}, {
177
- order: [
178
- ['salary', 'desc'],
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
- await runInInjectionContext(injector, async () => {
191
- const personRepo = injectRepository(Person);
192
- const empRepo = injectRepository(Employee);
193
- const mgrRepo = injectRepository(Manager);
194
- const cntRepo = injectRepository(Contractor);
195
- // Insert 5 entities: 2 employees, 2 managers, 1 contractor
196
- // Names ordered: A, B, C, D, E
197
- await empRepo.insert(Object.assign(new Employee(), { name: 'A_Emp', salary: 1000 }));
198
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'B_Mgr', salary: 2000, department: 'D1' }));
199
- await cntRepo.insert(Object.assign(new Contractor(), { name: 'C_Cnt', contractEnd: '2025' }));
200
- await empRepo.insert(Object.assign(new Employee(), { name: 'D_Emp', salary: 1000 }));
201
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'E_Mgr', salary: 2000, department: 'D2' }));
202
- // Load page 1 (size 2) sorted by name
203
- const page1 = await personRepo.loadManyByQuery({}, {
204
- order: [['name', 'asc']],
205
- limit: 2,
206
- offset: 0,
207
- includeSubclasses: [Employee, Manager, Contractor],
208
- });
209
- expect(page1).toHaveLength(2);
210
- expect(page1[0].name).toBe('A_Emp');
211
- expect(page1[0]).toBeInstanceOf(Employee);
212
- expect(page1[1].name).toBe('B_Mgr');
213
- expect(page1[1]).toBeInstanceOf(Manager);
214
- // Load page 2 (size 2)
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 runInInjectionContext(injector, async () => {
244
- const mgrRepo = injectRepository(Manager);
245
- await mgrRepo.transaction(async (tx) => {
246
- const txRepo = mgrRepo.withTransaction(tx);
247
- await txRepo.insert(Object.assign(new Manager(), { name: 'TxMgr1', salary: 1000, department: 'D1' }));
248
- // This one should fail due to constraint violation (missing department is not nullable in DB but maybe optional in class?
249
- // In our setup 'department' is NOT NULL.
250
- // Let's force a failure by passing a raw invalid object or similar, or just throwing.
251
- // A better DB constraint failure: Duplicate ID? Or invalid type?
252
- // Let's try inserting a manager with a missing required field using 'any' to bypass TS check.
253
- const invalidMgr = new Manager();
254
- invalidMgr.name = 'TxMgr2';
255
- invalidMgr.salary = 2000;
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 runInInjectionContext(injector, async () => {
273
- const mgrRepo = injectRepository(Manager);
274
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'MatchName', salary: 1000, department: 'Other' }));
275
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'OtherName', salary: 1000, department: 'MatchDept' }));
276
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'NoMatch', salary: 1000, department: 'Other' }));
277
- // Logic: name = 'MatchName' OR department = 'MatchDept'
278
- // ORM's `loadManyByQuery` usually takes an object for AND.
279
- // We might need to use `loadMany` with a custom query builder or check if `loadManyByQuery` supports arrays for OR?
280
- // If not supported easily, we skip this specific complex query for now, or use `loadMany` with simple filters.
281
- // Let's stick to simple filters that we know are supported: simple object matches (AND).
282
- // Let's test a deeply nested query filter if supported.
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 runInInjectionContext(injector, async () => {
293
- const personRepo = injectRepository(Person);
294
- const empRepo = injectRepository(Employee);
295
- const mgrRepo = injectRepository(Manager);
296
- await empRepo.insert(Object.assign(new Employee(), { name: 'E1', salary: 100 }));
297
- await mgrRepo.insert(Object.assign(new Manager(), { name: 'M1', salary: 200, department: 'D1' }));
298
- // Count on Person should see 2
299
- expect(await personRepo.count()).toBe(2);
300
- // Count on Employee should see 2 (Manager IS-A Employee)
301
- // Wait, does 'count()' on Employee include Managers by default?
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 +1,2 @@
1
+ /** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
1
2
  export {};
@@ -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 { configureOrm, Database } from '../server/index.js';
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 = new Injector('Test');
54
- configureOrm({
55
- repositoryConfig: { schema },
56
- connection: {
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 runInInjectionContext(injector, async () => {
92
- const repo = injectRepository(MappedChild);
93
- const child = await repo.insert(Object.assign(new MappedChild(), { displayName: 'Visible', secret: 'Hidden' }));
94
- expect(child.discriminator).toBe('child');
95
- expect(child.displayName).toBe('Visible');
96
- expect(child.secret).toBe('Hidden');
97
- // Verify DB column names
98
- const { rows: parentRows } = await db.execute(sql `SELECT display_name FROM ${sql.identifier(schema)}.${sql.identifier('mapped_parents')} WHERE id = ${child.id}`);
99
- expect(parentRows[0]['display_name']).toBe('Visible');
100
- const { rows: childRows } = await db.execute(sql `SELECT internal_secret FROM ${sql.identifier(schema)}.${sql.identifier('mapped_children')} WHERE id = ${child.id}`);
101
- expect(childRows[0]['internal_secret']).toBe('Hidden');
102
- // Load back
103
- const loaded = await repo.load(child.id);
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 runInInjectionContext(injector, async () => {
110
- const repo = injectRepository(MappedChild);
111
- await repo.insert(Object.assign(new MappedChild(), { displayName: 'A', secret: 'S1' }));
112
- await repo.insert(Object.assign(new MappedChild(), { displayName: 'B', secret: 'S2' }));
113
- const results = await repo.loadManyByQuery({
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 +1,2 @@
1
+ /** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
1
2
  export {};
@@ -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 { configureOrm, Database } from '../server/index.js';
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 = new Injector('Test');
52
- configureOrm({
53
- repositoryConfig: { schema },
54
- connection: {
55
- host: '127.0.0.1',
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 runInInjectionContext(injector, async () => {
91
- const repo = injectRepository(Subtype);
92
- const entity = await repo.insert(Object.assign(new Subtype(), { baseName: 'B1', subData: 'S1' }));
93
- await repo.delete(entity.id);
94
- const { rows } = await db.execute(sql `SELECT delete_timestamp FROM ${sql.identifier(schema)}.${sql.identifier('bases')} WHERE id = ${entity.id}`);
95
- expect(rows[0]['delete_timestamp']).not.toBeNull();
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 runInInjectionContext(injector, async () => {
103
- const baseRepo = injectRepository(Base);
104
- const subRepo = injectRepository(Subtype);
105
- const s1 = await subRepo.insert(Object.assign(new Subtype(), { baseName: 'Active', subData: 'S1' }));
106
- const s2 = await subRepo.insert(Object.assign(new Subtype(), { baseName: 'Deleted', subData: 'S2' }));
107
- await subRepo.delete(s2.id);
108
- const allActive = await baseRepo.loadAll({ includeSubclasses: true });
109
- expect(allActive).toHaveLength(1);
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
  });