@declaro/data 2.0.0-beta.14 → 2.0.0-beta.140
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/{LICENSE → LICENSE.md} +1 -1
- package/README.md +0 -0
- package/dist/browser/index.js +26 -0
- package/dist/browser/index.js.map +93 -0
- package/dist/node/index.cjs +13372 -0
- package/dist/node/index.cjs.map +93 -0
- package/dist/node/index.js +13351 -0
- package/dist/node/index.js.map +93 -0
- package/dist/ts/application/model-controller.d.ts +60 -0
- package/dist/ts/application/model-controller.d.ts.map +1 -0
- package/dist/ts/application/model-controller.test.d.ts +2 -0
- package/dist/ts/application/model-controller.test.d.ts.map +1 -0
- package/dist/ts/application/read-only-model-controller.d.ts +24 -0
- package/dist/ts/application/read-only-model-controller.d.ts.map +1 -0
- package/dist/ts/application/read-only-model-controller.test.d.ts +2 -0
- package/dist/ts/application/read-only-model-controller.test.d.ts.map +1 -0
- package/dist/ts/domain/events/domain-event.d.ts +41 -0
- package/dist/ts/domain/events/domain-event.d.ts.map +1 -0
- package/dist/ts/domain/events/domain-event.test.d.ts +2 -0
- package/dist/ts/domain/events/domain-event.test.d.ts.map +1 -0
- package/dist/ts/domain/events/event-types.d.ts +37 -0
- package/dist/ts/domain/events/event-types.d.ts.map +1 -0
- package/dist/ts/domain/events/mutation-event.d.ts +41 -0
- package/dist/ts/domain/events/mutation-event.d.ts.map +1 -0
- package/dist/ts/domain/events/mutation-event.test.d.ts +2 -0
- package/dist/ts/domain/events/mutation-event.test.d.ts.map +1 -0
- package/dist/ts/domain/events/query-event.d.ts +8 -0
- package/dist/ts/domain/events/query-event.d.ts.map +1 -0
- package/dist/ts/domain/events/query-event.test.d.ts +2 -0
- package/dist/ts/domain/events/query-event.test.d.ts.map +1 -0
- package/dist/ts/domain/events/request-event.d.ts +26 -0
- package/dist/ts/domain/events/request-event.d.ts.map +1 -0
- package/dist/ts/domain/events/request-event.test.d.ts +2 -0
- package/dist/ts/domain/events/request-event.test.d.ts.map +1 -0
- package/dist/ts/domain/interfaces/repository.d.ts +110 -0
- package/dist/ts/domain/interfaces/repository.d.ts.map +1 -0
- package/dist/ts/domain/models/pagination.d.ts +28 -0
- package/dist/ts/domain/models/pagination.d.ts.map +1 -0
- package/dist/ts/domain/services/base-model-service.d.ts +23 -0
- package/dist/ts/domain/services/base-model-service.d.ts.map +1 -0
- package/dist/ts/domain/services/model-service-args.d.ts +9 -0
- package/dist/ts/domain/services/model-service-args.d.ts.map +1 -0
- package/dist/ts/domain/services/model-service.d.ts +99 -0
- package/dist/ts/domain/services/model-service.d.ts.map +1 -0
- package/dist/ts/domain/services/model-service.normalization.test.d.ts +2 -0
- package/dist/ts/domain/services/model-service.normalization.test.d.ts.map +1 -0
- package/dist/ts/domain/services/model-service.test.d.ts +2 -0
- package/dist/ts/domain/services/model-service.test.d.ts.map +1 -0
- package/dist/ts/domain/services/read-only-model-service.d.ts +90 -0
- package/dist/ts/domain/services/read-only-model-service.d.ts.map +1 -0
- package/dist/ts/domain/services/read-only-model-service.test.d.ts +2 -0
- package/dist/ts/domain/services/read-only-model-service.test.d.ts.map +1 -0
- package/dist/ts/index.d.ts +18 -0
- package/dist/ts/index.d.ts.map +1 -0
- package/dist/ts/shared/utils/schema-inference.d.ts +23 -0
- package/dist/ts/shared/utils/schema-inference.d.ts.map +1 -0
- package/dist/ts/shared/utils/schema-inheritance.d.ts +24 -0
- package/dist/ts/shared/utils/schema-inheritance.d.ts.map +1 -0
- package/dist/ts/shared/utils/schema-inheritance.test.d.ts +2 -0
- package/dist/ts/shared/utils/schema-inheritance.test.d.ts.map +1 -0
- package/dist/ts/shared/utils/test/animal-schema.d.ts +57 -0
- package/dist/ts/shared/utils/test/animal-schema.d.ts.map +1 -0
- package/dist/ts/shared/utils/test/animal-trait-schema.d.ts +55 -0
- package/dist/ts/shared/utils/test/animal-trait-schema.d.ts.map +1 -0
- package/dist/ts/shared/utils/test/elephant-schema.d.ts +30 -0
- package/dist/ts/shared/utils/test/elephant-schema.d.ts.map +1 -0
- package/dist/ts/shared/utils/test/elephant-trait-schema.d.ts +26 -0
- package/dist/ts/shared/utils/test/elephant-trait-schema.d.ts.map +1 -0
- package/dist/ts/test/mock/models/mock-book-models.d.ts +42 -0
- package/dist/ts/test/mock/models/mock-book-models.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.assign.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.assign.test.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.basic.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.basic.test.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.bulk-upsert.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.bulk-upsert.test.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.count.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.count.test.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts +62 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.search.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.search.test.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.trash.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.trash.test.d.ts.map +1 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.upsert.test.d.ts +2 -0
- package/dist/ts/test/mock/repositories/mock-memory-repository.upsert.test.d.ts.map +1 -0
- package/package.json +46 -42
- package/src/application/model-controller.test.ts +694 -0
- package/src/application/model-controller.ts +186 -0
- package/src/application/read-only-model-controller.test.ts +335 -0
- package/src/application/read-only-model-controller.ts +79 -0
- package/src/domain/events/domain-event.test.ts +82 -0
- package/src/domain/events/domain-event.ts +69 -0
- package/src/domain/events/event-types.ts +37 -0
- package/src/domain/events/mutation-event.test.ts +390 -0
- package/src/domain/events/mutation-event.ts +53 -0
- package/src/domain/events/query-event.test.ts +228 -0
- package/src/domain/events/query-event.ts +14 -0
- package/src/domain/events/request-event.test.ts +38 -0
- package/src/domain/events/request-event.ts +47 -0
- package/src/domain/interfaces/repository.ts +136 -0
- package/src/domain/models/pagination.ts +28 -0
- package/src/domain/services/base-model-service.ts +54 -0
- package/src/domain/services/model-service-args.ts +9 -0
- package/src/domain/services/model-service.normalization.test.ts +704 -0
- package/src/domain/services/model-service.test.ts +1616 -0
- package/src/domain/services/model-service.ts +597 -0
- package/src/domain/services/read-only-model-service.test.ts +1130 -0
- package/src/domain/services/read-only-model-service.ts +211 -0
- package/src/index.ts +17 -4
- package/src/shared/utils/schema-inference.ts +26 -0
- package/src/shared/utils/schema-inheritance.test.ts +295 -0
- package/src/shared/utils/schema-inheritance.ts +28 -0
- package/src/shared/utils/test/animal-schema.ts +46 -0
- package/src/shared/utils/test/animal-trait-schema.ts +45 -0
- package/src/shared/utils/test/elephant-schema.ts +58 -0
- package/src/shared/utils/test/elephant-trait-schema.ts +53 -0
- package/src/test/mock/models/mock-book-models.ts +78 -0
- package/src/test/mock/repositories/mock-memory-repository.assign.test.ts +215 -0
- package/src/test/mock/repositories/mock-memory-repository.basic.test.ts +129 -0
- package/src/test/mock/repositories/mock-memory-repository.bulk-upsert.test.ts +159 -0
- package/src/test/mock/repositories/mock-memory-repository.count.test.ts +98 -0
- package/src/test/mock/repositories/mock-memory-repository.search.test.ts +265 -0
- package/src/test/mock/repositories/mock-memory-repository.trash.test.ts +736 -0
- package/src/test/mock/repositories/mock-memory-repository.ts +401 -0
- package/src/test/mock/repositories/mock-memory-repository.upsert.test.ts +108 -0
- package/dist/databaseConnection.d.ts +0 -24
- package/dist/datastoreAbstract.d.ts +0 -37
- package/dist/declaro-data.cjs +0 -1
- package/dist/declaro-data.mjs +0 -250
- package/dist/hydrateEntity.d.ts +0 -8
- package/dist/index.d.ts +0 -4
- package/dist/serverConnection.d.ts +0 -15
- package/dist/trackedStatus.d.ts +0 -15
- package/src/databaseConnection.ts +0 -137
- package/src/datastoreAbstract.ts +0 -190
- package/src/hydrateEntity.ts +0 -36
- package/src/placeholder.test.ts +0 -7
- package/src/serverConnection.ts +0 -74
- package/src/trackedStatus.ts +0 -35
- package/tsconfig.json +0 -10
- package/vite.config.ts +0 -23
|
@@ -0,0 +1,1130 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, spyOn, mock, beforeAll } from 'bun:test'
|
|
2
|
+
import { ReadOnlyModelService, type ILoadOptions } from './read-only-model-service'
|
|
3
|
+
import { MockMemoryRepository } from '../../test/mock/repositories/mock-memory-repository'
|
|
4
|
+
import { MockBookSchema, type MockBookDetail, type MockBookLookup } from '../../test/mock/models/mock-book-models'
|
|
5
|
+
import { EventManager } from '@declaro/core'
|
|
6
|
+
import type { QueryEvent } from '../events/query-event'
|
|
7
|
+
import type {
|
|
8
|
+
InferDetail,
|
|
9
|
+
InferFilters,
|
|
10
|
+
InferLookup,
|
|
11
|
+
InferSearchResults,
|
|
12
|
+
InferSummary,
|
|
13
|
+
} from '../../shared/utils/schema-inference'
|
|
14
|
+
|
|
15
|
+
describe('ReadOnlyModelService', () => {
|
|
16
|
+
const namespace = 'books'
|
|
17
|
+
const mockSchema = MockBookSchema
|
|
18
|
+
|
|
19
|
+
let repository: MockMemoryRepository<typeof mockSchema>
|
|
20
|
+
let emitter: EventManager
|
|
21
|
+
let service: ReadOnlyModelService<typeof mockSchema>
|
|
22
|
+
|
|
23
|
+
const beforeLoadSpy = mock(
|
|
24
|
+
(event: QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>) => {},
|
|
25
|
+
)
|
|
26
|
+
const afterLoadSpy = mock((event: QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>) => {})
|
|
27
|
+
|
|
28
|
+
const beforeLoadManySpy = mock(
|
|
29
|
+
(event: QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>) => {},
|
|
30
|
+
)
|
|
31
|
+
const afterLoadManySpy = mock(
|
|
32
|
+
(event: QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>) => {},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const beforeSearchSpy = mock(
|
|
36
|
+
(event: QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>) => {},
|
|
37
|
+
)
|
|
38
|
+
const afterSearchSpy = mock(
|
|
39
|
+
(event: QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>) => {},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
repository = new MockMemoryRepository({ schema: mockSchema })
|
|
44
|
+
emitter = new EventManager()
|
|
45
|
+
|
|
46
|
+
beforeLoadSpy.mockClear()
|
|
47
|
+
afterLoadSpy.mockClear()
|
|
48
|
+
beforeLoadManySpy.mockClear()
|
|
49
|
+
afterLoadManySpy.mockClear()
|
|
50
|
+
beforeSearchSpy.mockClear()
|
|
51
|
+
afterSearchSpy.mockClear()
|
|
52
|
+
|
|
53
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>>(
|
|
54
|
+
'books::book.beforeLoad',
|
|
55
|
+
beforeLoadSpy,
|
|
56
|
+
)
|
|
57
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>>(
|
|
58
|
+
'books::book.afterLoad',
|
|
59
|
+
afterLoadSpy,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>>(
|
|
63
|
+
'books::book.beforeLoadMany',
|
|
64
|
+
beforeLoadManySpy,
|
|
65
|
+
)
|
|
66
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>>(
|
|
67
|
+
'books::book.afterLoadMany',
|
|
68
|
+
afterLoadManySpy,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
emitter.on<QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>>(
|
|
72
|
+
'books::book.beforeSearch',
|
|
73
|
+
beforeSearchSpy,
|
|
74
|
+
)
|
|
75
|
+
emitter.on<QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>>(
|
|
76
|
+
'books::book.afterSearch',
|
|
77
|
+
afterSearchSpy,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
service = new ReadOnlyModelService({ repository, emitter, schema: mockSchema, namespace })
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should load a single record', async () => {
|
|
84
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
85
|
+
await repository.create(input)
|
|
86
|
+
|
|
87
|
+
const record = await service.load({ id: 42 })
|
|
88
|
+
|
|
89
|
+
expect(record).toEqual(input)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should return null when loading a non-existent record', async () => {
|
|
93
|
+
const record = await service.load({ id: 999 })
|
|
94
|
+
|
|
95
|
+
expect(record).toBeNull()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should load multiple records', async () => {
|
|
99
|
+
const input1 = { id: 42, title: 'Test Book 1', author: 'Author Name 1', publishedDate: new Date() }
|
|
100
|
+
const input2 = { id: 43, title: 'Test Book 2', author: 'Author Name 2', publishedDate: new Date() }
|
|
101
|
+
await repository.create(input1)
|
|
102
|
+
await repository.create(input2)
|
|
103
|
+
|
|
104
|
+
const records = await service.loadMany([{ id: 42 }, { id: 43 }])
|
|
105
|
+
|
|
106
|
+
expect(records).toEqual([input1, input2])
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should search for records', async () => {
|
|
110
|
+
const input1 = { id: 42, title: 'Test Book 1', author: 'Author Name 1', publishedDate: new Date() }
|
|
111
|
+
const input2 = { id: 43, title: 'Test Book 2', author: 'Author Name 2', publishedDate: new Date() }
|
|
112
|
+
await repository.create(input1)
|
|
113
|
+
await repository.create(input2)
|
|
114
|
+
|
|
115
|
+
const results = await service.search(
|
|
116
|
+
{ text: 'Test' },
|
|
117
|
+
{
|
|
118
|
+
sort: [
|
|
119
|
+
{
|
|
120
|
+
title: 'asc',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
author: 'desc',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
expect(results.results).toEqual([input1, input2])
|
|
130
|
+
expect(results.pagination.total).toBe(2)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('should return empty results when searching for non-existent records', async () => {
|
|
134
|
+
const results = await service.search({ text: 'Non-existent' })
|
|
135
|
+
|
|
136
|
+
expect(results.results).toEqual([])
|
|
137
|
+
expect(results.pagination.total).toBe(0)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should handle pagination options correctly', async () => {
|
|
141
|
+
// Create 5 items
|
|
142
|
+
for (let i = 1; i <= 5; i++) {
|
|
143
|
+
await repository.create({
|
|
144
|
+
id: i,
|
|
145
|
+
title: `Test Book ${i}`,
|
|
146
|
+
author: `Author ${i}`,
|
|
147
|
+
publishedDate: new Date(),
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Test first page with pageSize 2
|
|
152
|
+
const page1 = await service.search(
|
|
153
|
+
{},
|
|
154
|
+
{
|
|
155
|
+
pagination: { page: 1, pageSize: 2 },
|
|
156
|
+
},
|
|
157
|
+
)
|
|
158
|
+
expect(page1.results).toHaveLength(2)
|
|
159
|
+
expect(page1.pagination.page).toBe(1)
|
|
160
|
+
expect(page1.pagination.pageSize).toBe(2)
|
|
161
|
+
expect(page1.pagination.total).toBe(5)
|
|
162
|
+
expect(page1.pagination.totalPages).toBe(3)
|
|
163
|
+
|
|
164
|
+
// Test second page
|
|
165
|
+
const page2 = await service.search(
|
|
166
|
+
{},
|
|
167
|
+
{
|
|
168
|
+
pagination: { page: 2, pageSize: 2 },
|
|
169
|
+
},
|
|
170
|
+
)
|
|
171
|
+
expect(page2.results).toHaveLength(2)
|
|
172
|
+
expect(page2.pagination.page).toBe(2)
|
|
173
|
+
|
|
174
|
+
// Test last page
|
|
175
|
+
const page3 = await service.search(
|
|
176
|
+
{},
|
|
177
|
+
{
|
|
178
|
+
pagination: { page: 3, pageSize: 2 },
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
expect(page3.results).toHaveLength(1)
|
|
182
|
+
expect(page3.pagination.page).toBe(3)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('should handle sort options correctly', async () => {
|
|
186
|
+
const input1 = { id: 1, title: 'Z Book', author: 'Author A', publishedDate: new Date('2023-01-01') }
|
|
187
|
+
const input2 = { id: 2, title: 'A Book', author: 'Author B', publishedDate: new Date('2023-02-01') }
|
|
188
|
+
const input3 = { id: 3, title: 'M Book', author: 'Author C', publishedDate: new Date('2023-03-01') }
|
|
189
|
+
|
|
190
|
+
await repository.create(input1)
|
|
191
|
+
await repository.create(input2)
|
|
192
|
+
await repository.create(input3)
|
|
193
|
+
|
|
194
|
+
// Sort by title ascending
|
|
195
|
+
const titleAscResults = await service.search(
|
|
196
|
+
{},
|
|
197
|
+
{
|
|
198
|
+
sort: [{ title: 'asc' }],
|
|
199
|
+
},
|
|
200
|
+
)
|
|
201
|
+
expect(titleAscResults.results.map((r) => r.title)).toEqual(['A Book', 'M Book', 'Z Book'])
|
|
202
|
+
|
|
203
|
+
// Sort by title descending
|
|
204
|
+
const titleDescResults = await service.search(
|
|
205
|
+
{},
|
|
206
|
+
{
|
|
207
|
+
sort: [{ title: 'desc' }],
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
expect(titleDescResults.results.map((r) => r.title)).toEqual(['Z Book', 'M Book', 'A Book'])
|
|
211
|
+
|
|
212
|
+
// Sort by author ascending
|
|
213
|
+
const authorAscResults = await service.search(
|
|
214
|
+
{},
|
|
215
|
+
{
|
|
216
|
+
sort: [{ author: 'asc' }],
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
expect(authorAscResults.results.map((r) => r.author)).toEqual(['Author A', 'Author B', 'Author C'])
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('should handle combined filtering, sorting, and pagination', async () => {
|
|
223
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
224
|
+
schema: mockSchema,
|
|
225
|
+
filter: (data, filters) => {
|
|
226
|
+
if (filters.text) {
|
|
227
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
228
|
+
}
|
|
229
|
+
return true
|
|
230
|
+
},
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
const serviceWithFilter = new ReadOnlyModelService({
|
|
234
|
+
repository: repositoryWithFilter,
|
|
235
|
+
emitter,
|
|
236
|
+
namespace,
|
|
237
|
+
schema: mockSchema,
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
await repositoryWithFilter.create({ title: 'Test Z Book', author: 'Author 1', publishedDate: new Date() })
|
|
241
|
+
await repositoryWithFilter.create({ title: 'Test A Book', author: 'Author 2', publishedDate: new Date() })
|
|
242
|
+
await repositoryWithFilter.create({ title: 'Other Book', author: 'Author 3', publishedDate: new Date() })
|
|
243
|
+
await repositoryWithFilter.create({ title: 'Test M Book', author: 'Author 4', publishedDate: new Date() })
|
|
244
|
+
|
|
245
|
+
const results = await serviceWithFilter.search(
|
|
246
|
+
{ text: 'Test' },
|
|
247
|
+
{
|
|
248
|
+
sort: [{ title: 'asc' }],
|
|
249
|
+
pagination: { page: 1, pageSize: 2 },
|
|
250
|
+
},
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
expect(results.results).toHaveLength(2)
|
|
254
|
+
expect(results.results.map((r) => r.title)).toEqual(['Test A Book', 'Test M Book'])
|
|
255
|
+
expect(results.pagination.total).toBe(3) // 3 "Test" books total
|
|
256
|
+
expect(results.pagination.totalPages).toBe(2)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should trigger before and after events for load', async () => {
|
|
260
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
261
|
+
await repository.create(input)
|
|
262
|
+
|
|
263
|
+
const record = await service.load({ id: 42 })
|
|
264
|
+
|
|
265
|
+
expect(record).toEqual(input)
|
|
266
|
+
expect(beforeLoadSpy).toHaveBeenCalledTimes(1)
|
|
267
|
+
expect(beforeLoadSpy).toHaveBeenCalledWith(expect.objectContaining({ type: 'books::book.beforeLoad' }))
|
|
268
|
+
expect(afterLoadSpy).toHaveBeenCalledTimes(1)
|
|
269
|
+
expect(afterLoadSpy).toHaveBeenCalledWith(expect.objectContaining({ type: 'books::book.afterLoad' }))
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('should trigger before and after events for loadMany', async () => {
|
|
273
|
+
const input1 = { id: 42, title: 'Test Book 1', author: 'Author Name 1', publishedDate: new Date() }
|
|
274
|
+
const input2 = { id: 43, title: 'Test Book 2', author: 'Author Name 2', publishedDate: new Date() }
|
|
275
|
+
await repository.create(input1)
|
|
276
|
+
await repository.create(input2)
|
|
277
|
+
|
|
278
|
+
const records = await service.loadMany([{ id: 42 }, { id: 43 }])
|
|
279
|
+
|
|
280
|
+
expect(records).toEqual([input1, input2])
|
|
281
|
+
expect(beforeLoadManySpy).toHaveBeenCalledTimes(1)
|
|
282
|
+
expect(beforeLoadManySpy).toHaveBeenCalledWith(expect.objectContaining({ type: 'books::book.beforeLoadMany' }))
|
|
283
|
+
expect(afterLoadManySpy).toHaveBeenCalledTimes(1)
|
|
284
|
+
expect(afterLoadManySpy).toHaveBeenCalledWith(expect.objectContaining({ type: 'books::book.afterLoadMany' }))
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('should trigger before and after events for search', async () => {
|
|
288
|
+
const input1 = { id: 42, title: 'Test Book 1', author: 'Author Name 1', publishedDate: new Date() }
|
|
289
|
+
const input2 = { id: 43, title: 'Test Book 2', author: 'Author Name 2', publishedDate: new Date() }
|
|
290
|
+
await repository.create(input1)
|
|
291
|
+
await repository.create(input2)
|
|
292
|
+
|
|
293
|
+
const results = await service.search({ text: 'Test' })
|
|
294
|
+
|
|
295
|
+
expect(results.results).toEqual([input1, input2])
|
|
296
|
+
expect(results.pagination.total).toBe(2)
|
|
297
|
+
expect(beforeSearchSpy).toHaveBeenCalledTimes(1)
|
|
298
|
+
expect(beforeSearchSpy).toHaveBeenCalledWith(expect.objectContaining({ type: 'books::book.beforeSearch' }))
|
|
299
|
+
expect(afterSearchSpy).toHaveBeenCalledTimes(1)
|
|
300
|
+
expect(afterSearchSpy).toHaveBeenCalledWith(expect.objectContaining({ type: 'books::book.afterSearch' }))
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
describe('Response Normalization', () => {
|
|
304
|
+
class TestRepository extends MockMemoryRepository<typeof mockSchema> {
|
|
305
|
+
constructor() {
|
|
306
|
+
super({
|
|
307
|
+
schema: mockSchema,
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async load(input: MockBookLookup): Promise<MockBookDetail | null> {
|
|
312
|
+
const record = await super.load(input)
|
|
313
|
+
|
|
314
|
+
if (record) {
|
|
315
|
+
record.publishedDate = '2024-01-01' as any
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return record
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async loadMany(inputs: MockBookLookup[]): Promise<MockBookDetail[]> {
|
|
322
|
+
const records = await super.loadMany(inputs)
|
|
323
|
+
|
|
324
|
+
for (const record of records) {
|
|
325
|
+
record.publishedDate = '2024-01-01' as any
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return records
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async search(
|
|
332
|
+
filters: InferFilters<typeof mockSchema>,
|
|
333
|
+
options?: ILoadOptions,
|
|
334
|
+
): Promise<InferSearchResults<typeof mockSchema>> {
|
|
335
|
+
const results = await super.search(filters, options)
|
|
336
|
+
|
|
337
|
+
for (const record of results.results) {
|
|
338
|
+
record.publishedDate = '2024-01-01' as any
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return results
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
class TestServiceWithNormalization extends ReadOnlyModelService<typeof mockSchema> {
|
|
346
|
+
async normalizeDetail(detail: InferDetail<typeof mockSchema>): Promise<InferDetail<typeof mockSchema>> {
|
|
347
|
+
// Handle null case (e.g., when load returns null)
|
|
348
|
+
if (!detail) return detail
|
|
349
|
+
|
|
350
|
+
// Convert string dates back to Date objects
|
|
351
|
+
if (typeof detail.publishedDate === 'string') {
|
|
352
|
+
detail.publishedDate = new Date(detail.publishedDate) as any
|
|
353
|
+
}
|
|
354
|
+
return detail
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async normalizeSummary(summary: InferSummary<typeof mockSchema>): Promise<InferSummary<typeof mockSchema>> {
|
|
358
|
+
// Handle null case (e.g., when load returns null)
|
|
359
|
+
if (!summary) return summary
|
|
360
|
+
|
|
361
|
+
// Convert string dates back to Date objects
|
|
362
|
+
if (typeof summary.publishedDate === 'string') {
|
|
363
|
+
summary.publishedDate = new Date(summary.publishedDate) as any
|
|
364
|
+
}
|
|
365
|
+
return summary
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
let testService: TestServiceWithNormalization
|
|
370
|
+
|
|
371
|
+
beforeEach(() => {
|
|
372
|
+
repository = new TestRepository()
|
|
373
|
+
emitter = new EventManager()
|
|
374
|
+
|
|
375
|
+
testService = new TestServiceWithNormalization({ repository, emitter, schema: mockSchema, namespace })
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
it('should allow custom normalization of details in the load response when overridden', async () => {
|
|
379
|
+
const input = { id: 100, title: 'Normalization Test', author: 'Normalizer', publishedDate: new Date() }
|
|
380
|
+
await repository.create(input)
|
|
381
|
+
|
|
382
|
+
const record = await testService.load({ id: 100 })
|
|
383
|
+
|
|
384
|
+
const expectedDate = new Date('2024-01-01')
|
|
385
|
+
const actualDate = record.publishedDate
|
|
386
|
+
|
|
387
|
+
expect(actualDate).toEqual(expectedDate)
|
|
388
|
+
expect(actualDate).toBeInstanceOf(Date)
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
it('should allow custom normalization of details in the loadMany response when overridden', async () => {
|
|
392
|
+
const input1 = { id: 101, title: 'Normalization Test 1', author: 'Normalizer 1', publishedDate: new Date() }
|
|
393
|
+
const input2 = { id: 102, title: 'Normalization Test 2', author: 'Normalizer 2', publishedDate: new Date() }
|
|
394
|
+
await repository.create(input1)
|
|
395
|
+
await repository.create(input2)
|
|
396
|
+
|
|
397
|
+
const records = await testService.loadMany([{ id: 101 }, { id: 102 }])
|
|
398
|
+
|
|
399
|
+
const expectedDate = new Date('2024-01-01')
|
|
400
|
+
|
|
401
|
+
for (const record of records) {
|
|
402
|
+
const actualDate = record.publishedDate
|
|
403
|
+
expect(actualDate).toEqual(expectedDate)
|
|
404
|
+
expect(actualDate).toBeInstanceOf(Date)
|
|
405
|
+
}
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
it('should allow custom normalization of summaries in the search response when overridden', async () => {
|
|
409
|
+
const input1 = { id: 103, title: 'Normalization Test 3', author: 'Normalizer 3', publishedDate: new Date() }
|
|
410
|
+
const input2 = { id: 104, title: 'Normalization Test 4', author: 'Normalizer 4', publishedDate: new Date() }
|
|
411
|
+
await repository.create(input1)
|
|
412
|
+
await repository.create(input2)
|
|
413
|
+
|
|
414
|
+
const results = await testService.search({ text: 'Normalization' })
|
|
415
|
+
|
|
416
|
+
const expectedDate = new Date('2024-01-01')
|
|
417
|
+
|
|
418
|
+
for (const record of results.results) {
|
|
419
|
+
const actualDate = record.publishedDate
|
|
420
|
+
expect(actualDate).toEqual(expectedDate)
|
|
421
|
+
expect(actualDate).toBeInstanceOf(Date)
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
it('should not normalize data by default when normalization methods are not overridden', async () => {
|
|
426
|
+
const defaultService = new ReadOnlyModelService({ repository, emitter, schema: mockSchema, namespace })
|
|
427
|
+
|
|
428
|
+
const input = { id: 105, title: 'Default Test', author: 'Default Author', publishedDate: new Date() }
|
|
429
|
+
await repository.create(input)
|
|
430
|
+
|
|
431
|
+
const record = await defaultService.load({ id: 105 })
|
|
432
|
+
|
|
433
|
+
// Should return the raw string from repository since no normalization is applied
|
|
434
|
+
expect(record.publishedDate as any).toBe('2024-01-01')
|
|
435
|
+
expect(typeof record.publishedDate).toBe('string')
|
|
436
|
+
})
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
describe('Trash Functionality', () => {
|
|
440
|
+
beforeEach(async () => {
|
|
441
|
+
repository = new MockMemoryRepository({ schema: mockSchema })
|
|
442
|
+
emitter = new EventManager()
|
|
443
|
+
service = new ReadOnlyModelService({ repository, emitter, schema: mockSchema, namespace })
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
describe('load with trash options', () => {
|
|
447
|
+
it('should not load removed items by default', async () => {
|
|
448
|
+
const input = { id: 1, title: 'Book to Remove', author: 'Author', publishedDate: new Date() }
|
|
449
|
+
await repository.create(input)
|
|
450
|
+
await repository.remove({ id: 1 })
|
|
451
|
+
|
|
452
|
+
const record = await service.load({ id: 1 })
|
|
453
|
+
expect(record).toBeNull()
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
it('should load removed items with removedOnly option', async () => {
|
|
457
|
+
const input = { id: 2, title: 'Removed Book', author: 'Author', publishedDate: new Date() }
|
|
458
|
+
await repository.create(input)
|
|
459
|
+
await repository.remove({ id: 2 })
|
|
460
|
+
|
|
461
|
+
const record = await service.load({ id: 2 }, { removedOnly: true })
|
|
462
|
+
expect(record).not.toBeNull()
|
|
463
|
+
expect(record?.title).toBe('Removed Book')
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
it('should not load active items with removedOnly option', async () => {
|
|
467
|
+
const input = { id: 3, title: 'Active Book', author: 'Author', publishedDate: new Date() }
|
|
468
|
+
await repository.create(input)
|
|
469
|
+
|
|
470
|
+
const record = await service.load({ id: 3 }, { removedOnly: true })
|
|
471
|
+
expect(record).toBeNull()
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
it('should load removed items with includeRemoved option', async () => {
|
|
475
|
+
const input = { id: 4, title: 'Removed Book', author: 'Author', publishedDate: new Date() }
|
|
476
|
+
await repository.create(input)
|
|
477
|
+
await repository.remove({ id: 4 })
|
|
478
|
+
|
|
479
|
+
const record = await service.load({ id: 4 }, { includeRemoved: true })
|
|
480
|
+
expect(record).not.toBeNull()
|
|
481
|
+
expect(record?.title).toBe('Removed Book')
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
it('should load active items with includeRemoved option', async () => {
|
|
485
|
+
const input = { id: 5, title: 'Active Book', author: 'Author', publishedDate: new Date() }
|
|
486
|
+
await repository.create(input)
|
|
487
|
+
|
|
488
|
+
const record = await service.load({ id: 5 }, { includeRemoved: true })
|
|
489
|
+
expect(record).not.toBeNull()
|
|
490
|
+
expect(record?.title).toBe('Active Book')
|
|
491
|
+
})
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
describe('search with trash options', () => {
|
|
495
|
+
it('should not return removed items by default', async () => {
|
|
496
|
+
await repository.create({ id: 1, title: 'Active Book', author: 'Author 1', publishedDate: new Date() })
|
|
497
|
+
await repository.create({
|
|
498
|
+
id: 2,
|
|
499
|
+
title: 'Removed Book',
|
|
500
|
+
author: 'Author 2',
|
|
501
|
+
publishedDate: new Date(),
|
|
502
|
+
})
|
|
503
|
+
await repository.remove({ id: 2 })
|
|
504
|
+
|
|
505
|
+
const results = await service.search({})
|
|
506
|
+
expect(results.results).toHaveLength(1)
|
|
507
|
+
expect(results.results[0].title).toBe('Active Book')
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
it('should return only removed items with removedOnly option', async () => {
|
|
511
|
+
await repository.create({ id: 1, title: 'Active Book', author: 'Author 1', publishedDate: new Date() })
|
|
512
|
+
await repository.create({
|
|
513
|
+
id: 2,
|
|
514
|
+
title: 'Removed Book 1',
|
|
515
|
+
author: 'Author 2',
|
|
516
|
+
publishedDate: new Date(),
|
|
517
|
+
})
|
|
518
|
+
await repository.create({
|
|
519
|
+
id: 3,
|
|
520
|
+
title: 'Removed Book 2',
|
|
521
|
+
author: 'Author 3',
|
|
522
|
+
publishedDate: new Date(),
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
await repository.remove({ id: 2 })
|
|
526
|
+
await repository.remove({ id: 3 })
|
|
527
|
+
|
|
528
|
+
const results = await service.search({}, { removedOnly: true })
|
|
529
|
+
expect(results.results).toHaveLength(2)
|
|
530
|
+
expect(results.results.every((book) => book.title.startsWith('Removed'))).toBe(true)
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
it('should return both active and removed items with includeRemoved option', async () => {
|
|
534
|
+
await repository.create({
|
|
535
|
+
id: 1,
|
|
536
|
+
title: 'Active Book 1',
|
|
537
|
+
author: 'Author 1',
|
|
538
|
+
publishedDate: new Date(),
|
|
539
|
+
})
|
|
540
|
+
await repository.create({
|
|
541
|
+
id: 2,
|
|
542
|
+
title: 'Active Book 2',
|
|
543
|
+
author: 'Author 2',
|
|
544
|
+
publishedDate: new Date(),
|
|
545
|
+
})
|
|
546
|
+
await repository.create({
|
|
547
|
+
id: 3,
|
|
548
|
+
title: 'Removed Book',
|
|
549
|
+
author: 'Author 3',
|
|
550
|
+
publishedDate: new Date(),
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
await repository.remove({ id: 3 })
|
|
554
|
+
|
|
555
|
+
const results = await service.search({}, { includeRemoved: true })
|
|
556
|
+
expect(results.results).toHaveLength(3)
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
it('should filter removed items with removedOnly option', async () => {
|
|
560
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
561
|
+
schema: mockSchema,
|
|
562
|
+
filter: (data, filters) => {
|
|
563
|
+
if (filters.text) {
|
|
564
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
565
|
+
}
|
|
566
|
+
return true
|
|
567
|
+
},
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
const serviceWithFilter = new ReadOnlyModelService({
|
|
571
|
+
repository: repositoryWithFilter,
|
|
572
|
+
emitter,
|
|
573
|
+
namespace,
|
|
574
|
+
schema: mockSchema,
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
const removed1 = await repositoryWithFilter.create({
|
|
578
|
+
title: 'Test Removed Book',
|
|
579
|
+
author: 'Author 1',
|
|
580
|
+
publishedDate: new Date(),
|
|
581
|
+
})
|
|
582
|
+
const removed2 = await repositoryWithFilter.create({
|
|
583
|
+
title: 'Other Removed Book',
|
|
584
|
+
author: 'Author 2',
|
|
585
|
+
publishedDate: new Date(),
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
await repositoryWithFilter.remove({ id: removed1.id })
|
|
589
|
+
await repositoryWithFilter.remove({ id: removed2.id })
|
|
590
|
+
|
|
591
|
+
const results = await serviceWithFilter.search({ text: 'Test' }, { removedOnly: true })
|
|
592
|
+
expect(results.results).toHaveLength(1)
|
|
593
|
+
expect(results.results[0].title).toBe('Test Removed Book')
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
it('should filter across active and removed items with includeRemoved option', async () => {
|
|
597
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
598
|
+
schema: mockSchema,
|
|
599
|
+
filter: (data, filters) => {
|
|
600
|
+
if (filters.text) {
|
|
601
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
602
|
+
}
|
|
603
|
+
return true
|
|
604
|
+
},
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
const serviceWithFilter = new ReadOnlyModelService({
|
|
608
|
+
repository: repositoryWithFilter,
|
|
609
|
+
emitter,
|
|
610
|
+
namespace,
|
|
611
|
+
schema: mockSchema,
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
await repositoryWithFilter.create({
|
|
615
|
+
title: 'Test Active Book',
|
|
616
|
+
author: 'Author 1',
|
|
617
|
+
publishedDate: new Date(),
|
|
618
|
+
})
|
|
619
|
+
const removed = await repositoryWithFilter.create({
|
|
620
|
+
title: 'Test Removed Book',
|
|
621
|
+
author: 'Author 2',
|
|
622
|
+
publishedDate: new Date(),
|
|
623
|
+
})
|
|
624
|
+
await repositoryWithFilter.create({
|
|
625
|
+
title: 'Other Book',
|
|
626
|
+
author: 'Author 3',
|
|
627
|
+
publishedDate: new Date(),
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
await repositoryWithFilter.remove({ id: removed.id })
|
|
631
|
+
|
|
632
|
+
const results = await serviceWithFilter.search({ text: 'Test' }, { includeRemoved: true })
|
|
633
|
+
expect(results.results).toHaveLength(2)
|
|
634
|
+
expect(results.results.some((book) => book.title === 'Test Active Book')).toBe(true)
|
|
635
|
+
expect(results.results.some((book) => book.title === 'Test Removed Book')).toBe(true)
|
|
636
|
+
})
|
|
637
|
+
})
|
|
638
|
+
|
|
639
|
+
describe('count with trash options', () => {
|
|
640
|
+
it('should count only active items by default', async () => {
|
|
641
|
+
await repository.create({ id: 1, title: 'Active Book', author: 'Author 1', publishedDate: new Date() })
|
|
642
|
+
await repository.create({
|
|
643
|
+
id: 2,
|
|
644
|
+
title: 'Removed Book',
|
|
645
|
+
author: 'Author 2',
|
|
646
|
+
publishedDate: new Date(),
|
|
647
|
+
})
|
|
648
|
+
await repository.remove({ id: 2 })
|
|
649
|
+
|
|
650
|
+
const count = await service.count({})
|
|
651
|
+
expect(count).toBe(1)
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
it('should count only removed items with removedOnly option', async () => {
|
|
655
|
+
await repository.create({ id: 1, title: 'Active Book', author: 'Author 1', publishedDate: new Date() })
|
|
656
|
+
await repository.create({
|
|
657
|
+
id: 2,
|
|
658
|
+
title: 'Removed Book 1',
|
|
659
|
+
author: 'Author 2',
|
|
660
|
+
publishedDate: new Date(),
|
|
661
|
+
})
|
|
662
|
+
await repository.create({
|
|
663
|
+
id: 3,
|
|
664
|
+
title: 'Removed Book 2',
|
|
665
|
+
author: 'Author 3',
|
|
666
|
+
publishedDate: new Date(),
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
await repository.remove({ id: 2 })
|
|
670
|
+
await repository.remove({ id: 3 })
|
|
671
|
+
|
|
672
|
+
const count = await service.count({}, { removedOnly: true })
|
|
673
|
+
expect(count).toBe(2)
|
|
674
|
+
})
|
|
675
|
+
|
|
676
|
+
it('should count both active and removed items with includeRemoved option', async () => {
|
|
677
|
+
await repository.create({ id: 1, title: 'Active Book', author: 'Author 1', publishedDate: new Date() })
|
|
678
|
+
await repository.create({
|
|
679
|
+
id: 2,
|
|
680
|
+
title: 'Removed Book',
|
|
681
|
+
author: 'Author 2',
|
|
682
|
+
publishedDate: new Date(),
|
|
683
|
+
})
|
|
684
|
+
await repository.remove({ id: 2 })
|
|
685
|
+
|
|
686
|
+
const count = await service.count({}, { includeRemoved: true })
|
|
687
|
+
expect(count).toBe(2)
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
it('should count filtered active items by default', async () => {
|
|
691
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
692
|
+
schema: mockSchema,
|
|
693
|
+
filter: (data, filters) => {
|
|
694
|
+
if (filters.text) {
|
|
695
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
696
|
+
}
|
|
697
|
+
return true
|
|
698
|
+
},
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
const serviceWithFilter = new ReadOnlyModelService({
|
|
702
|
+
repository: repositoryWithFilter,
|
|
703
|
+
emitter,
|
|
704
|
+
namespace,
|
|
705
|
+
schema: mockSchema,
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
await repositoryWithFilter.create({
|
|
709
|
+
title: 'Test Book 1',
|
|
710
|
+
author: 'Author 1',
|
|
711
|
+
publishedDate: new Date(),
|
|
712
|
+
})
|
|
713
|
+
await repositoryWithFilter.create({
|
|
714
|
+
title: 'Test Book 2',
|
|
715
|
+
author: 'Author 2',
|
|
716
|
+
publishedDate: new Date(),
|
|
717
|
+
})
|
|
718
|
+
await repositoryWithFilter.create({
|
|
719
|
+
title: 'Other Book',
|
|
720
|
+
author: 'Author 3',
|
|
721
|
+
publishedDate: new Date(),
|
|
722
|
+
})
|
|
723
|
+
const removed = await repositoryWithFilter.create({
|
|
724
|
+
title: 'Test Book 3',
|
|
725
|
+
author: 'Author 4',
|
|
726
|
+
publishedDate: new Date(),
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
await repositoryWithFilter.remove({ id: removed.id })
|
|
730
|
+
|
|
731
|
+
const count = await serviceWithFilter.count({ text: 'Test' })
|
|
732
|
+
expect(count).toBe(2)
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
it('should count filtered removed items with removedOnly option', async () => {
|
|
736
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
737
|
+
schema: mockSchema,
|
|
738
|
+
filter: (data, filters) => {
|
|
739
|
+
if (filters.text) {
|
|
740
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
741
|
+
}
|
|
742
|
+
return true
|
|
743
|
+
},
|
|
744
|
+
})
|
|
745
|
+
|
|
746
|
+
const serviceWithFilter = new ReadOnlyModelService({
|
|
747
|
+
repository: repositoryWithFilter,
|
|
748
|
+
emitter,
|
|
749
|
+
namespace,
|
|
750
|
+
schema: mockSchema,
|
|
751
|
+
})
|
|
752
|
+
|
|
753
|
+
const removed1 = await repositoryWithFilter.create({
|
|
754
|
+
title: 'Test Removed Book 1',
|
|
755
|
+
author: 'Author 1',
|
|
756
|
+
publishedDate: new Date(),
|
|
757
|
+
})
|
|
758
|
+
const removed2 = await repositoryWithFilter.create({
|
|
759
|
+
title: 'Test Removed Book 2',
|
|
760
|
+
author: 'Author 2',
|
|
761
|
+
publishedDate: new Date(),
|
|
762
|
+
})
|
|
763
|
+
const removed3 = await repositoryWithFilter.create({
|
|
764
|
+
title: 'Other Removed Book',
|
|
765
|
+
author: 'Author 3',
|
|
766
|
+
publishedDate: new Date(),
|
|
767
|
+
})
|
|
768
|
+
await repositoryWithFilter.create({
|
|
769
|
+
title: 'Test Active Book',
|
|
770
|
+
author: 'Author 4',
|
|
771
|
+
publishedDate: new Date(),
|
|
772
|
+
})
|
|
773
|
+
|
|
774
|
+
await repositoryWithFilter.remove({ id: removed1.id })
|
|
775
|
+
await repositoryWithFilter.remove({ id: removed2.id })
|
|
776
|
+
await repositoryWithFilter.remove({ id: removed3.id })
|
|
777
|
+
|
|
778
|
+
const count = await serviceWithFilter.count({ text: 'Test' }, { removedOnly: true })
|
|
779
|
+
expect(count).toBe(2)
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
it('should count filtered items across active and removed with includeRemoved option', async () => {
|
|
783
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
784
|
+
schema: mockSchema,
|
|
785
|
+
filter: (data, filters) => {
|
|
786
|
+
if (filters.text) {
|
|
787
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
788
|
+
}
|
|
789
|
+
return true
|
|
790
|
+
},
|
|
791
|
+
})
|
|
792
|
+
|
|
793
|
+
const serviceWithFilter = new ReadOnlyModelService({
|
|
794
|
+
repository: repositoryWithFilter,
|
|
795
|
+
emitter,
|
|
796
|
+
namespace,
|
|
797
|
+
schema: mockSchema,
|
|
798
|
+
})
|
|
799
|
+
|
|
800
|
+
await repositoryWithFilter.create({
|
|
801
|
+
title: 'Test Active Book 1',
|
|
802
|
+
author: 'Author 1',
|
|
803
|
+
publishedDate: new Date(),
|
|
804
|
+
})
|
|
805
|
+
await repositoryWithFilter.create({
|
|
806
|
+
title: 'Test Active Book 2',
|
|
807
|
+
author: 'Author 2',
|
|
808
|
+
publishedDate: new Date(),
|
|
809
|
+
})
|
|
810
|
+
const removed1 = await repositoryWithFilter.create({
|
|
811
|
+
title: 'Test Removed Book 1',
|
|
812
|
+
author: 'Author 3',
|
|
813
|
+
publishedDate: new Date(),
|
|
814
|
+
})
|
|
815
|
+
const removed2 = await repositoryWithFilter.create({
|
|
816
|
+
title: 'Test Removed Book 2',
|
|
817
|
+
author: 'Author 4',
|
|
818
|
+
publishedDate: new Date(),
|
|
819
|
+
})
|
|
820
|
+
await repositoryWithFilter.create({
|
|
821
|
+
title: 'Other Active Book',
|
|
822
|
+
author: 'Author 5',
|
|
823
|
+
publishedDate: new Date(),
|
|
824
|
+
})
|
|
825
|
+
|
|
826
|
+
await repositoryWithFilter.remove({ id: removed1.id })
|
|
827
|
+
await repositoryWithFilter.remove({ id: removed2.id })
|
|
828
|
+
|
|
829
|
+
const count = await serviceWithFilter.count({ text: 'Test' }, { includeRemoved: true })
|
|
830
|
+
expect(count).toBe(4)
|
|
831
|
+
})
|
|
832
|
+
})
|
|
833
|
+
})
|
|
834
|
+
|
|
835
|
+
describe('doNotDispatchEvents Option', () => {
|
|
836
|
+
const beforeLoadSpy = mock(
|
|
837
|
+
(event: QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>) => {},
|
|
838
|
+
)
|
|
839
|
+
const afterLoadSpy = mock(
|
|
840
|
+
(event: QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>) => {},
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
const beforeLoadManySpy = mock(
|
|
844
|
+
(event: QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>) => {},
|
|
845
|
+
)
|
|
846
|
+
const afterLoadManySpy = mock(
|
|
847
|
+
(event: QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>) => {},
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
const beforeSearchSpy = mock(
|
|
851
|
+
(event: QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>) => {},
|
|
852
|
+
)
|
|
853
|
+
const afterSearchSpy = mock(
|
|
854
|
+
(event: QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>) => {},
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
const beforeCountSpy = mock((event: QueryEvent<number, InferFilters<typeof mockSchema>>) => {})
|
|
858
|
+
const afterCountSpy = mock((event: QueryEvent<number, InferFilters<typeof mockSchema>>) => {})
|
|
859
|
+
|
|
860
|
+
beforeEach(() => {
|
|
861
|
+
repository = new MockMemoryRepository({ schema: mockSchema })
|
|
862
|
+
emitter = new EventManager()
|
|
863
|
+
|
|
864
|
+
beforeLoadSpy.mockClear()
|
|
865
|
+
afterLoadSpy.mockClear()
|
|
866
|
+
beforeLoadManySpy.mockClear()
|
|
867
|
+
afterLoadManySpy.mockClear()
|
|
868
|
+
beforeSearchSpy.mockClear()
|
|
869
|
+
afterSearchSpy.mockClear()
|
|
870
|
+
beforeCountSpy.mockClear()
|
|
871
|
+
afterCountSpy.mockClear()
|
|
872
|
+
|
|
873
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>>(
|
|
874
|
+
'books::book.beforeLoad',
|
|
875
|
+
beforeLoadSpy,
|
|
876
|
+
)
|
|
877
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>, InferLookup<typeof mockSchema>>>(
|
|
878
|
+
'books::book.afterLoad',
|
|
879
|
+
afterLoadSpy,
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>>(
|
|
883
|
+
'books::book.beforeLoadMany',
|
|
884
|
+
beforeLoadManySpy,
|
|
885
|
+
)
|
|
886
|
+
emitter.on<QueryEvent<InferDetail<typeof mockSchema>[], InferLookup<typeof mockSchema>[]>>(
|
|
887
|
+
'books::book.afterLoadMany',
|
|
888
|
+
afterLoadManySpy,
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
emitter.on<QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>>(
|
|
892
|
+
'books::book.beforeSearch',
|
|
893
|
+
beforeSearchSpy,
|
|
894
|
+
)
|
|
895
|
+
emitter.on<QueryEvent<InferSearchResults<typeof mockSchema>[], InferFilters<typeof mockSchema>[]>>(
|
|
896
|
+
'books::book.afterSearch',
|
|
897
|
+
afterSearchSpy,
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
emitter.on<QueryEvent<number, InferFilters<typeof mockSchema>>>('books::book.beforeCount', beforeCountSpy)
|
|
901
|
+
emitter.on<QueryEvent<number, InferFilters<typeof mockSchema>>>('books::book.afterCount', afterCountSpy)
|
|
902
|
+
|
|
903
|
+
service = new ReadOnlyModelService({ repository, emitter, schema: mockSchema, namespace })
|
|
904
|
+
})
|
|
905
|
+
|
|
906
|
+
describe('load', () => {
|
|
907
|
+
it('should not dispatch events when doNotDispatchEvents is true', async () => {
|
|
908
|
+
const input = { id: 1, title: 'Test Book', author: 'Author', publishedDate: new Date() }
|
|
909
|
+
await repository.create(input)
|
|
910
|
+
|
|
911
|
+
const result = await service.load({ id: 1 }, { doNotDispatchEvents: true })
|
|
912
|
+
|
|
913
|
+
expect(result).toEqual(input)
|
|
914
|
+
expect(beforeLoadSpy).not.toHaveBeenCalled()
|
|
915
|
+
expect(afterLoadSpy).not.toHaveBeenCalled()
|
|
916
|
+
})
|
|
917
|
+
|
|
918
|
+
it('should dispatch events when doNotDispatchEvents is false', async () => {
|
|
919
|
+
const input = { id: 2, title: 'Test Book 2', author: 'Author', publishedDate: new Date() }
|
|
920
|
+
await repository.create(input)
|
|
921
|
+
|
|
922
|
+
const result = await service.load({ id: 2 }, { doNotDispatchEvents: false })
|
|
923
|
+
|
|
924
|
+
expect(result).toEqual(input)
|
|
925
|
+
expect(beforeLoadSpy).toHaveBeenCalledTimes(1)
|
|
926
|
+
expect(afterLoadSpy).toHaveBeenCalledTimes(1)
|
|
927
|
+
})
|
|
928
|
+
|
|
929
|
+
it('should dispatch events when doNotDispatchEvents is not specified', async () => {
|
|
930
|
+
const input = { id: 3, title: 'Test Book 3', author: 'Author', publishedDate: new Date() }
|
|
931
|
+
await repository.create(input)
|
|
932
|
+
|
|
933
|
+
const result = await service.load({ id: 3 })
|
|
934
|
+
|
|
935
|
+
expect(result).toEqual(input)
|
|
936
|
+
expect(beforeLoadSpy).toHaveBeenCalledTimes(1)
|
|
937
|
+
expect(afterLoadSpy).toHaveBeenCalledTimes(1)
|
|
938
|
+
})
|
|
939
|
+
})
|
|
940
|
+
|
|
941
|
+
describe('loadMany', () => {
|
|
942
|
+
it('should not dispatch events when doNotDispatchEvents is true', async () => {
|
|
943
|
+
const input1 = { id: 1, title: 'Test Book 1', author: 'Author 1', publishedDate: new Date() }
|
|
944
|
+
const input2 = { id: 2, title: 'Test Book 2', author: 'Author 2', publishedDate: new Date() }
|
|
945
|
+
await repository.create(input1)
|
|
946
|
+
await repository.create(input2)
|
|
947
|
+
|
|
948
|
+
const result = await service.loadMany([{ id: 1 }, { id: 2 }], { doNotDispatchEvents: true })
|
|
949
|
+
|
|
950
|
+
expect(result).toEqual([input1, input2])
|
|
951
|
+
expect(beforeLoadManySpy).not.toHaveBeenCalled()
|
|
952
|
+
expect(afterLoadManySpy).not.toHaveBeenCalled()
|
|
953
|
+
})
|
|
954
|
+
|
|
955
|
+
it('should dispatch events when doNotDispatchEvents is false', async () => {
|
|
956
|
+
const input1 = { id: 3, title: 'Test Book 3', author: 'Author 3', publishedDate: new Date() }
|
|
957
|
+
const input2 = { id: 4, title: 'Test Book 4', author: 'Author 4', publishedDate: new Date() }
|
|
958
|
+
await repository.create(input1)
|
|
959
|
+
await repository.create(input2)
|
|
960
|
+
|
|
961
|
+
const result = await service.loadMany([{ id: 3 }, { id: 4 }], { doNotDispatchEvents: false })
|
|
962
|
+
|
|
963
|
+
expect(result).toEqual([input1, input2])
|
|
964
|
+
expect(beforeLoadManySpy).toHaveBeenCalledTimes(1)
|
|
965
|
+
expect(afterLoadManySpy).toHaveBeenCalledTimes(1)
|
|
966
|
+
})
|
|
967
|
+
|
|
968
|
+
it('should dispatch events when doNotDispatchEvents is not specified', async () => {
|
|
969
|
+
const input1 = { id: 5, title: 'Test Book 5', author: 'Author 5', publishedDate: new Date() }
|
|
970
|
+
const input2 = { id: 6, title: 'Test Book 6', author: 'Author 6', publishedDate: new Date() }
|
|
971
|
+
await repository.create(input1)
|
|
972
|
+
await repository.create(input2)
|
|
973
|
+
|
|
974
|
+
const result = await service.loadMany([{ id: 5 }, { id: 6 }])
|
|
975
|
+
|
|
976
|
+
expect(result).toEqual([input1, input2])
|
|
977
|
+
expect(beforeLoadManySpy).toHaveBeenCalledTimes(1)
|
|
978
|
+
expect(afterLoadManySpy).toHaveBeenCalledTimes(1)
|
|
979
|
+
})
|
|
980
|
+
})
|
|
981
|
+
|
|
982
|
+
describe('search', () => {
|
|
983
|
+
it('should not dispatch events when doNotDispatchEvents is true', async () => {
|
|
984
|
+
const input1 = { id: 1, title: 'Test Book 1', author: 'Author 1', publishedDate: new Date() }
|
|
985
|
+
const input2 = { id: 2, title: 'Test Book 2', author: 'Author 2', publishedDate: new Date() }
|
|
986
|
+
await repository.create(input1)
|
|
987
|
+
await repository.create(input2)
|
|
988
|
+
|
|
989
|
+
const result = await service.search({}, { doNotDispatchEvents: true })
|
|
990
|
+
|
|
991
|
+
expect(result.results).toEqual([input1, input2])
|
|
992
|
+
expect(beforeSearchSpy).not.toHaveBeenCalled()
|
|
993
|
+
expect(afterSearchSpy).not.toHaveBeenCalled()
|
|
994
|
+
})
|
|
995
|
+
|
|
996
|
+
it('should dispatch events when doNotDispatchEvents is false', async () => {
|
|
997
|
+
const input1 = { id: 3, title: 'Test Book 3', author: 'Author 3', publishedDate: new Date() }
|
|
998
|
+
const input2 = { id: 4, title: 'Test Book 4', author: 'Author 4', publishedDate: new Date() }
|
|
999
|
+
await repository.create(input1)
|
|
1000
|
+
await repository.create(input2)
|
|
1001
|
+
|
|
1002
|
+
const result = await service.search({}, { doNotDispatchEvents: false })
|
|
1003
|
+
|
|
1004
|
+
expect(result.results).toEqual([input1, input2])
|
|
1005
|
+
expect(beforeSearchSpy).toHaveBeenCalledTimes(1)
|
|
1006
|
+
expect(afterSearchSpy).toHaveBeenCalledTimes(1)
|
|
1007
|
+
})
|
|
1008
|
+
|
|
1009
|
+
it('should dispatch events when doNotDispatchEvents is not specified', async () => {
|
|
1010
|
+
const input1 = { id: 5, title: 'Test Book 5', author: 'Author 5', publishedDate: new Date() }
|
|
1011
|
+
const input2 = { id: 6, title: 'Test Book 6', author: 'Author 6', publishedDate: new Date() }
|
|
1012
|
+
await repository.create(input1)
|
|
1013
|
+
await repository.create(input2)
|
|
1014
|
+
|
|
1015
|
+
const result = await service.search({})
|
|
1016
|
+
|
|
1017
|
+
expect(result.results).toEqual([input1, input2])
|
|
1018
|
+
expect(beforeSearchSpy).toHaveBeenCalledTimes(1)
|
|
1019
|
+
expect(afterSearchSpy).toHaveBeenCalledTimes(1)
|
|
1020
|
+
})
|
|
1021
|
+
})
|
|
1022
|
+
|
|
1023
|
+
describe('count', () => {
|
|
1024
|
+
it('should not dispatch events when doNotDispatchEvents is true', async () => {
|
|
1025
|
+
const input1 = { id: 1, title: 'Test Book 1', author: 'Author 1', publishedDate: new Date() }
|
|
1026
|
+
const input2 = { id: 2, title: 'Test Book 2', author: 'Author 2', publishedDate: new Date() }
|
|
1027
|
+
await repository.create(input1)
|
|
1028
|
+
await repository.create(input2)
|
|
1029
|
+
|
|
1030
|
+
const result = await service.count({}, { doNotDispatchEvents: true })
|
|
1031
|
+
|
|
1032
|
+
expect(result).toBe(2)
|
|
1033
|
+
expect(beforeCountSpy).not.toHaveBeenCalled()
|
|
1034
|
+
expect(afterCountSpy).not.toHaveBeenCalled()
|
|
1035
|
+
})
|
|
1036
|
+
|
|
1037
|
+
it('should dispatch events when doNotDispatchEvents is false', async () => {
|
|
1038
|
+
const input1 = { id: 3, title: 'Test Book 3', author: 'Author 3', publishedDate: new Date() }
|
|
1039
|
+
const input2 = { id: 4, title: 'Test Book 4', author: 'Author 4', publishedDate: new Date() }
|
|
1040
|
+
await repository.create(input1)
|
|
1041
|
+
await repository.create(input2)
|
|
1042
|
+
|
|
1043
|
+
const result = await service.count({}, { doNotDispatchEvents: false })
|
|
1044
|
+
|
|
1045
|
+
expect(result).toBe(2)
|
|
1046
|
+
expect(beforeCountSpy).toHaveBeenCalledTimes(1)
|
|
1047
|
+
expect(afterCountSpy).toHaveBeenCalledTimes(1)
|
|
1048
|
+
})
|
|
1049
|
+
|
|
1050
|
+
it('should dispatch events when doNotDispatchEvents is not specified', async () => {
|
|
1051
|
+
const input1 = { id: 5, title: 'Test Book 5', author: 'Author 5', publishedDate: new Date() }
|
|
1052
|
+
const input2 = { id: 6, title: 'Test Book 6', author: 'Author 6', publishedDate: new Date() }
|
|
1053
|
+
await repository.create(input1)
|
|
1054
|
+
await repository.create(input2)
|
|
1055
|
+
|
|
1056
|
+
const result = await service.count({})
|
|
1057
|
+
|
|
1058
|
+
expect(result).toBe(2)
|
|
1059
|
+
expect(beforeCountSpy).toHaveBeenCalledTimes(1)
|
|
1060
|
+
expect(afterCountSpy).toHaveBeenCalledTimes(1)
|
|
1061
|
+
})
|
|
1062
|
+
})
|
|
1063
|
+
})
|
|
1064
|
+
|
|
1065
|
+
describe('noCache Option', () => {
|
|
1066
|
+
beforeEach(() => {
|
|
1067
|
+
repository = new MockMemoryRepository({ schema: mockSchema })
|
|
1068
|
+
emitter = new EventManager()
|
|
1069
|
+
service = new ReadOnlyModelService({ repository, emitter, schema: mockSchema, namespace })
|
|
1070
|
+
})
|
|
1071
|
+
|
|
1072
|
+
describe('load', () => {
|
|
1073
|
+
it('should return the correct result when noCache is true', async () => {
|
|
1074
|
+
const input = { id: 1, title: 'Test Book', author: 'Author', publishedDate: new Date() }
|
|
1075
|
+
await repository.create(input)
|
|
1076
|
+
|
|
1077
|
+
const result = await service.load({ id: 1 }, { noCache: true })
|
|
1078
|
+
|
|
1079
|
+
expect(result).toEqual(input)
|
|
1080
|
+
})
|
|
1081
|
+
|
|
1082
|
+
it('should return the correct result when noCache is false', async () => {
|
|
1083
|
+
const input = { id: 2, title: 'Test Book 2', author: 'Author', publishedDate: new Date() }
|
|
1084
|
+
await repository.create(input)
|
|
1085
|
+
|
|
1086
|
+
const result = await service.load({ id: 2 }, { noCache: false })
|
|
1087
|
+
|
|
1088
|
+
expect(result).toEqual(input)
|
|
1089
|
+
})
|
|
1090
|
+
|
|
1091
|
+
it('should forward noCache: true to the repository', async () => {
|
|
1092
|
+
const input = { id: 3, title: 'Test Book 3', author: 'Author', publishedDate: new Date() }
|
|
1093
|
+
await repository.create(input)
|
|
1094
|
+
|
|
1095
|
+
const loadSpy = spyOn(repository, 'load')
|
|
1096
|
+
await service.load({ id: 3 }, { noCache: true })
|
|
1097
|
+
|
|
1098
|
+
expect(loadSpy).toHaveBeenCalledWith({ id: 3 }, expect.objectContaining({ noCache: true }))
|
|
1099
|
+
})
|
|
1100
|
+
})
|
|
1101
|
+
|
|
1102
|
+
describe('loadMany', () => {
|
|
1103
|
+
it('should return the correct results when noCache is true', async () => {
|
|
1104
|
+
const input1 = { id: 1, title: 'Test Book 1', author: 'Author 1', publishedDate: new Date() }
|
|
1105
|
+
const input2 = { id: 2, title: 'Test Book 2', author: 'Author 2', publishedDate: new Date() }
|
|
1106
|
+
await repository.create(input1)
|
|
1107
|
+
await repository.create(input2)
|
|
1108
|
+
|
|
1109
|
+
const result = await service.loadMany([{ id: 1 }, { id: 2 }], { noCache: true })
|
|
1110
|
+
|
|
1111
|
+
expect(result).toEqual([input1, input2])
|
|
1112
|
+
})
|
|
1113
|
+
|
|
1114
|
+
it('should forward noCache: true to the repository', async () => {
|
|
1115
|
+
const input1 = { id: 3, title: 'Test Book 3', author: 'Author 3', publishedDate: new Date() }
|
|
1116
|
+
const input2 = { id: 4, title: 'Test Book 4', author: 'Author 4', publishedDate: new Date() }
|
|
1117
|
+
await repository.create(input1)
|
|
1118
|
+
await repository.create(input2)
|
|
1119
|
+
|
|
1120
|
+
const loadManySpy = spyOn(repository, 'loadMany')
|
|
1121
|
+
await service.loadMany([{ id: 3 }, { id: 4 }], { noCache: true })
|
|
1122
|
+
|
|
1123
|
+
expect(loadManySpy).toHaveBeenCalledWith(
|
|
1124
|
+
[{ id: 3 }, { id: 4 }],
|
|
1125
|
+
expect.objectContaining({ noCache: true }),
|
|
1126
|
+
)
|
|
1127
|
+
})
|
|
1128
|
+
})
|
|
1129
|
+
})
|
|
1130
|
+
})
|