package kr.itn.itnhub.dashboard; import kr.itn.itnhub.AbstractDbTest; import kr.itn.itnhub.mattermost.MattermostClient; import kr.itn.itnhub.org.Organization; import kr.itn.itnhub.org.OrganizationMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import static org.hamcrest.Matchers.hasItem; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @AutoConfigureMockMvc @WithMockUser(roles = "ADMIN") class DashboardControllerTest extends AbstractDbTest { @Autowired MockMvc mvc; @Autowired OrganizationMapper orgMapper; @Autowired JdbcTemplate jdbc; @MockBean MattermostClient mattermost; private Long noChannelId; private Long stage2Id; private Long stage11Id; @BeforeEach void setUp() { jdbc.update("delete from organization"); // 001: 채널도 단계도 없는 기관 - 칸반의 "단계 미지정" 컬럼 대상 noChannelId = seed("001", "채널없는기관"); // 002: 채널 + 2단계, 권리확인 3건(완료 1건) / 권리처리 2건(완료 1건) stage2Id = seed("002", "권리확인중기관"); orgMapper.updateChannelIdMj(stage2Id, "ch-mj"); setStage(stage2Id, 2); insertReview(stage2Id, 1, "이용가능"); insertReview(stage2Id, 2, null); insertReview(stage2Id, 3, ""); insertProcess(stage2Id, 1, "처리완료"); insertProcess(stage2Id, 2, "미처리"); // 003: 채널 + 11단계 - 완료 집계 대상 stage11Id = seed("003", "최종완료기관"); orgMapper.updateChannelIdMj(stage11Id, "ch-mj-2"); orgMapper.updateChannelIdLaw(stage11Id, "ch-law-2"); setStage(stage11Id, 11); } private Long seed(String orgNo, String orgName) { Organization org = new Organization(); org.setOrgNo(orgNo); org.setOrgName(orgName); org.setChannelSlug(orgNo); orgMapper.upsertBySeed(org); return orgMapper.findByOrgNoAndOrgName(orgNo, orgName).getId(); } private void setStage(Long orgId, int stage) { orgMapper.updateStage(orgId, stage); jdbc.update("insert into stage_history (org_id, stage) values (?, ?)", orgId, stage); } private void insertReview(Long orgId, int seq, String reviewResult) { jdbc.update("insert into review_item (org_id, seq, review_result) values (?, ?, ?)", orgId, seq, reviewResult); } private void insertProcess(Long orgId, int seq, String status) { jdbc.update("insert into process_item (org_id, seq, process_status) values (?, ?, ?)", orgId, seq, status); } /** * KPI 기준 단계는 코드표(STAGE의 kpiFrom)에서 온다. 요구사항 [2]로 완료 기준이 * ⑪법률검토 최종완료에서 ⑬보고서 제출 완료로 올라갔다 - 보고서가 실제로 올라온 * 기관만 완료로 센다. 그래서 11단계인 003은 더 이상 완료가 아니다. */ @Test void KPI는_전체_기관과_단계별_도달_기관수를_센다() throws Exception { mvc.perform(get("/api/dashboard")) .andExpect(status().isOk()) // 신청 = 명부 전체 기관 수 .andExpect(jsonPath("$.summary.applied").value(3)) // 서류제출 = 2단계 이상 (002, 003) .andExpect(jsonPath("$.summary.docSubmitted").value(2)) // 완료 = 13단계 이상 (없음) .andExpect(jsonPath("$.summary.completed").value(0)) // 보고서 작성 = 12단계 이상 (없음) .andExpect(jsonPath("$.summary.reportWriting").value(0)); } @Test void 완료는_보고서_제출까지_끝난_기관만_센다() throws Exception { setStage(stage11Id, 13); mvc.perform(get("/api/dashboard")) .andExpect(status().isOk()) .andExpect(jsonPath("$.summary.completed").value(1)) .andExpect(jsonPath("$.summary.reportWriting").value(1)); } @Test void KPI의_권리확인_권리처리는_전_기관_건수_합계다() throws Exception { mvc.perform(get("/api/dashboard")) .andExpect(status().isOk()) .andExpect(jsonPath("$.summary.reviewTotal").value(3)) // 빈 문자열은 미완료로 센다 .andExpect(jsonPath("$.summary.reviewDone").value(1)) .andExpect(jsonPath("$.summary.processTotal").value(2)) .andExpect(jsonPath("$.summary.processDone").value(1)); } @Test void 권리관리_그래프는_DB의_분류값을_집계한다() throws Exception { jdbc.update("update review_item set kogl_type = '1유형', judged_kogl_type = '2유형', review_result = '신유형 개방', needs_processing = 'Y' where org_id = ? and seq = 1", stage2Id); jdbc.update("update review_item set review_result = '권리처리 추진 미희망' where org_id = ? and seq = 2", stage2Id); jdbc.update("update process_item set judged_kogl_type = '1유형', prior_kogl_type = '0유형' where org_id = ? and seq = 1", stage2Id); mvc.perform(get("/api/dashboard")) .andExpect(status().isOk()) .andExpect(jsonPath("$.rights.reviewSourceKoglTypes[0].label").value("미부착")) // 동률 항목의 정렬은 DB locale에 따라 달라질 수 있으므로 순서가 아닌 집계값을 검증한다. .andExpect(jsonPath("$.rights.reviewJudgedKoglTypes[*].label", hasItem("2유형"))) .andExpect(jsonPath("$.rights.reviewJudgedKoglTypes[?(@.label == '2유형')].count", hasItem(1))) .andExpect(jsonPath("$.rights.processTypes.length()").value(2)) .andExpect(jsonPath("$.rights.processStatuses.length()").value(2)) .andExpect(jsonPath("$.rights.changedKoglCount").value(1)) .andExpect(jsonPath("$.rights.reviewFreeUseCount").value(1)) .andExpect(jsonPath("$.rights.reviewNeedsProcessingCount").value(1)) .andExpect(jsonPath("$.rights.reviewNoProcessingCount").value(1)); } @Test void RE는_데이터_모델이_없어_항상_0이다() throws Exception { mvc.perform(get("/api/dashboard")) .andExpect(status().isOk()) .andExpect(jsonPath("$.summary.unresolvedRe").value(0)) .andExpect(jsonPath("$.orgs[0].reCount").value(0)); } @Test void 기관행은_연번순이고_채널_보유_여부를_내려준다() throws Exception { mvc.perform(get("/api/dashboard")) .andExpect(status().isOk()) .andExpect(jsonPath("$.orgs.length()").value(3)) .andExpect(jsonPath("$.orgs[0].orgNo").value("001")) .andExpect(jsonPath("$.orgs[0].status").value("INFO_PENDING")) .andExpect(jsonPath("$.orgs[0].hasChannel").value(false)) .andExpect(jsonPath("$.orgs[0].stage").doesNotExist()) .andExpect(jsonPath("$.orgs[1].orgNo").value("002")) .andExpect(jsonPath("$.orgs[1].hasChannel").value(true)) .andExpect(jsonPath("$.orgs[1].status").value("PARTIAL")) .andExpect(jsonPath("$.orgs[1].stage").value(2)); } @Test void 기관행에_권리확인_권리처리_진척이_기관별로_담긴다() throws Exception { mvc.perform(get("/api/dashboard")) .andExpect(status().isOk()) .andExpect(jsonPath("$.orgs[0].reviewTotal").value(0)) .andExpect(jsonPath("$.orgs[1].reviewTotal").value(3)) .andExpect(jsonPath("$.orgs[1].reviewDone").value(1)) .andExpect(jsonPath("$.orgs[1].processTotal").value(2)) .andExpect(jsonPath("$.orgs[1].processDone").value(1)); } @Test void 현재_단계_진입시각과_최근변동시각을_내려준다() throws Exception { mvc.perform(get("/api/dashboard")) .andExpect(status().isOk()) // 단계 이력이 없는 기관은 진입시각이 없다 .andExpect(jsonPath("$.orgs[0].stageChangedAt").doesNotExist()) .andExpect(jsonPath("$.orgs[1].stageChangedAt").isNumber()) // 최근변동은 organization.updated_at이 있어 항상 값이 있다 .andExpect(jsonPath("$.orgs[0].lastChangedAt").isNumber()) .andExpect(jsonPath("$.orgs[1].lastChangedAt").isNumber()); } }