@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,19 @@ 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 { BaseEntity, 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 Coverage', () => {
|
|
19
19
|
let injector;
|
|
20
20
|
let db;
|
|
21
|
+
let premiumRepo;
|
|
22
|
+
let plainRepo;
|
|
23
|
+
let simpleRepo;
|
|
21
24
|
const schema = 'test_orm_coverage';
|
|
22
25
|
let BaseItem = class BaseItem extends Entity {
|
|
23
26
|
type;
|
|
@@ -72,18 +75,12 @@ describe('ORM Repository Coverage', () => {
|
|
|
72
75
|
Table('plain_items', { schema })
|
|
73
76
|
], PlainItem);
|
|
74
77
|
beforeAll(async () => {
|
|
75
|
-
injector =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
user: 'tstdl',
|
|
82
|
-
password: 'wf7rq6glrk5jykne',
|
|
83
|
-
database: 'tstdl',
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
db = injector.resolve(Database);
|
|
78
|
+
({ injector, database: db } = await setupIntegrationTest({
|
|
79
|
+
orm: { schema },
|
|
80
|
+
}));
|
|
81
|
+
premiumRepo = injector.resolve(getRepository(PremiumItem));
|
|
82
|
+
plainRepo = injector.resolve(getRepository(PlainItem));
|
|
83
|
+
simpleRepo = injector.resolve(getRepository(SimpleItem));
|
|
87
84
|
await db.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
|
|
88
85
|
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('premium_items')} CASCADE`);
|
|
89
86
|
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('items')} CASCADE`);
|
|
@@ -129,175 +126,117 @@ describe('ORM Repository Coverage', () => {
|
|
|
129
126
|
`);
|
|
130
127
|
});
|
|
131
128
|
test('upsertMany on CTI without update object (default update)', async () => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
expect(upserted[0].name).toBe('Item 1 Updated');
|
|
142
|
-
expect(upserted[0].metadata.revision).toBeGreaterThan(inserted.metadata.revision);
|
|
143
|
-
});
|
|
129
|
+
const item1 = Object.assign(new PremiumItem(), { name: 'Item 1', code: 'C1' });
|
|
130
|
+
const inserted = await premiumRepo.insert(item1);
|
|
131
|
+
// Create a conflict by using the same ID
|
|
132
|
+
const conflictItem = Object.assign(new PremiumItem(), { id: inserted.id, name: 'Item 1 Updated', code: 'C1' });
|
|
133
|
+
// This should trigger the code path where `update` is undefined in upsertManyCTI
|
|
134
|
+
const upserted = await premiumRepo.upsertMany(['id'], [conflictItem]);
|
|
135
|
+
expect(upserted).toHaveLength(1);
|
|
136
|
+
expect(upserted[0].name).toBe('Item 1 Updated');
|
|
137
|
+
expect(upserted[0].metadata.revision).toBeGreaterThan(inserted.metadata.revision);
|
|
144
138
|
});
|
|
145
139
|
test('upsertMany with empty array', async () => {
|
|
146
|
-
await
|
|
147
|
-
|
|
148
|
-
const result = await repository.upsertMany(['id'], []);
|
|
149
|
-
expect(result).toHaveLength(0);
|
|
150
|
-
});
|
|
140
|
+
const result = await premiumRepo.upsertMany(['id'], []);
|
|
141
|
+
expect(result).toHaveLength(0);
|
|
151
142
|
});
|
|
152
143
|
test('updateManyByQuery on CTI with no matching rows', async () => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const results = await repository.updateManyByQuery({ name: 'NonExistent' }, { name: 'NewName' });
|
|
157
|
-
expect(results).toEqual([]);
|
|
158
|
-
});
|
|
144
|
+
// Query matching nothing
|
|
145
|
+
const results = await premiumRepo.updateManyByQuery({ name: 'NonExistent' }, { name: 'NewName' });
|
|
146
|
+
expect(results).toEqual([]);
|
|
159
147
|
});
|
|
160
148
|
test('tryUpdate returning undefined (non-CTI, no metadata)', async () => {
|
|
161
|
-
await
|
|
162
|
-
|
|
163
|
-
const result = await repository.tryUpdate('00000000-0000-0000-0000-000000000000', { description: 'New Desc' });
|
|
164
|
-
expect(result).toBeUndefined();
|
|
165
|
-
});
|
|
149
|
+
const result = await plainRepo.tryUpdate('00000000-0000-0000-0000-000000000000', { description: 'New Desc' });
|
|
150
|
+
expect(result).toBeUndefined();
|
|
166
151
|
});
|
|
167
152
|
test('tryUpdate returning undefined (CTI)', async () => {
|
|
168
|
-
await
|
|
169
|
-
|
|
170
|
-
const result = await repository.tryUpdate('00000000-0000-0000-0000-000000000000', { name: 'New Name' });
|
|
171
|
-
expect(result).toBeUndefined();
|
|
172
|
-
});
|
|
153
|
+
const result = await premiumRepo.tryUpdate('00000000-0000-0000-0000-000000000000', { name: 'New Name' });
|
|
154
|
+
expect(result).toBeUndefined();
|
|
173
155
|
});
|
|
174
156
|
test('tryUpdate returning undefined (CTI) - child field only', async () => {
|
|
175
157
|
// This hits line 1163 in tryUpdateCTI (else block where mappedUpdate is empty for base table)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const result = await repository.tryUpdate('00000000-0000-0000-0000-000000000000', { code: 'New Code' });
|
|
181
|
-
expect(result).toBeUndefined();
|
|
182
|
-
});
|
|
158
|
+
// Update ONLY 'code' (Child field).
|
|
159
|
+
// Base table update will be empty.
|
|
160
|
+
const result = await premiumRepo.tryUpdate('00000000-0000-0000-0000-000000000000', { code: 'New Code' });
|
|
161
|
+
expect(result).toBeUndefined();
|
|
183
162
|
});
|
|
184
163
|
test('tryDelete returning undefined (non-CTI, has metadata)', async () => {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const result = await repository.tryDelete('00000000-0000-0000-0000-000000000000');
|
|
188
|
-
expect(result).toBeUndefined();
|
|
189
|
-
});
|
|
164
|
+
const result = await simpleRepo.tryDelete('00000000-0000-0000-0000-000000000000');
|
|
165
|
+
expect(result).toBeUndefined();
|
|
190
166
|
});
|
|
191
167
|
test('tryDelete returning undefined (non-CTI, no metadata)', async () => {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const result = await repository.tryDelete('00000000-0000-0000-0000-000000000000');
|
|
196
|
-
expect(result).toBeUndefined();
|
|
197
|
-
});
|
|
168
|
+
// This hits tryHardDelete
|
|
169
|
+
const result = await plainRepo.tryDelete('00000000-0000-0000-0000-000000000000');
|
|
170
|
+
expect(result).toBeUndefined();
|
|
198
171
|
});
|
|
199
172
|
test('tryDeleteByQuery returning undefined (non-CTI, has metadata)', async () => {
|
|
200
|
-
await
|
|
201
|
-
|
|
202
|
-
const result = await repository.tryDeleteByQuery({ label: 'NonExistent' });
|
|
203
|
-
expect(result).toBeUndefined();
|
|
204
|
-
});
|
|
173
|
+
const result = await simpleRepo.tryDeleteByQuery({ label: 'NonExistent' });
|
|
174
|
+
expect(result).toBeUndefined();
|
|
205
175
|
});
|
|
206
176
|
test('tryDeleteByQuery returning undefined (non-CTI, no metadata)', async () => {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const result = await repository.tryDeleteByQuery({ description: 'NonExistent' });
|
|
211
|
-
expect(result).toBeUndefined();
|
|
212
|
-
});
|
|
177
|
+
// This hits tryHardDeleteByQuery
|
|
178
|
+
const result = await plainRepo.tryDeleteByQuery({ description: 'NonExistent' });
|
|
179
|
+
expect(result).toBeUndefined();
|
|
213
180
|
});
|
|
214
181
|
test('tryUpsertCTI returning undefined when update condition fails', async () => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const loaded = await repository.load(inserted.id);
|
|
225
|
-
expect(loaded.name).toBe('Condition Item'); // Should not have changed
|
|
226
|
-
});
|
|
182
|
+
const item = Object.assign(new PremiumItem(), { name: 'Condition Item', code: 'COND' });
|
|
183
|
+
const inserted = await premiumRepo.insert(item);
|
|
184
|
+
const conflictItem = Object.assign(new PremiumItem(), { id: inserted.id, name: 'Should Not Update', code: 'COND' });
|
|
185
|
+
// Upsert with a condition that evaluates to false
|
|
186
|
+
const result = await premiumRepo.tryUpsert(['id'], conflictItem, undefined, { set: { name: 'Impossible Name' } } // This query condition on the existing row must fail
|
|
187
|
+
);
|
|
188
|
+
expect(result).toBeUndefined();
|
|
189
|
+
const loaded = await premiumRepo.load(inserted.id);
|
|
190
|
+
expect(loaded.name).toBe('Condition Item'); // Should not have changed
|
|
227
191
|
});
|
|
228
192
|
test('Discriminator mismatch throws error', async () => {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
};
|
|
244
|
-
await expect(repository.mapToEntity(corruptRow)).rejects.toThrow('Discriminator mismatch');
|
|
245
|
-
});
|
|
193
|
+
// Simulate a row returned from DB that has wrong type
|
|
194
|
+
// PremiumItem expects type='premium'
|
|
195
|
+
const corruptRow = {
|
|
196
|
+
id: 'some-id',
|
|
197
|
+
type: 'wrong_type',
|
|
198
|
+
name: 'Test',
|
|
199
|
+
code: 'C1',
|
|
200
|
+
revision: 1,
|
|
201
|
+
revision_timestamp: new Date(),
|
|
202
|
+
create_timestamp: new Date(),
|
|
203
|
+
delete_timestamp: null,
|
|
204
|
+
attributes: {},
|
|
205
|
+
};
|
|
206
|
+
await expect(premiumRepo.mapToEntity(corruptRow)).rejects.toThrow('Discriminator mismatch');
|
|
246
207
|
});
|
|
247
208
|
test('updateManyByQuery (CTI) should return empty array if no IDs found', async () => {
|
|
248
|
-
//
|
|
249
|
-
await
|
|
250
|
-
|
|
251
|
-
// Query that returns nothing
|
|
252
|
-
const result = await repository.updateManyByQuery({ name: 'Ghost' }, { name: 'Buster' });
|
|
253
|
-
expect(result).toHaveLength(0);
|
|
254
|
-
});
|
|
209
|
+
// Query that returns nothing
|
|
210
|
+
const result = await premiumRepo.updateManyByQuery({ name: 'Ghost' }, { name: 'Buster' });
|
|
211
|
+
expect(result).toHaveLength(0);
|
|
255
212
|
});
|
|
256
213
|
test('updateManyByQuery on Non-CTI', async () => {
|
|
257
|
-
await
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
expect(results).toHaveLength(1);
|
|
262
|
-
expect(results[0].label).toBe('Updated');
|
|
263
|
-
});
|
|
214
|
+
await simpleRepo.insert(Object.assign(new SimpleItem(), { label: 'To Update' }));
|
|
215
|
+
const results = await simpleRepo.updateManyByQuery({ label: 'To Update' }, { label: 'Updated' });
|
|
216
|
+
expect(results).toHaveLength(1);
|
|
217
|
+
expect(results[0].label).toBe('Updated');
|
|
264
218
|
});
|
|
265
219
|
test('updateByQuery throws NotFoundError', async () => {
|
|
266
|
-
await
|
|
267
|
-
const repository = injectRepository(SimpleItem);
|
|
268
|
-
await expect(repository.updateByQuery({ label: 'Ghost' }, { label: 'Buster' })).rejects.toThrow('SimpleItem not found');
|
|
269
|
-
});
|
|
220
|
+
await expect(simpleRepo.updateByQuery({ label: 'Ghost' }, { label: 'Buster' })).rejects.toThrow('SimpleItem not found');
|
|
270
221
|
});
|
|
271
222
|
test('tryUpdateByQuery returns undefined (CTI)', async () => {
|
|
272
|
-
await
|
|
273
|
-
|
|
274
|
-
const result = await repository.tryUpdateByQuery({ name: 'Ghost' }, { name: 'Buster' });
|
|
275
|
-
expect(result).toBeUndefined();
|
|
276
|
-
});
|
|
223
|
+
const result = await premiumRepo.tryUpdateByQuery({ name: 'Ghost' }, { name: 'Buster' });
|
|
224
|
+
expect(result).toBeUndefined();
|
|
277
225
|
});
|
|
278
226
|
test('tryUpdateByQuery updates entity (CTI)', async () => {
|
|
279
|
-
await
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
expect(result).toBeDefined();
|
|
284
|
-
expect(result.name).toBe('Updated Target');
|
|
285
|
-
});
|
|
227
|
+
const item = await premiumRepo.insert(Object.assign(new PremiumItem(), { name: 'Target', code: 'T1' }));
|
|
228
|
+
const result = await premiumRepo.tryUpdateByQuery({ name: 'Target' }, { name: 'Updated Target' });
|
|
229
|
+
expect(result).toBeDefined();
|
|
230
|
+
expect(result.name).toBe('Updated Target');
|
|
286
231
|
});
|
|
287
232
|
test('deleteManyByQuery (Soft Delete)', async () => {
|
|
288
|
-
await
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
expect(results[0].label).toBe('To Delete');
|
|
294
|
-
expect(results[0].metadata.deleteTimestamp).not.toBeNull();
|
|
295
|
-
});
|
|
233
|
+
await simpleRepo.insert(Object.assign(new SimpleItem(), { label: 'To Delete' }));
|
|
234
|
+
const results = await simpleRepo.deleteManyByQuery({ label: 'To Delete' });
|
|
235
|
+
expect(results).toHaveLength(1);
|
|
236
|
+
expect(results[0].label).toBe('To Delete');
|
|
237
|
+
expect(results[0].metadata.deleteTimestamp).not.toBeNull();
|
|
296
238
|
});
|
|
297
239
|
test('deleteByQuery throws NotFoundError', async () => {
|
|
298
|
-
await
|
|
299
|
-
const repository = injectRepository(SimpleItem);
|
|
300
|
-
await expect(repository.deleteByQuery({ label: 'Ghost' })).rejects.toThrow('SimpleItem not found');
|
|
301
|
-
});
|
|
240
|
+
await expect(simpleRepo.deleteByQuery({ label: 'Ghost' })).rejects.toThrow('SimpleItem not found');
|
|
302
241
|
});
|
|
303
242
|
});
|