@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/dataset",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.13",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"browser": "dist-client/index.js",
|
|
6
6
|
"things-factory": true,
|
|
@@ -40,21 +40,21 @@
|
|
|
40
40
|
"@operato/shell": "^9.0.0",
|
|
41
41
|
"@operato/styles": "^9.0.0",
|
|
42
42
|
"@operato/utils": "^9.0.0",
|
|
43
|
-
"@things-factory/auth-base": "^9.
|
|
44
|
-
"@things-factory/aws-base": "^9.
|
|
45
|
-
"@things-factory/board-service": "^9.
|
|
46
|
-
"@things-factory/env": "^9.
|
|
47
|
-
"@things-factory/integration-base": "^9.
|
|
48
|
-
"@things-factory/organization": "^9.
|
|
49
|
-
"@things-factory/personalization": "^9.
|
|
50
|
-
"@things-factory/scheduler-client": "^9.
|
|
51
|
-
"@things-factory/shell": "^9.
|
|
52
|
-
"@things-factory/work-shift": "^9.
|
|
53
|
-
"@things-factory/worklist": "^9.
|
|
43
|
+
"@things-factory/auth-base": "^9.2.13",
|
|
44
|
+
"@things-factory/aws-base": "^9.2.13",
|
|
45
|
+
"@things-factory/board-service": "^9.2.13",
|
|
46
|
+
"@things-factory/env": "^9.2.13",
|
|
47
|
+
"@things-factory/integration-base": "^9.2.13",
|
|
48
|
+
"@things-factory/organization": "^9.2.13",
|
|
49
|
+
"@things-factory/personalization": "^9.2.13",
|
|
50
|
+
"@things-factory/scheduler-client": "^9.2.13",
|
|
51
|
+
"@things-factory/shell": "^9.2.13",
|
|
52
|
+
"@things-factory/work-shift": "^9.2.13",
|
|
53
|
+
"@things-factory/worklist": "^9.2.13",
|
|
54
54
|
"cron-parser": "^4.3.0",
|
|
55
55
|
"moment-timezone": "^0.5.45",
|
|
56
56
|
"simple-statistics": "^7.8.3",
|
|
57
57
|
"statistics": "^3.3.0"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "d65748803a86a1fb6c9810ea4f93519c6f44f6d5"
|
|
60
60
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug Test
|
|
3
|
+
* 엔티티 등록 디버깅
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { TestDatabase } from '../../../../test/test-database'
|
|
7
|
+
|
|
8
|
+
describe('Debug Entity Registration', () => {
|
|
9
|
+
let testDb: TestDatabase
|
|
10
|
+
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
testDb = TestDatabase.getInstance()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should debug entity registration', () => {
|
|
16
|
+
const ds = testDb.getDataSource()
|
|
17
|
+
|
|
18
|
+
console.log('DataSource initialized:', ds.isInitialized)
|
|
19
|
+
console.log('Number of entity metadatas:', ds.entityMetadatas.length)
|
|
20
|
+
|
|
21
|
+
ds.entityMetadatas.forEach((meta) => {
|
|
22
|
+
console.log('Registered entity:', meta.name)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should create and retrieve a Domain using entity name', async () => {
|
|
27
|
+
const manager = testDb.getManager()
|
|
28
|
+
|
|
29
|
+
const domain = await manager.save('Domain', {
|
|
30
|
+
name: 'Test',
|
|
31
|
+
subdomain: 'test',
|
|
32
|
+
timezone: 'UTC'
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
expect(domain.id).toBeDefined()
|
|
36
|
+
expect(domain.name).toBe('Test')
|
|
37
|
+
|
|
38
|
+
const found = await manager.findOne('Domain', { where: { id: domain.id } })
|
|
39
|
+
expect(found).toBeDefined()
|
|
40
|
+
expect(found?.name).toBe('Test')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OOC Lifecycle Integration Tests
|
|
3
|
+
* DataOoc 전체 생명주기 통합 테스트
|
|
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
|
+
dataSampleFactory,
|
|
14
|
+
dataOocFactory,
|
|
15
|
+
activityFactory,
|
|
16
|
+
activityInstanceFactory,
|
|
17
|
+
activityThreadFactory
|
|
18
|
+
} from '../../../../test/factories'
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
DataOocStatus,
|
|
22
|
+
ActivityInstanceStatus,
|
|
23
|
+
ActivityThreadStatus
|
|
24
|
+
} from '../../../../test/entities/schemas'
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
simulateOocReviewCompletion,
|
|
28
|
+
simulateOocResolveCompletion,
|
|
29
|
+
createHistoryEntry,
|
|
30
|
+
isValidOocTransition
|
|
31
|
+
} from '../../../../test/helpers/workflow-helpers'
|
|
32
|
+
|
|
33
|
+
describe('OOC Complete Lifecycle Integration Tests', () => {
|
|
34
|
+
let testDb: TestDatabase
|
|
35
|
+
|
|
36
|
+
beforeAll(async () => {
|
|
37
|
+
testDb = TestDatabase.getInstance()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('OOC Creation from DataSample', () => {
|
|
41
|
+
it('DataSample에서 ooc=true일 때 DataOoc이 생성되어야 한다', async () => {
|
|
42
|
+
await withTestTransaction(async (context) => {
|
|
43
|
+
const { tx } = context.state
|
|
44
|
+
|
|
45
|
+
// Given: OOC 조건의 DataSample
|
|
46
|
+
const { dataSet } = await dataSetFactory.createWithRoles({}, undefined, tx)
|
|
47
|
+
const dataSample = await dataSampleFactory.createOocSample({}, dataSet, tx)
|
|
48
|
+
|
|
49
|
+
// When: DataOoc 생성
|
|
50
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
51
|
+
{
|
|
52
|
+
state: DataOocStatus.ISSUED,
|
|
53
|
+
data: dataSample.data,
|
|
54
|
+
ooc: true,
|
|
55
|
+
oos: false
|
|
56
|
+
},
|
|
57
|
+
dataSet,
|
|
58
|
+
dataSample,
|
|
59
|
+
tx
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
// Then: OOC이 생성되어야 함
|
|
63
|
+
expect(dataOoc).toBeDefined()
|
|
64
|
+
expect(dataOoc.id).toBeDefined()
|
|
65
|
+
expect(dataOoc.ooc).toBe(true)
|
|
66
|
+
expect(dataOoc.state).toBe(DataOocStatus.ISSUED)
|
|
67
|
+
expect(dataOoc.dataSet?.id).toBe(dataSet.id)
|
|
68
|
+
expect(dataOoc.dataSample?.id).toBe(dataSample.id)
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('DataOoc 생성 시 초기 상태는 ISSUED여야 한다', async () => {
|
|
73
|
+
await withTestTransaction(async (context) => {
|
|
74
|
+
const { tx } = context.state
|
|
75
|
+
|
|
76
|
+
// Given & When: DataOoc 생성
|
|
77
|
+
const dataOoc = await dataOocFactory.create(
|
|
78
|
+
{ state: DataOocStatus.ISSUED },
|
|
79
|
+
tx
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
// Then
|
|
83
|
+
expect(dataOoc.state).toBe(DataOocStatus.ISSUED)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('DataOoc 생성 시 history 배열이 초기화되어야 한다', async () => {
|
|
88
|
+
await withTestTransaction(async (context) => {
|
|
89
|
+
const { tx, user } = context.state
|
|
90
|
+
|
|
91
|
+
// Given & When
|
|
92
|
+
const dataOoc = await dataOocFactory.create(
|
|
93
|
+
{
|
|
94
|
+
state: DataOocStatus.ISSUED,
|
|
95
|
+
history: [createHistoryEntry(user, DataOocStatus.ISSUED)]
|
|
96
|
+
},
|
|
97
|
+
tx
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
// Then
|
|
101
|
+
expect(dataOoc.history).toBeDefined()
|
|
102
|
+
expect(dataOoc.history?.length).toBe(1)
|
|
103
|
+
expect(dataOoc.history?.[0].state).toBe(DataOocStatus.ISSUED)
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('DataOoc의 data, judgment 필드가 DataSample에서 복사되어야 한다', async () => {
|
|
108
|
+
await withTestTransaction(async (context) => {
|
|
109
|
+
const { tx } = context.state
|
|
110
|
+
|
|
111
|
+
// Given
|
|
112
|
+
const { dataSet } = await dataSetFactory.createWithRoles({}, undefined, tx)
|
|
113
|
+
const sampleData = { temperature: 95, humidity: 50 }
|
|
114
|
+
const sampleJudgment = {
|
|
115
|
+
temperature: { ooc: true, oos: false },
|
|
116
|
+
humidity: { ooc: false, oos: false }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// When
|
|
120
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
121
|
+
{
|
|
122
|
+
data: sampleData,
|
|
123
|
+
judgment: sampleJudgment,
|
|
124
|
+
ooc: true
|
|
125
|
+
},
|
|
126
|
+
dataSet,
|
|
127
|
+
undefined,
|
|
128
|
+
tx
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
// Then
|
|
132
|
+
expect(dataOoc.data).toEqual(sampleData)
|
|
133
|
+
expect(dataOoc.judgment).toEqual(sampleJudgment)
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('OOC Review Activity Workflow', () => {
|
|
139
|
+
it('OOC Review 발행 시 supervisoryRole로 할당되어야 한다', async () => {
|
|
140
|
+
await withTestTransaction(async (context) => {
|
|
141
|
+
const { tx } = context.state
|
|
142
|
+
const domain = await domainFactory.create({}, tx)
|
|
143
|
+
|
|
144
|
+
// Given: DataSet with supervisoryRole
|
|
145
|
+
const { dataSet, supervisoryRole } = await dataSetFactory.createWithRoles(
|
|
146
|
+
{},
|
|
147
|
+
domain,
|
|
148
|
+
tx
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
// When: OOC Review Activity 발행 시뮬레이션
|
|
152
|
+
const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx)
|
|
153
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
154
|
+
{ state: DataOocStatus.ISSUED },
|
|
155
|
+
dataSet,
|
|
156
|
+
undefined,
|
|
157
|
+
tx
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const reviewInstance = await activityInstanceFactory.createWithActivity(
|
|
161
|
+
{
|
|
162
|
+
name: `[OOC 검토] ${dataSet.name}`,
|
|
163
|
+
state: ActivityInstanceStatus.Issued,
|
|
164
|
+
assigneeRole: supervisoryRole,
|
|
165
|
+
input: { dataOocId: dataOoc.id }
|
|
166
|
+
},
|
|
167
|
+
reviewActivity,
|
|
168
|
+
domain,
|
|
169
|
+
tx
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
// Then
|
|
173
|
+
expect(reviewInstance.assigneeRole?.id).toBe(supervisoryRole.id)
|
|
174
|
+
expect(reviewInstance.input?.dataOocId).toBe(dataOoc.id)
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('OOC Review 완료 시 DataOoc 상태가 REVIEWED로 전이되어야 한다', async () => {
|
|
179
|
+
await withTestTransaction(async (context) => {
|
|
180
|
+
const { tx, user } = context.state
|
|
181
|
+
|
|
182
|
+
// Given
|
|
183
|
+
const dataOoc = await dataOocFactory.create(
|
|
184
|
+
{ state: DataOocStatus.ISSUED },
|
|
185
|
+
tx
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
// When: Review 완료 시뮬레이션
|
|
189
|
+
const reviewed = await simulateOocReviewCompletion(
|
|
190
|
+
dataOoc,
|
|
191
|
+
'Temperature 조절 필요',
|
|
192
|
+
user,
|
|
193
|
+
{ tx, domain: context.state.domain, user }
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
// Then
|
|
197
|
+
expect(reviewed.state).toBe(DataOocStatus.REVIEWED)
|
|
198
|
+
expect(reviewed.reviewedAt).toBeDefined()
|
|
199
|
+
expect(reviewed.reviewer?.id).toBe(user.id)
|
|
200
|
+
expect(reviewed.correctiveInstruction).toBe('Temperature 조절 필요')
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('OOC Review 완료 시 history에 기록이 추가되어야 한다', async () => {
|
|
205
|
+
await withTestTransaction(async (context) => {
|
|
206
|
+
const { tx, user } = context.state
|
|
207
|
+
|
|
208
|
+
// Given
|
|
209
|
+
const initialHistory = [createHistoryEntry(user, DataOocStatus.ISSUED)]
|
|
210
|
+
const dataOoc = await dataOocFactory.create(
|
|
211
|
+
{
|
|
212
|
+
state: DataOocStatus.ISSUED,
|
|
213
|
+
history: initialHistory
|
|
214
|
+
},
|
|
215
|
+
tx
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
// When
|
|
219
|
+
const reviewed = await simulateOocReviewCompletion(
|
|
220
|
+
dataOoc,
|
|
221
|
+
'Instruction',
|
|
222
|
+
user,
|
|
223
|
+
{ tx, domain: context.state.domain, user }
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
// Then
|
|
227
|
+
expect(reviewed.history?.length).toBe(2)
|
|
228
|
+
expect(reviewed.history?.[1].state).toBe(DataOocStatus.REVIEWED)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
describe('OOC Resolve Activity Workflow', () => {
|
|
234
|
+
it('OOC Resolve 발행 시 resolverRole로 할당되어야 한다', async () => {
|
|
235
|
+
await withTestTransaction(async (context) => {
|
|
236
|
+
const { tx } = context.state
|
|
237
|
+
const domain = await domainFactory.create({}, tx)
|
|
238
|
+
|
|
239
|
+
// Given
|
|
240
|
+
const { dataSet, resolverRole } = await dataSetFactory.createWithRoles(
|
|
241
|
+
{},
|
|
242
|
+
domain,
|
|
243
|
+
tx
|
|
244
|
+
)
|
|
245
|
+
const resolveActivity = await activityFactory.createOocResolveActivity(domain, tx)
|
|
246
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
247
|
+
{ state: DataOocStatus.REVIEWED, correctiveInstruction: 'Fix temperature' },
|
|
248
|
+
dataSet,
|
|
249
|
+
undefined,
|
|
250
|
+
tx
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
// When
|
|
254
|
+
const resolveInstance = await activityInstanceFactory.createWithActivity(
|
|
255
|
+
{
|
|
256
|
+
name: `[OOC 조치] ${dataSet.name}`,
|
|
257
|
+
state: ActivityInstanceStatus.Issued,
|
|
258
|
+
assigneeRole: resolverRole,
|
|
259
|
+
input: {
|
|
260
|
+
dataOocId: dataOoc.id,
|
|
261
|
+
instruction: dataOoc.correctiveInstruction
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
resolveActivity,
|
|
265
|
+
domain,
|
|
266
|
+
tx
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
// Then
|
|
270
|
+
expect(resolveInstance.assigneeRole?.id).toBe(resolverRole.id)
|
|
271
|
+
expect(resolveInstance.input?.instruction).toBe('Fix temperature')
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('OOC Resolve 발행 시 outlierApprovalLine이 적용되어야 한다', async () => {
|
|
276
|
+
await withTestTransaction(async (context) => {
|
|
277
|
+
const { tx } = context.state
|
|
278
|
+
const domain = await domainFactory.create({}, tx)
|
|
279
|
+
const approverRole = await roleFactory.create({ name: 'Approver', domain }, tx)
|
|
280
|
+
|
|
281
|
+
// Given: DataSet with outlierApprovalLine
|
|
282
|
+
const outlierApprovalLine = [
|
|
283
|
+
{ type: 'Role', value: approverRole.id, approver: { id: approverRole.id, name: approverRole.name } }
|
|
284
|
+
]
|
|
285
|
+
const dataSet = await dataSetFactory.createWithDomain(
|
|
286
|
+
{ outlierApprovalLine },
|
|
287
|
+
domain,
|
|
288
|
+
tx
|
|
289
|
+
)
|
|
290
|
+
const resolveActivity = await activityFactory.createOocResolveActivity(domain, tx)
|
|
291
|
+
|
|
292
|
+
// When
|
|
293
|
+
const resolveInstance = await activityInstanceFactory.createWithActivity(
|
|
294
|
+
{
|
|
295
|
+
state: ActivityInstanceStatus.Issued,
|
|
296
|
+
approvalLine: dataSet.outlierApprovalLine
|
|
297
|
+
},
|
|
298
|
+
resolveActivity,
|
|
299
|
+
domain,
|
|
300
|
+
tx
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
// Then
|
|
304
|
+
expect(resolveInstance.approvalLine?.length).toBe(1)
|
|
305
|
+
expect(resolveInstance.approvalLine?.[0].type).toBe('Role')
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('OOC Resolve 완료 시 DataOoc 상태가 CORRECTED로 전이되어야 한다', async () => {
|
|
310
|
+
await withTestTransaction(async (context) => {
|
|
311
|
+
const { tx, user } = context.state
|
|
312
|
+
|
|
313
|
+
// Given: REVIEWED 상태의 DataOoc
|
|
314
|
+
const dataOoc = await dataOocFactory.create(
|
|
315
|
+
{
|
|
316
|
+
state: DataOocStatus.REVIEWED,
|
|
317
|
+
reviewedAt: new Date(),
|
|
318
|
+
correctiveInstruction: 'Fix it'
|
|
319
|
+
},
|
|
320
|
+
tx
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
// When: Resolve 완료 시뮬레이션
|
|
324
|
+
const corrected = await simulateOocResolveCompletion(
|
|
325
|
+
dataOoc,
|
|
326
|
+
'Temperature를 정상 범위로 조절함',
|
|
327
|
+
user,
|
|
328
|
+
{ tx, domain: context.state.domain, user }
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
// Then
|
|
332
|
+
expect(corrected.state).toBe(DataOocStatus.CORRECTED)
|
|
333
|
+
expect(corrected.correctedAt).toBeDefined()
|
|
334
|
+
expect(corrected.corrector?.id).toBe(user.id)
|
|
335
|
+
expect(corrected.correctiveAction).toBe('Temperature를 정상 범위로 조절함')
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
describe('Complete OOC Workflow End-to-End', () => {
|
|
341
|
+
it('전체 워크플로우: ISSUED -> REVIEWED -> CORRECTED', async () => {
|
|
342
|
+
await withTestTransaction(async (context) => {
|
|
343
|
+
const { tx, domain, user } = context.state
|
|
344
|
+
|
|
345
|
+
// 1. Setup
|
|
346
|
+
const { dataSet, supervisoryRole, resolverRole } = await dataSetFactory.createWithRoles(
|
|
347
|
+
{},
|
|
348
|
+
domain,
|
|
349
|
+
tx
|
|
350
|
+
)
|
|
351
|
+
const reviewer = await userFactory.create({ name: 'Reviewer' }, tx)
|
|
352
|
+
const corrector = await userFactory.create({ name: 'Corrector' }, tx)
|
|
353
|
+
|
|
354
|
+
// 2. OOC 생성 (ISSUED)
|
|
355
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
356
|
+
{
|
|
357
|
+
state: DataOocStatus.ISSUED,
|
|
358
|
+
history: [createHistoryEntry(user, DataOocStatus.ISSUED)]
|
|
359
|
+
},
|
|
360
|
+
dataSet,
|
|
361
|
+
undefined,
|
|
362
|
+
tx
|
|
363
|
+
)
|
|
364
|
+
expect(dataOoc.state).toBe(DataOocStatus.ISSUED)
|
|
365
|
+
|
|
366
|
+
// 3. Review 완료 (REVIEWED)
|
|
367
|
+
const reviewed = await simulateOocReviewCompletion(
|
|
368
|
+
dataOoc,
|
|
369
|
+
'Temperature 조절 필요',
|
|
370
|
+
reviewer,
|
|
371
|
+
{ tx, domain, user: reviewer }
|
|
372
|
+
)
|
|
373
|
+
expect(reviewed.state).toBe(DataOocStatus.REVIEWED)
|
|
374
|
+
expect(reviewed.correctiveInstruction).toBe('Temperature 조절 필요')
|
|
375
|
+
|
|
376
|
+
// 4. Resolve 완료 (CORRECTED)
|
|
377
|
+
const corrected = await simulateOocResolveCompletion(
|
|
378
|
+
reviewed,
|
|
379
|
+
'Temperature를 정상 범위로 조절함',
|
|
380
|
+
corrector,
|
|
381
|
+
{ tx, domain, user: corrector }
|
|
382
|
+
)
|
|
383
|
+
expect(corrected.state).toBe(DataOocStatus.CORRECTED)
|
|
384
|
+
expect(corrected.correctiveAction).toBe('Temperature를 정상 범위로 조절함')
|
|
385
|
+
|
|
386
|
+
// 5. 최종 상태 검증
|
|
387
|
+
expect(corrected.reviewedAt).toBeDefined()
|
|
388
|
+
expect(corrected.correctedAt).toBeDefined()
|
|
389
|
+
expect(corrected.reviewer?.id).toBe(reviewer.id)
|
|
390
|
+
expect(corrected.corrector?.id).toBe(corrector.id)
|
|
391
|
+
expect(corrected.history?.length).toBe(3)
|
|
392
|
+
})
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
it('워크플로우 중 잘못된 상태 전이는 불가능해야 한다', async () => {
|
|
396
|
+
await withTestTransaction(async (context) => {
|
|
397
|
+
const { tx } = context.state
|
|
398
|
+
|
|
399
|
+
// Given: ISSUED 상태의 DataOoc
|
|
400
|
+
const dataOoc = await dataOocFactory.create(
|
|
401
|
+
{ state: DataOocStatus.ISSUED },
|
|
402
|
+
tx
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
// Then: ISSUED에서 CORRECTED로 직접 전이는 불가
|
|
406
|
+
expect(isValidOocTransition(DataOocStatus.ISSUED, DataOocStatus.CORRECTED)).toBe(false)
|
|
407
|
+
})
|
|
408
|
+
})
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
describe('Edge Cases', () => {
|
|
412
|
+
it('supervisoryRole이 없으면 Review Activity가 발행되지 않아야 한다', async () => {
|
|
413
|
+
await withTestTransaction(async (context) => {
|
|
414
|
+
const { tx } = context.state
|
|
415
|
+
const domain = await domainFactory.create({}, tx)
|
|
416
|
+
|
|
417
|
+
// Given: supervisoryRole이 없는 DataSet
|
|
418
|
+
const dataSet = await dataSetFactory.createWithDomain(
|
|
419
|
+
{ supervisoryRole: undefined },
|
|
420
|
+
domain,
|
|
421
|
+
tx
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
// Then: supervisoryRole이 없음
|
|
425
|
+
expect(dataSet.supervisoryRole).toBeUndefined()
|
|
426
|
+
})
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
it('resolverRole이 없으면 Resolve Activity가 발행되지 않아야 한다', async () => {
|
|
430
|
+
await withTestTransaction(async (context) => {
|
|
431
|
+
const { tx } = context.state
|
|
432
|
+
const domain = await domainFactory.create({}, tx)
|
|
433
|
+
|
|
434
|
+
// Given: resolverRole이 없는 DataSet
|
|
435
|
+
const dataSet = await dataSetFactory.createWithDomain(
|
|
436
|
+
{ resolverRole: undefined },
|
|
437
|
+
domain,
|
|
438
|
+
tx
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
// Then: resolverRole이 없음
|
|
442
|
+
expect(dataSet.resolverRole).toBeUndefined()
|
|
443
|
+
})
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('CORRECTED 상태의 DataOoc은 더 이상 전이할 수 없다', async () => {
|
|
447
|
+
await withTestTransaction(async (context) => {
|
|
448
|
+
const { tx } = context.state
|
|
449
|
+
|
|
450
|
+
// Given: CORRECTED 상태의 DataOoc
|
|
451
|
+
const dataOoc = await dataOocFactory.create(
|
|
452
|
+
{
|
|
453
|
+
state: DataOocStatus.CORRECTED,
|
|
454
|
+
correctedAt: new Date()
|
|
455
|
+
},
|
|
456
|
+
tx
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
// Then
|
|
460
|
+
expect(dataOoc.state).toBe(DataOocStatus.CORRECTED)
|
|
461
|
+
expect(isValidOocTransition(DataOocStatus.CORRECTED, DataOocStatus.ISSUED)).toBe(false)
|
|
462
|
+
expect(isValidOocTransition(DataOocStatus.CORRECTED, DataOocStatus.REVIEWED)).toBe(false)
|
|
463
|
+
})
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
it('DataOoc 생성 시 domain이 설정되어야 한다', async () => {
|
|
467
|
+
await withTestTransaction(async (context) => {
|
|
468
|
+
const { tx, domain } = context.state
|
|
469
|
+
|
|
470
|
+
// Given & When
|
|
471
|
+
const { dataSet } = await dataSetFactory.createWithRoles({}, domain, tx)
|
|
472
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
473
|
+
{ state: DataOocStatus.ISSUED },
|
|
474
|
+
dataSet,
|
|
475
|
+
undefined,
|
|
476
|
+
tx
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
// Then
|
|
480
|
+
expect(dataOoc.domain?.id).toBe(domain.id)
|
|
481
|
+
})
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
})
|