@tstdl/base 0.93.144 → 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/authentication.api.d.ts +9 -0
- package/authentication/authentication.api.js +3 -0
- package/authentication/client/authentication.service.js +5 -5
- package/authentication/client/http-client.middleware.js +6 -2
- package/authentication/tests/authentication.client-middleware.test.js +35 -0
- package/authentication/tests/authentication.client-service-refresh.test.js +7 -0
- 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 { StringProperty } from '../../schema/index.js';
|
|
12
|
-
import { dropTables, setupIntegrationTest, truncateTables } from '../../testing/index.js';
|
|
13
11
|
import { sql } from 'drizzle-orm';
|
|
14
12
|
import { beforeAll, beforeEach, describe, expect, test } from 'vitest';
|
|
13
|
+
import { StringProperty } from '../../schema/index.js';
|
|
14
|
+
import { dropTables, setupIntegrationTest, truncateTables } from '../../testing/index.js';
|
|
15
15
|
import { ChildEntity, Column, Inheritance, Table } from '../decorators.js';
|
|
16
16
|
import { Entity } from '../entity.js';
|
|
17
|
-
import {
|
|
17
|
+
import { getRepository } from '../server/index.js';
|
|
18
18
|
describe('ORM Repository CTI (Integration)', () => {
|
|
19
19
|
let injector;
|
|
20
20
|
let database;
|
|
21
|
+
let adminRepository;
|
|
22
|
+
let guestRepository;
|
|
23
|
+
let userRepository;
|
|
24
|
+
let managerRepository;
|
|
21
25
|
const schema = 'test_orm_cti';
|
|
22
26
|
let BaseUser = class BaseUser extends Entity {
|
|
23
27
|
type;
|
|
@@ -88,6 +92,10 @@ describe('ORM Repository CTI (Integration)', () => {
|
|
|
88
92
|
], Manager);
|
|
89
93
|
beforeAll(async () => {
|
|
90
94
|
({ injector, database } = await setupIntegrationTest({ orm: { schema } }));
|
|
95
|
+
adminRepository = injector.resolve(getRepository(Admin));
|
|
96
|
+
guestRepository = injector.resolve(getRepository(Guest));
|
|
97
|
+
userRepository = injector.resolve(getRepository(BaseUser));
|
|
98
|
+
managerRepository = injector.resolve(getRepository(Manager));
|
|
91
99
|
await database.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
|
|
92
100
|
await dropTables(database, schema, ['managers', 'staff', 'guests', 'admins', 'users']);
|
|
93
101
|
await database.execute(sql `
|
|
@@ -141,185 +149,150 @@ describe('ORM Repository CTI (Integration)', () => {
|
|
|
141
149
|
await truncateTables(database, schema, ['users']);
|
|
142
150
|
});
|
|
143
151
|
test('should insert into both parent and child tables', async () => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
expect(adminRow['type']).toBe('admin');
|
|
165
|
-
expect(adminRow['role']).toBe('SuperAdmin');
|
|
166
|
-
});
|
|
152
|
+
const newAdmin = new Admin();
|
|
153
|
+
newAdmin.name = 'Alice';
|
|
154
|
+
newAdmin.role = 'SuperAdmin';
|
|
155
|
+
const inserted = await adminRepository.insert(newAdmin);
|
|
156
|
+
expect(inserted).toBeInstanceOf(Admin);
|
|
157
|
+
expect(inserted.id).toBeDefined();
|
|
158
|
+
expect(inserted.name).toBe('Alice');
|
|
159
|
+
expect(inserted.role).toBe('SuperAdmin');
|
|
160
|
+
expect(inserted.type).toBe('admin');
|
|
161
|
+
expect(inserted.metadata.revision).toBe(1);
|
|
162
|
+
// Verify DB state
|
|
163
|
+
const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
|
|
164
|
+
expect(userRow).toBeDefined();
|
|
165
|
+
expect(userRow['type']).toBe('admin');
|
|
166
|
+
expect(userRow['name']).toBe('Alice');
|
|
167
|
+
expect(userRow['revision']).toBe(1);
|
|
168
|
+
const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
|
|
169
|
+
expect(adminRow).toBeDefined();
|
|
170
|
+
expect(adminRow['type']).toBe('admin');
|
|
171
|
+
expect(adminRow['role']).toBe('SuperAdmin');
|
|
167
172
|
});
|
|
168
173
|
test('should insert many into both parent and child tables', async () => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
expect(insertedAdmins[1].type).toBe('admin');
|
|
181
|
-
for (const inserted of insertedAdmins) {
|
|
182
|
-
const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
|
|
183
|
-
expect(userRow['name']).toBe(inserted.name);
|
|
184
|
-
const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
|
|
185
|
-
expect(adminRow['role']).toBe(inserted.role);
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
test('should update both parent and child tables', async () => {
|
|
190
|
-
await runInInjectionContext(injector, async () => {
|
|
191
|
-
const repository = injectRepository(Admin);
|
|
192
|
-
const newAdmin = Object.assign(new Admin(), { name: 'Alice', role: 'SuperAdmin' });
|
|
193
|
-
const inserted = await repository.insert(newAdmin);
|
|
194
|
-
const update = { name: 'Alice Updated', role: 'MegaAdmin' };
|
|
195
|
-
const updated = await repository.update(inserted.id, update);
|
|
196
|
-
expect(updated.name).toBe('Alice Updated');
|
|
197
|
-
expect(updated.role).toBe('MegaAdmin');
|
|
198
|
-
expect(updated.metadata.revision).toBe(2);
|
|
199
|
-
// Verify DB state
|
|
174
|
+
const admins = [
|
|
175
|
+
Object.assign(new Admin(), { name: 'Bob', role: 'Admin' }),
|
|
176
|
+
Object.assign(new Admin(), { name: 'Charlie', role: 'Moderator' }),
|
|
177
|
+
];
|
|
178
|
+
const insertedAdmins = await adminRepository.insertMany(admins);
|
|
179
|
+
expect(insertedAdmins).toHaveLength(2);
|
|
180
|
+
expect(insertedAdmins[0].name).toBe('Bob');
|
|
181
|
+
expect(insertedAdmins[1].name).toBe('Charlie');
|
|
182
|
+
expect(insertedAdmins[0].type).toBe('admin');
|
|
183
|
+
expect(insertedAdmins[1].type).toBe('admin');
|
|
184
|
+
for (const inserted of insertedAdmins) {
|
|
200
185
|
const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
|
|
201
|
-
expect(userRow['name']).toBe(
|
|
202
|
-
expect(userRow['revision']).toBe(2);
|
|
186
|
+
expect(userRow['name']).toBe(inserted.name);
|
|
203
187
|
const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
|
|
204
|
-
expect(adminRow['role']).toBe(
|
|
205
|
-
}
|
|
188
|
+
expect(adminRow['role']).toBe(inserted.role);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
test('should update both parent and child tables', async () => {
|
|
192
|
+
const newAdmin = Object.assign(new Admin(), { name: 'Alice', role: 'SuperAdmin' });
|
|
193
|
+
const inserted = await adminRepository.insert(newAdmin);
|
|
194
|
+
const update = { name: 'Alice Updated', role: 'MegaAdmin' };
|
|
195
|
+
const updated = await adminRepository.update(inserted.id, update);
|
|
196
|
+
expect(updated.name).toBe('Alice Updated');
|
|
197
|
+
expect(updated.role).toBe('MegaAdmin');
|
|
198
|
+
expect(updated.metadata.revision).toBe(2);
|
|
199
|
+
// Verify DB state
|
|
200
|
+
const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
|
|
201
|
+
expect(userRow['name']).toBe('Alice Updated');
|
|
202
|
+
expect(userRow['revision']).toBe(2);
|
|
203
|
+
const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
|
|
204
|
+
expect(adminRow['role']).toBe('MegaAdmin');
|
|
206
205
|
});
|
|
207
206
|
test('should soft delete from parent table', async () => {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
|
|
218
|
-
expect(adminRow).toBeDefined();
|
|
219
|
-
});
|
|
207
|
+
const newAdmin = Object.assign(new Admin(), { name: 'Alice', role: 'SuperAdmin' });
|
|
208
|
+
const inserted = await adminRepository.insert(newAdmin);
|
|
209
|
+
await adminRepository.delete(inserted.id);
|
|
210
|
+
// Verify DB state
|
|
211
|
+
const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
|
|
212
|
+
expect(userRow['delete_timestamp']).not.toBeNull();
|
|
213
|
+
// Child table should still have the row (soft delete only affects parent)
|
|
214
|
+
const { rows: [adminRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
|
|
215
|
+
expect(adminRow).toBeDefined();
|
|
220
216
|
});
|
|
221
217
|
test('should hard delete from both tables via cascade', async () => {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const { rows: adminRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
|
|
231
|
-
expect(adminRows).toHaveLength(0);
|
|
232
|
-
});
|
|
218
|
+
const newAdmin = Object.assign(new Admin(), { name: 'Alice', role: 'SuperAdmin' });
|
|
219
|
+
const inserted = await adminRepository.insert(newAdmin);
|
|
220
|
+
await adminRepository.hardDelete(inserted.id);
|
|
221
|
+
// Verify DB state
|
|
222
|
+
const { rows: userRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
|
|
223
|
+
expect(userRows).toHaveLength(0);
|
|
224
|
+
const { rows: adminRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('admins')} WHERE id = ${inserted.id}`);
|
|
225
|
+
expect(adminRows).toHaveLength(0);
|
|
233
226
|
});
|
|
234
227
|
test('should load entities polymorphically from parent repository', async () => {
|
|
235
|
-
await
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
expect(guest.name).toBe('Guest1');
|
|
259
|
-
expect(guest).toBeInstanceOf(Guest);
|
|
260
|
-
expect(guest.name).toBe('Guest1');
|
|
261
|
-
expect(guest.permissions).toBe('Read');
|
|
262
|
-
});
|
|
228
|
+
await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin1', role: 'Super' }));
|
|
229
|
+
await guestRepository.insert(Object.assign(new Guest(), { name: 'Guest1', permissions: 'Read' }));
|
|
230
|
+
// Load without subclasses (default)
|
|
231
|
+
const users = await userRepository.loadAll();
|
|
232
|
+
expect(users).toHaveLength(2);
|
|
233
|
+
expect(users[0]).toBeInstanceOf(BaseUser);
|
|
234
|
+
expect(users[1]).toBeInstanceOf(BaseUser);
|
|
235
|
+
expect(users[0].role).toBeUndefined();
|
|
236
|
+
// Load with subclasses
|
|
237
|
+
const polymorphicUsers = await userRepository.loadAll({ includeSubclasses: true });
|
|
238
|
+
expect(polymorphicUsers).toHaveLength(2);
|
|
239
|
+
const admin = polymorphicUsers.find((u) => u instanceof Admin);
|
|
240
|
+
const guest = polymorphicUsers.find((u) => u instanceof Guest);
|
|
241
|
+
expect(admin).toBeDefined();
|
|
242
|
+
expect(admin).toBeInstanceOf(Admin);
|
|
243
|
+
expect(admin.name).toBe('Admin1');
|
|
244
|
+
expect(admin.role).toBe('Super');
|
|
245
|
+
expect(guest).toBeDefined();
|
|
246
|
+
expect(guest).toBeInstanceOf(Guest);
|
|
247
|
+
expect(guest.name).toBe('Guest1');
|
|
248
|
+
expect(guest).toBeInstanceOf(Guest);
|
|
249
|
+
expect(guest.name).toBe('Guest1');
|
|
250
|
+
expect(guest.permissions).toBe('Read');
|
|
263
251
|
});
|
|
264
252
|
test('should load polymorphic subset from parent repository', async () => {
|
|
265
|
-
await
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
expect(admin).toBeInstanceOf(Admin);
|
|
277
|
-
expect(admin.role).toBe('Super');
|
|
278
|
-
expect(guestAsBase).not.toBeInstanceOf(Guest);
|
|
279
|
-
expect(guestAsBase).toBeInstanceOf(BaseUser);
|
|
280
|
-
});
|
|
253
|
+
await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin1', role: 'Super' }));
|
|
254
|
+
await guestRepository.insert(Object.assign(new Guest(), { name: 'Guest1', permissions: 'Read' }));
|
|
255
|
+
// Load ONLY Admins polymorphically
|
|
256
|
+
const onlyAdmins = await userRepository.loadManyByQuery({}, { includeSubclasses: [Admin] });
|
|
257
|
+
expect(onlyAdmins).toHaveLength(2); // Still returns all users from base table
|
|
258
|
+
const admin = onlyAdmins.find((u) => u instanceof Admin);
|
|
259
|
+
const guestAsBase = onlyAdmins.find((u) => u.name === 'Guest1');
|
|
260
|
+
expect(admin).toBeInstanceOf(Admin);
|
|
261
|
+
expect(admin.role).toBe('Super');
|
|
262
|
+
expect(guestAsBase).not.toBeInstanceOf(Guest);
|
|
263
|
+
expect(guestAsBase).toBeInstanceOf(BaseUser);
|
|
281
264
|
});
|
|
282
265
|
test('should count and check existence for child entities', async () => {
|
|
283
|
-
await
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
expect(await adminRepository.has(admins[0].id)).toBe(true);
|
|
293
|
-
expect(await adminRepository.hasByQuery({ name: 'Admin2' })).toBe(true);
|
|
294
|
-
expect(await adminRepository.hasByQuery({ name: 'Guest1' })).toBe(false); // Different table
|
|
295
|
-
});
|
|
266
|
+
await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin1', role: 'Super' }));
|
|
267
|
+
await adminRepository.insert(Object.assign(new Admin(), { name: 'Admin2', role: 'Normal' }));
|
|
268
|
+
await guestRepository.insert(Object.assign(new Guest(), { name: 'Guest1', permissions: 'Read' }));
|
|
269
|
+
expect(await adminRepository.count()).toBe(2);
|
|
270
|
+
expect(await guestRepository.count()).toBe(1);
|
|
271
|
+
const admins = await adminRepository.loadAll();
|
|
272
|
+
expect(await adminRepository.has(admins[0].id)).toBe(true);
|
|
273
|
+
expect(await adminRepository.hasByQuery({ name: 'Admin2' })).toBe(true);
|
|
274
|
+
expect(await adminRepository.hasByQuery({ name: 'Guest1' })).toBe(false); // Different table
|
|
296
275
|
});
|
|
297
276
|
test('should query child entities by parent fields', async () => {
|
|
298
|
-
await
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
expect(results[0].name).toBe('TargetAdmin');
|
|
305
|
-
expect(results[0].role).toBe('Super');
|
|
306
|
-
});
|
|
277
|
+
await adminRepository.insert(Object.assign(new Admin(), { name: 'TargetAdmin', role: 'Super' }));
|
|
278
|
+
await adminRepository.insert(Object.assign(new Admin(), { name: 'OtherAdmin', role: 'Normal' }));
|
|
279
|
+
const results = await adminRepository.loadManyByQuery({ name: 'TargetAdmin' });
|
|
280
|
+
expect(results).toHaveLength(1);
|
|
281
|
+
expect(results[0].name).toBe('TargetAdmin');
|
|
282
|
+
expect(results[0].role).toBe('Super');
|
|
307
283
|
});
|
|
308
284
|
test('should perform partial updates on parent or child fields', async () => {
|
|
309
|
-
await
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
expect(updated.name).toBe('Alice Renamed');
|
|
321
|
-
expect(updated.role).toBe('Demoted');
|
|
322
|
-
});
|
|
285
|
+
const inserted = await adminRepository.insert(Object.assign(new Admin(), { name: 'Alice', role: 'Super' }));
|
|
286
|
+
// Update only parent field
|
|
287
|
+
await adminRepository.update(inserted.id, { name: 'Alice Renamed' });
|
|
288
|
+
let updated = await adminRepository.load(inserted.id);
|
|
289
|
+
expect(updated.name).toBe('Alice Renamed');
|
|
290
|
+
expect(updated.role).toBe('Super');
|
|
291
|
+
// Update only child field
|
|
292
|
+
await adminRepository.update(inserted.id, { role: 'Demoted' });
|
|
293
|
+
updated = await adminRepository.load(inserted.id);
|
|
294
|
+
expect(updated.name).toBe('Alice Renamed');
|
|
295
|
+
expect(updated.role).toBe('Demoted');
|
|
323
296
|
});
|
|
324
297
|
test('should fail when violating discriminator check constraint', async () => {
|
|
325
298
|
// Attempt manual insert with mismatched type
|
|
@@ -344,99 +317,74 @@ describe('ORM Repository CTI (Integration)', () => {
|
|
|
344
317
|
await expect(query).rejects.toThrow();
|
|
345
318
|
});
|
|
346
319
|
test('should support nested inheritance', async () => {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const { rows: [managerRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('managers')} WHERE id = ${inserted.id}`);
|
|
364
|
-
expect(managerRow['department']).toBe('Executive');
|
|
365
|
-
});
|
|
320
|
+
const manager = new Manager();
|
|
321
|
+
manager.name = 'Big Boss';
|
|
322
|
+
manager.employeeId = 'EMP001';
|
|
323
|
+
manager.department = 'Executive';
|
|
324
|
+
const inserted = await managerRepository.insert(manager);
|
|
325
|
+
expect(inserted.name).toBe('Big Boss');
|
|
326
|
+
expect(inserted.employeeId).toBe('EMP001');
|
|
327
|
+
expect(inserted.department).toBe('Executive');
|
|
328
|
+
expect(inserted.type).toBe('manager');
|
|
329
|
+
// Verify DB state - all 3 tables should have rows
|
|
330
|
+
const { rows: [userRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
|
|
331
|
+
expect(userRow['name']).toBe('Big Boss');
|
|
332
|
+
const { rows: [staffRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('staff')} WHERE id = ${inserted.id}`);
|
|
333
|
+
expect(staffRow['employee_id']).toBe('EMP001');
|
|
334
|
+
const { rows: [managerRow] } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('managers')} WHERE id = ${inserted.id}`);
|
|
335
|
+
expect(managerRow['department']).toBe('Executive');
|
|
366
336
|
});
|
|
367
337
|
test('should rollback parent insert if child insert fails', async () => {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const { rows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')}`);
|
|
377
|
-
expect(rows).toHaveLength(0);
|
|
378
|
-
});
|
|
338
|
+
// Attempt to insert an admin with a missing role (should fail because of NOT NULL)
|
|
339
|
+
const invalidAdmin = new Admin();
|
|
340
|
+
invalidAdmin.name = 'Broken Admin';
|
|
341
|
+
// role is missing
|
|
342
|
+
await expect(adminRepository.insert(invalidAdmin)).rejects.toThrow();
|
|
343
|
+
// Verify that NO row was created in the users table
|
|
344
|
+
const { rows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')}`);
|
|
345
|
+
expect(rows).toHaveLength(0);
|
|
379
346
|
});
|
|
380
347
|
test('should count and check existence polymorphically', async () => {
|
|
381
|
-
await
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
await guestRepository.insert(Object.assign(new Guest(), { name: 'G1', permissions: 'P1' }));
|
|
387
|
-
expect(await userRepository.count()).toBe(2);
|
|
388
|
-
expect(await userRepository.hasByQuery({ name: 'A1' })).toBe(true);
|
|
389
|
-
expect(await userRepository.hasByQuery({ name: 'G1' })).toBe(true);
|
|
390
|
-
});
|
|
348
|
+
await adminRepository.insert(Object.assign(new Admin(), { name: 'A1', role: 'R1' }));
|
|
349
|
+
await guestRepository.insert(Object.assign(new Guest(), { name: 'G1', permissions: 'P1' }));
|
|
350
|
+
expect(await userRepository.count()).toBe(2);
|
|
351
|
+
expect(await userRepository.hasByQuery({ name: 'A1' })).toBe(true);
|
|
352
|
+
expect(await userRepository.hasByQuery({ name: 'G1' })).toBe(true);
|
|
391
353
|
});
|
|
392
354
|
test('should load many by IDs polymorphically', async () => {
|
|
393
|
-
await
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const results = await userRepository.loadMany([a1.id, g1.id], { includeSubclasses: true });
|
|
400
|
-
expect(results).toHaveLength(2);
|
|
401
|
-
expect(results.find((u) => u.id === a1.id)).toBeInstanceOf(Admin);
|
|
402
|
-
expect(results.find((u) => u.id === g1.id)).toBeInstanceOf(Guest);
|
|
403
|
-
});
|
|
355
|
+
const a1 = await adminRepository.insert(Object.assign(new Admin(), { name: 'A1', role: 'R1' }));
|
|
356
|
+
const g1 = await guestRepository.insert(Object.assign(new Guest(), { name: 'G1', permissions: 'P1' }));
|
|
357
|
+
const results = await userRepository.loadMany([a1.id, g1.id], { includeSubclasses: true });
|
|
358
|
+
expect(results).toHaveLength(2);
|
|
359
|
+
expect(results.find((u) => u.id === a1.id)).toBeInstanceOf(Admin);
|
|
360
|
+
expect(results.find((u) => u.id === g1.id)).toBeInstanceOf(Guest);
|
|
404
361
|
});
|
|
405
362
|
test('should update many child entities', async () => {
|
|
406
|
-
await
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
expect(results[0].role).toBe('New');
|
|
413
|
-
expect(results[1].role).toBe('New');
|
|
414
|
-
});
|
|
363
|
+
const a1 = await adminRepository.insert(Object.assign(new Admin(), { name: 'A1', role: 'Old' }));
|
|
364
|
+
const a2 = await adminRepository.insert(Object.assign(new Admin(), { name: 'A2', role: 'Old' }));
|
|
365
|
+
await adminRepository.updateMany([a1.id, a2.id], { role: 'New' });
|
|
366
|
+
const results = await adminRepository.loadMany([a1.id, a2.id]);
|
|
367
|
+
expect(results[0].role).toBe('New');
|
|
368
|
+
expect(results[1].role).toBe('New');
|
|
415
369
|
});
|
|
416
370
|
test('should update nested inheritance fields', async () => {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
expect(updated.employeeId).toBe('E2');
|
|
425
|
-
expect(updated.department).toBe('D2');
|
|
426
|
-
});
|
|
371
|
+
const manager = Object.assign(new Manager(), { name: 'Boss', employeeId: 'E1', department: 'D1' });
|
|
372
|
+
const inserted = await managerRepository.insert(manager);
|
|
373
|
+
await managerRepository.update(inserted.id, { name: 'New Boss', employeeId: 'E2', department: 'D2' });
|
|
374
|
+
const updated = await managerRepository.load(inserted.id);
|
|
375
|
+
expect(updated.name).toBe('New Boss');
|
|
376
|
+
expect(updated.employeeId).toBe('E2');
|
|
377
|
+
expect(updated.department).toBe('D2');
|
|
427
378
|
});
|
|
428
379
|
test('should delete from nested inheritance tables', async () => {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
expect(staffRows).toHaveLength(0);
|
|
439
|
-
expect(managerRows).toHaveLength(0);
|
|
440
|
-
});
|
|
380
|
+
const manager = Object.assign(new Manager(), { name: 'Boss', employeeId: 'E1', department: 'D1' });
|
|
381
|
+
const inserted = await managerRepository.insert(manager);
|
|
382
|
+
await managerRepository.hardDelete(inserted.id);
|
|
383
|
+
const { rows: userRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('users')} WHERE id = ${inserted.id}`);
|
|
384
|
+
const { rows: staffRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('staff')} WHERE id = ${inserted.id}`);
|
|
385
|
+
const { rows: managerRows } = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('managers')} WHERE id = ${inserted.id}`);
|
|
386
|
+
expect(userRows).toHaveLength(0);
|
|
387
|
+
expect(staffRows).toHaveLength(0);
|
|
388
|
+
expect(managerRows).toHaveLength(0);
|
|
441
389
|
});
|
|
442
390
|
});
|