@declaro/data 2.0.0-beta.95 → 2.0.0-beta.97
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/dist/browser/index.js +2 -2
- package/dist/browser/index.js.map +3 -3
- package/dist/node/index.cjs +13 -7
- package/dist/node/index.cjs.map +3 -3
- package/dist/node/index.js +13 -7
- package/dist/node/index.js.map +3 -3
- package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts +8 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/application/model-controller.test.ts +15 -0
- package/src/application/read-only-model-controller.test.ts +8 -0
- package/src/test/mock/repositories/mock-memory-repository.test.ts +204 -0
- package/src/test/mock/repositories/mock-memory-repository.ts +24 -9
|
@@ -7,6 +7,7 @@ export interface IMockMemoryRepositoryArgs<TSchema extends AnyModelSchema> {
|
|
|
7
7
|
schema: TSchema;
|
|
8
8
|
lookup?: (data: InferDetail<TSchema>, lookup: InferLookup<TSchema>) => boolean;
|
|
9
9
|
filter?: (data: InferSummary<TSchema>, filters: InferFilters<TSchema>) => boolean;
|
|
10
|
+
assign?: (data: InferDetail<TSchema>, input: InferInput<TSchema>) => InferDetail<TSchema>;
|
|
10
11
|
}
|
|
11
12
|
export declare class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRepository<TSchema> {
|
|
12
13
|
protected args: IMockMemoryRepositoryArgs<TSchema>;
|
|
@@ -31,6 +32,13 @@ export declare class MockMemoryRepository<TSchema extends AnyModelSchema> implem
|
|
|
31
32
|
* @returns Filtered array of items
|
|
32
33
|
*/
|
|
33
34
|
protected applyFilters(input: InferFilters<TSchema>): InferDetail<TSchema>[];
|
|
35
|
+
/**
|
|
36
|
+
* Assign input data to existing data using the provided assign function or default Object.assign
|
|
37
|
+
* @param existingData - The existing data to merge with
|
|
38
|
+
* @param input - The input data to assign
|
|
39
|
+
* @returns The merged data
|
|
40
|
+
*/
|
|
41
|
+
protected assignInput(existingData: InferDetail<TSchema>, input: InferInput<TSchema>): InferDetail<TSchema>;
|
|
34
42
|
protected generatePrimaryKey(): Promise<string | number>;
|
|
35
43
|
}
|
|
36
44
|
//# sourceMappingURL=mock-memory-repository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-memory-repository.d.ts","sourceRoot":"","sources":["../../../../../src/test/mock/repositories/mock-memory-repository.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAqB,MAAM,eAAe,CAAA;AAC5F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAA;AAExE,OAAO,KAAK,EACR,WAAW,EACX,YAAY,EACZ,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,YAAY,EACf,MAAM,wCAAwC,CAAA;AAE/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kDAAkD,CAAA;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAA;AAE5F,MAAM,WAAW,yBAAyB,CAAC,OAAO,SAAS,cAAc;IACrE,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,OAAO,CAAA;IAC9E,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"mock-memory-repository.d.ts","sourceRoot":"","sources":["../../../../../src/test/mock/repositories/mock-memory-repository.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@declaro/data",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.97",
|
|
4
4
|
"description": "A data-mapper framework for managing application data across integrated systems.",
|
|
5
5
|
"main": "dist/node/index.cjs",
|
|
6
6
|
"module": "dist/node/index.js",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"@declaro/zod": "^2.0.0-beta.51"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@declaro/auth": "^2.0.0-beta.
|
|
26
|
-
"@declaro/core": "^2.0.0-beta.
|
|
27
|
-
"@declaro/zod": "^2.0.0-beta.
|
|
25
|
+
"@declaro/auth": "^2.0.0-beta.97",
|
|
26
|
+
"@declaro/core": "^2.0.0-beta.97",
|
|
27
|
+
"@declaro/zod": "^2.0.0-beta.97",
|
|
28
28
|
"crypto-browserify": "^3.12.1",
|
|
29
29
|
"typescript": "^5.8.3",
|
|
30
30
|
"uuid": "^11.1.0",
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"require": "./dist/node/index.cjs",
|
|
44
44
|
"browser": "./dist/browser/index.js"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "f5735a57bfd6cc342c60e9974395c389f43c3f93"
|
|
47
47
|
}
|
|
@@ -23,18 +23,21 @@ describe('ModelController', () => {
|
|
|
23
23
|
getMockAuthSession({
|
|
24
24
|
claims: ['books::book.write:all'],
|
|
25
25
|
}),
|
|
26
|
+
null,
|
|
26
27
|
authService,
|
|
27
28
|
)
|
|
28
29
|
invalidAuthValidator = new AuthValidator(
|
|
29
30
|
getMockAuthSession({
|
|
30
31
|
claims: ['authors::author.write:all'],
|
|
31
32
|
}),
|
|
33
|
+
null,
|
|
32
34
|
authService,
|
|
33
35
|
)
|
|
34
36
|
readAuthValidator = new AuthValidator(
|
|
35
37
|
getMockAuthSession({
|
|
36
38
|
claims: ['books::book.read:all'],
|
|
37
39
|
}),
|
|
40
|
+
null,
|
|
38
41
|
authService,
|
|
39
42
|
)
|
|
40
43
|
service = new ModelService({
|
|
@@ -274,6 +277,7 @@ describe('ModelController', () => {
|
|
|
274
277
|
getMockAuthSession({
|
|
275
278
|
claims: ['books::book.create:all', 'books::book.update:all'],
|
|
276
279
|
}),
|
|
280
|
+
null,
|
|
277
281
|
authService,
|
|
278
282
|
)
|
|
279
283
|
const controller = new ModelController(service, createUpdateValidator)
|
|
@@ -289,6 +293,7 @@ describe('ModelController', () => {
|
|
|
289
293
|
getMockAuthSession({
|
|
290
294
|
claims: ['books::book.create:all', 'books::book.update:all'],
|
|
291
295
|
}),
|
|
296
|
+
null,
|
|
292
297
|
authService,
|
|
293
298
|
)
|
|
294
299
|
const controller = new ModelController(service, createUpdateValidator)
|
|
@@ -308,6 +313,7 @@ describe('ModelController', () => {
|
|
|
308
313
|
getMockAuthSession({
|
|
309
314
|
claims: ['books::book.create:all'],
|
|
310
315
|
}),
|
|
316
|
+
null,
|
|
311
317
|
authService,
|
|
312
318
|
)
|
|
313
319
|
const controller = new ModelController(service, createOnlyValidator)
|
|
@@ -322,6 +328,7 @@ describe('ModelController', () => {
|
|
|
322
328
|
getMockAuthSession({
|
|
323
329
|
claims: ['books::book.update:all'],
|
|
324
330
|
}),
|
|
331
|
+
null,
|
|
325
332
|
authService,
|
|
326
333
|
)
|
|
327
334
|
const controller = new ModelController(service, updateOnlyValidator)
|
|
@@ -336,6 +343,7 @@ describe('ModelController', () => {
|
|
|
336
343
|
getMockAuthSession({
|
|
337
344
|
claims: ['books::book.create:all'],
|
|
338
345
|
}),
|
|
346
|
+
null,
|
|
339
347
|
authService,
|
|
340
348
|
)
|
|
341
349
|
const controller = new ModelController(service, createOnlyValidator)
|
|
@@ -353,6 +361,7 @@ describe('ModelController', () => {
|
|
|
353
361
|
getMockAuthSession({
|
|
354
362
|
claims: ['books::book.update:all'],
|
|
355
363
|
}),
|
|
364
|
+
null,
|
|
356
365
|
authService,
|
|
357
366
|
)
|
|
358
367
|
const controller = new ModelController(service, updateOnlyValidator)
|
|
@@ -390,6 +399,7 @@ describe('ModelController', () => {
|
|
|
390
399
|
getMockAuthSession({
|
|
391
400
|
claims: ['books::book.create:all'],
|
|
392
401
|
}),
|
|
402
|
+
null,
|
|
393
403
|
authService,
|
|
394
404
|
)
|
|
395
405
|
const controller = new ModelController(service, createOnlyValidator)
|
|
@@ -405,6 +415,7 @@ describe('ModelController', () => {
|
|
|
405
415
|
getMockAuthSession({
|
|
406
416
|
claims: ['books::book.update:all'],
|
|
407
417
|
}),
|
|
418
|
+
null,
|
|
408
419
|
authService,
|
|
409
420
|
)
|
|
410
421
|
const controller = new ModelController(service, updateOnlyValidator)
|
|
@@ -423,6 +434,7 @@ describe('ModelController', () => {
|
|
|
423
434
|
getMockAuthSession({
|
|
424
435
|
claims: ['books::book.remove:all'],
|
|
425
436
|
}),
|
|
437
|
+
null,
|
|
426
438
|
authService,
|
|
427
439
|
)
|
|
428
440
|
const controller = new ModelController(service, removeOnlyValidator)
|
|
@@ -440,6 +452,7 @@ describe('ModelController', () => {
|
|
|
440
452
|
getMockAuthSession({
|
|
441
453
|
claims: ['books::book.restore:all'],
|
|
442
454
|
}),
|
|
455
|
+
null,
|
|
443
456
|
authService,
|
|
444
457
|
)
|
|
445
458
|
const controller = new ModelController(service, restoreOnlyValidator)
|
|
@@ -458,6 +471,7 @@ describe('ModelController', () => {
|
|
|
458
471
|
getMockAuthSession({
|
|
459
472
|
claims: ['users::user.write:all'], // Wrong namespace
|
|
460
473
|
}),
|
|
474
|
+
null,
|
|
461
475
|
authService,
|
|
462
476
|
)
|
|
463
477
|
const controller = new ModelController(service, wrongNamespaceValidator)
|
|
@@ -474,6 +488,7 @@ describe('ModelController', () => {
|
|
|
474
488
|
getMockAuthSession({
|
|
475
489
|
claims: ['books::author.write:all'], // Wrong resource
|
|
476
490
|
}),
|
|
491
|
+
null,
|
|
477
492
|
authService,
|
|
478
493
|
)
|
|
479
494
|
const controller = new ModelController(service, wrongResourceValidator)
|
|
@@ -22,12 +22,14 @@ describe('ReadOnlyModelController', () => {
|
|
|
22
22
|
getMockAuthSession({
|
|
23
23
|
claims: ['books::book.read:all'],
|
|
24
24
|
}),
|
|
25
|
+
null,
|
|
25
26
|
authService,
|
|
26
27
|
)
|
|
27
28
|
invalidAuthValidator = new AuthValidator(
|
|
28
29
|
getMockAuthSession({
|
|
29
30
|
claims: ['authors::author.read:all'],
|
|
30
31
|
}),
|
|
32
|
+
null,
|
|
31
33
|
authService,
|
|
32
34
|
)
|
|
33
35
|
service = new ReadOnlyModelService({
|
|
@@ -203,6 +205,7 @@ describe('ReadOnlyModelController', () => {
|
|
|
203
205
|
getMockAuthSession({
|
|
204
206
|
claims: ['books::book.load:all'],
|
|
205
207
|
}),
|
|
208
|
+
null,
|
|
206
209
|
authService,
|
|
207
210
|
)
|
|
208
211
|
const controller = new ReadOnlyModelController(service, loadOnlyValidator)
|
|
@@ -220,6 +223,7 @@ describe('ReadOnlyModelController', () => {
|
|
|
220
223
|
getMockAuthSession({
|
|
221
224
|
claims: ['books::book.loadMany:all'],
|
|
222
225
|
}),
|
|
226
|
+
null,
|
|
223
227
|
authService,
|
|
224
228
|
)
|
|
225
229
|
const controller = new ReadOnlyModelController(service, loadManyOnlyValidator)
|
|
@@ -239,6 +243,7 @@ describe('ReadOnlyModelController', () => {
|
|
|
239
243
|
getMockAuthSession({
|
|
240
244
|
claims: ['books::book.search:all'],
|
|
241
245
|
}),
|
|
246
|
+
null,
|
|
242
247
|
authService,
|
|
243
248
|
)
|
|
244
249
|
const controller = new ReadOnlyModelController(service, searchOnlyValidator)
|
|
@@ -259,6 +264,7 @@ describe('ReadOnlyModelController', () => {
|
|
|
259
264
|
getMockAuthSession({
|
|
260
265
|
claims: ['books::book.count:all'],
|
|
261
266
|
}),
|
|
267
|
+
null,
|
|
262
268
|
authService,
|
|
263
269
|
)
|
|
264
270
|
const controller = new ReadOnlyModelController(service, countOnlyValidator)
|
|
@@ -278,6 +284,7 @@ describe('ReadOnlyModelController', () => {
|
|
|
278
284
|
getMockAuthSession({
|
|
279
285
|
claims: ['users::user.read:all'], // Wrong namespace
|
|
280
286
|
}),
|
|
287
|
+
null,
|
|
281
288
|
authService,
|
|
282
289
|
)
|
|
283
290
|
const controller = new ReadOnlyModelController(service, wrongNamespaceValidator)
|
|
@@ -293,6 +300,7 @@ describe('ReadOnlyModelController', () => {
|
|
|
293
300
|
getMockAuthSession({
|
|
294
301
|
claims: ['books::author.read:all'], // Wrong resource
|
|
295
302
|
}),
|
|
303
|
+
null,
|
|
296
304
|
authService,
|
|
297
305
|
)
|
|
298
306
|
const controller = new ReadOnlyModelController(service, wrongResourceValidator)
|
|
@@ -712,4 +712,208 @@ describe('MockMemoryRepository', () => {
|
|
|
712
712
|
expect(await repository.load({ id: 100 })).toEqual(inputs[99])
|
|
713
713
|
})
|
|
714
714
|
})
|
|
715
|
+
|
|
716
|
+
describe('assign functionality', () => {
|
|
717
|
+
it('should use default Object.assign for create when no custom assign function is provided', async () => {
|
|
718
|
+
const input = { title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
719
|
+
const createdItem = await repository.create(input)
|
|
720
|
+
|
|
721
|
+
expect(createdItem).toMatchObject(input)
|
|
722
|
+
expect(createdItem.id).toBeDefined()
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
it('should use default Object.assign for update when no custom assign function is provided', async () => {
|
|
726
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
727
|
+
const createdItem = await repository.create(input)
|
|
728
|
+
|
|
729
|
+
const updateInput = { title: 'Updated Book', author: 'Updated Author', publishedDate: new Date() }
|
|
730
|
+
const updatedItem = await repository.update({ id: createdItem.id }, updateInput)
|
|
731
|
+
|
|
732
|
+
expect(updatedItem).toEqual({
|
|
733
|
+
id: createdItem.id,
|
|
734
|
+
title: 'Updated Book',
|
|
735
|
+
author: 'Updated Author',
|
|
736
|
+
publishedDate: updateInput.publishedDate,
|
|
737
|
+
})
|
|
738
|
+
})
|
|
739
|
+
|
|
740
|
+
it('should use default Object.assign for upsert when no custom assign function is provided', async () => {
|
|
741
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
742
|
+
const upsertedItem = await repository.upsert(input)
|
|
743
|
+
|
|
744
|
+
expect(upsertedItem).toEqual(input)
|
|
745
|
+
|
|
746
|
+
const updateInput = { id: 42, title: 'Updated Book', author: 'Updated Author', publishedDate: new Date() }
|
|
747
|
+
const updatedItem = await repository.upsert(updateInput)
|
|
748
|
+
|
|
749
|
+
expect(updatedItem).toEqual(updateInput)
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
it('should use custom assign function for create operation', async () => {
|
|
753
|
+
const customAssignMock = mock((existing: any, input: any) => ({
|
|
754
|
+
...existing,
|
|
755
|
+
...input,
|
|
756
|
+
customField: 'custom_create_value',
|
|
757
|
+
}))
|
|
758
|
+
|
|
759
|
+
const customRepository = new MockMemoryRepository({
|
|
760
|
+
schema: mockSchema,
|
|
761
|
+
assign: customAssignMock,
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
const input = { title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
765
|
+
const createdItem = await customRepository.create(input)
|
|
766
|
+
|
|
767
|
+
expect(customAssignMock).toHaveBeenCalledWith({}, input)
|
|
768
|
+
expect((createdItem as any).customField).toBe('custom_create_value')
|
|
769
|
+
expect(createdItem).toMatchObject(input)
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
it('should use custom assign function for update operation', async () => {
|
|
773
|
+
const customAssignMock = mock((existing: any, input: any) => ({
|
|
774
|
+
...existing,
|
|
775
|
+
...input,
|
|
776
|
+
customField: 'custom_update_value',
|
|
777
|
+
lastModified: new Date('2023-01-01'),
|
|
778
|
+
}))
|
|
779
|
+
|
|
780
|
+
const customRepository = new MockMemoryRepository({
|
|
781
|
+
schema: mockSchema,
|
|
782
|
+
assign: customAssignMock,
|
|
783
|
+
})
|
|
784
|
+
|
|
785
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
786
|
+
const createdItem = await customRepository.create(input)
|
|
787
|
+
|
|
788
|
+
const updateInput = { title: 'Updated Book', author: 'Updated Author', publishedDate: new Date() }
|
|
789
|
+
const updatedItem = await customRepository.update({ id: createdItem.id }, updateInput)
|
|
790
|
+
|
|
791
|
+
expect(customAssignMock).toHaveBeenCalledWith(createdItem, updateInput)
|
|
792
|
+
expect((updatedItem as any).customField).toBe('custom_update_value')
|
|
793
|
+
expect((updatedItem as any).lastModified).toEqual(new Date('2023-01-01'))
|
|
794
|
+
expect(updatedItem.title).toBe('Updated Book')
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
it('should use custom assign function for upsert operation on existing item', async () => {
|
|
798
|
+
const customAssignMock = mock((existing: any, input: any) => ({
|
|
799
|
+
...existing,
|
|
800
|
+
...input,
|
|
801
|
+
mergeTimestamp: new Date('2023-01-01'),
|
|
802
|
+
isUpserted: true,
|
|
803
|
+
}))
|
|
804
|
+
|
|
805
|
+
const customRepository = new MockMemoryRepository({
|
|
806
|
+
schema: mockSchema,
|
|
807
|
+
assign: customAssignMock,
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
811
|
+
const createdItem = await customRepository.create(input)
|
|
812
|
+
|
|
813
|
+
const upsertInput = { id: 42, title: 'Upserted Book', author: 'Upserted Author', publishedDate: new Date() }
|
|
814
|
+
const upsertedItem = await customRepository.upsert(upsertInput)
|
|
815
|
+
|
|
816
|
+
// Should have been called twice: once for create, once for upsert
|
|
817
|
+
expect(customAssignMock).toHaveBeenCalledTimes(2)
|
|
818
|
+
expect(customAssignMock).toHaveBeenLastCalledWith(createdItem, upsertInput)
|
|
819
|
+
expect((upsertedItem as any).mergeTimestamp).toEqual(new Date('2023-01-01'))
|
|
820
|
+
expect((upsertedItem as any).isUpserted).toBe(true)
|
|
821
|
+
expect(upsertedItem.title).toBe('Upserted Book')
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
it('should use custom assign function for upsert operation on new item', async () => {
|
|
825
|
+
const customAssignMock = mock((existing: any, input: any) => ({
|
|
826
|
+
...existing,
|
|
827
|
+
...input,
|
|
828
|
+
createdViaUpsert: true,
|
|
829
|
+
}))
|
|
830
|
+
|
|
831
|
+
const customRepository = new MockMemoryRepository({
|
|
832
|
+
schema: mockSchema,
|
|
833
|
+
assign: customAssignMock,
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
837
|
+
const upsertedItem = await customRepository.upsert(input)
|
|
838
|
+
|
|
839
|
+
expect(customAssignMock).toHaveBeenCalledWith({}, input)
|
|
840
|
+
expect((upsertedItem as any).createdViaUpsert).toBe(true)
|
|
841
|
+
expect(upsertedItem).toMatchObject(input)
|
|
842
|
+
})
|
|
843
|
+
|
|
844
|
+
it('should use custom assign function for bulkUpsert operation', async () => {
|
|
845
|
+
const customAssignMock = mock((existing: any, input: any) => ({
|
|
846
|
+
...existing,
|
|
847
|
+
...input,
|
|
848
|
+
bulkProcessed: true,
|
|
849
|
+
}))
|
|
850
|
+
|
|
851
|
+
const customRepository = new MockMemoryRepository({
|
|
852
|
+
schema: mockSchema,
|
|
853
|
+
assign: customAssignMock,
|
|
854
|
+
})
|
|
855
|
+
|
|
856
|
+
const inputs = [
|
|
857
|
+
{ id: 1, title: 'Book 1', author: 'Author 1', publishedDate: new Date() },
|
|
858
|
+
{ id: 2, title: 'Book 2', author: 'Author 2', publishedDate: new Date() },
|
|
859
|
+
]
|
|
860
|
+
|
|
861
|
+
const results = await customRepository.bulkUpsert(inputs)
|
|
862
|
+
|
|
863
|
+
expect(customAssignMock).toHaveBeenCalledTimes(2)
|
|
864
|
+
expect((results[0] as any).bulkProcessed).toBe(true)
|
|
865
|
+
expect((results[1] as any).bulkProcessed).toBe(true)
|
|
866
|
+
expect(results[0]).toMatchObject(inputs[0])
|
|
867
|
+
expect(results[1]).toMatchObject(inputs[1])
|
|
868
|
+
})
|
|
869
|
+
|
|
870
|
+
it('should handle complex custom assign logic with conditional merging', async () => {
|
|
871
|
+
const customAssign = (existing: any, input: any) => {
|
|
872
|
+
const result = { ...existing, ...input }
|
|
873
|
+
|
|
874
|
+
// Custom logic: preserve original author if input doesn't have one
|
|
875
|
+
if (!input.author && existing.author) {
|
|
876
|
+
result.author = existing.author
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// Custom logic: track modification count
|
|
880
|
+
result.modificationCount = (existing.modificationCount || 0) + 1
|
|
881
|
+
|
|
882
|
+
return result
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const customRepository = new MockMemoryRepository({
|
|
886
|
+
schema: mockSchema,
|
|
887
|
+
assign: customAssign,
|
|
888
|
+
})
|
|
889
|
+
|
|
890
|
+
const input = { id: 42, title: 'Test Book', author: 'Original Author', publishedDate: new Date() }
|
|
891
|
+
const createdItem = await customRepository.create(input)
|
|
892
|
+
|
|
893
|
+
expect((createdItem as any).modificationCount).toBe(1)
|
|
894
|
+
|
|
895
|
+
// Update with partial data - use existing values for required fields
|
|
896
|
+
const updateInput = {
|
|
897
|
+
title: 'Updated Book',
|
|
898
|
+
author: createdItem.author,
|
|
899
|
+
publishedDate: createdItem.publishedDate,
|
|
900
|
+
}
|
|
901
|
+
const updatedItem = await customRepository.update({ id: createdItem.id }, updateInput)
|
|
902
|
+
|
|
903
|
+
expect(updatedItem.author).toBe('Original Author')
|
|
904
|
+
expect(updatedItem.title).toBe('Updated Book')
|
|
905
|
+
expect((updatedItem as any).modificationCount).toBe(2)
|
|
906
|
+
|
|
907
|
+
// Update with new author
|
|
908
|
+
const updateWithAuthor = {
|
|
909
|
+
title: updatedItem.title,
|
|
910
|
+
author: 'New Author',
|
|
911
|
+
publishedDate: updatedItem.publishedDate,
|
|
912
|
+
}
|
|
913
|
+
const finalItem = await customRepository.update({ id: createdItem.id }, updateWithAuthor)
|
|
914
|
+
|
|
915
|
+
expect(finalItem.author).toBe('New Author')
|
|
916
|
+
expect((finalItem as any).modificationCount).toBe(3)
|
|
917
|
+
})
|
|
918
|
+
})
|
|
715
919
|
})
|
|
@@ -17,6 +17,7 @@ export interface IMockMemoryRepositoryArgs<TSchema extends AnyModelSchema> {
|
|
|
17
17
|
schema: TSchema
|
|
18
18
|
lookup?: (data: InferDetail<TSchema>, lookup: InferLookup<TSchema>) => boolean
|
|
19
19
|
filter?: (data: InferSummary<TSchema>, filters: InferFilters<TSchema>) => boolean
|
|
20
|
+
assign?: (data: InferDetail<TSchema>, input: InferInput<TSchema>) => InferDetail<TSchema>
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRepository<TSchema> {
|
|
@@ -140,17 +141,16 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
|
|
|
140
141
|
throw new Error('Item with the same primary key already exists')
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
}
|
|
144
|
+
const baseData = {} as InferDetail<TSchema>
|
|
145
|
+
const payload = this.assignInput(baseData, input)
|
|
146
146
|
|
|
147
147
|
if (!payload[this.entityMetadata.primaryKey]) {
|
|
148
148
|
// Generate a new primary key if not provided
|
|
149
149
|
payload[this.entityMetadata.primaryKey!] = await this.generatePrimaryKey()
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
this.data.set(payload[this.entityMetadata.primaryKey!], payload
|
|
153
|
-
return payload
|
|
152
|
+
this.data.set(payload[this.entityMetadata.primaryKey!], payload)
|
|
153
|
+
return payload
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
async update(lookup: InferLookup<TSchema>, input: InferInput<TSchema>): Promise<InferDetail<TSchema>> {
|
|
@@ -167,7 +167,7 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
|
|
|
167
167
|
throw new Error('Item not found')
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
const updatedItem =
|
|
170
|
+
const updatedItem = this.assignInput(existingItem, input)
|
|
171
171
|
this.data.set(primaryKeyValue, updatedItem)
|
|
172
172
|
return updatedItem
|
|
173
173
|
}
|
|
@@ -179,13 +179,13 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
|
|
|
179
179
|
|
|
180
180
|
async upsert(input: InferInput<TSchema>, options?: ICreateOptions | IUpdateOptions): Promise<InferDetail<TSchema>> {
|
|
181
181
|
const primaryKeyValue = input[this.entityMetadata.primaryKey]
|
|
182
|
-
let existingItem: InferDetail<TSchema>
|
|
182
|
+
let existingItem: InferDetail<TSchema> = {} as InferDetail<TSchema>
|
|
183
183
|
|
|
184
184
|
if (primaryKeyValue) {
|
|
185
|
-
existingItem = this.data.get(primaryKeyValue) ?? {}
|
|
185
|
+
existingItem = this.data.get(primaryKeyValue) ?? ({} as InferDetail<TSchema>)
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
const updatedItem =
|
|
188
|
+
const updatedItem = this.assignInput(existingItem, input)
|
|
189
189
|
if (!updatedItem[this.entityMetadata.primaryKey!]) {
|
|
190
190
|
updatedItem[this.entityMetadata.primaryKey!] = await this.generatePrimaryKey()
|
|
191
191
|
}
|
|
@@ -218,6 +218,21 @@ export class MockMemoryRepository<TSchema extends AnyModelSchema> implements IRe
|
|
|
218
218
|
})
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Assign input data to existing data using the provided assign function or default Object.assign
|
|
223
|
+
* @param existingData - The existing data to merge with
|
|
224
|
+
* @param input - The input data to assign
|
|
225
|
+
* @returns The merged data
|
|
226
|
+
*/
|
|
227
|
+
protected assignInput(existingData: InferDetail<TSchema>, input: InferInput<TSchema>): InferDetail<TSchema> {
|
|
228
|
+
if (typeof this.args.assign === 'function') {
|
|
229
|
+
return this.args.assign(existingData, input)
|
|
230
|
+
} else {
|
|
231
|
+
// Default implementation using Object.assign
|
|
232
|
+
return Object.assign({}, existingData, input) as InferDetail<TSchema>
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
221
236
|
protected async generatePrimaryKey() {
|
|
222
237
|
const lookupModel: Model<any, any> = this.args.schema.definition.lookup
|
|
223
238
|
const lookupMeta = await lookupModel.toJSONSchema()
|