@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,694 @@
|
|
|
1
|
+
import { AuthValidator, getMockAuthSession, mockAuthConfig, MockAuthService } from '@declaro/auth'
|
|
2
|
+
import { EventManager, PermissionError } from '@declaro/core'
|
|
3
|
+
import { beforeEach, describe, expect, it } from 'bun:test'
|
|
4
|
+
import { ModelService } from '../domain/services/model-service'
|
|
5
|
+
import { MockBookSchema } from '../test/mock/models/mock-book-models'
|
|
6
|
+
import { MockMemoryRepository } from '../test/mock/repositories/mock-memory-repository'
|
|
7
|
+
import { ModelController } from './model-controller'
|
|
8
|
+
|
|
9
|
+
describe('ModelController', () => {
|
|
10
|
+
const namespace = 'books'
|
|
11
|
+
const mockSchema = MockBookSchema
|
|
12
|
+
const authService = new MockAuthService(mockAuthConfig)
|
|
13
|
+
|
|
14
|
+
let repository: MockMemoryRepository<typeof mockSchema>
|
|
15
|
+
let service: ModelService<typeof mockSchema>
|
|
16
|
+
let authValidator: AuthValidator
|
|
17
|
+
let invalidAuthValidator: AuthValidator
|
|
18
|
+
let readAuthValidator: AuthValidator
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
repository = new MockMemoryRepository({ schema: mockSchema })
|
|
22
|
+
authValidator = new AuthValidator(
|
|
23
|
+
getMockAuthSession({
|
|
24
|
+
claims: ['books::book.write:all'],
|
|
25
|
+
}),
|
|
26
|
+
null,
|
|
27
|
+
authService,
|
|
28
|
+
)
|
|
29
|
+
invalidAuthValidator = new AuthValidator(
|
|
30
|
+
getMockAuthSession({
|
|
31
|
+
claims: ['authors::author.write:all'],
|
|
32
|
+
}),
|
|
33
|
+
null,
|
|
34
|
+
authService,
|
|
35
|
+
)
|
|
36
|
+
readAuthValidator = new AuthValidator(
|
|
37
|
+
getMockAuthSession({
|
|
38
|
+
claims: ['books::book.read:all'],
|
|
39
|
+
}),
|
|
40
|
+
null,
|
|
41
|
+
authService,
|
|
42
|
+
)
|
|
43
|
+
service = new ModelService({
|
|
44
|
+
repository,
|
|
45
|
+
emitter: new EventManager(),
|
|
46
|
+
schema: mockSchema,
|
|
47
|
+
namespace,
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should create a record if permissions are valid', async () => {
|
|
52
|
+
const controller = new ModelController(service, authValidator)
|
|
53
|
+
|
|
54
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
55
|
+
const record = await controller.create(input)
|
|
56
|
+
|
|
57
|
+
expect(record).toEqual(input)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should throw PermissionError if permissions are invalid for create', async () => {
|
|
61
|
+
const controller = new ModelController(service, invalidAuthValidator)
|
|
62
|
+
|
|
63
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
64
|
+
await expect(controller.create(input)).rejects.toThrow(PermissionError)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should update a record if permissions are valid', async () => {
|
|
68
|
+
const controller = new ModelController(service, authValidator)
|
|
69
|
+
|
|
70
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
71
|
+
await repository.create(input)
|
|
72
|
+
|
|
73
|
+
const updatedInput = { title: 'Updated Title', author: 'Updated Author', publishedDate: new Date() }
|
|
74
|
+
const updatedRecord = await controller.update({ id: 42 }, updatedInput)
|
|
75
|
+
|
|
76
|
+
expect(updatedRecord).toEqual({ id: 42, ...updatedInput })
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('should throw PermissionError if permissions are invalid for update', async () => {
|
|
80
|
+
const controller = new ModelController(service, invalidAuthValidator)
|
|
81
|
+
|
|
82
|
+
const updatedInput = { title: 'Updated Title', author: 'Updated Author', publishedDate: new Date() }
|
|
83
|
+
await expect(controller.update({ id: 42 }, updatedInput)).rejects.toThrow(PermissionError)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should remove a record if permissions are valid', async () => {
|
|
87
|
+
const controller = new ModelController(service, authValidator)
|
|
88
|
+
|
|
89
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
90
|
+
await repository.create(input)
|
|
91
|
+
|
|
92
|
+
const removedRecord = await controller.remove({ id: 42 })
|
|
93
|
+
|
|
94
|
+
expect(removedRecord).toEqual(input)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should throw PermissionError if permissions are invalid for remove', async () => {
|
|
98
|
+
const controller = new ModelController(service, invalidAuthValidator)
|
|
99
|
+
|
|
100
|
+
await expect(controller.remove({ id: 42 })).rejects.toThrow(PermissionError)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('should restore a record if permissions are valid', async () => {
|
|
104
|
+
const controller = new ModelController(service, authValidator)
|
|
105
|
+
|
|
106
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
107
|
+
await repository.create(input)
|
|
108
|
+
await service.remove({ id: 42 })
|
|
109
|
+
|
|
110
|
+
const restoredRecord = await controller.restore({ id: 42 })
|
|
111
|
+
|
|
112
|
+
expect(restoredRecord).toEqual(input)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should throw PermissionError if permissions are invalid for restore', async () => {
|
|
116
|
+
const controller = new ModelController(service, invalidAuthValidator)
|
|
117
|
+
|
|
118
|
+
await expect(controller.restore({ id: 42 })).rejects.toThrow(PermissionError)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// Test inherited search functionality from ReadOnlyModelController
|
|
122
|
+
it('should search records with pagination and sorting', async () => {
|
|
123
|
+
const controller = new ModelController(service, readAuthValidator)
|
|
124
|
+
|
|
125
|
+
// Create test data
|
|
126
|
+
const books = [
|
|
127
|
+
{ id: 1, title: 'Book A', author: 'Author 1', publishedDate: new Date('2020-01-01') },
|
|
128
|
+
{ id: 2, title: 'Book B', author: 'Author 2', publishedDate: new Date('2021-01-01') },
|
|
129
|
+
{ id: 3, title: 'Book C', author: 'Author 3', publishedDate: new Date('2022-01-01') },
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
for (const book of books) {
|
|
133
|
+
await repository.create(book)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Test search with pagination
|
|
137
|
+
const paginatedResult = await controller.search(
|
|
138
|
+
{},
|
|
139
|
+
{
|
|
140
|
+
pagination: { page: 1, pageSize: 2 },
|
|
141
|
+
},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
expect(paginatedResult.results).toHaveLength(2)
|
|
145
|
+
expect(paginatedResult.pagination.total).toBe(3)
|
|
146
|
+
expect(paginatedResult.pagination.page).toBe(1)
|
|
147
|
+
expect(paginatedResult.pagination.pageSize).toBe(2)
|
|
148
|
+
|
|
149
|
+
// Test search with sorting
|
|
150
|
+
const sortedResult = await controller.search(
|
|
151
|
+
{},
|
|
152
|
+
{
|
|
153
|
+
sort: [{ title: 'desc' }],
|
|
154
|
+
},
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
expect(sortedResult.results).toHaveLength(3)
|
|
158
|
+
expect(sortedResult.results[0].title).toBe('Book C')
|
|
159
|
+
expect(sortedResult.results[1].title).toBe('Book B')
|
|
160
|
+
expect(sortedResult.results[2].title).toBe('Book A')
|
|
161
|
+
|
|
162
|
+
// Test search with both pagination and sorting
|
|
163
|
+
const combinedResult = await controller.search(
|
|
164
|
+
{},
|
|
165
|
+
{
|
|
166
|
+
pagination: { page: 1, pageSize: 1 },
|
|
167
|
+
sort: [{ title: 'desc' }],
|
|
168
|
+
},
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
expect(combinedResult.results).toHaveLength(1)
|
|
172
|
+
expect(combinedResult.results[0].title).toBe('Book C')
|
|
173
|
+
expect(combinedResult.pagination.total).toBe(3)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('should search records with filters', async () => {
|
|
177
|
+
// Create a repository with a custom filter function for search
|
|
178
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
179
|
+
schema: mockSchema,
|
|
180
|
+
filter: (data, filters) => {
|
|
181
|
+
if (filters.text) {
|
|
182
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
183
|
+
}
|
|
184
|
+
return true
|
|
185
|
+
},
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const serviceWithFilter = new ModelService({
|
|
189
|
+
repository: repositoryWithFilter,
|
|
190
|
+
emitter: new EventManager(),
|
|
191
|
+
schema: mockSchema,
|
|
192
|
+
namespace,
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const controller = new ModelController(serviceWithFilter, readAuthValidator)
|
|
196
|
+
|
|
197
|
+
// Create test data
|
|
198
|
+
const books = [
|
|
199
|
+
{ id: 1, title: 'Test Book', author: 'Author 1', publishedDate: new Date('2020-01-01') },
|
|
200
|
+
{ id: 2, title: 'Another Book', author: 'Author 2', publishedDate: new Date('2021-01-01') },
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
for (const book of books) {
|
|
204
|
+
await repositoryWithFilter.create(book)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Test search with filter
|
|
208
|
+
const filteredResult = await controller.search({ text: 'Test' })
|
|
209
|
+
|
|
210
|
+
expect(filteredResult.results).toHaveLength(1)
|
|
211
|
+
expect(filteredResult.results[0].title).toBe('Test Book')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('should upsert a record if permissions are valid', async () => {
|
|
215
|
+
const controller = new ModelController(service, authValidator)
|
|
216
|
+
|
|
217
|
+
// Test creating a new record via upsert
|
|
218
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
219
|
+
const upsertedRecord = await controller.upsert(input)
|
|
220
|
+
|
|
221
|
+
expect(upsertedRecord).toEqual(input)
|
|
222
|
+
|
|
223
|
+
// Test updating an existing record via upsert
|
|
224
|
+
const updateInput = { id: 42, title: 'Updated Book', author: 'Updated Author', publishedDate: new Date() }
|
|
225
|
+
const updatedRecord = await controller.upsert(updateInput)
|
|
226
|
+
|
|
227
|
+
expect(updatedRecord).toEqual(updateInput)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should throw PermissionError if permissions are invalid for upsert', async () => {
|
|
231
|
+
const controller = new ModelController(service, invalidAuthValidator)
|
|
232
|
+
|
|
233
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
234
|
+
|
|
235
|
+
await expect(controller.upsert(input)).rejects.toThrow(PermissionError)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should bulk upsert records if permissions are valid', async () => {
|
|
239
|
+
const controller = new ModelController(service, authValidator)
|
|
240
|
+
|
|
241
|
+
const inputs = [
|
|
242
|
+
{ id: 1, title: 'Book 1', author: 'Author 1', publishedDate: new Date() },
|
|
243
|
+
{ id: 2, title: 'Book 2', author: 'Author 2', publishedDate: new Date() },
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
const upsertedRecords = await controller.bulkUpsert(inputs)
|
|
247
|
+
|
|
248
|
+
expect(upsertedRecords).toHaveLength(2)
|
|
249
|
+
expect(upsertedRecords).toEqual(inputs)
|
|
250
|
+
|
|
251
|
+
// Test updating existing records via bulk upsert
|
|
252
|
+
const updateInputs = [
|
|
253
|
+
{ id: 1, title: 'Updated Book 1', author: 'Updated Author 1', publishedDate: new Date() },
|
|
254
|
+
{ id: 2, title: 'Updated Book 2', author: 'Updated Author 2', publishedDate: new Date() },
|
|
255
|
+
]
|
|
256
|
+
|
|
257
|
+
const updatedRecords = await controller.bulkUpsert(updateInputs)
|
|
258
|
+
|
|
259
|
+
expect(updatedRecords).toHaveLength(2)
|
|
260
|
+
expect(updatedRecords).toEqual(updateInputs)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('should throw PermissionError if permissions are invalid for bulkUpsert', async () => {
|
|
264
|
+
const controller = new ModelController(service, invalidAuthValidator)
|
|
265
|
+
|
|
266
|
+
const inputs = [
|
|
267
|
+
{ id: 1, title: 'Book 1', author: 'Author 1', publishedDate: new Date() },
|
|
268
|
+
{ id: 2, title: 'Book 2', author: 'Author 2', publishedDate: new Date() },
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
await expect(controller.bulkUpsert(inputs)).rejects.toThrow(PermissionError)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
describe('upsert and bulkUpsert permission logic', () => {
|
|
275
|
+
it('should allow upsert with create AND update permissions', async () => {
|
|
276
|
+
const createUpdateValidator = new AuthValidator(
|
|
277
|
+
getMockAuthSession({
|
|
278
|
+
claims: ['books::book.create:all', 'books::book.update:all'],
|
|
279
|
+
}),
|
|
280
|
+
null,
|
|
281
|
+
authService,
|
|
282
|
+
)
|
|
283
|
+
const controller = new ModelController(service, createUpdateValidator)
|
|
284
|
+
|
|
285
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
286
|
+
const upsertedRecord = await controller.upsert(input)
|
|
287
|
+
|
|
288
|
+
expect(upsertedRecord).toEqual(input)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('should allow bulkUpsert with create AND update permissions', async () => {
|
|
292
|
+
const createUpdateValidator = new AuthValidator(
|
|
293
|
+
getMockAuthSession({
|
|
294
|
+
claims: ['books::book.create:all', 'books::book.update:all'],
|
|
295
|
+
}),
|
|
296
|
+
null,
|
|
297
|
+
authService,
|
|
298
|
+
)
|
|
299
|
+
const controller = new ModelController(service, createUpdateValidator)
|
|
300
|
+
|
|
301
|
+
const inputs = [
|
|
302
|
+
{ id: 1, title: 'Book 1', author: 'Author 1', publishedDate: new Date() },
|
|
303
|
+
{ id: 2, title: 'Book 2', author: 'Author 2', publishedDate: new Date() },
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
const upsertedRecords = await controller.bulkUpsert(inputs)
|
|
307
|
+
|
|
308
|
+
expect(upsertedRecords).toEqual(inputs)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('should reject upsert with only create permission', async () => {
|
|
312
|
+
const createOnlyValidator = new AuthValidator(
|
|
313
|
+
getMockAuthSession({
|
|
314
|
+
claims: ['books::book.create:all'],
|
|
315
|
+
}),
|
|
316
|
+
null,
|
|
317
|
+
authService,
|
|
318
|
+
)
|
|
319
|
+
const controller = new ModelController(service, createOnlyValidator)
|
|
320
|
+
|
|
321
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
322
|
+
|
|
323
|
+
await expect(controller.upsert(input)).rejects.toThrow(PermissionError)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it('should reject upsert with only update permission', async () => {
|
|
327
|
+
const updateOnlyValidator = new AuthValidator(
|
|
328
|
+
getMockAuthSession({
|
|
329
|
+
claims: ['books::book.update:all'],
|
|
330
|
+
}),
|
|
331
|
+
null,
|
|
332
|
+
authService,
|
|
333
|
+
)
|
|
334
|
+
const controller = new ModelController(service, updateOnlyValidator)
|
|
335
|
+
|
|
336
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
337
|
+
|
|
338
|
+
await expect(controller.upsert(input)).rejects.toThrow(PermissionError)
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
it('should reject bulkUpsert with only create permission', async () => {
|
|
342
|
+
const createOnlyValidator = new AuthValidator(
|
|
343
|
+
getMockAuthSession({
|
|
344
|
+
claims: ['books::book.create:all'],
|
|
345
|
+
}),
|
|
346
|
+
null,
|
|
347
|
+
authService,
|
|
348
|
+
)
|
|
349
|
+
const controller = new ModelController(service, createOnlyValidator)
|
|
350
|
+
|
|
351
|
+
const inputs = [
|
|
352
|
+
{ id: 1, title: 'Book 1', author: 'Author 1', publishedDate: new Date() },
|
|
353
|
+
{ id: 2, title: 'Book 2', author: 'Author 2', publishedDate: new Date() },
|
|
354
|
+
]
|
|
355
|
+
|
|
356
|
+
await expect(controller.bulkUpsert(inputs)).rejects.toThrow(PermissionError)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it('should reject bulkUpsert with only update permission', async () => {
|
|
360
|
+
const updateOnlyValidator = new AuthValidator(
|
|
361
|
+
getMockAuthSession({
|
|
362
|
+
claims: ['books::book.update:all'],
|
|
363
|
+
}),
|
|
364
|
+
null,
|
|
365
|
+
authService,
|
|
366
|
+
)
|
|
367
|
+
const controller = new ModelController(service, updateOnlyValidator)
|
|
368
|
+
|
|
369
|
+
const inputs = [
|
|
370
|
+
{ id: 1, title: 'Book 1', author: 'Author 1', publishedDate: new Date() },
|
|
371
|
+
{ id: 2, title: 'Book 2', author: 'Author 2', publishedDate: new Date() },
|
|
372
|
+
]
|
|
373
|
+
|
|
374
|
+
await expect(controller.bulkUpsert(inputs)).rejects.toThrow(PermissionError)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('should allow upsert and bulkUpsert with write permission', async () => {
|
|
378
|
+
// This is already tested in the main tests, but including here for completeness
|
|
379
|
+
const controller = new ModelController(service, authValidator) // authValidator has write:all
|
|
380
|
+
|
|
381
|
+
// Test upsert
|
|
382
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
383
|
+
const upsertedRecord = await controller.upsert(input)
|
|
384
|
+
expect(upsertedRecord).toEqual(input)
|
|
385
|
+
|
|
386
|
+
// Test bulkUpsert
|
|
387
|
+
const inputs = [
|
|
388
|
+
{ id: 1, title: 'Book 1', author: 'Author 1', publishedDate: new Date() },
|
|
389
|
+
{ id: 2, title: 'Book 2', author: 'Author 2', publishedDate: new Date() },
|
|
390
|
+
]
|
|
391
|
+
const upsertedRecords = await controller.bulkUpsert(inputs)
|
|
392
|
+
expect(upsertedRecords).toEqual(inputs)
|
|
393
|
+
})
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
describe('granular permission testing', () => {
|
|
397
|
+
it('should allow create with specific create permission', async () => {
|
|
398
|
+
const createOnlyValidator = new AuthValidator(
|
|
399
|
+
getMockAuthSession({
|
|
400
|
+
claims: ['books::book.create:all'],
|
|
401
|
+
}),
|
|
402
|
+
null,
|
|
403
|
+
authService,
|
|
404
|
+
)
|
|
405
|
+
const controller = new ModelController(service, createOnlyValidator)
|
|
406
|
+
|
|
407
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
408
|
+
const record = await controller.create(input)
|
|
409
|
+
|
|
410
|
+
expect(record).toEqual(input)
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
it('should allow update with specific update permission', async () => {
|
|
414
|
+
const updateOnlyValidator = new AuthValidator(
|
|
415
|
+
getMockAuthSession({
|
|
416
|
+
claims: ['books::book.update:all'],
|
|
417
|
+
}),
|
|
418
|
+
null,
|
|
419
|
+
authService,
|
|
420
|
+
)
|
|
421
|
+
const controller = new ModelController(service, updateOnlyValidator)
|
|
422
|
+
|
|
423
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
424
|
+
await repository.create(input)
|
|
425
|
+
|
|
426
|
+
const updatedInput = { title: 'Updated Title', author: 'Updated Author', publishedDate: new Date() }
|
|
427
|
+
const updatedRecord = await controller.update({ id: 42 }, updatedInput)
|
|
428
|
+
|
|
429
|
+
expect(updatedRecord).toEqual({ id: 42, ...updatedInput })
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
it('should allow remove with specific remove permission', async () => {
|
|
433
|
+
const removeOnlyValidator = new AuthValidator(
|
|
434
|
+
getMockAuthSession({
|
|
435
|
+
claims: ['books::book.remove:all'],
|
|
436
|
+
}),
|
|
437
|
+
null,
|
|
438
|
+
authService,
|
|
439
|
+
)
|
|
440
|
+
const controller = new ModelController(service, removeOnlyValidator)
|
|
441
|
+
|
|
442
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
443
|
+
await repository.create(input)
|
|
444
|
+
|
|
445
|
+
const removedRecord = await controller.remove({ id: 42 })
|
|
446
|
+
|
|
447
|
+
expect(removedRecord).toEqual(input)
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
it('should allow restore with specific restore permission', async () => {
|
|
451
|
+
const restoreOnlyValidator = new AuthValidator(
|
|
452
|
+
getMockAuthSession({
|
|
453
|
+
claims: ['books::book.restore:all'],
|
|
454
|
+
}),
|
|
455
|
+
null,
|
|
456
|
+
authService,
|
|
457
|
+
)
|
|
458
|
+
const controller = new ModelController(service, restoreOnlyValidator)
|
|
459
|
+
|
|
460
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
461
|
+
await repository.create(input)
|
|
462
|
+
await service.remove({ id: 42 })
|
|
463
|
+
|
|
464
|
+
const restoredRecord = await controller.restore({ id: 42 })
|
|
465
|
+
|
|
466
|
+
expect(restoredRecord).toEqual(input)
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it('should reject operations with wrong namespace permissions', async () => {
|
|
470
|
+
const wrongNamespaceValidator = new AuthValidator(
|
|
471
|
+
getMockAuthSession({
|
|
472
|
+
claims: ['users::user.write:all'], // Wrong namespace
|
|
473
|
+
}),
|
|
474
|
+
null,
|
|
475
|
+
authService,
|
|
476
|
+
)
|
|
477
|
+
const controller = new ModelController(service, wrongNamespaceValidator)
|
|
478
|
+
|
|
479
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
480
|
+
|
|
481
|
+
await expect(controller.create(input)).rejects.toThrow(PermissionError)
|
|
482
|
+
await expect(controller.upsert(input)).rejects.toThrow(PermissionError)
|
|
483
|
+
await expect(controller.bulkUpsert([input])).rejects.toThrow(PermissionError)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
it('should reject operations with wrong resource permissions', async () => {
|
|
487
|
+
const wrongResourceValidator = new AuthValidator(
|
|
488
|
+
getMockAuthSession({
|
|
489
|
+
claims: ['books::author.write:all'], // Wrong resource
|
|
490
|
+
}),
|
|
491
|
+
null,
|
|
492
|
+
authService,
|
|
493
|
+
)
|
|
494
|
+
const controller = new ModelController(service, wrongResourceValidator)
|
|
495
|
+
|
|
496
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
497
|
+
|
|
498
|
+
await expect(controller.create(input)).rejects.toThrow(PermissionError)
|
|
499
|
+
await expect(controller.upsert(input)).rejects.toThrow(PermissionError)
|
|
500
|
+
await expect(controller.bulkUpsert([input])).rejects.toThrow(PermissionError)
|
|
501
|
+
})
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
describe('Trash Functionality', () => {
|
|
505
|
+
let permanentlyDeleteValidator: AuthValidator
|
|
506
|
+
let permanentlyDeleteFromTrashValidator: AuthValidator
|
|
507
|
+
let emptyTrashValidator: AuthValidator
|
|
508
|
+
|
|
509
|
+
beforeEach(() => {
|
|
510
|
+
permanentlyDeleteValidator = new AuthValidator(
|
|
511
|
+
getMockAuthSession({
|
|
512
|
+
claims: ['books::book.permanently-delete:all'],
|
|
513
|
+
}),
|
|
514
|
+
null,
|
|
515
|
+
authService,
|
|
516
|
+
)
|
|
517
|
+
permanentlyDeleteFromTrashValidator = new AuthValidator(
|
|
518
|
+
getMockAuthSession({
|
|
519
|
+
claims: ['books::book.permanently-delete-from-trash:all'],
|
|
520
|
+
}),
|
|
521
|
+
null,
|
|
522
|
+
authService,
|
|
523
|
+
)
|
|
524
|
+
emptyTrashValidator = new AuthValidator(
|
|
525
|
+
getMockAuthSession({
|
|
526
|
+
claims: ['books::book.empty-trash:all'],
|
|
527
|
+
}),
|
|
528
|
+
null,
|
|
529
|
+
authService,
|
|
530
|
+
)
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
describe('permanentlyDelete', () => {
|
|
534
|
+
it('should permanently delete a record if permissions are valid', async () => {
|
|
535
|
+
const controller = new ModelController(service, permanentlyDeleteValidator)
|
|
536
|
+
|
|
537
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
538
|
+
await repository.create(input)
|
|
539
|
+
|
|
540
|
+
const deletedRecord = await controller.permanentlyDelete({ id: 42 })
|
|
541
|
+
|
|
542
|
+
expect(deletedRecord).toEqual(input)
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
it('should throw PermissionError if permissions are invalid', async () => {
|
|
546
|
+
const controller = new ModelController(service, invalidAuthValidator)
|
|
547
|
+
|
|
548
|
+
await expect(controller.permanentlyDelete({ id: 42 })).rejects.toThrow(PermissionError)
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
it('should allow permanentlyDelete with specific permanently-delete permission', async () => {
|
|
552
|
+
const controller = new ModelController(service, permanentlyDeleteValidator)
|
|
553
|
+
|
|
554
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
555
|
+
await repository.create(input)
|
|
556
|
+
|
|
557
|
+
const deletedRecord = await controller.permanentlyDelete({ id: 42 })
|
|
558
|
+
|
|
559
|
+
expect(deletedRecord).toEqual(input)
|
|
560
|
+
})
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
describe('permanentlyDeleteFromTrash', () => {
|
|
564
|
+
it('should permanently delete from trash if permissions are valid', async () => {
|
|
565
|
+
const controller = new ModelController(service, permanentlyDeleteFromTrashValidator)
|
|
566
|
+
|
|
567
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
568
|
+
await repository.create(input)
|
|
569
|
+
await repository.remove({ id: 42 })
|
|
570
|
+
|
|
571
|
+
const deletedRecord = await controller.permanentlyDeleteFromTrash({ id: 42 })
|
|
572
|
+
|
|
573
|
+
expect(deletedRecord).toEqual(input)
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
it('should throw PermissionError if permissions are invalid', async () => {
|
|
577
|
+
const controller = new ModelController(service, invalidAuthValidator)
|
|
578
|
+
|
|
579
|
+
await expect(controller.permanentlyDeleteFromTrash({ id: 42 })).rejects.toThrow(PermissionError)
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
it('should allow with permanently-delete-from-trash permission', async () => {
|
|
583
|
+
const controller = new ModelController(service, permanentlyDeleteFromTrashValidator)
|
|
584
|
+
|
|
585
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
586
|
+
await repository.create(input)
|
|
587
|
+
await repository.remove({ id: 42 })
|
|
588
|
+
|
|
589
|
+
const deletedRecord = await controller.permanentlyDeleteFromTrash({ id: 42 })
|
|
590
|
+
|
|
591
|
+
expect(deletedRecord).toEqual(input)
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
it('should allow with permanently-delete permission', async () => {
|
|
595
|
+
const controller = new ModelController(service, permanentlyDeleteValidator)
|
|
596
|
+
|
|
597
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
598
|
+
await repository.create(input)
|
|
599
|
+
await repository.remove({ id: 42 })
|
|
600
|
+
|
|
601
|
+
const deletedRecord = await controller.permanentlyDeleteFromTrash({ id: 42 })
|
|
602
|
+
|
|
603
|
+
expect(deletedRecord).toEqual(input)
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
it('should allow with empty-trash permission', async () => {
|
|
607
|
+
const controller = new ModelController(service, emptyTrashValidator)
|
|
608
|
+
|
|
609
|
+
const input = { id: 42, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
610
|
+
await repository.create(input)
|
|
611
|
+
await repository.remove({ id: 42 })
|
|
612
|
+
|
|
613
|
+
const deletedRecord = await controller.permanentlyDeleteFromTrash({ id: 42 })
|
|
614
|
+
|
|
615
|
+
expect(deletedRecord).toEqual(input)
|
|
616
|
+
})
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
describe('emptyTrash', () => {
|
|
620
|
+
it('should empty trash if permissions are valid', async () => {
|
|
621
|
+
const controller = new ModelController(service, emptyTrashValidator)
|
|
622
|
+
|
|
623
|
+
const input1 = { id: 1, title: 'Test Book 1', author: 'Author Name', publishedDate: new Date() }
|
|
624
|
+
const input2 = { id: 2, title: 'Test Book 2', author: 'Author Name', publishedDate: new Date() }
|
|
625
|
+
await repository.create(input1)
|
|
626
|
+
await repository.create(input2)
|
|
627
|
+
await repository.remove({ id: 1 })
|
|
628
|
+
await repository.remove({ id: 2 })
|
|
629
|
+
|
|
630
|
+
const count = await controller.emptyTrash()
|
|
631
|
+
|
|
632
|
+
expect(count).toBe(2)
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
it('should throw PermissionError if permissions are invalid', async () => {
|
|
636
|
+
const controller = new ModelController(service, invalidAuthValidator)
|
|
637
|
+
|
|
638
|
+
await expect(controller.emptyTrash()).rejects.toThrow(PermissionError)
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
it('should empty trash with filters if permissions are valid', async () => {
|
|
642
|
+
const repositoryWithFilter = new MockMemoryRepository({
|
|
643
|
+
schema: mockSchema,
|
|
644
|
+
filter: (data, filters) => {
|
|
645
|
+
if (filters.text) {
|
|
646
|
+
return data.title.toLowerCase().includes(filters.text.toLowerCase())
|
|
647
|
+
}
|
|
648
|
+
return true
|
|
649
|
+
},
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
const serviceWithFilter = new ModelService({
|
|
653
|
+
repository: repositoryWithFilter,
|
|
654
|
+
emitter: new EventManager(),
|
|
655
|
+
namespace,
|
|
656
|
+
schema: mockSchema,
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
const controller = new ModelController(serviceWithFilter, emptyTrashValidator)
|
|
660
|
+
|
|
661
|
+
await repositoryWithFilter.create({
|
|
662
|
+
id: 1,
|
|
663
|
+
title: 'Test Book 1',
|
|
664
|
+
author: 'Author Name',
|
|
665
|
+
publishedDate: new Date(),
|
|
666
|
+
})
|
|
667
|
+
await repositoryWithFilter.create({
|
|
668
|
+
id: 2,
|
|
669
|
+
title: 'Other Book 2',
|
|
670
|
+
author: 'Author Name',
|
|
671
|
+
publishedDate: new Date(),
|
|
672
|
+
})
|
|
673
|
+
await repositoryWithFilter.remove({ id: 1 })
|
|
674
|
+
await repositoryWithFilter.remove({ id: 2 })
|
|
675
|
+
|
|
676
|
+
const count = await controller.emptyTrash({ text: 'Test' })
|
|
677
|
+
|
|
678
|
+
expect(count).toBe(1)
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
it('should allow emptyTrash with specific empty-trash permission', async () => {
|
|
682
|
+
const controller = new ModelController(service, emptyTrashValidator)
|
|
683
|
+
|
|
684
|
+
const input = { id: 1, title: 'Test Book', author: 'Author Name', publishedDate: new Date() }
|
|
685
|
+
await repository.create(input)
|
|
686
|
+
await repository.remove({ id: 1 })
|
|
687
|
+
|
|
688
|
+
const count = await controller.emptyTrash()
|
|
689
|
+
|
|
690
|
+
expect(count).toBe(1)
|
|
691
|
+
})
|
|
692
|
+
})
|
|
693
|
+
})
|
|
694
|
+
})
|