@mbc-cqrs-serverless/master 0.1.69-beta.0 → 0.1.71-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/master-data.controller.spec.d.ts +1 -0
- package/dist/controllers/master-data.controller.spec.js +341 -0
- package/dist/controllers/master-data.controller.spec.js.map +1 -0
- package/dist/controllers/master-setting.controller.d.ts +2 -1
- package/dist/controllers/master-setting.controller.js +12 -0
- package/dist/controllers/master-setting.controller.js.map +1 -1
- package/dist/controllers/master-setting.controller.spec.d.ts +1 -0
- package/dist/controllers/master-setting.controller.spec.js +164 -0
- package/dist/controllers/master-setting.controller.spec.js.map +1 -0
- package/dist/custom-task/custom-task.module.d.ts +2 -0
- package/dist/custom-task/custom-task.module.js +30 -0
- package/dist/custom-task/custom-task.module.js.map +1 -0
- package/dist/custom-task/event/task-queue-event-factory.d.ts +6 -0
- package/dist/custom-task/event/task-queue-event-factory.js +23 -0
- package/dist/custom-task/event/task-queue-event-factory.js.map +1 -0
- package/dist/custom-task/my-task.controller.d.ts +9 -0
- package/dist/custom-task/my-task.controller.js +56 -0
- package/dist/custom-task/my-task.controller.js.map +1 -0
- package/dist/custom-task/my-task.controller.spec.d.ts +1 -0
- package/dist/custom-task/my-task.controller.spec.js +147 -0
- package/dist/custom-task/my-task.controller.spec.js.map +1 -0
- package/dist/custom-task/my-task.service.d.ts +12 -0
- package/dist/custom-task/my-task.service.js +76 -0
- package/dist/custom-task/my-task.service.js.map +1 -0
- package/dist/custom-task/my-task.service.spec.d.ts +1 -0
- package/dist/custom-task/my-task.service.spec.js +111 -0
- package/dist/custom-task/my-task.service.spec.js.map +1 -0
- package/dist/handler/master-sfn-task.event.d.ts +3 -0
- package/dist/handler/master-sfn-task.event.js +8 -0
- package/dist/handler/master-sfn-task.event.js.map +1 -0
- package/dist/handler/master-sfn-task.handler.d.ts +24 -0
- package/dist/handler/master-sfn-task.handler.js +242 -0
- package/dist/handler/master-sfn-task.handler.js.map +1 -0
- package/dist/helpers/index.d.ts +7 -0
- package/dist/helpers/index.js +15 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/master.module.js +9 -0
- package/dist/master.module.js.map +1 -1
- package/dist/services/master-data.service.spec.d.ts +1 -0
- package/dist/services/master-data.service.spec.js +1385 -0
- package/dist/services/master-data.service.spec.js.map +1 -0
- package/dist/services/master-setting.service.d.ts +7 -2
- package/dist/services/master-setting.service.js +29 -2
- package/dist/services/master-setting.service.js.map +1 -1
- package/dist/services/master-setting.service.spec.js +754 -27
- package/dist/services/master-setting.service.spec.js.map +1 -1
- package/dist/update-scheme.js +1 -10
- package/dist/update-scheme.js.map +1 -1
- package/package.json +5 -5
- package/src/templates/master/handler/master-rds.handler.ts +0 -4
- package/src/templates/master/master.module.ts +2 -9
- package/src/templates/custom-task/custom-task.module.ts +0 -18
- package/src/templates/custom-task/event/task-queue-event-factory.ts +0 -26
- package/src/templates/custom-task/my-task.controller.ts +0 -28
- package/src/templates/custom-task/my-task.service.ts +0 -99
- package/src/templates/master/handler/master-sfn-task.event.ts +0 -3
- package/src/templates/master/handler/master-sfn-task.handler.ts +0 -332
- package/src/templates/master/master-setting.controller.ts +0 -23
- package/src/templates/master/master-setting.service.ts +0 -55
|
@@ -0,0 +1,1385 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const testing_1 = require("@nestjs/testing");
|
|
4
|
+
const ts_jest_1 = require("@golevelup/ts-jest");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const master_data_service_1 = require("./master-data.service");
|
|
7
|
+
const core_1 = require("@mbc-cqrs-serverless/core");
|
|
8
|
+
const master_module_definition_1 = require("../master.module-definition");
|
|
9
|
+
const entities_1 = require("../entities");
|
|
10
|
+
const dto_1 = require("../dto");
|
|
11
|
+
jest.mock('@mbc-cqrs-serverless/core', () => ({
|
|
12
|
+
...jest.requireActual('@mbc-cqrs-serverless/core'),
|
|
13
|
+
getUserContext: jest.fn(),
|
|
14
|
+
}));
|
|
15
|
+
describe('MasterDataService', () => {
|
|
16
|
+
let service;
|
|
17
|
+
let prismaService;
|
|
18
|
+
let commandService;
|
|
19
|
+
let dataService;
|
|
20
|
+
let mockGetUserContext;
|
|
21
|
+
const mockUserContext = {
|
|
22
|
+
tenantCode: 'TEST_TENANT',
|
|
23
|
+
userId: 'test-user-id',
|
|
24
|
+
tenantRole: 'ADMIN',
|
|
25
|
+
};
|
|
26
|
+
const mockInvokeContext = {
|
|
27
|
+
context: { awsRequestId: 'test-request-id' },
|
|
28
|
+
event: { requestContext: { http: { sourceIp: '127.0.0.1' } } },
|
|
29
|
+
};
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
const mockPrismaService = {
|
|
32
|
+
master: {
|
|
33
|
+
findMany: jest.fn(),
|
|
34
|
+
count: jest.fn(),
|
|
35
|
+
aggregate: jest.fn(),
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
mockGetUserContext = core_1.getUserContext;
|
|
39
|
+
mockGetUserContext.mockReturnValue(mockUserContext);
|
|
40
|
+
const module = await testing_1.Test.createTestingModule({
|
|
41
|
+
providers: [
|
|
42
|
+
master_data_service_1.MasterDataService,
|
|
43
|
+
{
|
|
44
|
+
provide: master_module_definition_1.PRISMA_SERVICE,
|
|
45
|
+
useValue: mockPrismaService,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
provide: core_1.CommandService,
|
|
49
|
+
useValue: (0, ts_jest_1.createMock)(),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
provide: core_1.DataService,
|
|
53
|
+
useValue: (0, ts_jest_1.createMock)(),
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
}).compile();
|
|
57
|
+
service = module.get(master_data_service_1.MasterDataService);
|
|
58
|
+
prismaService = module.get(master_module_definition_1.PRISMA_SERVICE);
|
|
59
|
+
commandService = module.get(core_1.CommandService);
|
|
60
|
+
dataService = module.get(core_1.DataService);
|
|
61
|
+
});
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
jest.clearAllMocks();
|
|
64
|
+
});
|
|
65
|
+
it('should be defined', () => {
|
|
66
|
+
expect(service).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
describe('listByRds', () => {
|
|
69
|
+
it('should list master data by RDS with search criteria', async () => {
|
|
70
|
+
const searchDto = {
|
|
71
|
+
keyword: 'test',
|
|
72
|
+
code: 'TEST_CODE',
|
|
73
|
+
settingCode: 'SETTING_CODE',
|
|
74
|
+
pageSize: 10,
|
|
75
|
+
page: 1,
|
|
76
|
+
orderBys: ['seq', 'masterCode'],
|
|
77
|
+
};
|
|
78
|
+
const mockDeletedSettings = [{ masterCode: 'DELETED_CODE' }];
|
|
79
|
+
const mockItems = [
|
|
80
|
+
{
|
|
81
|
+
id: 'test-id-1',
|
|
82
|
+
masterCode: 'CODE1',
|
|
83
|
+
name: 'Test Item 1',
|
|
84
|
+
tenantCode: 'TEST_TENANT',
|
|
85
|
+
masterType: 'DATA',
|
|
86
|
+
seq: 1,
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
prismaService.master.findMany
|
|
90
|
+
.mockResolvedValueOnce(mockDeletedSettings)
|
|
91
|
+
.mockResolvedValueOnce(mockItems);
|
|
92
|
+
prismaService.master.count.mockResolvedValue(1);
|
|
93
|
+
const result = await service.listByRds(searchDto, { invokeContext: mockInvokeContext });
|
|
94
|
+
expect(result).toBeInstanceOf(dto_1.MasterRdsListEntity);
|
|
95
|
+
expect(result.total).toBe(1);
|
|
96
|
+
expect(result.items).toHaveLength(1);
|
|
97
|
+
expect(result.items[0]).toBeInstanceOf(dto_1.MasterRdsEntity);
|
|
98
|
+
expect(prismaService.master.findMany).toHaveBeenCalledTimes(2);
|
|
99
|
+
expect(prismaService.master.count).toHaveBeenCalledTimes(1);
|
|
100
|
+
});
|
|
101
|
+
it('should handle empty search results', async () => {
|
|
102
|
+
const searchDto = {
|
|
103
|
+
pageSize: 10,
|
|
104
|
+
page: 1,
|
|
105
|
+
};
|
|
106
|
+
prismaService.master.findMany
|
|
107
|
+
.mockResolvedValueOnce([])
|
|
108
|
+
.mockResolvedValueOnce([]);
|
|
109
|
+
prismaService.master.count.mockResolvedValue(0);
|
|
110
|
+
const result = await service.listByRds(searchDto, { invokeContext: mockInvokeContext });
|
|
111
|
+
expect(result.total).toBe(0);
|
|
112
|
+
expect(result.items).toHaveLength(0);
|
|
113
|
+
});
|
|
114
|
+
it('should filter by deleted settings', async () => {
|
|
115
|
+
const searchDto = {
|
|
116
|
+
isDeleted: false,
|
|
117
|
+
pageSize: 10,
|
|
118
|
+
page: 1,
|
|
119
|
+
};
|
|
120
|
+
const mockDeletedSettings = [
|
|
121
|
+
{ masterCode: 'DELETED_CODE1' },
|
|
122
|
+
{ masterCode: 'DELETED_CODE2' },
|
|
123
|
+
];
|
|
124
|
+
prismaService.master.findMany
|
|
125
|
+
.mockResolvedValueOnce(mockDeletedSettings)
|
|
126
|
+
.mockResolvedValueOnce([]);
|
|
127
|
+
prismaService.master.count.mockResolvedValue(0);
|
|
128
|
+
await service.listByRds(searchDto, { invokeContext: mockInvokeContext });
|
|
129
|
+
expect(prismaService.master.findMany).toHaveBeenCalledWith(expect.objectContaining({
|
|
130
|
+
where: expect.objectContaining({
|
|
131
|
+
masterTypeCode: {
|
|
132
|
+
notIn: ['DELETED_CODE1', 'DELETED_CODE2'],
|
|
133
|
+
},
|
|
134
|
+
isDeleted: false,
|
|
135
|
+
}),
|
|
136
|
+
}));
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe('list', () => {
|
|
140
|
+
it('should list master data by tenant and setting code', async () => {
|
|
141
|
+
const searchDto = {
|
|
142
|
+
tenantCode: 'TEST_TENANT',
|
|
143
|
+
settingCode: 'SETTING_CODE',
|
|
144
|
+
};
|
|
145
|
+
const mockResponse = {
|
|
146
|
+
items: [
|
|
147
|
+
new core_1.DataEntity({
|
|
148
|
+
pk: 'MASTER#TEST_TENANT',
|
|
149
|
+
sk: 'SETTING_CODE#DATA1',
|
|
150
|
+
id: 'test-id',
|
|
151
|
+
code: 'DATA1',
|
|
152
|
+
name: 'Test Data',
|
|
153
|
+
version: 1,
|
|
154
|
+
type: 'MASTER',
|
|
155
|
+
tenantCode: 'TEST_TENANT',
|
|
156
|
+
createdAt: new Date(),
|
|
157
|
+
updatedAt: new Date(),
|
|
158
|
+
}),
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
dataService.listItemsByPk.mockResolvedValue(mockResponse);
|
|
162
|
+
const result = await service.list(searchDto);
|
|
163
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataListEntity);
|
|
164
|
+
expect(dataService.listItemsByPk).toHaveBeenCalledWith('MASTER#TEST_TENANT', expect.objectContaining({
|
|
165
|
+
sk: expect.objectContaining({
|
|
166
|
+
skExpession: 'begins_with(sk, :settingCode)',
|
|
167
|
+
skAttributeValues: {
|
|
168
|
+
':settingCode': 'SETTING_CODE#',
|
|
169
|
+
},
|
|
170
|
+
}),
|
|
171
|
+
}));
|
|
172
|
+
});
|
|
173
|
+
it('should use COMMON tenant when tenantCode is not provided', async () => {
|
|
174
|
+
const searchDto = {
|
|
175
|
+
settingCode: 'SETTING_CODE',
|
|
176
|
+
};
|
|
177
|
+
const mockResponse = {
|
|
178
|
+
items: [],
|
|
179
|
+
};
|
|
180
|
+
dataService.listItemsByPk.mockResolvedValue(mockResponse);
|
|
181
|
+
await service.list(searchDto);
|
|
182
|
+
expect(dataService.listItemsByPk).toHaveBeenCalledWith('MASTER#COMMON', expect.any(Object));
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
describe('get', () => {
|
|
186
|
+
it('should get master data by key', async () => {
|
|
187
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
188
|
+
const mockData = {
|
|
189
|
+
pk: key.pk,
|
|
190
|
+
sk: key.sk,
|
|
191
|
+
id: 'test-id',
|
|
192
|
+
code: 'DATA1',
|
|
193
|
+
name: 'Test Data',
|
|
194
|
+
version: 1,
|
|
195
|
+
type: 'MASTER',
|
|
196
|
+
tenantCode: 'TEST_TENANT',
|
|
197
|
+
createdAt: new Date(),
|
|
198
|
+
updatedAt: new Date(),
|
|
199
|
+
};
|
|
200
|
+
dataService.getItem.mockResolvedValue(mockData);
|
|
201
|
+
const result = await service.get(key);
|
|
202
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
203
|
+
expect(dataService.getItem).toHaveBeenCalledWith(key);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe('create', () => {
|
|
207
|
+
it('should create new master data when not exists', async () => {
|
|
208
|
+
const createDto = {
|
|
209
|
+
settingCode: 'SETTING_CODE',
|
|
210
|
+
code: 'DATA_CODE',
|
|
211
|
+
tenantCode: 'TEST_TENANT',
|
|
212
|
+
name: 'Test Data',
|
|
213
|
+
seq: 1,
|
|
214
|
+
attributes: { key: 'value' },
|
|
215
|
+
};
|
|
216
|
+
dataService.getItem.mockResolvedValue(null);
|
|
217
|
+
const mockCommandResult = {
|
|
218
|
+
id: 'MASTER#TEST_TENANT#SETTING_CODE#DATA_CODE',
|
|
219
|
+
pk: 'MASTER#TEST_TENANT',
|
|
220
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
221
|
+
version: 1,
|
|
222
|
+
type: 'MASTER',
|
|
223
|
+
tenantCode: 'TEST_TENANT',
|
|
224
|
+
name: createDto.name,
|
|
225
|
+
code: createDto.code,
|
|
226
|
+
seq: createDto.seq,
|
|
227
|
+
attributes: createDto.attributes,
|
|
228
|
+
isDeleted: false,
|
|
229
|
+
createdAt: new Date(),
|
|
230
|
+
updatedAt: new Date(),
|
|
231
|
+
};
|
|
232
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
233
|
+
const result = await service.create(createDto, { invokeContext: mockInvokeContext });
|
|
234
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
235
|
+
expect(commandService.publishAsync).toHaveBeenCalledWith(expect.objectContaining({
|
|
236
|
+
id: 'MASTER#TEST_TENANT#SETTING_CODE#DATA_CODE',
|
|
237
|
+
pk: 'MASTER#TEST_TENANT',
|
|
238
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
239
|
+
version: 0,
|
|
240
|
+
type: 'MASTER',
|
|
241
|
+
isDeleted: false,
|
|
242
|
+
code: createDto.code,
|
|
243
|
+
name: createDto.name,
|
|
244
|
+
seq: createDto.seq,
|
|
245
|
+
attributes: createDto.attributes,
|
|
246
|
+
settingCode: 'SETTING_CODE',
|
|
247
|
+
tenantCode: 'TEST_TENANT',
|
|
248
|
+
}), { invokeContext: mockInvokeContext });
|
|
249
|
+
});
|
|
250
|
+
it('should throw BadRequestException when data already exists', async () => {
|
|
251
|
+
const createDto = {
|
|
252
|
+
settingCode: 'SETTING_CODE',
|
|
253
|
+
code: 'DATA_CODE',
|
|
254
|
+
tenantCode: 'TEST_TENANT',
|
|
255
|
+
name: 'Test Data',
|
|
256
|
+
seq: 1,
|
|
257
|
+
};
|
|
258
|
+
const existingData = {
|
|
259
|
+
id: 'existing-id',
|
|
260
|
+
pk: 'MASTER#TEST_TENANT',
|
|
261
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
262
|
+
code: 'DATA_CODE',
|
|
263
|
+
name: 'Existing Data',
|
|
264
|
+
version: 1,
|
|
265
|
+
type: 'MASTER',
|
|
266
|
+
tenantCode: 'TEST_TENANT',
|
|
267
|
+
isDeleted: false,
|
|
268
|
+
createdAt: new Date(),
|
|
269
|
+
updatedAt: new Date(),
|
|
270
|
+
};
|
|
271
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
272
|
+
await expect(service.create(createDto, { invokeContext: mockInvokeContext }))
|
|
273
|
+
.rejects.toThrow(common_1.BadRequestException);
|
|
274
|
+
expect(commandService.publishAsync).not.toHaveBeenCalled();
|
|
275
|
+
});
|
|
276
|
+
it('should create when existing data is deleted', async () => {
|
|
277
|
+
const createDto = {
|
|
278
|
+
settingCode: 'SETTING_CODE',
|
|
279
|
+
code: 'DATA_CODE',
|
|
280
|
+
tenantCode: 'TEST_TENANT',
|
|
281
|
+
name: 'Test Data',
|
|
282
|
+
seq: 1,
|
|
283
|
+
};
|
|
284
|
+
const existingDeletedData = {
|
|
285
|
+
id: 'existing-id',
|
|
286
|
+
pk: 'MASTER#TEST_TENANT',
|
|
287
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
288
|
+
code: 'DATA_CODE',
|
|
289
|
+
name: 'Deleted Data',
|
|
290
|
+
version: 2,
|
|
291
|
+
type: 'MASTER',
|
|
292
|
+
tenantCode: 'TEST_TENANT',
|
|
293
|
+
isDeleted: true,
|
|
294
|
+
createdAt: new Date(),
|
|
295
|
+
updatedAt: new Date(),
|
|
296
|
+
};
|
|
297
|
+
dataService.getItem.mockResolvedValue(existingDeletedData);
|
|
298
|
+
const mockCommandResult = {
|
|
299
|
+
id: 'MASTER#TEST_TENANT#SETTING_CODE#DATA_CODE',
|
|
300
|
+
pk: 'MASTER#TEST_TENANT',
|
|
301
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
302
|
+
version: 2,
|
|
303
|
+
type: 'MASTER',
|
|
304
|
+
tenantCode: 'TEST_TENANT',
|
|
305
|
+
name: createDto.name,
|
|
306
|
+
code: createDto.code,
|
|
307
|
+
seq: createDto.seq,
|
|
308
|
+
attributes: createDto.attributes,
|
|
309
|
+
isDeleted: false,
|
|
310
|
+
createdAt: new Date(),
|
|
311
|
+
updatedAt: new Date(),
|
|
312
|
+
};
|
|
313
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
314
|
+
const result = await service.create(createDto, { invokeContext: mockInvokeContext });
|
|
315
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
316
|
+
expect(commandService.publishAsync).toHaveBeenCalledWith(expect.objectContaining({
|
|
317
|
+
version: 2,
|
|
318
|
+
}), { invokeContext: mockInvokeContext });
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
describe('update', () => {
|
|
322
|
+
it('should update existing master data', async () => {
|
|
323
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
324
|
+
const updateDto = {
|
|
325
|
+
name: 'Updated Name',
|
|
326
|
+
seq: 2,
|
|
327
|
+
attributes: { updated: true },
|
|
328
|
+
};
|
|
329
|
+
const existingData = {
|
|
330
|
+
id: 'test-id',
|
|
331
|
+
pk: key.pk,
|
|
332
|
+
sk: key.sk,
|
|
333
|
+
version: 1,
|
|
334
|
+
code: 'DATA1',
|
|
335
|
+
name: 'Original Name',
|
|
336
|
+
seq: 1,
|
|
337
|
+
type: 'MASTER',
|
|
338
|
+
tenantCode: 'TEST_TENANT',
|
|
339
|
+
isDeleted: false,
|
|
340
|
+
attributes: { original: true },
|
|
341
|
+
createdAt: new Date(),
|
|
342
|
+
updatedAt: new Date(),
|
|
343
|
+
};
|
|
344
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
345
|
+
const mockUpdateResult = {
|
|
346
|
+
...existingData,
|
|
347
|
+
name: updateDto.name,
|
|
348
|
+
seq: updateDto.seq,
|
|
349
|
+
attributes: updateDto.attributes,
|
|
350
|
+
updatedAt: new Date(),
|
|
351
|
+
};
|
|
352
|
+
commandService.publishPartialUpdateAsync.mockResolvedValue(mockUpdateResult);
|
|
353
|
+
const result = await service.update(key, updateDto, { invokeContext: mockInvokeContext });
|
|
354
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
355
|
+
expect(commandService.publishPartialUpdateAsync).toHaveBeenCalledWith(expect.objectContaining({
|
|
356
|
+
id: existingData.id,
|
|
357
|
+
pk: existingData.pk,
|
|
358
|
+
sk: existingData.sk,
|
|
359
|
+
version: existingData.version,
|
|
360
|
+
name: updateDto.name,
|
|
361
|
+
seq: updateDto.seq,
|
|
362
|
+
attributes: updateDto.attributes,
|
|
363
|
+
}), { invokeContext: mockInvokeContext });
|
|
364
|
+
});
|
|
365
|
+
it('should throw NotFoundException when data does not exist', async () => {
|
|
366
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
367
|
+
const updateDto = {
|
|
368
|
+
name: 'Updated Name',
|
|
369
|
+
};
|
|
370
|
+
dataService.getItem.mockResolvedValue(null);
|
|
371
|
+
await expect(service.update(key, updateDto, { invokeContext: mockInvokeContext }))
|
|
372
|
+
.rejects.toThrow(common_1.NotFoundException);
|
|
373
|
+
expect(commandService.publishPartialUpdateAsync).not.toHaveBeenCalled();
|
|
374
|
+
});
|
|
375
|
+
it('should preserve existing values when update fields are not provided', async () => {
|
|
376
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
377
|
+
const updateDto = {
|
|
378
|
+
seq: 2,
|
|
379
|
+
};
|
|
380
|
+
const existingData = {
|
|
381
|
+
id: 'test-id',
|
|
382
|
+
pk: key.pk,
|
|
383
|
+
sk: key.sk,
|
|
384
|
+
version: 1,
|
|
385
|
+
code: 'DATA1',
|
|
386
|
+
name: 'Original Name',
|
|
387
|
+
seq: 1,
|
|
388
|
+
type: 'MASTER',
|
|
389
|
+
tenantCode: 'TEST_TENANT',
|
|
390
|
+
isDeleted: false,
|
|
391
|
+
attributes: { original: true },
|
|
392
|
+
createdAt: new Date(),
|
|
393
|
+
updatedAt: new Date(),
|
|
394
|
+
};
|
|
395
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
396
|
+
const mockUpdateResult = {
|
|
397
|
+
...existingData,
|
|
398
|
+
seq: updateDto.seq,
|
|
399
|
+
updatedAt: new Date(),
|
|
400
|
+
};
|
|
401
|
+
commandService.publishPartialUpdateAsync.mockResolvedValue(mockUpdateResult);
|
|
402
|
+
await service.update(key, updateDto, { invokeContext: mockInvokeContext });
|
|
403
|
+
expect(commandService.publishPartialUpdateAsync).toHaveBeenCalledWith(expect.objectContaining({
|
|
404
|
+
name: existingData.name,
|
|
405
|
+
seq: updateDto.seq,
|
|
406
|
+
isDeleted: existingData.isDeleted,
|
|
407
|
+
attributes: existingData.attributes,
|
|
408
|
+
}), { invokeContext: mockInvokeContext });
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
describe('delete', () => {
|
|
412
|
+
it('should soft delete existing master data', async () => {
|
|
413
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
414
|
+
const existingData = {
|
|
415
|
+
id: 'test-id',
|
|
416
|
+
pk: key.pk,
|
|
417
|
+
sk: key.sk,
|
|
418
|
+
version: 1,
|
|
419
|
+
code: 'DATA1',
|
|
420
|
+
name: 'Test Data',
|
|
421
|
+
type: 'MASTER',
|
|
422
|
+
tenantCode: 'TEST_TENANT',
|
|
423
|
+
isDeleted: false,
|
|
424
|
+
createdAt: new Date(),
|
|
425
|
+
updatedAt: new Date(),
|
|
426
|
+
};
|
|
427
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
428
|
+
const mockDeleteResult = {
|
|
429
|
+
...existingData,
|
|
430
|
+
isDeleted: true,
|
|
431
|
+
updatedAt: new Date(),
|
|
432
|
+
};
|
|
433
|
+
commandService.publishPartialUpdateAsync.mockResolvedValue(mockDeleteResult);
|
|
434
|
+
const result = await service.delete(key, { invokeContext: mockInvokeContext });
|
|
435
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
436
|
+
expect(commandService.publishPartialUpdateAsync).toHaveBeenCalledWith(expect.objectContaining({
|
|
437
|
+
...existingData,
|
|
438
|
+
isDeleted: true,
|
|
439
|
+
}), { invokeContext: mockInvokeContext });
|
|
440
|
+
});
|
|
441
|
+
it('should throw NotFoundException when data does not exist', async () => {
|
|
442
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
443
|
+
dataService.getItem.mockResolvedValue(null);
|
|
444
|
+
await expect(service.delete(key, { invokeContext: mockInvokeContext }))
|
|
445
|
+
.rejects.toThrow(common_1.NotFoundException);
|
|
446
|
+
expect(commandService.publishPartialUpdateAsync).not.toHaveBeenCalled();
|
|
447
|
+
});
|
|
448
|
+
it('should throw BadRequestException when data is already deleted', async () => {
|
|
449
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
450
|
+
const existingData = {
|
|
451
|
+
id: 'test-id',
|
|
452
|
+
pk: key.pk,
|
|
453
|
+
sk: key.sk,
|
|
454
|
+
code: 'DATA1',
|
|
455
|
+
name: 'Test Data',
|
|
456
|
+
version: 1,
|
|
457
|
+
type: 'MASTER',
|
|
458
|
+
tenantCode: 'TEST_TENANT',
|
|
459
|
+
isDeleted: true,
|
|
460
|
+
createdAt: new Date(),
|
|
461
|
+
updatedAt: new Date(),
|
|
462
|
+
};
|
|
463
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
464
|
+
await expect(service.delete(key, { invokeContext: mockInvokeContext }))
|
|
465
|
+
.rejects.toThrow(common_1.BadRequestException);
|
|
466
|
+
expect(commandService.publishPartialUpdateAsync).not.toHaveBeenCalled();
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
describe('checkExistCode', () => {
|
|
470
|
+
it('should return true when code exists and is not deleted', async () => {
|
|
471
|
+
const mockData = {
|
|
472
|
+
id: 'test-id',
|
|
473
|
+
pk: 'MASTER#TEST_TENANT',
|
|
474
|
+
sk: 'SETTING_TYPE#CODE1',
|
|
475
|
+
code: 'CODE1',
|
|
476
|
+
name: 'Test Data',
|
|
477
|
+
version: 1,
|
|
478
|
+
type: 'MASTER',
|
|
479
|
+
tenantCode: 'TEST_TENANT',
|
|
480
|
+
isDeleted: false,
|
|
481
|
+
createdAt: new Date(),
|
|
482
|
+
updatedAt: new Date(),
|
|
483
|
+
};
|
|
484
|
+
dataService.getItem.mockResolvedValue(mockData);
|
|
485
|
+
const result = await service.checkExistCode('TEST_TENANT', 'SETTING_TYPE', 'CODE1');
|
|
486
|
+
expect(result).toBe(true);
|
|
487
|
+
expect(dataService.getItem).toHaveBeenCalledWith({
|
|
488
|
+
pk: 'MASTER#TEST_TENANT',
|
|
489
|
+
sk: 'SETTING_TYPE#CODE1',
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
it('should return false when code does not exist', async () => {
|
|
493
|
+
dataService.getItem.mockResolvedValue(null);
|
|
494
|
+
const result = await service.checkExistCode('TEST_TENANT', 'SETTING_TYPE', 'CODE1');
|
|
495
|
+
expect(result).toBe(false);
|
|
496
|
+
});
|
|
497
|
+
it('should return false when code exists but is deleted', async () => {
|
|
498
|
+
const mockData = {
|
|
499
|
+
id: 'test-id',
|
|
500
|
+
pk: 'MASTER#TEST_TENANT',
|
|
501
|
+
sk: 'SETTING_TYPE#CODE1',
|
|
502
|
+
code: 'CODE1',
|
|
503
|
+
name: 'Test Data',
|
|
504
|
+
version: 1,
|
|
505
|
+
type: 'MASTER',
|
|
506
|
+
tenantCode: 'TEST_TENANT',
|
|
507
|
+
isDeleted: true,
|
|
508
|
+
createdAt: new Date(),
|
|
509
|
+
updatedAt: new Date(),
|
|
510
|
+
};
|
|
511
|
+
dataService.getItem.mockResolvedValue(mockData);
|
|
512
|
+
const result = await service.checkExistCode('TEST_TENANT', 'SETTING_TYPE', 'CODE1');
|
|
513
|
+
expect(result).toBe(false);
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
describe('getDetail', () => {
|
|
517
|
+
it('should get master data detail', async () => {
|
|
518
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
519
|
+
const mockData = {
|
|
520
|
+
pk: key.pk,
|
|
521
|
+
sk: key.sk,
|
|
522
|
+
id: 'test-id',
|
|
523
|
+
code: 'DATA1',
|
|
524
|
+
name: 'Test Data',
|
|
525
|
+
version: 1,
|
|
526
|
+
type: 'MASTER',
|
|
527
|
+
tenantCode: 'TEST_TENANT',
|
|
528
|
+
createdAt: new Date(),
|
|
529
|
+
updatedAt: new Date(),
|
|
530
|
+
};
|
|
531
|
+
dataService.getItem.mockResolvedValue(mockData);
|
|
532
|
+
const result = await service.getDetail(key);
|
|
533
|
+
expect(result).toBeInstanceOf(dto_1.MasterRdsEntity);
|
|
534
|
+
expect(dataService.getItem).toHaveBeenCalledWith(key);
|
|
535
|
+
});
|
|
536
|
+
it('should throw NotFoundException when data does not exist', async () => {
|
|
537
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
538
|
+
dataService.getItem.mockResolvedValue(null);
|
|
539
|
+
await expect(service.getDetail(key)).rejects.toThrow(common_1.NotFoundException);
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
describe('createSetting', () => {
|
|
543
|
+
it('should create setting with auto-generated sequence', async () => {
|
|
544
|
+
const createDto = {
|
|
545
|
+
settingCode: 'SETTING_CODE',
|
|
546
|
+
name: 'Test Setting',
|
|
547
|
+
attributes: { key: 'value' },
|
|
548
|
+
};
|
|
549
|
+
prismaService.master.aggregate.mockResolvedValue({
|
|
550
|
+
_max: { seq: 5 },
|
|
551
|
+
});
|
|
552
|
+
dataService.getItem.mockResolvedValue(null);
|
|
553
|
+
const mockCommandResult = {
|
|
554
|
+
id: 'generated-id',
|
|
555
|
+
pk: `MASTER#${mockUserContext.tenantCode}`,
|
|
556
|
+
sk: `${createDto.settingCode}#generated-ulid`,
|
|
557
|
+
version: 1,
|
|
558
|
+
type: 'MASTER',
|
|
559
|
+
tenantCode: mockUserContext.tenantCode,
|
|
560
|
+
code: 'generated-ulid',
|
|
561
|
+
name: createDto.name,
|
|
562
|
+
seq: 6,
|
|
563
|
+
attributes: createDto.attributes,
|
|
564
|
+
isDeleted: false,
|
|
565
|
+
createdAt: new Date(),
|
|
566
|
+
updatedAt: new Date(),
|
|
567
|
+
};
|
|
568
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
569
|
+
const result = await service.createSetting(createDto, mockInvokeContext);
|
|
570
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
571
|
+
expect(prismaService.master.aggregate).toHaveBeenCalledWith({
|
|
572
|
+
_max: { seq: true },
|
|
573
|
+
where: {
|
|
574
|
+
tenantCode: mockUserContext.tenantCode,
|
|
575
|
+
masterType: 'MASTER_DATA',
|
|
576
|
+
masterTypeCode: createDto.settingCode,
|
|
577
|
+
},
|
|
578
|
+
});
|
|
579
|
+
expect(createDto.attributes['seq']).toBe(6);
|
|
580
|
+
});
|
|
581
|
+
it('should create setting with provided sequence', async () => {
|
|
582
|
+
const createDto = {
|
|
583
|
+
settingCode: 'SETTING_CODE',
|
|
584
|
+
name: 'Test Setting',
|
|
585
|
+
seq: 10,
|
|
586
|
+
attributes: { key: 'value' },
|
|
587
|
+
};
|
|
588
|
+
dataService.getItem.mockResolvedValue(null);
|
|
589
|
+
const mockCommandResult = {
|
|
590
|
+
id: 'generated-id',
|
|
591
|
+
pk: `MASTER#${mockUserContext.tenantCode}`,
|
|
592
|
+
sk: `${createDto.settingCode}#generated-ulid`,
|
|
593
|
+
version: 1,
|
|
594
|
+
type: 'MASTER',
|
|
595
|
+
tenantCode: mockUserContext.tenantCode,
|
|
596
|
+
code: 'generated-ulid',
|
|
597
|
+
name: createDto.name,
|
|
598
|
+
seq: 10,
|
|
599
|
+
attributes: createDto.attributes,
|
|
600
|
+
isDeleted: false,
|
|
601
|
+
createdAt: new Date(),
|
|
602
|
+
updatedAt: new Date(),
|
|
603
|
+
};
|
|
604
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
605
|
+
const result = await service.createSetting(createDto, mockInvokeContext);
|
|
606
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
607
|
+
expect(prismaService.master.aggregate).not.toHaveBeenCalled();
|
|
608
|
+
});
|
|
609
|
+
it('should handle first sequence when no existing data', async () => {
|
|
610
|
+
const createDto = {
|
|
611
|
+
settingCode: 'SETTING_CODE',
|
|
612
|
+
name: 'Test Setting',
|
|
613
|
+
attributes: { key: 'value' },
|
|
614
|
+
};
|
|
615
|
+
prismaService.master.aggregate.mockResolvedValue({
|
|
616
|
+
_max: { seq: null },
|
|
617
|
+
});
|
|
618
|
+
dataService.getItem.mockResolvedValue(null);
|
|
619
|
+
const mockCommandResult = {
|
|
620
|
+
id: 'generated-id',
|
|
621
|
+
pk: `MASTER#${mockUserContext.tenantCode}`,
|
|
622
|
+
sk: `${createDto.settingCode}#generated-ulid`,
|
|
623
|
+
version: 1,
|
|
624
|
+
type: 'MASTER',
|
|
625
|
+
tenantCode: mockUserContext.tenantCode,
|
|
626
|
+
code: 'generated-ulid',
|
|
627
|
+
name: createDto.name,
|
|
628
|
+
seq: 1,
|
|
629
|
+
attributes: createDto.attributes,
|
|
630
|
+
isDeleted: false,
|
|
631
|
+
createdAt: new Date(),
|
|
632
|
+
updatedAt: new Date(),
|
|
633
|
+
};
|
|
634
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
635
|
+
await service.createSetting(createDto, mockInvokeContext);
|
|
636
|
+
expect(createDto.attributes['seq']).toBe(1);
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
describe('updateSetting', () => {
|
|
640
|
+
it('should update setting', async () => {
|
|
641
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
642
|
+
const updateDto = {
|
|
643
|
+
name: 'Updated Setting',
|
|
644
|
+
seq: 5,
|
|
645
|
+
};
|
|
646
|
+
const existingData = {
|
|
647
|
+
id: 'test-id',
|
|
648
|
+
pk: key.pk,
|
|
649
|
+
sk: key.sk,
|
|
650
|
+
code: 'DATA1',
|
|
651
|
+
name: 'Original Setting',
|
|
652
|
+
version: 1,
|
|
653
|
+
type: 'MASTER',
|
|
654
|
+
tenantCode: 'TEST_TENANT',
|
|
655
|
+
seq: 1,
|
|
656
|
+
isDeleted: false,
|
|
657
|
+
createdAt: new Date(),
|
|
658
|
+
updatedAt: new Date(),
|
|
659
|
+
};
|
|
660
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
661
|
+
const mockUpdateResult = {
|
|
662
|
+
...existingData,
|
|
663
|
+
name: updateDto.name,
|
|
664
|
+
seq: updateDto.seq,
|
|
665
|
+
updatedAt: new Date(),
|
|
666
|
+
};
|
|
667
|
+
commandService.publishPartialUpdateAsync.mockResolvedValue(mockUpdateResult);
|
|
668
|
+
const result = await service.updateSetting(key, updateDto, mockInvokeContext);
|
|
669
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
670
|
+
expect(commandService.publishPartialUpdateAsync).toHaveBeenCalledWith(expect.objectContaining(updateDto), { invokeContext: mockInvokeContext });
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
describe('deleteSetting', () => {
|
|
674
|
+
it('should delete setting', async () => {
|
|
675
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
676
|
+
const existingData = {
|
|
677
|
+
id: 'test-id',
|
|
678
|
+
pk: key.pk,
|
|
679
|
+
sk: key.sk,
|
|
680
|
+
code: 'DATA1',
|
|
681
|
+
name: 'Test Data',
|
|
682
|
+
version: 1,
|
|
683
|
+
type: 'MASTER',
|
|
684
|
+
tenantCode: 'TEST_TENANT',
|
|
685
|
+
isDeleted: false,
|
|
686
|
+
createdAt: new Date(),
|
|
687
|
+
updatedAt: new Date(),
|
|
688
|
+
};
|
|
689
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
690
|
+
const mockDeleteResult = {
|
|
691
|
+
...existingData,
|
|
692
|
+
isDeleted: true,
|
|
693
|
+
updatedAt: new Date(),
|
|
694
|
+
};
|
|
695
|
+
commandService.publishPartialUpdateAsync.mockResolvedValue(mockDeleteResult);
|
|
696
|
+
const result = await service.deleteSetting(key, mockInvokeContext);
|
|
697
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
698
|
+
expect(commandService.publishPartialUpdateAsync).toHaveBeenCalledWith(expect.objectContaining({
|
|
699
|
+
...existingData,
|
|
700
|
+
isDeleted: true,
|
|
701
|
+
}), { invokeContext: mockInvokeContext });
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
/**
|
|
705
|
+
* Test Overview: Tests comprehensive error handling scenarios for MasterDataService operations
|
|
706
|
+
* Purpose: Ensures the service properly handles database failures, validation errors, and edge cases
|
|
707
|
+
* Details: Verifies error handling for Prisma failures, CommandService errors, and concurrent operations
|
|
708
|
+
*/
|
|
709
|
+
describe('Error Handling Scenarios', () => {
|
|
710
|
+
describe('create - Database Error Handling', () => {
|
|
711
|
+
it('should handle Prisma database connection errors gracefully', async () => {
|
|
712
|
+
const createDto = {
|
|
713
|
+
settingCode: 'SETTING_CODE',
|
|
714
|
+
code: 'DATA_CODE',
|
|
715
|
+
tenantCode: 'TEST_TENANT',
|
|
716
|
+
name: 'Test Data',
|
|
717
|
+
seq: 1,
|
|
718
|
+
};
|
|
719
|
+
const dbError = new Error('Database connection failed');
|
|
720
|
+
dbError.name = 'PrismaClientKnownRequestError';
|
|
721
|
+
dataService.getItem.mockRejectedValue(dbError);
|
|
722
|
+
await expect(service.create(createDto, { invokeContext: mockInvokeContext }))
|
|
723
|
+
.rejects.toThrow('Database connection failed');
|
|
724
|
+
expect(commandService.publishAsync).not.toHaveBeenCalled();
|
|
725
|
+
});
|
|
726
|
+
it('should handle CommandService publish failures', async () => {
|
|
727
|
+
const createDto = {
|
|
728
|
+
settingCode: 'SETTING_CODE',
|
|
729
|
+
code: 'DATA_CODE',
|
|
730
|
+
tenantCode: 'TEST_TENANT',
|
|
731
|
+
name: 'Test Data',
|
|
732
|
+
seq: 1,
|
|
733
|
+
};
|
|
734
|
+
dataService.getItem.mockResolvedValue(null);
|
|
735
|
+
const commandError = new Error('Command publish failed');
|
|
736
|
+
commandService.publishAsync.mockRejectedValue(commandError);
|
|
737
|
+
await expect(service.create(createDto, { invokeContext: mockInvokeContext }))
|
|
738
|
+
.rejects.toThrow('Command publish failed');
|
|
739
|
+
});
|
|
740
|
+
it('should handle concurrent creation attempts with version conflicts', async () => {
|
|
741
|
+
const createDto = {
|
|
742
|
+
settingCode: 'SETTING_CODE',
|
|
743
|
+
code: 'DATA_CODE',
|
|
744
|
+
tenantCode: 'TEST_TENANT',
|
|
745
|
+
name: 'Test Data',
|
|
746
|
+
seq: 1,
|
|
747
|
+
};
|
|
748
|
+
dataService.getItem.mockResolvedValue(null);
|
|
749
|
+
const versionError = new Error('Version conflict');
|
|
750
|
+
versionError.name = 'ConditionalCheckFailedException';
|
|
751
|
+
commandService.publishAsync.mockRejectedValue(versionError);
|
|
752
|
+
await expect(service.create(createDto, { invokeContext: mockInvokeContext }))
|
|
753
|
+
.rejects.toThrow('Version conflict');
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
describe('update - Error Handling', () => {
|
|
757
|
+
it('should handle Prisma query timeout errors', async () => {
|
|
758
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
759
|
+
const updateDto = {
|
|
760
|
+
name: 'Updated Name',
|
|
761
|
+
seq: 2,
|
|
762
|
+
};
|
|
763
|
+
const timeoutError = new Error('Query timeout');
|
|
764
|
+
timeoutError.name = 'PrismaClientKnownRequestError';
|
|
765
|
+
dataService.getItem.mockRejectedValue(timeoutError);
|
|
766
|
+
await expect(service.update(key, updateDto, { invokeContext: mockInvokeContext }))
|
|
767
|
+
.rejects.toThrow('Query timeout');
|
|
768
|
+
expect(commandService.publishPartialUpdateAsync).not.toHaveBeenCalled();
|
|
769
|
+
});
|
|
770
|
+
it('should handle CommandService partial update failures', async () => {
|
|
771
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
772
|
+
const updateDto = {
|
|
773
|
+
name: 'Updated Name',
|
|
774
|
+
seq: 2,
|
|
775
|
+
};
|
|
776
|
+
const existingData = {
|
|
777
|
+
id: 'test-id',
|
|
778
|
+
pk: 'MASTER#TEST_TENANT',
|
|
779
|
+
sk: 'SETTING#DATA1',
|
|
780
|
+
code: 'DATA1',
|
|
781
|
+
name: 'Original Name',
|
|
782
|
+
version: 1,
|
|
783
|
+
type: 'MASTER',
|
|
784
|
+
tenantCode: 'TEST_TENANT',
|
|
785
|
+
isDeleted: false,
|
|
786
|
+
createdAt: new Date(),
|
|
787
|
+
updatedAt: new Date(),
|
|
788
|
+
};
|
|
789
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
790
|
+
const updateError = new Error('Update command failed');
|
|
791
|
+
commandService.publishPartialUpdateAsync.mockRejectedValue(updateError);
|
|
792
|
+
await expect(service.update(key, updateDto, { invokeContext: mockInvokeContext }))
|
|
793
|
+
.rejects.toThrow('Update command failed');
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
describe('delete - Error Handling', () => {
|
|
797
|
+
it('should handle database access errors during deletion', async () => {
|
|
798
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
799
|
+
const accessError = new Error('Access denied');
|
|
800
|
+
accessError.name = 'PrismaClientKnownRequestError';
|
|
801
|
+
dataService.getItem.mockRejectedValue(accessError);
|
|
802
|
+
await expect(service.delete(key, { invokeContext: mockInvokeContext }))
|
|
803
|
+
.rejects.toThrow('Access denied');
|
|
804
|
+
expect(commandService.publishPartialUpdateAsync).not.toHaveBeenCalled();
|
|
805
|
+
});
|
|
806
|
+
it('should handle deletion command failures', async () => {
|
|
807
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
808
|
+
const existingData = {
|
|
809
|
+
id: 'test-id',
|
|
810
|
+
pk: 'MASTER#TEST_TENANT',
|
|
811
|
+
sk: 'SETTING#DATA1',
|
|
812
|
+
code: 'DATA1',
|
|
813
|
+
name: 'Test Data',
|
|
814
|
+
version: 1,
|
|
815
|
+
type: 'MASTER',
|
|
816
|
+
tenantCode: 'TEST_TENANT',
|
|
817
|
+
isDeleted: false,
|
|
818
|
+
createdAt: new Date(),
|
|
819
|
+
updatedAt: new Date(),
|
|
820
|
+
};
|
|
821
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
822
|
+
const deleteError = new Error('Delete command failed');
|
|
823
|
+
commandService.publishPartialUpdateAsync.mockRejectedValue(deleteError);
|
|
824
|
+
await expect(service.delete(key, { invokeContext: mockInvokeContext }))
|
|
825
|
+
.rejects.toThrow('Delete command failed');
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
/**
|
|
830
|
+
* Test Overview: Tests edge cases and boundary conditions for MasterDataService operations
|
|
831
|
+
* Purpose: Ensures the service handles unusual inputs and boundary conditions properly
|
|
832
|
+
* Details: Verifies behavior with empty values, special characters, and extreme data sizes
|
|
833
|
+
*/
|
|
834
|
+
describe('Edge Cases and Boundary Conditions', () => {
|
|
835
|
+
describe('create - Edge Cases', () => {
|
|
836
|
+
it('should handle empty string values in create data', async () => {
|
|
837
|
+
const createDto = {
|
|
838
|
+
settingCode: '',
|
|
839
|
+
code: '',
|
|
840
|
+
tenantCode: 'TEST_TENANT',
|
|
841
|
+
name: '',
|
|
842
|
+
seq: 0,
|
|
843
|
+
attributes: {},
|
|
844
|
+
};
|
|
845
|
+
dataService.getItem.mockResolvedValue(null);
|
|
846
|
+
const mockCommandResult = {
|
|
847
|
+
id: 'MASTER#TEST_TENANT##',
|
|
848
|
+
pk: 'MASTER#TEST_TENANT',
|
|
849
|
+
sk: '#',
|
|
850
|
+
version: 1,
|
|
851
|
+
type: 'MASTER',
|
|
852
|
+
tenantCode: 'TEST_TENANT',
|
|
853
|
+
name: '',
|
|
854
|
+
code: '',
|
|
855
|
+
seq: 0,
|
|
856
|
+
attributes: {},
|
|
857
|
+
isDeleted: false,
|
|
858
|
+
createdAt: new Date(),
|
|
859
|
+
updatedAt: new Date(),
|
|
860
|
+
};
|
|
861
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
862
|
+
const result = await service.create(createDto, { invokeContext: mockInvokeContext });
|
|
863
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
864
|
+
expect(result.name).toBe('');
|
|
865
|
+
expect(result.code).toBe('');
|
|
866
|
+
});
|
|
867
|
+
it('should handle special characters in master data fields', async () => {
|
|
868
|
+
const createDto = {
|
|
869
|
+
settingCode: 'SETTING_CODE',
|
|
870
|
+
code: 'DATA_CODE_特殊文字@#$%',
|
|
871
|
+
tenantCode: 'TEST_TENANT',
|
|
872
|
+
name: 'Test Data with 特殊文字 & symbols!',
|
|
873
|
+
seq: 1,
|
|
874
|
+
attributes: {
|
|
875
|
+
specialKey: 'value with 特殊文字',
|
|
876
|
+
unicodeKey: '🚀🎉',
|
|
877
|
+
jsonString: '{"nested": "value"}'
|
|
878
|
+
},
|
|
879
|
+
};
|
|
880
|
+
dataService.getItem.mockResolvedValue(null);
|
|
881
|
+
const mockCommandResult = {
|
|
882
|
+
id: 'MASTER#TEST_TENANT#SETTING_CODE#DATA_CODE_特殊文字@#$%',
|
|
883
|
+
pk: 'MASTER#TEST_TENANT',
|
|
884
|
+
sk: 'SETTING_CODE#DATA_CODE_特殊文字@#$%',
|
|
885
|
+
version: 1,
|
|
886
|
+
type: 'MASTER',
|
|
887
|
+
tenantCode: 'TEST_TENANT',
|
|
888
|
+
name: createDto.name,
|
|
889
|
+
code: createDto.code,
|
|
890
|
+
seq: createDto.seq,
|
|
891
|
+
attributes: createDto.attributes,
|
|
892
|
+
isDeleted: false,
|
|
893
|
+
createdAt: new Date(),
|
|
894
|
+
updatedAt: new Date(),
|
|
895
|
+
};
|
|
896
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
897
|
+
const result = await service.create(createDto, { invokeContext: mockInvokeContext });
|
|
898
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
899
|
+
expect(result.code).toBe('DATA_CODE_特殊文字@#$%');
|
|
900
|
+
expect(result.name).toBe('Test Data with 特殊文字 & symbols!');
|
|
901
|
+
});
|
|
902
|
+
it('should handle large attribute objects', async () => {
|
|
903
|
+
const largeAttributes = {};
|
|
904
|
+
for (let i = 0; i < 100; i++) {
|
|
905
|
+
largeAttributes[`key${i}`] = `value${i}`.repeat(100);
|
|
906
|
+
}
|
|
907
|
+
const createDto = {
|
|
908
|
+
settingCode: 'SETTING_CODE',
|
|
909
|
+
code: 'DATA_CODE',
|
|
910
|
+
tenantCode: 'TEST_TENANT',
|
|
911
|
+
name: 'Test Data',
|
|
912
|
+
seq: 1,
|
|
913
|
+
attributes: largeAttributes,
|
|
914
|
+
};
|
|
915
|
+
dataService.getItem.mockResolvedValue(null);
|
|
916
|
+
const mockCommandResult = {
|
|
917
|
+
id: 'MASTER#TEST_TENANT#SETTING_CODE#DATA_CODE',
|
|
918
|
+
pk: 'MASTER#TEST_TENANT',
|
|
919
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
920
|
+
version: 1,
|
|
921
|
+
type: 'MASTER',
|
|
922
|
+
tenantCode: 'TEST_TENANT',
|
|
923
|
+
name: createDto.name,
|
|
924
|
+
code: createDto.code,
|
|
925
|
+
seq: createDto.seq,
|
|
926
|
+
attributes: largeAttributes,
|
|
927
|
+
isDeleted: false,
|
|
928
|
+
createdAt: new Date(),
|
|
929
|
+
updatedAt: new Date(),
|
|
930
|
+
};
|
|
931
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
932
|
+
const result = await service.create(createDto, { invokeContext: mockInvokeContext });
|
|
933
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
934
|
+
expect(Object.keys(result.attributes)).toHaveLength(100);
|
|
935
|
+
});
|
|
936
|
+
it('should handle negative sequence numbers', async () => {
|
|
937
|
+
const createDto = {
|
|
938
|
+
settingCode: 'SETTING_CODE',
|
|
939
|
+
code: 'DATA_CODE',
|
|
940
|
+
tenantCode: 'TEST_TENANT',
|
|
941
|
+
name: 'Test Data',
|
|
942
|
+
seq: -1,
|
|
943
|
+
};
|
|
944
|
+
dataService.getItem.mockResolvedValue(null);
|
|
945
|
+
const mockCommandResult = {
|
|
946
|
+
id: 'MASTER#TEST_TENANT#SETTING_CODE#DATA_CODE',
|
|
947
|
+
pk: 'MASTER#TEST_TENANT',
|
|
948
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
949
|
+
version: 1,
|
|
950
|
+
type: 'MASTER',
|
|
951
|
+
tenantCode: 'TEST_TENANT',
|
|
952
|
+
name: createDto.name,
|
|
953
|
+
code: createDto.code,
|
|
954
|
+
seq: -1,
|
|
955
|
+
isDeleted: false,
|
|
956
|
+
createdAt: new Date(),
|
|
957
|
+
updatedAt: new Date(),
|
|
958
|
+
};
|
|
959
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
960
|
+
const result = await service.create(createDto, { invokeContext: mockInvokeContext });
|
|
961
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
962
|
+
expect(result.seq).toBe(-1);
|
|
963
|
+
});
|
|
964
|
+
});
|
|
965
|
+
describe('update - Edge Cases', () => {
|
|
966
|
+
it('should handle updates with null attributes', async () => {
|
|
967
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
968
|
+
const updateDto = {
|
|
969
|
+
name: 'Updated Name',
|
|
970
|
+
attributes: null,
|
|
971
|
+
};
|
|
972
|
+
const existingData = {
|
|
973
|
+
id: 'test-id',
|
|
974
|
+
pk: 'MASTER#TEST_TENANT',
|
|
975
|
+
sk: 'SETTING#DATA1',
|
|
976
|
+
code: 'DATA1',
|
|
977
|
+
name: 'Original Name',
|
|
978
|
+
version: 1,
|
|
979
|
+
type: 'MASTER',
|
|
980
|
+
tenantCode: 'TEST_TENANT',
|
|
981
|
+
isDeleted: false,
|
|
982
|
+
attributes: { original: true },
|
|
983
|
+
createdAt: new Date(),
|
|
984
|
+
updatedAt: new Date(),
|
|
985
|
+
};
|
|
986
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
987
|
+
const mockUpdateResult = {
|
|
988
|
+
...existingData,
|
|
989
|
+
name: updateDto.name,
|
|
990
|
+
attributes: null,
|
|
991
|
+
updatedAt: new Date(),
|
|
992
|
+
};
|
|
993
|
+
commandService.publishPartialUpdateAsync.mockResolvedValue(mockUpdateResult);
|
|
994
|
+
const result = await service.update(key, updateDto, { invokeContext: mockInvokeContext });
|
|
995
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
996
|
+
expect(result.attributes).toBeNull();
|
|
997
|
+
});
|
|
998
|
+
it('should handle extremely long name values', async () => {
|
|
999
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
1000
|
+
const longName = 'A'.repeat(1000);
|
|
1001
|
+
const updateDto = {
|
|
1002
|
+
name: longName,
|
|
1003
|
+
};
|
|
1004
|
+
const existingData = {
|
|
1005
|
+
id: 'test-id',
|
|
1006
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1007
|
+
sk: 'SETTING#DATA1',
|
|
1008
|
+
code: 'DATA1',
|
|
1009
|
+
name: 'Original Name',
|
|
1010
|
+
version: 1,
|
|
1011
|
+
type: 'MASTER',
|
|
1012
|
+
tenantCode: 'TEST_TENANT',
|
|
1013
|
+
isDeleted: false,
|
|
1014
|
+
createdAt: new Date(),
|
|
1015
|
+
updatedAt: new Date(),
|
|
1016
|
+
};
|
|
1017
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
1018
|
+
const mockUpdateResult = {
|
|
1019
|
+
...existingData,
|
|
1020
|
+
name: longName,
|
|
1021
|
+
updatedAt: new Date(),
|
|
1022
|
+
};
|
|
1023
|
+
commandService.publishPartialUpdateAsync.mockResolvedValue(mockUpdateResult);
|
|
1024
|
+
const result = await service.update(key, updateDto, { invokeContext: mockInvokeContext });
|
|
1025
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
1026
|
+
expect(result.name).toBe(longName);
|
|
1027
|
+
expect(result.name.length).toBe(1000);
|
|
1028
|
+
});
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
/**
|
|
1032
|
+
* Test Overview: Tests deleted data re-addition scenarios for MasterDataService
|
|
1033
|
+
* Purpose: Ensures deleted master data can be properly recreated with correct version handling
|
|
1034
|
+
* Details: Verifies version increment, attribute preservation, and state transitions from deleted to active
|
|
1035
|
+
*/
|
|
1036
|
+
describe('Deleted Data Re-addition Scenarios', () => {
|
|
1037
|
+
describe('create - Deleted Data Recreation', () => {
|
|
1038
|
+
it('should recreate deleted data with incremented version', async () => {
|
|
1039
|
+
const createDto = {
|
|
1040
|
+
settingCode: 'SETTING_CODE',
|
|
1041
|
+
code: 'DATA_CODE',
|
|
1042
|
+
tenantCode: 'TEST_TENANT',
|
|
1043
|
+
name: 'Recreated Data',
|
|
1044
|
+
seq: 2,
|
|
1045
|
+
attributes: { recreated: true },
|
|
1046
|
+
};
|
|
1047
|
+
const existingDeletedData = {
|
|
1048
|
+
id: 'existing-id',
|
|
1049
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1050
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
1051
|
+
code: 'DATA_CODE',
|
|
1052
|
+
name: 'Original Data',
|
|
1053
|
+
version: 3,
|
|
1054
|
+
type: 'MASTER',
|
|
1055
|
+
tenantCode: 'TEST_TENANT',
|
|
1056
|
+
isDeleted: true,
|
|
1057
|
+
attributes: { original: true },
|
|
1058
|
+
createdAt: new Date('2023-01-01'),
|
|
1059
|
+
updatedAt: new Date('2023-01-02'),
|
|
1060
|
+
};
|
|
1061
|
+
dataService.getItem.mockResolvedValue(existingDeletedData);
|
|
1062
|
+
const mockCommandResult = {
|
|
1063
|
+
id: 'MASTER#TEST_TENANT#SETTING_CODE#DATA_CODE',
|
|
1064
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1065
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
1066
|
+
version: 3,
|
|
1067
|
+
type: 'MASTER',
|
|
1068
|
+
tenantCode: 'TEST_TENANT',
|
|
1069
|
+
name: createDto.name,
|
|
1070
|
+
code: createDto.code,
|
|
1071
|
+
seq: createDto.seq,
|
|
1072
|
+
attributes: createDto.attributes,
|
|
1073
|
+
isDeleted: false,
|
|
1074
|
+
createdAt: new Date('2023-01-01'),
|
|
1075
|
+
updatedAt: new Date(),
|
|
1076
|
+
};
|
|
1077
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
1078
|
+
const result = await service.create(createDto, { invokeContext: mockInvokeContext });
|
|
1079
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
1080
|
+
expect(result.version).toBe(3);
|
|
1081
|
+
expect(result.isDeleted).toBe(false);
|
|
1082
|
+
expect(result.name).toBe('Recreated Data');
|
|
1083
|
+
expect(result.attributes).toEqual({ recreated: true });
|
|
1084
|
+
expect(commandService.publishAsync).toHaveBeenCalledWith(expect.objectContaining({
|
|
1085
|
+
version: 3,
|
|
1086
|
+
isDeleted: false,
|
|
1087
|
+
name: 'Recreated Data',
|
|
1088
|
+
attributes: { recreated: true },
|
|
1089
|
+
}), { invokeContext: mockInvokeContext });
|
|
1090
|
+
});
|
|
1091
|
+
it('should handle multiple deletion and recreation cycles', async () => {
|
|
1092
|
+
const createDto = {
|
|
1093
|
+
settingCode: 'SETTING_CODE',
|
|
1094
|
+
code: 'DATA_CODE',
|
|
1095
|
+
tenantCode: 'TEST_TENANT',
|
|
1096
|
+
name: 'Third Recreation',
|
|
1097
|
+
seq: 1,
|
|
1098
|
+
attributes: { cycle: 3 },
|
|
1099
|
+
};
|
|
1100
|
+
const existingDeletedData = {
|
|
1101
|
+
id: 'existing-id',
|
|
1102
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1103
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
1104
|
+
code: 'DATA_CODE',
|
|
1105
|
+
name: 'Second Recreation',
|
|
1106
|
+
version: 5,
|
|
1107
|
+
type: 'MASTER',
|
|
1108
|
+
tenantCode: 'TEST_TENANT',
|
|
1109
|
+
isDeleted: true,
|
|
1110
|
+
attributes: { cycle: 2 },
|
|
1111
|
+
createdAt: new Date('2023-01-01'),
|
|
1112
|
+
updatedAt: new Date('2023-01-03'),
|
|
1113
|
+
};
|
|
1114
|
+
dataService.getItem.mockResolvedValue(existingDeletedData);
|
|
1115
|
+
const mockCommandResult = {
|
|
1116
|
+
id: 'MASTER#TEST_TENANT#SETTING_CODE#DATA_CODE',
|
|
1117
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1118
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
1119
|
+
version: 5,
|
|
1120
|
+
type: 'MASTER',
|
|
1121
|
+
tenantCode: 'TEST_TENANT',
|
|
1122
|
+
name: createDto.name,
|
|
1123
|
+
code: createDto.code,
|
|
1124
|
+
seq: createDto.seq,
|
|
1125
|
+
attributes: createDto.attributes,
|
|
1126
|
+
isDeleted: false,
|
|
1127
|
+
createdAt: new Date('2023-01-01'),
|
|
1128
|
+
updatedAt: new Date(),
|
|
1129
|
+
};
|
|
1130
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
1131
|
+
const result = await service.create(createDto, { invokeContext: mockInvokeContext });
|
|
1132
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
1133
|
+
expect(result.version).toBe(5);
|
|
1134
|
+
expect(result.isDeleted).toBe(false);
|
|
1135
|
+
expect(result.attributes).toEqual({ cycle: 3 });
|
|
1136
|
+
});
|
|
1137
|
+
it('should preserve original creation timestamp when recreating deleted data', async () => {
|
|
1138
|
+
const originalCreatedAt = new Date('2023-01-01T10:00:00Z');
|
|
1139
|
+
const createDto = {
|
|
1140
|
+
settingCode: 'SETTING_CODE',
|
|
1141
|
+
code: 'DATA_CODE',
|
|
1142
|
+
tenantCode: 'TEST_TENANT',
|
|
1143
|
+
name: 'Recreated Data',
|
|
1144
|
+
seq: 1,
|
|
1145
|
+
};
|
|
1146
|
+
const existingDeletedData = {
|
|
1147
|
+
id: 'existing-id',
|
|
1148
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1149
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
1150
|
+
code: 'DATA_CODE',
|
|
1151
|
+
name: 'Original Data',
|
|
1152
|
+
version: 2,
|
|
1153
|
+
type: 'MASTER',
|
|
1154
|
+
tenantCode: 'TEST_TENANT',
|
|
1155
|
+
isDeleted: true,
|
|
1156
|
+
createdAt: originalCreatedAt,
|
|
1157
|
+
updatedAt: new Date('2023-01-02'),
|
|
1158
|
+
};
|
|
1159
|
+
dataService.getItem.mockResolvedValue(existingDeletedData);
|
|
1160
|
+
const mockCommandResult = {
|
|
1161
|
+
id: 'MASTER#TEST_TENANT#SETTING_CODE#DATA_CODE',
|
|
1162
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1163
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
1164
|
+
version: 2,
|
|
1165
|
+
type: 'MASTER',
|
|
1166
|
+
tenantCode: 'TEST_TENANT',
|
|
1167
|
+
name: createDto.name,
|
|
1168
|
+
code: createDto.code,
|
|
1169
|
+
seq: createDto.seq,
|
|
1170
|
+
isDeleted: false,
|
|
1171
|
+
createdAt: originalCreatedAt,
|
|
1172
|
+
updatedAt: new Date(),
|
|
1173
|
+
};
|
|
1174
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
1175
|
+
const result = await service.create(createDto, { invokeContext: mockInvokeContext });
|
|
1176
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
1177
|
+
expect(result.createdAt).toEqual(originalCreatedAt);
|
|
1178
|
+
expect(result.isDeleted).toBe(false);
|
|
1179
|
+
});
|
|
1180
|
+
it('should handle recreation with different tenant codes', async () => {
|
|
1181
|
+
const createDto = {
|
|
1182
|
+
settingCode: 'SETTING_CODE',
|
|
1183
|
+
code: 'DATA_CODE',
|
|
1184
|
+
tenantCode: 'NEW_TENANT',
|
|
1185
|
+
name: 'Recreated Data',
|
|
1186
|
+
seq: 1,
|
|
1187
|
+
};
|
|
1188
|
+
dataService.getItem.mockResolvedValue(null);
|
|
1189
|
+
const mockCommandResult = {
|
|
1190
|
+
id: 'MASTER#NEW_TENANT#SETTING_CODE#DATA_CODE',
|
|
1191
|
+
pk: 'MASTER#NEW_TENANT',
|
|
1192
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
1193
|
+
version: 1,
|
|
1194
|
+
type: 'MASTER',
|
|
1195
|
+
tenantCode: 'NEW_TENANT',
|
|
1196
|
+
name: createDto.name,
|
|
1197
|
+
code: createDto.code,
|
|
1198
|
+
seq: createDto.seq,
|
|
1199
|
+
isDeleted: false,
|
|
1200
|
+
createdAt: new Date(),
|
|
1201
|
+
updatedAt: new Date(),
|
|
1202
|
+
};
|
|
1203
|
+
commandService.publishAsync.mockResolvedValue(mockCommandResult);
|
|
1204
|
+
const result = await service.create(createDto, { invokeContext: mockInvokeContext });
|
|
1205
|
+
expect(result).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
1206
|
+
expect(result.tenantCode).toBe('NEW_TENANT');
|
|
1207
|
+
expect(result.version).toBe(1);
|
|
1208
|
+
});
|
|
1209
|
+
});
|
|
1210
|
+
describe('checkExistCode - Deleted Data Scenarios', () => {
|
|
1211
|
+
it('should return false for deleted data when checking existence', async () => {
|
|
1212
|
+
const mockDeletedData = {
|
|
1213
|
+
id: 'test-id',
|
|
1214
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1215
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
1216
|
+
code: 'DATA_CODE',
|
|
1217
|
+
name: 'Deleted Data',
|
|
1218
|
+
version: 2,
|
|
1219
|
+
type: 'MASTER',
|
|
1220
|
+
tenantCode: 'TEST_TENANT',
|
|
1221
|
+
isDeleted: true,
|
|
1222
|
+
createdAt: new Date(),
|
|
1223
|
+
updatedAt: new Date(),
|
|
1224
|
+
};
|
|
1225
|
+
dataService.getItem.mockResolvedValue(mockDeletedData);
|
|
1226
|
+
const result = await service.checkExistCode('TEST_TENANT', 'SETTING_CODE', 'DATA_CODE');
|
|
1227
|
+
expect(result).toBe(false);
|
|
1228
|
+
expect(dataService.getItem).toHaveBeenCalledWith({
|
|
1229
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1230
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
1231
|
+
});
|
|
1232
|
+
});
|
|
1233
|
+
it('should handle null data when checking existence', async () => {
|
|
1234
|
+
dataService.getItem.mockResolvedValue(null);
|
|
1235
|
+
const result = await service.checkExistCode('TEST_TENANT', 'SETTING_CODE', 'NONEXISTENT_CODE');
|
|
1236
|
+
expect(result).toBe(false);
|
|
1237
|
+
});
|
|
1238
|
+
});
|
|
1239
|
+
});
|
|
1240
|
+
/**
|
|
1241
|
+
* Test Overview: Tests concurrent operation scenarios for MasterDataService
|
|
1242
|
+
* Purpose: Ensures the service handles simultaneous operations correctly with proper version control
|
|
1243
|
+
* Details: Verifies race condition handling, version conflicts, and concurrent CRUD operations
|
|
1244
|
+
*/
|
|
1245
|
+
describe('Concurrent Operation Scenarios', () => {
|
|
1246
|
+
describe('create - Concurrent Operations', () => {
|
|
1247
|
+
it('should handle concurrent creation attempts correctly', async () => {
|
|
1248
|
+
const createDto1 = {
|
|
1249
|
+
settingCode: 'SETTING_CODE',
|
|
1250
|
+
code: 'DATA_CODE',
|
|
1251
|
+
tenantCode: 'TEST_TENANT',
|
|
1252
|
+
name: 'First Creation',
|
|
1253
|
+
seq: 1,
|
|
1254
|
+
};
|
|
1255
|
+
const createDto2 = {
|
|
1256
|
+
settingCode: 'SETTING_CODE',
|
|
1257
|
+
code: 'DATA_CODE',
|
|
1258
|
+
tenantCode: 'TEST_TENANT',
|
|
1259
|
+
name: 'Second Creation',
|
|
1260
|
+
seq: 2,
|
|
1261
|
+
};
|
|
1262
|
+
dataService.getItem.mockResolvedValue(null);
|
|
1263
|
+
const mockCommandResult1 = {
|
|
1264
|
+
id: 'MASTER#TEST_TENANT#SETTING_CODE#DATA_CODE',
|
|
1265
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1266
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
1267
|
+
version: 1,
|
|
1268
|
+
type: 'MASTER',
|
|
1269
|
+
tenantCode: 'TEST_TENANT',
|
|
1270
|
+
name: createDto1.name,
|
|
1271
|
+
code: createDto1.code,
|
|
1272
|
+
seq: createDto1.seq,
|
|
1273
|
+
isDeleted: false,
|
|
1274
|
+
createdAt: new Date(),
|
|
1275
|
+
updatedAt: new Date(),
|
|
1276
|
+
};
|
|
1277
|
+
const versionConflictError = new Error('Version conflict');
|
|
1278
|
+
versionConflictError.name = 'ConditionalCheckFailedException';
|
|
1279
|
+
commandService.publishAsync
|
|
1280
|
+
.mockResolvedValueOnce(mockCommandResult1)
|
|
1281
|
+
.mockRejectedValueOnce(versionConflictError);
|
|
1282
|
+
const result1 = await service.create(createDto1, { invokeContext: mockInvokeContext });
|
|
1283
|
+
await expect(service.create(createDto2, { invokeContext: mockInvokeContext }))
|
|
1284
|
+
.rejects.toThrow('Version conflict');
|
|
1285
|
+
expect(result1).toBeInstanceOf(entities_1.MasterDataEntity);
|
|
1286
|
+
expect(result1.name).toBe('First Creation');
|
|
1287
|
+
});
|
|
1288
|
+
it('should handle concurrent deletion and recreation', async () => {
|
|
1289
|
+
const createDto = {
|
|
1290
|
+
settingCode: 'SETTING_CODE',
|
|
1291
|
+
code: 'DATA_CODE',
|
|
1292
|
+
tenantCode: 'TEST_TENANT',
|
|
1293
|
+
name: 'Recreated Data',
|
|
1294
|
+
seq: 1,
|
|
1295
|
+
};
|
|
1296
|
+
const existingData = {
|
|
1297
|
+
id: 'existing-id',
|
|
1298
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1299
|
+
sk: 'SETTING_CODE#DATA_CODE',
|
|
1300
|
+
code: 'DATA_CODE',
|
|
1301
|
+
name: 'Original Data',
|
|
1302
|
+
version: 2,
|
|
1303
|
+
type: 'MASTER',
|
|
1304
|
+
tenantCode: 'TEST_TENANT',
|
|
1305
|
+
isDeleted: false,
|
|
1306
|
+
createdAt: new Date(),
|
|
1307
|
+
updatedAt: new Date(),
|
|
1308
|
+
};
|
|
1309
|
+
const deletedData = {
|
|
1310
|
+
...existingData,
|
|
1311
|
+
isDeleted: true,
|
|
1312
|
+
version: 3,
|
|
1313
|
+
};
|
|
1314
|
+
dataService.getItem
|
|
1315
|
+
.mockResolvedValueOnce(existingData)
|
|
1316
|
+
.mockResolvedValueOnce(deletedData);
|
|
1317
|
+
const mockDeleteResult = {
|
|
1318
|
+
...existingData,
|
|
1319
|
+
isDeleted: true,
|
|
1320
|
+
version: 3,
|
|
1321
|
+
updatedAt: new Date(),
|
|
1322
|
+
};
|
|
1323
|
+
const mockRecreateResult = {
|
|
1324
|
+
...deletedData,
|
|
1325
|
+
name: createDto.name,
|
|
1326
|
+
isDeleted: false,
|
|
1327
|
+
version: 3,
|
|
1328
|
+
updatedAt: new Date(),
|
|
1329
|
+
};
|
|
1330
|
+
commandService.publishPartialUpdateAsync.mockResolvedValue(mockDeleteResult);
|
|
1331
|
+
commandService.publishAsync.mockResolvedValue(mockRecreateResult);
|
|
1332
|
+
const deleteResult = await service.delete({ pk: existingData.pk, sk: existingData.sk }, { invokeContext: mockInvokeContext });
|
|
1333
|
+
const recreateResult = await service.create(createDto, { invokeContext: mockInvokeContext });
|
|
1334
|
+
expect(deleteResult.isDeleted).toBe(true);
|
|
1335
|
+
expect(recreateResult.isDeleted).toBe(false);
|
|
1336
|
+
expect(recreateResult.version).toBe(3);
|
|
1337
|
+
});
|
|
1338
|
+
});
|
|
1339
|
+
describe('update - Concurrent Operations', () => {
|
|
1340
|
+
it('should handle concurrent update attempts with version conflicts', async () => {
|
|
1341
|
+
const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#DATA1' };
|
|
1342
|
+
const updateDto1 = {
|
|
1343
|
+
name: 'First Update',
|
|
1344
|
+
seq: 1,
|
|
1345
|
+
};
|
|
1346
|
+
const updateDto2 = {
|
|
1347
|
+
name: 'Second Update',
|
|
1348
|
+
seq: 2,
|
|
1349
|
+
};
|
|
1350
|
+
const existingData = {
|
|
1351
|
+
id: 'test-id',
|
|
1352
|
+
pk: 'MASTER#TEST_TENANT',
|
|
1353
|
+
sk: 'SETTING#DATA1',
|
|
1354
|
+
code: 'DATA1',
|
|
1355
|
+
name: 'Original Name',
|
|
1356
|
+
version: 1,
|
|
1357
|
+
type: 'MASTER',
|
|
1358
|
+
tenantCode: 'TEST_TENANT',
|
|
1359
|
+
isDeleted: false,
|
|
1360
|
+
createdAt: new Date(),
|
|
1361
|
+
updatedAt: new Date(),
|
|
1362
|
+
};
|
|
1363
|
+
dataService.getItem.mockResolvedValue(existingData);
|
|
1364
|
+
const mockUpdateResult1 = {
|
|
1365
|
+
...existingData,
|
|
1366
|
+
name: updateDto1.name,
|
|
1367
|
+
seq: updateDto1.seq,
|
|
1368
|
+
version: 2,
|
|
1369
|
+
updatedAt: new Date(),
|
|
1370
|
+
};
|
|
1371
|
+
const versionConflictError = new Error('Version conflict');
|
|
1372
|
+
versionConflictError.name = 'ConditionalCheckFailedException';
|
|
1373
|
+
commandService.publishPartialUpdateAsync
|
|
1374
|
+
.mockResolvedValueOnce(mockUpdateResult1)
|
|
1375
|
+
.mockRejectedValueOnce(versionConflictError);
|
|
1376
|
+
const result1 = await service.update(key, updateDto1, { invokeContext: mockInvokeContext });
|
|
1377
|
+
await expect(service.update(key, updateDto2, { invokeContext: mockInvokeContext }))
|
|
1378
|
+
.rejects.toThrow('Version conflict');
|
|
1379
|
+
expect(result1.name).toBe('First Update');
|
|
1380
|
+
expect(result1.version).toBe(2);
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
1383
|
+
});
|
|
1384
|
+
});
|
|
1385
|
+
//# sourceMappingURL=master-data.service.spec.js.map
|