@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,21 +8,23 @@ 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
|
};
|
|
11
|
+
import { sql } from 'drizzle-orm';
|
|
12
|
+
import { beforeAll, beforeEach, describe, expect, test } from 'vitest';
|
|
10
13
|
import { NotFoundError } from '../../errors/not-found.error.js';
|
|
11
|
-
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
12
14
|
import { StringProperty } from '../../schema/index.js';
|
|
13
15
|
import { dropTables, setupIntegrationTest, truncateTables } from '../../testing/index.js';
|
|
14
16
|
import { toArrayAsync } from '../../utils/async-iterable-helpers/to-array.js';
|
|
15
|
-
import { sql } from 'drizzle-orm';
|
|
16
|
-
import { beforeAll, describe, expect, test } from 'vitest';
|
|
17
17
|
import { ChildEntity, Column, Inheritance, Table } from '../decorators.js';
|
|
18
18
|
import { BaseEntity, Entity } from '../entity.js';
|
|
19
|
-
import {
|
|
20
|
-
import { injectRepository } from '../server/repository.js';
|
|
19
|
+
import { getRepository } from '../server/repository.js';
|
|
21
20
|
import { ENCRYPTION_SECRET } from '../server/tokens.js';
|
|
22
21
|
describe('ORM Repository Extra Coverage', () => {
|
|
23
22
|
let injector;
|
|
24
23
|
let database;
|
|
24
|
+
let baseItemRepo;
|
|
25
|
+
let premiumItemRepo;
|
|
26
|
+
let simpleItemRepo;
|
|
27
|
+
let plainItemRepo;
|
|
25
28
|
const schema = 'test_orm_extra_coverage';
|
|
26
29
|
let BaseItem = class BaseItem extends Entity {
|
|
27
30
|
type;
|
|
@@ -78,6 +81,10 @@ describe('ORM Repository Extra Coverage', () => {
|
|
|
78
81
|
beforeAll(async () => {
|
|
79
82
|
({ injector, database } = await setupIntegrationTest({ orm: { schema } }));
|
|
80
83
|
injector.register(ENCRYPTION_SECRET, { useValue: new Uint8Array(32).fill(1) });
|
|
84
|
+
baseItemRepo = injector.resolve(getRepository(BaseItem));
|
|
85
|
+
premiumItemRepo = injector.resolve(getRepository(PremiumItem));
|
|
86
|
+
simpleItemRepo = injector.resolve(getRepository(SimpleItem));
|
|
87
|
+
plainItemRepo = injector.resolve(getRepository(PlainItem));
|
|
81
88
|
await dropTables(database, schema, ['premium_items', 'items', 'simple_items', 'plain_items']);
|
|
82
89
|
await database.execute(sql `
|
|
83
90
|
CREATE TABLE ${sql.identifier(schema)}.${sql.identifier('items')} (
|
|
@@ -118,429 +125,280 @@ describe('ORM Repository Extra Coverage', () => {
|
|
|
118
125
|
)
|
|
119
126
|
`);
|
|
120
127
|
});
|
|
121
|
-
|
|
128
|
+
beforeEach(async () => {
|
|
122
129
|
await truncateTables(database, schema, ['premium_items', 'items', 'simple_items', 'plain_items']);
|
|
123
130
|
});
|
|
124
131
|
test('loadManyCursor should work', async () => {
|
|
125
|
-
await
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
expect(results).toHaveLength(1);
|
|
131
|
-
expect(results[0].id).toBe(item.id);
|
|
132
|
-
});
|
|
132
|
+
const item = await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Cursor Test' }));
|
|
133
|
+
const cursor = simpleItemRepo.loadManyCursor([item.id]);
|
|
134
|
+
const results = await toArrayAsync(cursor);
|
|
135
|
+
expect(results).toHaveLength(1);
|
|
136
|
+
expect(results[0].id).toBe(item.id);
|
|
133
137
|
});
|
|
134
138
|
test('loadAllCursor should work', async () => {
|
|
135
|
-
await
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const results = await toArrayAsync(cursor);
|
|
140
|
-
expect(results.length).toBeGreaterThan(0);
|
|
141
|
-
});
|
|
139
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'All Cursor Test' }));
|
|
140
|
+
const cursor = simpleItemRepo.loadAllCursor();
|
|
141
|
+
const results = await toArrayAsync(cursor);
|
|
142
|
+
expect(results.length).toBeGreaterThan(0);
|
|
142
143
|
});
|
|
143
144
|
test('tryLoadByQuery with order option', async () => {
|
|
144
|
-
await
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const item = await repository.tryLoadByQuery({}, { order: { label: 'desc' } });
|
|
149
|
-
expect(item.label).toBe('B');
|
|
150
|
-
});
|
|
145
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'A' }));
|
|
146
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'B' }));
|
|
147
|
+
const item = await simpleItemRepo.tryLoadByQuery({}, { order: { label: 'desc' } });
|
|
148
|
+
expect(item.label).toBe('B');
|
|
151
149
|
});
|
|
152
150
|
test('insertManyIfNotExists with empty array', async () => {
|
|
153
|
-
await
|
|
154
|
-
|
|
155
|
-
const results = await repository.insertManyIfNotExists(['label'], []);
|
|
156
|
-
expect(results).toHaveLength(0);
|
|
157
|
-
});
|
|
151
|
+
const results = await simpleItemRepo.insertManyIfNotExists(['label'], []);
|
|
152
|
+
expect(results).toHaveLength(0);
|
|
158
153
|
});
|
|
159
154
|
test('insertManyIfNotExists with items', async () => {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const results2 = await repository.insertManyIfNotExists(['label'], [item]);
|
|
166
|
-
expect(results2).toHaveLength(0);
|
|
167
|
-
});
|
|
155
|
+
const item = Object.assign(new SimpleItem(), { label: 'Unique' });
|
|
156
|
+
const results = await simpleItemRepo.insertManyIfNotExists(['label'], [item]);
|
|
157
|
+
expect(results).toHaveLength(1);
|
|
158
|
+
const results2 = await simpleItemRepo.insertManyIfNotExists(['label'], [item]);
|
|
159
|
+
expect(results2).toHaveLength(0);
|
|
168
160
|
});
|
|
169
161
|
test('tryInsert returning entity', async () => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const conflict = await repository.tryInsert(inserted); // Should conflict on ID
|
|
177
|
-
expect(conflict).toBeUndefined();
|
|
178
|
-
});
|
|
162
|
+
const item = Object.assign(new SimpleItem(), { label: 'TryInsert' });
|
|
163
|
+
const inserted = await simpleItemRepo.tryInsert(item);
|
|
164
|
+
expect(inserted).toBeDefined();
|
|
165
|
+
expect(inserted.label).toBe('TryInsert');
|
|
166
|
+
const conflict = await simpleItemRepo.tryInsert(inserted); // Should conflict on ID
|
|
167
|
+
expect(conflict).toBeUndefined();
|
|
179
168
|
});
|
|
180
169
|
test('loadMany with empty array', async () => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const results = await repository.loadMany([]);
|
|
184
|
-
expect(results).toHaveLength(0);
|
|
185
|
-
});
|
|
170
|
+
const results = await simpleItemRepo.loadMany([]);
|
|
171
|
+
expect(results).toHaveLength(0);
|
|
186
172
|
});
|
|
187
173
|
test('insertMany for non-CTI with empty array', async () => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const results = await repository.insertMany([]);
|
|
191
|
-
expect(results).toHaveLength(0);
|
|
192
|
-
});
|
|
174
|
+
const results = await simpleItemRepo.insertMany([]);
|
|
175
|
+
expect(results).toHaveLength(0);
|
|
193
176
|
});
|
|
194
177
|
test('countByQuery returning count', async () => {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const newCount = await repository.count();
|
|
200
|
-
expect(newCount).toBe(initialCount + 1);
|
|
201
|
-
});
|
|
178
|
+
const initialCount = await simpleItemRepo.count();
|
|
179
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Count Test' }));
|
|
180
|
+
const newCount = await simpleItemRepo.count();
|
|
181
|
+
expect(newCount).toBe(initialCount + 1);
|
|
202
182
|
});
|
|
203
183
|
test('mapManyToColumns and mapManyToInsertColumns', async () => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
expect(insertCols).toHaveLength(1);
|
|
212
|
-
expect(insertCols[0].label).toBe('Map Test');
|
|
213
|
-
});
|
|
184
|
+
const item = Object.assign(new SimpleItem(), { label: 'Map Test' });
|
|
185
|
+
const cols = await simpleItemRepo.mapManyToColumns([item]);
|
|
186
|
+
expect(cols).toHaveLength(1);
|
|
187
|
+
expect(cols[0].label).toBe('Map Test');
|
|
188
|
+
const insertCols = await simpleItemRepo.mapManyToInsertColumns([item]);
|
|
189
|
+
expect(insertCols).toHaveLength(1);
|
|
190
|
+
expect(insertCols[0].label).toBe('Map Test');
|
|
214
191
|
});
|
|
215
192
|
test('tryUpsert on non-CTI returning undefined when condition fails', async () => {
|
|
216
|
-
await
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const result = await repository.tryUpsert(['id'], conflictItem, undefined, { set: { label: 'Impossible' } });
|
|
221
|
-
expect(result).toBeUndefined();
|
|
222
|
-
});
|
|
193
|
+
const item = await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Upsert Condition' }));
|
|
194
|
+
const conflictItem = Object.assign(new SimpleItem(), { id: item.id, label: 'Should Fail' });
|
|
195
|
+
const result = await simpleItemRepo.tryUpsert(['id'], conflictItem, undefined, { set: { label: 'Impossible' } });
|
|
196
|
+
expect(result).toBeUndefined();
|
|
223
197
|
});
|
|
224
198
|
test('tryUpdateByQuery on non-CTI', async () => {
|
|
225
|
-
await
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const result2 = await repository.tryUpdateByQuery({ label: 'Ghost' }, { label: 'Buster' });
|
|
232
|
-
expect(result2).toBeUndefined();
|
|
233
|
-
});
|
|
199
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'UpdateByQuery' }));
|
|
200
|
+
const result = await simpleItemRepo.tryUpdateByQuery({ label: 'UpdateByQuery' }, { label: 'UpdatedByQuery' });
|
|
201
|
+
expect(result).toBeDefined();
|
|
202
|
+
expect(result.label).toBe('UpdatedByQuery');
|
|
203
|
+
const result2 = await simpleItemRepo.tryUpdateByQuery({ label: 'Ghost' }, { label: 'Buster' });
|
|
204
|
+
expect(result2).toBeUndefined();
|
|
234
205
|
});
|
|
235
206
|
test('tryDeleteByQuery on CTI', async () => {
|
|
236
|
-
await
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const result2 = await repository.tryDeleteByQuery({ name: 'Ghost' });
|
|
243
|
-
expect(result2).toBeUndefined();
|
|
244
|
-
});
|
|
207
|
+
const item = await premiumItemRepo.insert(Object.assign(new PremiumItem(), { name: 'DeleteMe', code: 'D1' }));
|
|
208
|
+
const result = await premiumItemRepo.tryDeleteByQuery({ name: 'DeleteMe' });
|
|
209
|
+
expect(result).toBeDefined();
|
|
210
|
+
expect(result.metadata.deleteTimestamp).not.toBeNull();
|
|
211
|
+
const result2 = await premiumItemRepo.tryDeleteByQuery({ name: 'Ghost' });
|
|
212
|
+
expect(result2).toBeUndefined();
|
|
245
213
|
});
|
|
246
214
|
test('getIdLimitQuery for update', async () => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const query = repository.getIdLimitQuery({ label: 'Any' }).for('update');
|
|
250
|
-
expect(query).toBeDefined();
|
|
251
|
-
});
|
|
215
|
+
const query = simpleItemRepo.getIdLimitQuery({ label: 'Any' }).for('update');
|
|
216
|
+
expect(query).toBeDefined();
|
|
252
217
|
});
|
|
253
218
|
test('applySelect with distinct and distinctOn', async () => {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const q2 = repository.applySelect(database, ['label']);
|
|
259
|
-
expect(q2).toBeDefined();
|
|
260
|
-
});
|
|
219
|
+
const q1 = simpleItemRepo.applySelect(database, true);
|
|
220
|
+
expect(q1).toBeDefined();
|
|
221
|
+
const q2 = simpleItemRepo.applySelect(database, ['label']);
|
|
222
|
+
expect(q2).toBeDefined();
|
|
261
223
|
});
|
|
262
224
|
test('mapToColumns and mapToInsertColumns', async () => {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const insertCol = await repository.mapToInsertColumns(item);
|
|
269
|
-
expect(insertCol.label).toBe('Map Single');
|
|
270
|
-
});
|
|
225
|
+
const item = Object.assign(new SimpleItem(), { label: 'Map Single' });
|
|
226
|
+
const col = await simpleItemRepo.mapToColumns(item);
|
|
227
|
+
expect(col.label).toBe('Map Single');
|
|
228
|
+
const insertCol = await simpleItemRepo.mapToInsertColumns(item);
|
|
229
|
+
expect(insertCol.label).toBe('Map Single');
|
|
271
230
|
});
|
|
272
231
|
test('hasAll with duplicates and missing IDs', async () => {
|
|
273
|
-
await
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
expect(await repository.hasAll([item.id, '00000000-0000-0000-0000-000000000000'])).toBe(false);
|
|
278
|
-
expect(await repository.hasAll([])).toBe(false);
|
|
279
|
-
});
|
|
232
|
+
const item = await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'HasAll' }));
|
|
233
|
+
expect(await simpleItemRepo.hasAll([item.id, item.id])).toBe(true);
|
|
234
|
+
expect(await simpleItemRepo.hasAll([item.id, '00000000-0000-0000-0000-000000000000'])).toBe(false);
|
|
235
|
+
expect(await simpleItemRepo.hasAll([])).toBe(false);
|
|
280
236
|
});
|
|
281
237
|
test('loadManyByQueryCursor should work', async () => {
|
|
282
|
-
await
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const results = await toArrayAsync(cursor);
|
|
287
|
-
expect(results).toHaveLength(1);
|
|
288
|
-
});
|
|
238
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Many Cursor' }));
|
|
239
|
+
const cursor = simpleItemRepo.loadManyByQueryCursor({ label: 'Many Cursor' });
|
|
240
|
+
const results = await toArrayAsync(cursor);
|
|
241
|
+
expect(results).toHaveLength(1);
|
|
289
242
|
});
|
|
290
243
|
test('upsert (non-CTI) with wheres', async () => {
|
|
291
|
-
await
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
await expect(repository.upsert(['label'], Object.assign(new SimpleItem(), { label: 'Upsert Updated' }), { label: 'Should Not Happen' }, { set: { label: 'Wrong' } }))
|
|
299
|
-
.rejects.toThrow('upsert failed to return a row');
|
|
300
|
-
});
|
|
244
|
+
const item = await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Upsert' }));
|
|
245
|
+
// This should succeed
|
|
246
|
+
const upserted = await simpleItemRepo.upsert(['label'], Object.assign(new SimpleItem(), { label: 'Upsert' }), { label: 'Upsert Updated' }, { set: { label: 'Upsert' } });
|
|
247
|
+
expect(upserted.label).toBe('Upsert Updated');
|
|
248
|
+
// This should fail to return a row due to condition
|
|
249
|
+
await expect(simpleItemRepo.upsert(['label'], Object.assign(new SimpleItem(), { label: 'Upsert Updated' }), { label: 'Should Not Happen' }, { set: { label: 'Wrong' } }))
|
|
250
|
+
.rejects.toThrow('upsert failed to return a row');
|
|
301
251
|
});
|
|
302
252
|
test('updateMany (non-CTI) with empty array', async () => {
|
|
303
|
-
await
|
|
304
|
-
const repository = injectRepository(SimpleItem);
|
|
305
|
-
expect(await repository.updateMany([], { label: 'None' })).toEqual([]);
|
|
306
|
-
});
|
|
253
|
+
expect(await simpleItemRepo.updateMany([], { label: 'None' })).toEqual([]);
|
|
307
254
|
});
|
|
308
255
|
test('hardDeleteByQuery throws NotFoundError', async () => {
|
|
309
|
-
await
|
|
310
|
-
const repository = injectRepository(SimpleItem);
|
|
311
|
-
await expect(repository.hardDeleteByQuery({ label: 'Ghost' })).rejects.toThrow('SimpleItem not found');
|
|
312
|
-
});
|
|
256
|
+
await expect(simpleItemRepo.hardDeleteByQuery({ label: 'Ghost' })).rejects.toThrow('SimpleItem not found');
|
|
313
257
|
});
|
|
314
258
|
test('hardDeleteMany with empty array', async () => {
|
|
315
|
-
await
|
|
316
|
-
const repository = injectRepository(SimpleItem);
|
|
317
|
-
expect(await repository.hardDeleteMany([])).toEqual([]);
|
|
318
|
-
});
|
|
259
|
+
expect(await simpleItemRepo.hardDeleteMany([])).toEqual([]);
|
|
319
260
|
});
|
|
320
261
|
test('convertOrderBy with various inputs', async () => {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
expect(repository.convertOrderBy(['label', ['id', 'desc']])).toHaveLength(2);
|
|
325
|
-
expect(repository.convertOrderBy(sql `label`)).toHaveLength(1);
|
|
326
|
-
});
|
|
262
|
+
expect(simpleItemRepo.convertOrderBy('label')).toBeDefined();
|
|
263
|
+
expect(simpleItemRepo.convertOrderBy(['label', ['id', 'desc']])).toHaveLength(2);
|
|
264
|
+
expect(simpleItemRepo.convertOrderBy(sql `label`)).toHaveLength(1);
|
|
327
265
|
});
|
|
328
266
|
test('mapManyToEntity and mapToEntity with subclasses', async () => {
|
|
329
|
-
await
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const rows = await database.execute(sql `SELECT * FROM ${sql.identifier(schema)}.${sql.identifier('items')} i LEFT JOIN ${sql.identifier(schema)}.${sql.identifier('premium_items')} p ON i.id = p.id`);
|
|
335
|
-
// We need to manually construct the polymorphic row structure if we want to test mapManyToEntity directly with subclasses
|
|
336
|
-
// But loadAll({ includeSubclasses: true }) already does this.
|
|
337
|
-
});
|
|
267
|
+
const item = await premiumItemRepo.insert(Object.assign(new PremiumItem(), { name: 'P1', code: 'C1' }));
|
|
268
|
+
const polymorphic = await baseItemRepo.load(item.id, { includeSubclasses: true });
|
|
269
|
+
expect(polymorphic).toBeInstanceOf(PremiumItem);
|
|
270
|
+
// We need to manually construct the polymorphic row structure if we want to test mapManyToEntity directly with subclasses
|
|
271
|
+
// But loadAll({ includeSubclasses: true }) already does this.
|
|
338
272
|
});
|
|
339
273
|
test('tryUpdateCTI with no changes', async () => {
|
|
340
|
-
await
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const updated = await repository.tryUpdate(item.id, {});
|
|
344
|
-
expect(updated.id).toBe(item.id);
|
|
345
|
-
});
|
|
274
|
+
const item = await premiumItemRepo.insert(Object.assign(new PremiumItem(), { name: 'NoChange', code: 'NC' }));
|
|
275
|
+
const updated = await premiumItemRepo.tryUpdate(item.id, {});
|
|
276
|
+
expect(updated.id).toBe(item.id);
|
|
346
277
|
});
|
|
347
278
|
test('tsvector search with various options', async () => {
|
|
348
|
-
await
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
});
|
|
374
|
-
expect(r4).toHaveLength(1);
|
|
375
|
-
});
|
|
279
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Search Item' }));
|
|
280
|
+
// score: false
|
|
281
|
+
const r1 = await simpleItemRepo.search({
|
|
282
|
+
query: { $tsvector: { fields: ['label'], query: 'Search' } },
|
|
283
|
+
score: false,
|
|
284
|
+
});
|
|
285
|
+
expect(r1[0].score).toBeUndefined();
|
|
286
|
+
// highlight variants
|
|
287
|
+
const r2 = await simpleItemRepo.search({
|
|
288
|
+
query: { $tsvector: { fields: ['label'], query: 'Search' } },
|
|
289
|
+
highlight: 'label',
|
|
290
|
+
});
|
|
291
|
+
expect(r2[0].highlight).toBeDefined();
|
|
292
|
+
const r3 = await simpleItemRepo.search({
|
|
293
|
+
query: { $tsvector: { fields: ['label'], query: 'Search' } },
|
|
294
|
+
highlight: { source: sql `label`, minWords: 5, maxWords: 10 },
|
|
295
|
+
});
|
|
296
|
+
expect(r3[0].highlight).toBeDefined();
|
|
297
|
+
// offset and limit
|
|
298
|
+
const r4 = await simpleItemRepo.search({
|
|
299
|
+
query: { $tsvector: { fields: ['label'], query: 'Search' } },
|
|
300
|
+
offset: 0,
|
|
301
|
+
limit: 1,
|
|
302
|
+
});
|
|
303
|
+
expect(r4).toHaveLength(1);
|
|
376
304
|
});
|
|
377
305
|
test('trigram search with rank: false', async () => {
|
|
378
|
-
await
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
query: { $trigram: { fields: ['label'], query: 'Trigram' } },
|
|
383
|
-
rank: false,
|
|
384
|
-
});
|
|
385
|
-
expect(results).toHaveLength(1);
|
|
306
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Trigram' }));
|
|
307
|
+
const results = await simpleItemRepo.search({
|
|
308
|
+
query: { $trigram: { fields: ['label'], query: 'Trigram' } },
|
|
309
|
+
rank: false,
|
|
386
310
|
});
|
|
311
|
+
expect(results).toHaveLength(1);
|
|
387
312
|
});
|
|
388
313
|
test('PlainItem (no metadata) operations', async () => {
|
|
389
|
-
await
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const exists = await repository.has(item.id);
|
|
396
|
-
expect(exists).toBe(false);
|
|
397
|
-
});
|
|
314
|
+
const item = await plainItemRepo.insert(Object.assign(new PlainItem(), { description: 'Plain' }));
|
|
315
|
+
expect(plainItemRepo.hasMetadata).toBe(false);
|
|
316
|
+
const deleted = await plainItemRepo.delete(item.id);
|
|
317
|
+
expect(deleted.description).toBe('Plain');
|
|
318
|
+
const exists = await plainItemRepo.has(item.id);
|
|
319
|
+
expect(exists).toBe(false);
|
|
398
320
|
});
|
|
399
321
|
test('convertOrderBy with object', async () => {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const order = repository.convertOrderBy({ label: 'desc' });
|
|
403
|
-
expect(order).toHaveLength(1);
|
|
404
|
-
});
|
|
322
|
+
const order = simpleItemRepo.convertOrderBy({ label: 'desc' });
|
|
323
|
+
expect(order).toHaveLength(1);
|
|
405
324
|
});
|
|
406
325
|
test('tryHardDelete returning undefined', async () => {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const result = await repository.tryHardDelete('00000000-0000-0000-0000-000000000000');
|
|
410
|
-
expect(result).toBeUndefined();
|
|
411
|
-
});
|
|
326
|
+
const result = await simpleItemRepo.tryHardDelete('00000000-0000-0000-0000-000000000000');
|
|
327
|
+
expect(result).toBeUndefined();
|
|
412
328
|
});
|
|
413
329
|
test('tryHardDeleteByQuery returning undefined', async () => {
|
|
414
|
-
await
|
|
415
|
-
|
|
416
|
-
const result = await repository.tryHardDeleteByQuery({ label: 'Ghost' });
|
|
417
|
-
expect(result).toBeUndefined();
|
|
418
|
-
});
|
|
330
|
+
const result = await simpleItemRepo.tryHardDeleteByQuery({ label: 'Ghost' });
|
|
331
|
+
expect(result).toBeUndefined();
|
|
419
332
|
});
|
|
420
333
|
test('hardDeleteMany with empty array branch', async () => {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
const results = await repository.hardDeleteMany([]);
|
|
424
|
-
expect(results).toEqual([]);
|
|
425
|
-
});
|
|
334
|
+
const results = await simpleItemRepo.hardDeleteMany([]);
|
|
335
|
+
expect(results).toEqual([]);
|
|
426
336
|
});
|
|
427
337
|
test('hardDeleteManyByQuery works', async () => {
|
|
428
|
-
await
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const results = await repository.hardDeleteManyByQuery({ label: 'To Hard Delete' });
|
|
432
|
-
expect(results).toHaveLength(1);
|
|
433
|
-
});
|
|
338
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'To Hard Delete' }));
|
|
339
|
+
const results = await simpleItemRepo.hardDeleteManyByQuery({ label: 'To Hard Delete' });
|
|
340
|
+
expect(results).toHaveLength(1);
|
|
434
341
|
});
|
|
435
342
|
test('applySelect with false (default)', async () => {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const q = repository.applySelect(database, undefined, false);
|
|
439
|
-
expect(q).toBeDefined();
|
|
440
|
-
});
|
|
343
|
+
const q = simpleItemRepo.applySelect(database, undefined, false);
|
|
344
|
+
expect(q).toBeDefined();
|
|
441
345
|
});
|
|
442
346
|
test('load should throw NotFoundError', async () => {
|
|
443
|
-
await
|
|
444
|
-
const repository = injectRepository(SimpleItem);
|
|
445
|
-
await expect(repository.load('00000000-0000-0000-0000-000000000000')).rejects.toThrow(NotFoundError);
|
|
446
|
-
});
|
|
347
|
+
await expect(simpleItemRepo.load('00000000-0000-0000-0000-000000000000')).rejects.toThrow(NotFoundError);
|
|
447
348
|
});
|
|
448
349
|
test('tsvector search with rank number', async () => {
|
|
449
|
-
await
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
query: { $tsvector: { fields: ['label'], query: 'Rank' } },
|
|
454
|
-
rank: { normalization: 1 }, // normalization
|
|
455
|
-
});
|
|
456
|
-
expect(results).toHaveLength(1);
|
|
350
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Rank' }));
|
|
351
|
+
const results = await simpleItemRepo.search({
|
|
352
|
+
query: { $tsvector: { fields: ['label'], query: 'Rank' } },
|
|
353
|
+
rank: { normalization: 1 }, // normalization
|
|
457
354
|
});
|
|
355
|
+
expect(results).toHaveLength(1);
|
|
458
356
|
});
|
|
459
357
|
test('encryptionSecret usage in getTransformContext', async () => {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
// Call it again to hit the cached branch
|
|
466
|
-
const context2 = await repository.getTransformContext();
|
|
467
|
-
expect(context2).toBe(context);
|
|
468
|
-
});
|
|
469
|
-
});
|
|
358
|
+
const context = await simpleItemRepo.getTransformContext();
|
|
359
|
+
expect(context.encryptionKey).toBeDefined();
|
|
360
|
+
// Call it again to hit the cached branch
|
|
361
|
+
const context2 = await simpleItemRepo.getTransformContext();
|
|
362
|
+
expect(context2).toBe(context);
|
|
470
363
|
});
|
|
471
364
|
test('tsvector search with rank true/false', async () => {
|
|
472
|
-
await
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
query: { $tsvector: { fields: ['label'], query: 'Rank' } },
|
|
481
|
-
rank: false,
|
|
482
|
-
});
|
|
365
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Rank' }));
|
|
366
|
+
await simpleItemRepo.search({
|
|
367
|
+
query: { $tsvector: { fields: ['label'], query: 'Rank' } },
|
|
368
|
+
rank: true,
|
|
369
|
+
});
|
|
370
|
+
await simpleItemRepo.search({
|
|
371
|
+
query: { $tsvector: { fields: ['label'], query: 'Rank' } },
|
|
372
|
+
rank: false,
|
|
483
373
|
});
|
|
484
374
|
});
|
|
485
375
|
test('trigram search with threshold', async () => {
|
|
486
|
-
await
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const results = await repository.search({
|
|
491
|
-
query: { $trigram: { fields: ['label'], query: 'Trigram', threshold: 0.1 } },
|
|
492
|
-
});
|
|
493
|
-
expect(results).toHaveLength(1);
|
|
376
|
+
await simpleItemRepo.insert(Object.assign(new SimpleItem(), { label: 'Trigram Threshold' }));
|
|
377
|
+
await database.execute(sql `SET pg_trgm.similarity_threshold = 0.1`);
|
|
378
|
+
const results = await simpleItemRepo.search({
|
|
379
|
+
query: { $trigram: { fields: ['label'], query: 'Trigram', threshold: 0.1 } },
|
|
494
380
|
});
|
|
381
|
+
expect(results).toHaveLength(1);
|
|
495
382
|
});
|
|
496
383
|
test('repository resolution inside transaction', async () => {
|
|
497
|
-
await
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
expect(repository).toBeDefined();
|
|
501
|
-
});
|
|
384
|
+
await simpleItemRepo.transaction(async (transaction) => {
|
|
385
|
+
const repository = injector.resolve(getRepository(SimpleItem)).withTransaction(transaction);
|
|
386
|
+
expect(repository).toBeDefined();
|
|
502
387
|
});
|
|
503
388
|
});
|
|
504
389
|
test('updateManyCTI with no matches', async () => {
|
|
505
|
-
await
|
|
506
|
-
|
|
507
|
-
const results = await repository.updateMany(['00000000-0000-0000-0000-000000000000'], { name: 'Ghost' });
|
|
508
|
-
expect(results).toEqual([]);
|
|
509
|
-
});
|
|
390
|
+
const results = await premiumItemRepo.updateMany(['00000000-0000-0000-0000-000000000000'], { name: 'Ghost' });
|
|
391
|
+
expect(results).toEqual([]);
|
|
510
392
|
});
|
|
511
393
|
test('upsertMany on CTI with wheres and update object', async () => {
|
|
512
|
-
await
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
expect(results[0].name).toBe('UpsertManyCTI Updated');
|
|
519
|
-
expect(results[0].code).toBe('U2');
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
test('updateManyCTI with no matches', async () => {
|
|
523
|
-
await runInInjectionContext(injector, async () => {
|
|
524
|
-
const repository = injectRepository(PremiumItem);
|
|
525
|
-
const results = await repository.updateMany(['00000000-0000-0000-0000-000000000000'], { name: 'Ghost' });
|
|
526
|
-
expect(results).toEqual([]);
|
|
527
|
-
});
|
|
528
|
-
});
|
|
529
|
-
test('upsertMany on CTI with wheres and update object', async () => {
|
|
530
|
-
await runInInjectionContext(injector, async () => {
|
|
531
|
-
const repository = injectRepository(PremiumItem);
|
|
532
|
-
const item = await repository.insert(Object.assign(new PremiumItem(), { name: 'UpsertManyCTI', code: 'U1' }));
|
|
533
|
-
const conflictItem = Object.assign(new PremiumItem(), { id: item.id, name: 'UpsertManyCTI', code: 'U1' });
|
|
534
|
-
const results = await repository.upsertMany(['id'], [conflictItem], { name: 'UpsertManyCTI Updated', code: 'U2' }, { target: { name: 'UpsertManyCTI' } });
|
|
535
|
-
expect(results).toHaveLength(1);
|
|
536
|
-
expect(results[0].name).toBe('UpsertManyCTI Updated');
|
|
537
|
-
expect(results[0].code).toBe('U2');
|
|
538
|
-
});
|
|
394
|
+
const item = await premiumItemRepo.insert(Object.assign(new PremiumItem(), { name: 'UpsertManyCTI', code: 'U1' }));
|
|
395
|
+
const conflictItem = Object.assign(new PremiumItem(), { id: item.id, name: 'UpsertManyCTI', code: 'U1' });
|
|
396
|
+
const results = await premiumItemRepo.upsertMany(['id'], [conflictItem], { name: 'UpsertManyCTI Updated', code: 'U2' }, { target: { name: 'UpsertManyCTI' } });
|
|
397
|
+
expect(results).toHaveLength(1);
|
|
398
|
+
expect(results[0].name).toBe('UpsertManyCTI Updated');
|
|
399
|
+
expect(results[0].code).toBe('U2');
|
|
539
400
|
});
|
|
540
401
|
test('processExpirations with no expiration columns', async () => {
|
|
541
|
-
await
|
|
542
|
-
const repository = injectRepository(SimpleItem);
|
|
543
|
-
await repository.processExpirations(); // Should just return
|
|
544
|
-
});
|
|
402
|
+
await simpleItemRepo.processExpirations(); // Should just return
|
|
545
403
|
});
|
|
546
404
|
});
|