@things-factory/dataset 9.1.19 → 9.2.13
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-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/activities/activity-data-review.js +5 -2
- package/dist-server/activities/activity-data-review.js.map +1 -1
- package/dist-server/activities/activity-ooc-review.js +4 -1
- package/dist-server/activities/activity-ooc-review.js.map +1 -1
- package/dist-server/controllers/create-data-ooc.js +2 -1
- package/dist-server/controllers/create-data-ooc.js.map +1 -1
- package/dist-server/controllers/create-data-sample.js +2 -2
- package/dist-server/controllers/create-data-sample.js.map +1 -1
- package/dist-server/controllers/issue-data-collection-task.js +9 -5
- package/dist-server/controllers/issue-data-collection-task.js.map +1 -1
- package/dist-server/controllers/issue-ooc-resolve.js +11 -6
- package/dist-server/controllers/issue-ooc-resolve.js.map +1 -1
- package/dist-server/controllers/issue-ooc-review.js +9 -6
- package/dist-server/controllers/issue-ooc-review.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/spec/integration/debug.spec.ts +42 -0
- package/spec/integration/ooc-lifecycle.spec.ts +484 -0
- package/spec/integration/ooc-workflow.spec.ts +276 -0
- package/spec/integration/simple.spec.ts +62 -0
- package/spec/unit/controllers/activity-callbacks.spec.ts +609 -0
- package/spec/unit/controllers/create-data-ooc.spec.ts +310 -0
- package/spec/unit/controllers/issue-ooc-resolve.spec.ts +431 -0
- package/spec/unit/controllers/issue-ooc-review.spec.ts +288 -0
- package/spec/unit/data-use-case.spec.ts +150 -0
- package/spec/unit/ooc-state-transition.spec.ts +233 -0
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Callback Unit Tests
|
|
3
|
+
* OOC Review/Resolve Activity callback 테스트
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { TestDatabase } from '../../../../../test/test-database'
|
|
7
|
+
import { withTestTransaction } from '../../../../../test/test-context'
|
|
8
|
+
import {
|
|
9
|
+
domainFactory,
|
|
10
|
+
userFactory,
|
|
11
|
+
roleFactory,
|
|
12
|
+
dataSetFactory,
|
|
13
|
+
dataOocFactory,
|
|
14
|
+
activityFactory,
|
|
15
|
+
activityInstanceFactory,
|
|
16
|
+
activityThreadFactory
|
|
17
|
+
} from '../../../../../test/factories'
|
|
18
|
+
import {
|
|
19
|
+
DataOocStatus,
|
|
20
|
+
ActivityInstanceStatus,
|
|
21
|
+
ActivityThreadStatus
|
|
22
|
+
} from '../../../../../test/entities/schemas'
|
|
23
|
+
|
|
24
|
+
describe('Activity Callbacks', () => {
|
|
25
|
+
let testDb: TestDatabase
|
|
26
|
+
|
|
27
|
+
beforeAll(async () => {
|
|
28
|
+
testDb = TestDatabase.getInstance()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('ActivityOocReview Callback', () => {
|
|
32
|
+
describe('Review Activity 완료 시 동작', () => {
|
|
33
|
+
it('ActivityInstance가 Ended 상태가 되면 DataOoc 상태가 REVIEWED로 전이되어야 한다', async () => {
|
|
34
|
+
await withTestTransaction(async (context) => {
|
|
35
|
+
const { tx, domain, user } = context.state
|
|
36
|
+
|
|
37
|
+
// Given: Review Activity와 DataOoc 설정
|
|
38
|
+
const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx)
|
|
39
|
+
const { dataSet, supervisoryRole } = await dataSetFactory.createWithRoles({}, domain, tx)
|
|
40
|
+
let dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
41
|
+
{ state: DataOocStatus.ISSUED },
|
|
42
|
+
dataSet,
|
|
43
|
+
undefined,
|
|
44
|
+
tx
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
// Review ActivityInstance 생성
|
|
48
|
+
const reviewInstance = await activityInstanceFactory.createWithActivity(
|
|
49
|
+
{
|
|
50
|
+
name: `[OOC 검토] ${dataSet.name}`,
|
|
51
|
+
state: ActivityInstanceStatus.Issued,
|
|
52
|
+
input: { dataOocId: dataOoc.id },
|
|
53
|
+
output: {},
|
|
54
|
+
assigneeRole: supervisoryRole
|
|
55
|
+
},
|
|
56
|
+
reviewActivity,
|
|
57
|
+
domain,
|
|
58
|
+
tx
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
// When: ActivityInstance가 Ended 상태로 변경 (callback 시뮬레이션)
|
|
62
|
+
const terminatedAt = new Date()
|
|
63
|
+
const correctiveInstruction = 'Temperature를 정상 범위로 조절하세요'
|
|
64
|
+
|
|
65
|
+
reviewInstance.state = ActivityInstanceStatus.Ended
|
|
66
|
+
reviewInstance.terminatedAt = terminatedAt
|
|
67
|
+
reviewInstance.output = { instruction: correctiveInstruction }
|
|
68
|
+
await tx.save('ActivityInstance', reviewInstance)
|
|
69
|
+
|
|
70
|
+
// Callback 로직 시뮬레이션: DataOoc 업데이트
|
|
71
|
+
dataOoc = await tx.getRepository('DataOoc').findOne({
|
|
72
|
+
where: { id: dataOoc.id },
|
|
73
|
+
relations: ['dataSet']
|
|
74
|
+
}) as any
|
|
75
|
+
|
|
76
|
+
dataOoc.reviewedAt = terminatedAt
|
|
77
|
+
dataOoc.reviewer = user
|
|
78
|
+
dataOoc.correctiveInstruction = correctiveInstruction
|
|
79
|
+
dataOoc.state = DataOocStatus.REVIEWED
|
|
80
|
+
dataOoc = await tx.save('DataOoc', dataOoc)
|
|
81
|
+
|
|
82
|
+
// Then
|
|
83
|
+
expect(dataOoc.state).toBe(DataOocStatus.REVIEWED)
|
|
84
|
+
expect(dataOoc.reviewedAt).toEqual(terminatedAt)
|
|
85
|
+
expect(dataOoc.reviewer?.id).toBe(user.id)
|
|
86
|
+
expect(dataOoc.correctiveInstruction).toBe(correctiveInstruction)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('Review 완료 시 correctiveInstruction이 output.instruction에서 가져와야 한다', async () => {
|
|
91
|
+
await withTestTransaction(async (context) => {
|
|
92
|
+
const { tx, domain, user } = context.state
|
|
93
|
+
|
|
94
|
+
// Given
|
|
95
|
+
const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx)
|
|
96
|
+
const { dataSet } = await dataSetFactory.createWithRoles({}, domain, tx)
|
|
97
|
+
let dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
98
|
+
{ state: DataOocStatus.ISSUED },
|
|
99
|
+
dataSet,
|
|
100
|
+
undefined,
|
|
101
|
+
tx
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const instruction = '습도 레벨을 30-70% 사이로 유지하세요'
|
|
105
|
+
const reviewInstance = await activityInstanceFactory.createWithActivity(
|
|
106
|
+
{
|
|
107
|
+
state: ActivityInstanceStatus.Ended,
|
|
108
|
+
input: { dataOocId: dataOoc.id },
|
|
109
|
+
output: { instruction },
|
|
110
|
+
terminatedAt: new Date()
|
|
111
|
+
},
|
|
112
|
+
reviewActivity,
|
|
113
|
+
domain,
|
|
114
|
+
tx
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// When: Callback 로직
|
|
118
|
+
const outputInstruction = reviewInstance.output?.instruction
|
|
119
|
+
|
|
120
|
+
dataOoc.correctiveInstruction = outputInstruction
|
|
121
|
+
dataOoc.state = DataOocStatus.REVIEWED
|
|
122
|
+
dataOoc = await tx.save('DataOoc', dataOoc)
|
|
123
|
+
|
|
124
|
+
// Then
|
|
125
|
+
expect(dataOoc.correctiveInstruction).toBe(instruction)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('Review 완료 후 issueOocResolve가 호출되어야 한다 (Resolve 발행)', async () => {
|
|
130
|
+
await withTestTransaction(async (context) => {
|
|
131
|
+
const { tx, domain, user } = context.state
|
|
132
|
+
|
|
133
|
+
// Given
|
|
134
|
+
const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx)
|
|
135
|
+
const resolveActivity = await activityFactory.createOocResolveActivity(domain, tx)
|
|
136
|
+
const { dataSet, supervisoryRole, resolverRole } = await dataSetFactory.createWithRoles({}, domain, tx)
|
|
137
|
+
|
|
138
|
+
let dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
139
|
+
{ state: DataOocStatus.ISSUED },
|
|
140
|
+
dataSet,
|
|
141
|
+
undefined,
|
|
142
|
+
tx
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
const instruction = 'Fix the issue'
|
|
146
|
+
const terminatedAt = new Date()
|
|
147
|
+
|
|
148
|
+
// When: Review 완료 및 Callback 전체 로직 시뮬레이션
|
|
149
|
+
// 1. DataOoc REVIEWED로 업데이트
|
|
150
|
+
dataOoc.reviewedAt = terminatedAt
|
|
151
|
+
dataOoc.reviewer = user
|
|
152
|
+
dataOoc.correctiveInstruction = instruction
|
|
153
|
+
dataOoc.state = DataOocStatus.REVIEWED
|
|
154
|
+
dataOoc = await tx.save('DataOoc', dataOoc)
|
|
155
|
+
|
|
156
|
+
// 2. issueOocResolve 호출 (Resolve Activity 발행)
|
|
157
|
+
const resolveInstance = await activityInstanceFactory.createWithActivity(
|
|
158
|
+
{
|
|
159
|
+
name: `[OOC 조치] ${dataSet.name}`,
|
|
160
|
+
state: ActivityInstanceStatus.Issued,
|
|
161
|
+
input: {
|
|
162
|
+
dataOocId: dataOoc.id,
|
|
163
|
+
instruction
|
|
164
|
+
},
|
|
165
|
+
assigneeRole: resolverRole,
|
|
166
|
+
approvalLine: dataSet.outlierApprovalLine || []
|
|
167
|
+
},
|
|
168
|
+
resolveActivity,
|
|
169
|
+
domain,
|
|
170
|
+
tx
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
dataOoc.resolveActivityInstance = resolveInstance
|
|
174
|
+
dataOoc = await tx.save('DataOoc', dataOoc)
|
|
175
|
+
|
|
176
|
+
// Then
|
|
177
|
+
expect(dataOoc.state).toBe(DataOocStatus.REVIEWED)
|
|
178
|
+
expect(dataOoc.resolveActivityInstance).toBeDefined()
|
|
179
|
+
expect(dataOoc.resolveActivityInstance?.input?.instruction).toBe(instruction)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('Ended가 아닌 상태에서는 callback 로직이 실행되지 않아야 한다', async () => {
|
|
184
|
+
await withTestTransaction(async (context) => {
|
|
185
|
+
const { tx, domain } = context.state
|
|
186
|
+
|
|
187
|
+
// Given
|
|
188
|
+
const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx)
|
|
189
|
+
const { dataSet } = await dataSetFactory.createWithRoles({}, domain, tx)
|
|
190
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
191
|
+
{ state: DataOocStatus.ISSUED },
|
|
192
|
+
dataSet,
|
|
193
|
+
undefined,
|
|
194
|
+
tx
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
const reviewInstance = await activityInstanceFactory.createWithActivity(
|
|
198
|
+
{
|
|
199
|
+
state: ActivityInstanceStatus.Started, // Ended가 아님
|
|
200
|
+
input: { dataOocId: dataOoc.id }
|
|
201
|
+
},
|
|
202
|
+
reviewActivity,
|
|
203
|
+
domain,
|
|
204
|
+
tx
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
// When: Callback 조건 체크
|
|
208
|
+
const shouldExecuteCallback = reviewInstance.state === ActivityInstanceStatus.Ended
|
|
209
|
+
|
|
210
|
+
// Then
|
|
211
|
+
expect(shouldExecuteCallback).toBe(false)
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('ActivityOocResolve Callback', () => {
|
|
218
|
+
describe('Resolve Activity 완료 시 동작', () => {
|
|
219
|
+
it('ActivityInstance가 Ended 상태가 되면 DataOoc 상태가 CORRECTED로 전이되어야 한다', async () => {
|
|
220
|
+
await withTestTransaction(async (context) => {
|
|
221
|
+
const { tx, domain, user } = context.state
|
|
222
|
+
|
|
223
|
+
// Given: REVIEWED 상태의 DataOoc과 Resolve Activity
|
|
224
|
+
const resolveActivity = await activityFactory.createOocResolveActivity(domain, tx)
|
|
225
|
+
const { dataSet, resolverRole } = await dataSetFactory.createWithRoles({}, domain, tx)
|
|
226
|
+
let dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
227
|
+
{
|
|
228
|
+
state: DataOocStatus.REVIEWED,
|
|
229
|
+
reviewedAt: new Date(),
|
|
230
|
+
correctiveInstruction: 'Fix temperature'
|
|
231
|
+
},
|
|
232
|
+
dataSet,
|
|
233
|
+
undefined,
|
|
234
|
+
tx
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
// Resolve ActivityInstance 생성
|
|
238
|
+
const resolveInstance = await activityInstanceFactory.createWithActivity(
|
|
239
|
+
{
|
|
240
|
+
name: `[OOC 조치] ${dataSet.name}`,
|
|
241
|
+
state: ActivityInstanceStatus.Issued,
|
|
242
|
+
input: {
|
|
243
|
+
dataOocId: dataOoc.id,
|
|
244
|
+
instruction: dataOoc.correctiveInstruction
|
|
245
|
+
},
|
|
246
|
+
output: {},
|
|
247
|
+
assigneeRole: resolverRole
|
|
248
|
+
},
|
|
249
|
+
resolveActivity,
|
|
250
|
+
domain,
|
|
251
|
+
tx
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
// Thread 생성 (corrector 정보 획득용)
|
|
255
|
+
const assignee = await userFactory.create({ name: 'Corrector User' }, tx)
|
|
256
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
257
|
+
{
|
|
258
|
+
state: ActivityThreadStatus.Ended,
|
|
259
|
+
terminatedAt: new Date()
|
|
260
|
+
},
|
|
261
|
+
resolveInstance,
|
|
262
|
+
assignee,
|
|
263
|
+
tx
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
// When: ActivityInstance가 Ended 상태로 변경 (callback 시뮬레이션)
|
|
267
|
+
const terminatedAt = new Date()
|
|
268
|
+
const correctiveAction = 'Temperature를 정상 범위로 조절 완료'
|
|
269
|
+
|
|
270
|
+
resolveInstance.state = ActivityInstanceStatus.Ended
|
|
271
|
+
resolveInstance.terminatedAt = terminatedAt
|
|
272
|
+
resolveInstance.output = { action: correctiveAction }
|
|
273
|
+
await tx.save('ActivityInstance', resolveInstance)
|
|
274
|
+
|
|
275
|
+
// Callback 로직 시뮬레이션:
|
|
276
|
+
// 1. Ended 상태의 ActivityThread에서 corrector 조회
|
|
277
|
+
const endedThreads = await tx.getRepository('ActivityThread').find({
|
|
278
|
+
where: {
|
|
279
|
+
domain: { id: domain.id },
|
|
280
|
+
activityInstance: { id: resolveInstance.id },
|
|
281
|
+
state: ActivityThreadStatus.Ended
|
|
282
|
+
},
|
|
283
|
+
relations: ['assignee']
|
|
284
|
+
})
|
|
285
|
+
const corrector = endedThreads[0]?.assignee
|
|
286
|
+
|
|
287
|
+
// 2. DataOoc 업데이트
|
|
288
|
+
dataOoc = await tx.getRepository('DataOoc').findOne({
|
|
289
|
+
where: { id: dataOoc.id }
|
|
290
|
+
}) as any
|
|
291
|
+
|
|
292
|
+
dataOoc.correctedAt = terminatedAt
|
|
293
|
+
dataOoc.corrector = corrector
|
|
294
|
+
dataOoc.correctiveAction = correctiveAction
|
|
295
|
+
dataOoc.state = DataOocStatus.CORRECTED
|
|
296
|
+
dataOoc = await tx.save('DataOoc', dataOoc)
|
|
297
|
+
|
|
298
|
+
// Then
|
|
299
|
+
expect(dataOoc.state).toBe(DataOocStatus.CORRECTED)
|
|
300
|
+
expect(dataOoc.correctedAt).toEqual(terminatedAt)
|
|
301
|
+
expect(dataOoc.corrector?.id).toBe(assignee.id)
|
|
302
|
+
expect(dataOoc.correctiveAction).toBe(correctiveAction)
|
|
303
|
+
})
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('correctiveAction은 output.action에서 가져와야 한다', async () => {
|
|
307
|
+
await withTestTransaction(async (context) => {
|
|
308
|
+
const { tx, domain, user } = context.state
|
|
309
|
+
|
|
310
|
+
// Given
|
|
311
|
+
const resolveActivity = await activityFactory.createOocResolveActivity(domain, tx)
|
|
312
|
+
const { dataSet } = await dataSetFactory.createWithRoles({}, domain, tx)
|
|
313
|
+
let dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
314
|
+
{ state: DataOocStatus.REVIEWED },
|
|
315
|
+
dataSet,
|
|
316
|
+
undefined,
|
|
317
|
+
tx
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
const action = '온도 조절 밸브를 교체하고 캘리브레이션 수행'
|
|
321
|
+
const resolveInstance = await activityInstanceFactory.createWithActivity(
|
|
322
|
+
{
|
|
323
|
+
state: ActivityInstanceStatus.Ended,
|
|
324
|
+
input: { dataOocId: dataOoc.id },
|
|
325
|
+
output: { action },
|
|
326
|
+
terminatedAt: new Date()
|
|
327
|
+
},
|
|
328
|
+
resolveActivity,
|
|
329
|
+
domain,
|
|
330
|
+
tx
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
// When: Callback 로직
|
|
334
|
+
const outputAction = resolveInstance.output?.action
|
|
335
|
+
|
|
336
|
+
dataOoc.correctiveAction = outputAction
|
|
337
|
+
dataOoc.state = DataOocStatus.CORRECTED
|
|
338
|
+
dataOoc = await tx.save('DataOoc', dataOoc)
|
|
339
|
+
|
|
340
|
+
// Then
|
|
341
|
+
expect(dataOoc.correctiveAction).toBe(action)
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('corrector는 Ended 상태의 ActivityThread의 assignee여야 한다', async () => {
|
|
346
|
+
await withTestTransaction(async (context) => {
|
|
347
|
+
const { tx, domain } = context.state
|
|
348
|
+
|
|
349
|
+
// Given
|
|
350
|
+
const resolveActivity = await activityFactory.createOocResolveActivity(domain, tx)
|
|
351
|
+
const { dataSet } = await dataSetFactory.createWithRoles({}, domain, tx)
|
|
352
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
353
|
+
{ state: DataOocStatus.REVIEWED },
|
|
354
|
+
dataSet,
|
|
355
|
+
undefined,
|
|
356
|
+
tx
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
const resolveInstance = await activityInstanceFactory.createWithActivity(
|
|
360
|
+
{
|
|
361
|
+
state: ActivityInstanceStatus.Ended,
|
|
362
|
+
input: { dataOocId: dataOoc.id }
|
|
363
|
+
},
|
|
364
|
+
resolveActivity,
|
|
365
|
+
domain,
|
|
366
|
+
tx
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
// 여러 Thread 중 Ended 상태인 Thread의 assignee
|
|
370
|
+
const worker1 = await userFactory.create({ name: 'Worker 1' }, tx)
|
|
371
|
+
const worker2 = await userFactory.create({ name: 'Worker 2' }, tx)
|
|
372
|
+
|
|
373
|
+
await activityThreadFactory.createWithInstanceAndAssignee(
|
|
374
|
+
{ state: ActivityThreadStatus.Aborted }, // Aborted - 제외
|
|
375
|
+
resolveInstance,
|
|
376
|
+
worker1,
|
|
377
|
+
tx
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
const endedThread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
381
|
+
{ state: ActivityThreadStatus.Ended }, // Ended - 선택됨
|
|
382
|
+
resolveInstance,
|
|
383
|
+
worker2,
|
|
384
|
+
tx
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
// When: Ended 상태의 Thread 조회
|
|
388
|
+
const endedThreads = await tx.getRepository('ActivityThread').find({
|
|
389
|
+
where: {
|
|
390
|
+
activityInstance: { id: resolveInstance.id },
|
|
391
|
+
state: ActivityThreadStatus.Ended
|
|
392
|
+
},
|
|
393
|
+
relations: ['assignee']
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
// Then: Ended 상태의 Thread의 assignee가 corrector
|
|
397
|
+
expect(endedThreads.length).toBe(1)
|
|
398
|
+
expect(endedThreads[0].assignee?.id).toBe(worker2.id)
|
|
399
|
+
})
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
it('Ended가 아닌 상태에서는 callback 로직이 실행되지 않아야 한다', async () => {
|
|
403
|
+
await withTestTransaction(async (context) => {
|
|
404
|
+
const { tx, domain } = context.state
|
|
405
|
+
|
|
406
|
+
// Given
|
|
407
|
+
const resolveActivity = await activityFactory.createOocResolveActivity(domain, tx)
|
|
408
|
+
const { dataSet } = await dataSetFactory.createWithRoles({}, domain, tx)
|
|
409
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
410
|
+
{ state: DataOocStatus.REVIEWED },
|
|
411
|
+
dataSet,
|
|
412
|
+
undefined,
|
|
413
|
+
tx
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
const resolveInstance = await activityInstanceFactory.createWithActivity(
|
|
417
|
+
{
|
|
418
|
+
state: ActivityInstanceStatus.Submitted, // Ended가 아님
|
|
419
|
+
input: { dataOocId: dataOoc.id }
|
|
420
|
+
},
|
|
421
|
+
resolveActivity,
|
|
422
|
+
domain,
|
|
423
|
+
tx
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
// When: Callback 조건 체크
|
|
427
|
+
const shouldExecuteCallback = resolveInstance.state === ActivityInstanceStatus.Ended
|
|
428
|
+
|
|
429
|
+
// Then
|
|
430
|
+
expect(shouldExecuteCallback).toBe(false)
|
|
431
|
+
})
|
|
432
|
+
})
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
describe('DataOoc 조회 및 업데이트', () => {
|
|
436
|
+
it('callback에서 dataOocId로 DataOoc을 조회할 수 있어야 한다', async () => {
|
|
437
|
+
await withTestTransaction(async (context) => {
|
|
438
|
+
const { tx, domain } = context.state
|
|
439
|
+
|
|
440
|
+
// Given
|
|
441
|
+
const { dataSet } = await dataSetFactory.createWithRoles({}, domain, tx)
|
|
442
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
443
|
+
{ state: DataOocStatus.REVIEWED },
|
|
444
|
+
dataSet,
|
|
445
|
+
undefined,
|
|
446
|
+
tx
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
// When: input에서 dataOocId 추출 후 조회
|
|
450
|
+
const input = { dataOocId: dataOoc.id }
|
|
451
|
+
const foundDataOoc = await tx.getRepository('DataOoc').findOne({
|
|
452
|
+
where: {
|
|
453
|
+
domain: { id: domain.id },
|
|
454
|
+
id: input.dataOocId
|
|
455
|
+
},
|
|
456
|
+
relations: ['dataSet']
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
// Then
|
|
460
|
+
expect(foundDataOoc).toBeDefined()
|
|
461
|
+
expect(foundDataOoc?.id).toBe(dataOoc.id)
|
|
462
|
+
expect(foundDataOoc?.dataSet?.id).toBe(dataSet.id)
|
|
463
|
+
})
|
|
464
|
+
})
|
|
465
|
+
})
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
describe('전체 워크플로우 Callback 연계', () => {
|
|
469
|
+
it('OOC 생성 → Review Callback → Resolve Callback 전체 플로우', async () => {
|
|
470
|
+
await withTestTransaction(async (context) => {
|
|
471
|
+
const { tx, domain, user } = context.state
|
|
472
|
+
|
|
473
|
+
// Setup
|
|
474
|
+
const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx)
|
|
475
|
+
const resolveActivity = await activityFactory.createOocResolveActivity(domain, tx)
|
|
476
|
+
const { dataSet, supervisoryRole, resolverRole } = await dataSetFactory.createWithRoles({}, domain, tx)
|
|
477
|
+
|
|
478
|
+
const reviewer = await userFactory.create({ name: 'Reviewer' }, tx)
|
|
479
|
+
const corrector = await userFactory.create({ name: 'Corrector' }, tx)
|
|
480
|
+
|
|
481
|
+
// 1. DataOoc 생성 (ISSUED)
|
|
482
|
+
let dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
483
|
+
{
|
|
484
|
+
state: DataOocStatus.ISSUED,
|
|
485
|
+
history: [
|
|
486
|
+
{
|
|
487
|
+
user: { id: user.id, name: user.name },
|
|
488
|
+
state: DataOocStatus.ISSUED,
|
|
489
|
+
timestamp: new Date().toISOString()
|
|
490
|
+
}
|
|
491
|
+
]
|
|
492
|
+
},
|
|
493
|
+
dataSet,
|
|
494
|
+
undefined,
|
|
495
|
+
tx
|
|
496
|
+
)
|
|
497
|
+
expect(dataOoc.state).toBe(DataOocStatus.ISSUED)
|
|
498
|
+
|
|
499
|
+
// 2. Review Activity 발행
|
|
500
|
+
const reviewInstance = await activityInstanceFactory.createWithActivity(
|
|
501
|
+
{
|
|
502
|
+
state: ActivityInstanceStatus.Issued,
|
|
503
|
+
input: { dataOocId: dataOoc.id },
|
|
504
|
+
assigneeRole: supervisoryRole
|
|
505
|
+
},
|
|
506
|
+
reviewActivity,
|
|
507
|
+
domain,
|
|
508
|
+
tx
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
// 3. Review Callback 시뮬레이션 (Ended 상태로)
|
|
512
|
+
const reviewTerminatedAt = new Date()
|
|
513
|
+
const instruction = 'Temperature 조절 필요'
|
|
514
|
+
|
|
515
|
+
reviewInstance.state = ActivityInstanceStatus.Ended
|
|
516
|
+
reviewInstance.terminatedAt = reviewTerminatedAt
|
|
517
|
+
reviewInstance.output = { instruction }
|
|
518
|
+
await tx.save('ActivityInstance', reviewInstance)
|
|
519
|
+
|
|
520
|
+
// DataOoc 업데이트 (Review Callback)
|
|
521
|
+
dataOoc = await tx.getRepository('DataOoc').findOne({
|
|
522
|
+
where: { id: dataOoc.id },
|
|
523
|
+
relations: ['dataSet']
|
|
524
|
+
}) as any
|
|
525
|
+
|
|
526
|
+
dataOoc.reviewedAt = reviewTerminatedAt
|
|
527
|
+
dataOoc.reviewer = reviewer
|
|
528
|
+
dataOoc.correctiveInstruction = instruction
|
|
529
|
+
dataOoc.state = DataOocStatus.REVIEWED
|
|
530
|
+
|
|
531
|
+
// history 업데이트
|
|
532
|
+
const history = dataOoc.history || []
|
|
533
|
+
history.push({
|
|
534
|
+
user: { id: reviewer.id, name: reviewer.name },
|
|
535
|
+
state: DataOocStatus.REVIEWED,
|
|
536
|
+
timestamp: reviewTerminatedAt.toISOString()
|
|
537
|
+
})
|
|
538
|
+
dataOoc.history = history
|
|
539
|
+
dataOoc = await tx.save('DataOoc', dataOoc)
|
|
540
|
+
|
|
541
|
+
expect(dataOoc.state).toBe(DataOocStatus.REVIEWED)
|
|
542
|
+
|
|
543
|
+
// 4. Resolve Activity 발행 (Review Callback에서 호출)
|
|
544
|
+
const resolveInstance = await activityInstanceFactory.createWithActivity(
|
|
545
|
+
{
|
|
546
|
+
state: ActivityInstanceStatus.Issued,
|
|
547
|
+
input: { dataOocId: dataOoc.id, instruction },
|
|
548
|
+
assigneeRole: resolverRole
|
|
549
|
+
},
|
|
550
|
+
resolveActivity,
|
|
551
|
+
domain,
|
|
552
|
+
tx
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
// 5. Resolve Thread 생성 및 완료
|
|
556
|
+
const resolveThread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
557
|
+
{ state: ActivityThreadStatus.Ended, terminatedAt: new Date() },
|
|
558
|
+
resolveInstance,
|
|
559
|
+
corrector,
|
|
560
|
+
tx
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
// 6. Resolve Callback 시뮬레이션 (Ended 상태로)
|
|
564
|
+
const resolveTerminatedAt = new Date()
|
|
565
|
+
const action = 'Temperature를 정상 범위로 조절함'
|
|
566
|
+
|
|
567
|
+
resolveInstance.state = ActivityInstanceStatus.Ended
|
|
568
|
+
resolveInstance.terminatedAt = resolveTerminatedAt
|
|
569
|
+
resolveInstance.output = { action }
|
|
570
|
+
await tx.save('ActivityInstance', resolveInstance)
|
|
571
|
+
|
|
572
|
+
// DataOoc 업데이트 (Resolve Callback)
|
|
573
|
+
dataOoc = await tx.getRepository('DataOoc').findOne({
|
|
574
|
+
where: { id: dataOoc.id },
|
|
575
|
+
relations: ['reviewer']
|
|
576
|
+
}) as any
|
|
577
|
+
|
|
578
|
+
dataOoc.correctedAt = resolveTerminatedAt
|
|
579
|
+
dataOoc.corrector = corrector
|
|
580
|
+
dataOoc.correctiveAction = action
|
|
581
|
+
dataOoc.state = DataOocStatus.CORRECTED
|
|
582
|
+
|
|
583
|
+
// history 업데이트
|
|
584
|
+
dataOoc.history.push({
|
|
585
|
+
user: { id: corrector.id, name: corrector.name },
|
|
586
|
+
state: DataOocStatus.CORRECTED,
|
|
587
|
+
comment: action,
|
|
588
|
+
timestamp: resolveTerminatedAt.toISOString()
|
|
589
|
+
})
|
|
590
|
+
dataOoc = await tx.save('DataOoc', dataOoc)
|
|
591
|
+
|
|
592
|
+
// Then: 최종 상태 확인 - 다시 조회하여 relations 포함
|
|
593
|
+
const finalDataOoc = await tx.getRepository('DataOoc').findOne({
|
|
594
|
+
where: { id: dataOoc.id },
|
|
595
|
+
relations: ['reviewer', 'corrector']
|
|
596
|
+
}) as any
|
|
597
|
+
|
|
598
|
+
expect(finalDataOoc.state).toBe(DataOocStatus.CORRECTED)
|
|
599
|
+
expect(finalDataOoc.reviewedAt).toBeDefined()
|
|
600
|
+
expect(finalDataOoc.correctedAt).toBeDefined()
|
|
601
|
+
expect(finalDataOoc.reviewer?.id).toBe(reviewer.id)
|
|
602
|
+
expect(finalDataOoc.corrector?.id).toBe(corrector.id)
|
|
603
|
+
expect(finalDataOoc.correctiveInstruction).toBe(instruction)
|
|
604
|
+
expect(finalDataOoc.correctiveAction).toBe(action)
|
|
605
|
+
expect(finalDataOoc.history.length).toBe(3)
|
|
606
|
+
})
|
|
607
|
+
})
|
|
608
|
+
})
|
|
609
|
+
})
|