@things-factory/dataset 9.1.19 → 9.2.5
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 +12 -12
- 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,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OOC Workflow Integration Tests
|
|
3
|
+
* OOC(Out Of Control) 처리 워크플로우 통합 테스트
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { TestDatabase } from '../../../../test/test-database'
|
|
7
|
+
import { createTestContext, 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 { DataOocStatus, ActivityInstanceStatus, ActivityThreadStatus } from '../../../../test/entities/schemas'
|
|
21
|
+
|
|
22
|
+
describe('OOC Workflow Integration Tests', () => {
|
|
23
|
+
let testDb: TestDatabase
|
|
24
|
+
|
|
25
|
+
beforeAll(async () => {
|
|
26
|
+
testDb = TestDatabase.getInstance()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('OOC 생성 및 상태 관리', () => {
|
|
30
|
+
it('DataOoc이 ISSUED 상태로 생성되어야 한다', async () => {
|
|
31
|
+
await withTestTransaction(async (context) => {
|
|
32
|
+
const { tx } = context.state
|
|
33
|
+
|
|
34
|
+
// Given: 데이터셋 생성
|
|
35
|
+
const { dataSet, domain } = await dataSetFactory.createWithRoles({}, undefined, tx)
|
|
36
|
+
|
|
37
|
+
// When: OOC 생성
|
|
38
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
39
|
+
{ state: DataOocStatus.ISSUED },
|
|
40
|
+
dataSet,
|
|
41
|
+
undefined,
|
|
42
|
+
tx
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
// Then: ISSUED 상태 확인
|
|
46
|
+
expect(dataOoc.state).toBe(DataOocStatus.ISSUED)
|
|
47
|
+
expect(dataOoc.dataSet?.id).toBe(dataSet.id)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('DataOoc 상태가 ISSUED → REVIEWED → CORRECTED로 전이되어야 한다', async () => {
|
|
52
|
+
await withTestTransaction(async (context) => {
|
|
53
|
+
const { tx } = context.state
|
|
54
|
+
|
|
55
|
+
// Given: OOC 생성
|
|
56
|
+
const dataOoc = await dataOocFactory.create({ state: DataOocStatus.ISSUED }, tx)
|
|
57
|
+
|
|
58
|
+
// When: REVIEWED 상태로 변경
|
|
59
|
+
dataOoc.state = DataOocStatus.REVIEWED
|
|
60
|
+
dataOoc.reviewedAt = new Date()
|
|
61
|
+
const reviewed = await tx.getRepository('DataOoc').save(dataOoc)
|
|
62
|
+
|
|
63
|
+
// Then: REVIEWED 상태 확인
|
|
64
|
+
expect(reviewed.state).toBe(DataOocStatus.REVIEWED)
|
|
65
|
+
expect(reviewed.reviewedAt).toBeDefined()
|
|
66
|
+
|
|
67
|
+
// When: CORRECTED 상태로 변경
|
|
68
|
+
reviewed.state = DataOocStatus.CORRECTED
|
|
69
|
+
reviewed.correctedAt = new Date()
|
|
70
|
+
const corrected = await tx.getRepository('DataOoc').save(reviewed)
|
|
71
|
+
|
|
72
|
+
// Then: CORRECTED 상태 확인
|
|
73
|
+
expect(corrected.state).toBe(DataOocStatus.CORRECTED)
|
|
74
|
+
expect(corrected.correctedAt).toBeDefined()
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('Activity Instance 생명주기', () => {
|
|
80
|
+
it('ActivityInstance가 Issued 상태로 생성되어야 한다', async () => {
|
|
81
|
+
await withTestTransaction(async (context) => {
|
|
82
|
+
const { tx } = context.state
|
|
83
|
+
const domain = await domainFactory.create({}, tx)
|
|
84
|
+
|
|
85
|
+
// Given: OOC Review Activity 생성
|
|
86
|
+
const activity = await activityFactory.createOocReviewActivity(domain, tx)
|
|
87
|
+
|
|
88
|
+
// When: ActivityInstance 생성
|
|
89
|
+
const instance = await activityInstanceFactory.createWithActivity(
|
|
90
|
+
{ state: ActivityInstanceStatus.Issued },
|
|
91
|
+
activity,
|
|
92
|
+
domain,
|
|
93
|
+
tx
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
// Then
|
|
97
|
+
expect(instance.state).toBe(ActivityInstanceStatus.Issued)
|
|
98
|
+
expect(instance.activity?.id).toBe(activity.id)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('ActivityThread가 할당되면 Instance 상태가 Assigned로 변경되어야 한다', async () => {
|
|
103
|
+
await withTestTransaction(async (context) => {
|
|
104
|
+
const { tx } = context.state
|
|
105
|
+
const domain = await domainFactory.create({}, tx)
|
|
106
|
+
|
|
107
|
+
// Given: Instance 생성
|
|
108
|
+
const instance = await activityInstanceFactory.createWithActivity(
|
|
109
|
+
{ state: ActivityInstanceStatus.Issued },
|
|
110
|
+
undefined,
|
|
111
|
+
domain,
|
|
112
|
+
tx
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
// When: Thread 할당
|
|
116
|
+
const user = await userFactory.create({}, tx)
|
|
117
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
118
|
+
{ state: ActivityThreadStatus.Assigned },
|
|
119
|
+
instance,
|
|
120
|
+
user,
|
|
121
|
+
tx
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
// Then
|
|
125
|
+
expect(thread.state).toBe(ActivityThreadStatus.Assigned)
|
|
126
|
+
expect(thread.assignee?.id).toBe(user.id)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe('결재라인 처리', () => {
|
|
132
|
+
it('결재라인이 없는 경우 submit 시 바로 Ended 상태가 되어야 한다', async () => {
|
|
133
|
+
await withTestTransaction(async (context) => {
|
|
134
|
+
const { tx } = context.state
|
|
135
|
+
const domain = await domainFactory.create({}, tx)
|
|
136
|
+
|
|
137
|
+
// Given: 결재라인 없는 Instance
|
|
138
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
139
|
+
[], // 빈 결재라인
|
|
140
|
+
{ state: ActivityInstanceStatus.Started },
|
|
141
|
+
undefined,
|
|
142
|
+
domain,
|
|
143
|
+
tx
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
const user = await userFactory.create({}, tx)
|
|
147
|
+
const thread = await activityThreadFactory.createWithInstanceAndAssignee(
|
|
148
|
+
{ state: ActivityThreadStatus.Started },
|
|
149
|
+
instance,
|
|
150
|
+
user,
|
|
151
|
+
tx
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
// When: Thread 완료 시뮬레이션
|
|
155
|
+
thread.state = ActivityThreadStatus.Ended
|
|
156
|
+
thread.terminatedAt = new Date()
|
|
157
|
+
const endedThread = await tx.getRepository('ActivityThread').save(thread)
|
|
158
|
+
|
|
159
|
+
// Then
|
|
160
|
+
expect(endedThread.state).toBe(ActivityThreadStatus.Ended)
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('ROLE 타입 결재라인이 올바르게 저장되어야 한다', async () => {
|
|
165
|
+
await withTestTransaction(async (context) => {
|
|
166
|
+
const { tx } = context.state
|
|
167
|
+
const domain = await domainFactory.create({}, tx)
|
|
168
|
+
const approverRole = await roleFactory.create({ name: 'Approver Role', domain }, tx)
|
|
169
|
+
|
|
170
|
+
// Given: ROLE 타입 결재라인
|
|
171
|
+
const approvalLine = [
|
|
172
|
+
{
|
|
173
|
+
type: 'Role',
|
|
174
|
+
approver: { id: approverRole.id, name: approverRole.name }
|
|
175
|
+
// value는 저장되지 않음 (클라이언트 동작과 일치)
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
// When: Instance 생성
|
|
180
|
+
const instance = await activityInstanceFactory.createWithApprovalLine(
|
|
181
|
+
approvalLine,
|
|
182
|
+
{},
|
|
183
|
+
undefined,
|
|
184
|
+
domain,
|
|
185
|
+
tx
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
// Then: 결재라인 확인
|
|
189
|
+
expect(instance.approvalLine).toBeDefined()
|
|
190
|
+
expect(instance.approvalLine?.length).toBe(1)
|
|
191
|
+
expect(instance.approvalLine?.[0].type).toBe('Role')
|
|
192
|
+
expect(instance.approvalLine?.[0].approver?.id).toBe(approverRole.id)
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
describe('OOC Review → Resolve 워크플로우', () => {
|
|
198
|
+
it('전체 OOC 워크플로우가 정상 동작해야 한다', async () => {
|
|
199
|
+
await withTestTransaction(async (context) => {
|
|
200
|
+
const { tx, domain, user } = context.state
|
|
201
|
+
|
|
202
|
+
// 1. DataSet with roles 생성
|
|
203
|
+
const { dataSet, supervisoryRole, resolverRole } = await dataSetFactory.createWithRoles(
|
|
204
|
+
{},
|
|
205
|
+
domain,
|
|
206
|
+
tx
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
// 2. OOC Activities 생성
|
|
210
|
+
const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx)
|
|
211
|
+
const resolveActivity = await activityFactory.createOocResolveActivity(domain, tx)
|
|
212
|
+
|
|
213
|
+
// 3. DataOoc 생성 (ISSUED 상태)
|
|
214
|
+
const dataOoc = await dataOocFactory.createWithDataSetAndSample(
|
|
215
|
+
{ state: DataOocStatus.ISSUED },
|
|
216
|
+
dataSet,
|
|
217
|
+
undefined,
|
|
218
|
+
tx
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
expect(dataOoc.state).toBe(DataOocStatus.ISSUED)
|
|
222
|
+
|
|
223
|
+
// 4. OOC Review Instance 생성 시뮬레이션
|
|
224
|
+
const reviewInstance = await activityInstanceFactory.createWithActivity(
|
|
225
|
+
{
|
|
226
|
+
name: `[OOC 검토] ${dataSet.name}`,
|
|
227
|
+
state: ActivityInstanceStatus.Issued,
|
|
228
|
+
assigneeRole: supervisoryRole,
|
|
229
|
+
input: { dataOocId: dataOoc.id }
|
|
230
|
+
},
|
|
231
|
+
reviewActivity,
|
|
232
|
+
domain,
|
|
233
|
+
tx
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
expect(reviewInstance.input?.dataOocId).toBe(dataOoc.id)
|
|
237
|
+
|
|
238
|
+
// 5. Review 완료 시뮬레이션 → DataOoc REVIEWED
|
|
239
|
+
dataOoc.state = DataOocStatus.REVIEWED
|
|
240
|
+
dataOoc.reviewedAt = new Date()
|
|
241
|
+
dataOoc.correctiveInstruction = 'Temperature 조절 필요'
|
|
242
|
+
await tx.getRepository('DataOoc').save(dataOoc)
|
|
243
|
+
|
|
244
|
+
// 6. OOC Resolve Instance 생성 시뮬레이션
|
|
245
|
+
const resolveInstance = await activityInstanceFactory.createWithActivity(
|
|
246
|
+
{
|
|
247
|
+
name: `[OOC 조치] ${dataSet.name}`,
|
|
248
|
+
state: ActivityInstanceStatus.Issued,
|
|
249
|
+
assigneeRole: resolverRole,
|
|
250
|
+
input: {
|
|
251
|
+
dataOocId: dataOoc.id,
|
|
252
|
+
instruction: dataOoc.correctiveInstruction
|
|
253
|
+
},
|
|
254
|
+
approvalLine: dataSet.outlierApprovalLine || []
|
|
255
|
+
},
|
|
256
|
+
resolveActivity,
|
|
257
|
+
domain,
|
|
258
|
+
tx
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
expect(resolveInstance.input?.instruction).toBe('Temperature 조절 필요')
|
|
262
|
+
|
|
263
|
+
// 7. Resolve 완료 시뮬레이션 → DataOoc CORRECTED
|
|
264
|
+
dataOoc.state = DataOocStatus.CORRECTED
|
|
265
|
+
dataOoc.correctedAt = new Date()
|
|
266
|
+
dataOoc.correctiveAction = 'Temperature를 정상 범위로 조절함'
|
|
267
|
+
const finalOoc = await tx.getRepository('DataOoc').save(dataOoc)
|
|
268
|
+
|
|
269
|
+
// Then: 최종 상태 확인
|
|
270
|
+
expect(finalOoc.state).toBe(DataOocStatus.CORRECTED)
|
|
271
|
+
expect(finalOoc.correctiveInstruction).toBe('Temperature 조절 필요')
|
|
272
|
+
expect(finalOoc.correctiveAction).toBe('Temperature를 정상 범위로 조절함')
|
|
273
|
+
})
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple Database Integration Test
|
|
3
|
+
* 기본 데이터베이스 연결 및 엔티티 테스트
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { TestDatabase } from '../../../../test/test-database'
|
|
7
|
+
import { Domain, DataSet } from '../../../../test/entities/schemas'
|
|
8
|
+
|
|
9
|
+
describe('Simple Database Test', () => {
|
|
10
|
+
let testDb: TestDatabase
|
|
11
|
+
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
testDb = TestDatabase.getInstance()
|
|
14
|
+
// Ensure initialized
|
|
15
|
+
if (!testDb.getDataSource().isInitialized) {
|
|
16
|
+
await testDb.initialize()
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should have Domain entity registered', async () => {
|
|
21
|
+
const metadata = testDb.getDataSource().getMetadata('Domain')
|
|
22
|
+
expect(metadata).toBeDefined()
|
|
23
|
+
expect(metadata.name).toBe('Domain')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should create and retrieve a Domain', async () => {
|
|
27
|
+
const manager = testDb.getManager()
|
|
28
|
+
|
|
29
|
+
const domain = await manager.save('Domain', {
|
|
30
|
+
name: 'Test',
|
|
31
|
+
subdomain: 'test',
|
|
32
|
+
timezone: 'UTC'
|
|
33
|
+
}) as Domain
|
|
34
|
+
|
|
35
|
+
expect(domain.id).toBeDefined()
|
|
36
|
+
expect(domain.name).toBe('Test')
|
|
37
|
+
|
|
38
|
+
const found = await manager.findOne('Domain', { where: { id: domain.id } }) as Domain | null
|
|
39
|
+
expect(found).toBeDefined()
|
|
40
|
+
expect(found?.name).toBe('Test')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should create a DataSet with Domain', async () => {
|
|
44
|
+
const manager = testDb.getManager()
|
|
45
|
+
|
|
46
|
+
const domain = await manager.save('Domain', {
|
|
47
|
+
name: 'Test Domain',
|
|
48
|
+
subdomain: 'test-ds',
|
|
49
|
+
timezone: 'UTC'
|
|
50
|
+
}) as Domain
|
|
51
|
+
|
|
52
|
+
const dataSet = await manager.save('DataSet', {
|
|
53
|
+
name: 'Test DataSet',
|
|
54
|
+
type: 'manual',
|
|
55
|
+
active: true,
|
|
56
|
+
domain
|
|
57
|
+
}) as DataSet
|
|
58
|
+
|
|
59
|
+
expect(dataSet.id).toBeDefined()
|
|
60
|
+
expect(dataSet.name).toBe('Test DataSet')
|
|
61
|
+
})
|
|
62
|
+
})
|