/** * issue-ooc-review Controller Unit Tests * OOC Review Activity 발행 컨트롤러 테스트 */ import { TestDatabase } from '../../../../../test/test-database' import { withTestTransaction } from '../../../../../test/test-context' import { domainFactory, userFactory, roleFactory, dataSetFactory, dataSampleFactory, dataOocFactory, activityFactory, activityInstanceFactory } from '../../../../../test/factories' import { DataOocStatus, ActivityInstanceStatus } from '../../../../../test/entities/schemas' describe('issue-ooc-review Controller', () => { let testDb: TestDatabase beforeAll(async () => { testDb = TestDatabase.getInstance() }) describe('OOC Review Activity 발행 조건', () => { it('Activity가 존재하고 supervisoryRole이 있으면 Review Instance가 생성되어야 한다', async () => { await withTestTransaction(async (context) => { const { tx, domain } = context.state // Given: OOC Review Activity와 supervisoryRole이 있는 DataSet const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx) const supervisoryRole = await roleFactory.create({ name: 'Supervisor', domain }, tx) const dataSet = await dataSetFactory.createWithDomain( { supervisoryRole }, domain, tx ) const dataOoc = await dataOocFactory.createWithDataSetAndSample( { state: DataOocStatus.ISSUED }, dataSet, undefined, tx ) // When: issueOocReview 로직 시뮬레이션 const activityInstance = await activityInstanceFactory.createWithActivity( { name: `[OOC 검토] ${dataSet.name}`, description: dataSet.description, state: ActivityInstanceStatus.Issued, dueAt: new Date(dataOoc.collectedAt.getTime() + reviewActivity.standardTime * 1000), input: { dataOocId: dataOoc.id }, assigneeRole: supervisoryRole, threadsMin: 1, threadsMax: 1, approvalLine: [] }, reviewActivity, domain, tx ) // Then expect(activityInstance).toBeDefined() expect(activityInstance.name).toBe(`[OOC 검토] ${dataSet.name}`) expect(activityInstance.input?.dataOocId).toBe(dataOoc.id) expect(activityInstance.assigneeRole?.id).toBe(supervisoryRole.id) expect(activityInstance.activity?.id).toBe(reviewActivity.id) }) }) it('supervisoryRole이 없으면 Review Activity가 발행되지 않아야 한다', async () => { await withTestTransaction(async (context) => { const { tx, domain } = context.state // Given: supervisoryRole이 없는 DataSet const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx) const dataSet = await dataSetFactory.createWithDomain( { supervisoryRole: undefined, supervisoryRoleId: undefined }, domain, tx ) // When & Then: supervisoryRole 체크 const supervisoryRoleId = dataSet.supervisoryRoleId expect(supervisoryRoleId).toBeFalsy() // issueOocReview 로직에서는 이 경우 console.error만 출력하고 리턴 // Activity Instance가 생성되지 않음 }) }) it('OOC Review Activity가 설치되지 않으면 Review가 발행되지 않아야 한다', async () => { await withTestTransaction(async (context) => { const { tx, domain } = context.state // Given: OOC Review Activity가 없는 상태 const { dataSet, supervisoryRole } = await dataSetFactory.createWithRoles({}, domain, tx) // When: Activity 조회 const activity = await tx .getRepository('Activity') .findOne({ where: { domain: { id: domain.id }, name: 'OOC Review' } }) // Then: Activity가 없음 expect(activity).toBeNull() // issueOocReview 로직에서는 이 경우 console.warn만 출력하고 리턴 }) }) }) describe('OOC Review Instance 속성', () => { it('dueAt은 collectedAt + standardTime으로 계산되어야 한다', async () => { await withTestTransaction(async (context) => { const { tx, domain } = context.state // Given const standardTime = 48 * 60 * 60 // 48시간 const reviewActivity = await activityFactory.createWithDomain( { name: 'OOC Review', standardTime }, domain, tx ) const { dataSet, supervisoryRole } = await dataSetFactory.createWithRoles({}, domain, tx) const collectedAt = new Date('2024-01-15T10:00:00.000Z') const dataOoc = await dataOocFactory.createWithDataSetAndSample( { state: DataOocStatus.ISSUED, collectedAt }, dataSet, undefined, tx ) // When: dueAt 계산 const expectedDueAt = new Date(collectedAt.getTime() + standardTime * 1000) // Then expect(expectedDueAt.getTime()).toBe(collectedAt.getTime() + standardTime * 1000) }) }) it('input에 dataOocId가 포함되어야 한다', async () => { await withTestTransaction(async (context) => { const { tx, domain } = context.state // Given const { dataSet, supervisoryRole } = await dataSetFactory.createWithRoles({}, domain, tx) const dataOoc = await dataOocFactory.createWithDataSetAndSample( { state: DataOocStatus.ISSUED }, dataSet, undefined, tx ) // When: issueOocReview 입력 구성 const input = { dataOocId: dataOoc.id } // Then expect(input.dataOocId).toBe(dataOoc.id) }) }) it('threadsMin과 threadsMax가 1로 설정되어야 한다 (single 담당자)', async () => { await withTestTransaction(async (context) => { const { tx, domain } = context.state // Given const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx) const { dataSet, supervisoryRole } = await dataSetFactory.createWithRoles({}, domain, tx) // When: ActivityInstance 생성 const activityInstance = await activityInstanceFactory.createWithActivity( { threadsMin: 1, threadsMax: 1, assigneeRole: supervisoryRole }, reviewActivity, domain, tx ) // Then expect(activityInstance.threadsMin).toBe(1) expect(activityInstance.threadsMax).toBe(1) }) }) it('approvalLine은 빈 배열이어야 한다 (Review는 결재 없음)', async () => { await withTestTransaction(async (context) => { const { tx, domain } = context.state // Given const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx) const { dataSet, supervisoryRole } = await dataSetFactory.createWithRoles({}, domain, tx) // When: ActivityInstance 생성 (issueOocReview 방식) const activityInstance = await activityInstanceFactory.createWithActivity( { approvalLine: [], assigneeRole: supervisoryRole }, reviewActivity, domain, tx ) // Then expect(activityInstance.approvalLine).toEqual([]) }) }) }) describe('DataOoc과 ReviewActivityInstance 연결', () => { it('DataOoc의 reviewActivityInstance 필드에 생성된 Instance가 저장되어야 한다', async () => { await withTestTransaction(async (context) => { const { tx, domain } = context.state // Given const reviewActivity = await activityFactory.createOocReviewActivity(domain, tx) const { dataSet, supervisoryRole } = await dataSetFactory.createWithRoles({}, domain, tx) let dataOoc = await dataOocFactory.createWithDataSetAndSample( { state: DataOocStatus.ISSUED }, dataSet, undefined, tx ) // When: Review Instance 생성 및 DataOoc에 연결 const reviewInstance = await activityInstanceFactory.createWithActivity( { name: `[OOC 검토] ${dataSet.name}`, state: ActivityInstanceStatus.Issued, input: { dataOocId: dataOoc.id }, assigneeRole: supervisoryRole }, reviewActivity, domain, tx ) dataOoc.reviewActivityInstance = reviewInstance dataOoc = await tx.save('DataOoc', dataOoc) // Then expect(dataOoc.reviewActivityInstance).toBeDefined() expect(dataOoc.reviewActivityInstance?.id).toBe(reviewInstance.id) }) }) }) describe('Parent Domain 지원', () => { it('domain.parentId가 있으면 parent domain의 Activity도 조회해야 한다', async () => { await withTestTransaction(async (context) => { const { tx } = context.state // Given: Parent Domain과 Child Domain 구조 const parentDomain = await domainFactory.create({ name: 'Parent Domain' }, tx) const childDomain = await domainFactory.create( { name: 'Child Domain', parentId: parentDomain.id }, tx ) // Parent Domain에 Activity 생성 const reviewActivity = await activityFactory.createOocReviewActivity(parentDomain, tx) // When: Child Domain에서 Activity 조회 (In 연산자 사용) const activities = await tx.getRepository('Activity').find({ where: [ { domain: { id: childDomain.id }, name: 'OOC Review' }, { domain: { id: parentDomain.id }, name: 'OOC Review' } ] }) // Then: Parent의 Activity가 조회됨 expect(activities.length).toBeGreaterThan(0) expect(activities[0].id).toBe(reviewActivity.id) }) }) }) })