package kr.itn.itnhub.code; import kr.itn.itnhub.AbstractDbTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; /** * 시드가 설계서대로 들어갔는지 확인한다. 코드값이 데이터가 된 이상 오타 하나가 화면 선택지를 * 통째로 날리므로, 그룹 간 참조가 맞물리는지까지 여기서 잡는다. */ class CodeSeedTest extends AbstractDbTest { @Autowired JdbcTemplate jdbc; @Test void 진행단계는_13개이며_13번이_보고서_제출_완료다() { List labels = jdbc.queryForList( "select label from code where group_id = 'STAGE' order by sort_order", String.class); assertThat(labels).hasSize(13); assertThat(labels.get(0)).isEqualTo("신청"); assertThat(labels.get(11)).isEqualTo("보고서 작성"); assertThat(labels.get(12)).isEqualTo("보고서 제출 완료"); } @Test void 진행상태는_1번만_신청이고_13번만_완료다() { List applied = jdbc.queryForList( "select code from code where group_id = 'STAGE' and attrs->>'progress' = 'applied'", String.class); List done = jdbc.queryForList( "select code from code where group_id = 'STAGE' and attrs->>'progress' = 'done'", String.class); Integer active = jdbc.queryForObject( "select count(*) from code where group_id = 'STAGE' and attrs->>'progress' = 'active'", Integer.class); assertThat(applied).containsExactly("1"); assertThat(done).containsExactly("13"); assertThat(active).isEqualTo(11); } @Test void KPI_임계값이_세_개_지정되어_있다() { assertThat(kpiStage("docSubmitted")).isEqualTo("2"); assertThat(kpiStage("reportWriting")).isEqualTo("12"); // 요구사항 [2]: 보고서가 실제로 올라온 기관만 완료로 센다. assertThat(kpiStage("completed")).isEqualTo("13"); } private String kpiStage(String kpi) { return jdbc.queryForObject( "select code from code where group_id = 'STAGE' and attrs->>'kpiFrom' = ?", String.class, kpi); } @Test void 모든_단계가_업무구간에_속한다() { Integer orphan = jdbc.queryForObject( "select count(*) from code s where s.group_id = 'STAGE' " + "and not exists (select 1 from code g where g.group_id = 'STAGE_GROUP' " + " and g.code = s.attrs->>'groupKey')", Integer.class); assertThat(orphan).isZero(); } @Test void 공공누리_유형은_사용처별로_다르게_노출된다() { // 세 화면의 목록이 7개/5개/6개로 달랐던 이유는 쓰이는 자리가 달라서다. assertThat(koglScope("review")).hasSize(7).doesNotContain("유형 확인 필요"); assertThat(koglScope("process")).hasSize(6).doesNotContain("보류"); } @Test void 기존_부착유형에는_조사표에서_온_값도_들어간다() { // 운영 DB 실측(2026-07-28): kogl_type에 '유형 확인 필요' 1,858건, '제4유형' 68건이 // 저장돼 있었다. 코드로 등록해 두지 않으면 상세 화면에서 선택 안 됨으로 보인다. assertThat(koglScope("prior")).containsExactly( "0유형", "1유형", "2유형", "3유형", "4유형", "유형 확인 필요", "제4유형"); } @Test void 조사표에서_온_값은_변호사_판정_선택지가_아니다() { Integer leaked = jdbc.queryForObject( "select count(*) from code c " + "where c.group_id = 'KOGL_TYPE' " + " and (c.attrs->>'surveyOnly')::boolean is true " + " and exists (select 1 from jsonb_array_elements_text(c.attrs->'scopes') s(v) " + " where s.v in ('review', 'process'))", Integer.class); assertThat(leaked).isZero(); } private List koglScope(String scope) { return jdbc.queryForList( "select code from code c where c.group_id = 'KOGL_TYPE' " + " and exists (select 1 from jsonb_array_elements_text(c.attrs->'scopes') s(v) " + " where s.v = ?) " + "order by sort_order", String.class, scope); } @Test void 세부항목의_허용_처리결과는_실제_처리결과_코드다() { Integer orphan = jdbc.queryForObject( "select count(*) from code m, " + " lateral jsonb_array_elements_text(coalesce(m.attrs->'allowed', '[]'::jsonb)) a(v) " + "where m.group_id = 'REVIEW_MINOR' " + " and not exists (select 1 from code r " + " where r.group_id = 'REVIEW_RESULT' and r.code = a.v)", Integer.class); assertThat(orphan).isZero(); } @Test void 대분류와_세부의_추천_처리결과도_실제_코드다() { Integer orphan = jdbc.queryForObject( "select count(*) from code c " + "where c.group_id in ('REVIEW_MAJOR', 'REVIEW_MINOR') " + " and c.attrs->>'suggest' is not null " + " and not exists (select 1 from code r " + " where r.group_id = 'REVIEW_RESULT' and r.code = c.attrs->>'suggest')", Integer.class); assertThat(orphan).isZero(); } @Test void 처리결과가_지정하는_공공누리_유형도_실제_코드다() { Integer orphan = jdbc.queryForObject( "select count(*) from code c " + "where c.group_id = 'REVIEW_RESULT' " + " and c.attrs->>'koglSet' is not null " + " and not exists (select 1 from code k " + " where k.group_id = 'KOGL_TYPE' and k.code = c.attrs->>'koglSet')", Integer.class); assertThat(orphan).isZero(); } @Test void 열두_그룹이_모두_시드된다() { Integer groups = jdbc.queryForObject("select count(*) from code_group", Integer.class); Integer empty = jdbc.queryForObject( "select count(*) from code_group g " + "where not exists (select 1 from code c where c.group_id = g.group_id)", Integer.class); assertThat(groups).isEqualTo(12); assertThat(empty).isZero(); } /** * tone은 화면에 실제로 쓰이는 색과 같아야 한다. 다르면 Phase 2에서 화면 색이 조용히 바뀐다. * 기준: Dashboard.tsx의 GROUP_COLOR(칸반 컬럼 색 띠). */ @Test void 업무구간_색이_지금_화면과_같다() { assertThat(tone("STAGE_GROUP", "intake")).isEqualTo("slate"); assertThat(tone("STAGE_GROUP", "assign")).isEqualTo("indigo"); assertThat(tone("STAGE_GROUP", "check")).isEqualTo("emerald"); assertThat(tone("STAGE_GROUP", "process")).isEqualTo("amber"); assertThat(tone("STAGE_GROUP", "closing")).isEqualTo("violet"); } /** 기준: ContactsPage.tsx의 CATEGORY_BADGE_STYLES. */ @Test void 담당자구분_색이_지금_화면과_같다() { assertThat(tone("CONTACT_CATEGORY", "APPLICANT")).isEqualTo("blue"); assertThat(tone("CONTACT_CATEGORY", "MJ")).isEqualTo("emerald"); assertThat(tone("CONTACT_CATEGORY", "LAWYER")).isEqualTo("violet"); assertThat(tone("CONTACT_CATEGORY", "OPERATOR")).isEqualTo("sky"); assertThat(tone("CONTACT_CATEGORY", "ITN")).isEqualTo("orange"); } /** 기준: ReviewBoard.tsx의 REVIEW_RESULT_BADGE_STYLES. */ @Test void 처리결과_색이_지금_화면과_같다() { assertThat(tone("REVIEW_RESULT", "신유형 개방")).isEqualTo("emerald"); assertThat(tone("REVIEW_RESULT", "계약서 등 재확인")).isEqualTo("amber"); assertThat(tone("REVIEW_RESULT", "권리처리 추진")).isEqualTo("blue"); assertThat(tone("REVIEW_RESULT", "권리처리 추진 미희망")).isEqualTo("slate"); assertThat(tone("REVIEW_RESULT", "개방불가")).isEqualTo("red"); } /** * MJ는 화면에 '문정원'으로 나오지만 담당자 명부 엑셀에는 '주관기관'으로 적혀 온다. * 이 둘을 헷갈리면 화면 표시가 바뀌거나 엑셀 업로드가 깨진다. */ @Test void 담당자구분은_화면표시와_엑셀표기를_따로_갖는다() { assertThat(label("CONTACT_CATEGORY", "MJ")).isEqualTo("문정원"); assertThat(attr("CONTACT_CATEGORY", "MJ", "importAlias")).isEqualTo("주관기관"); // 나머지는 화면표시와 엑셀표기가 같다. assertThat(label("CONTACT_CATEGORY", "LAWYER")).isEqualTo("변호사"); assertThat(attr("CONTACT_CATEGORY", "LAWYER", "importAlias")).isEqualTo("변호사"); } private String tone(String groupId, String code) { return attr(groupId, code, "tone"); } private String attr(String groupId, String code, String key) { return jdbc.queryForObject( "select attrs->>? from code where group_id = ? and code = ?", String.class, key, groupId, code); } private String label(String groupId, String code) { return jdbc.queryForObject( "select label from code where group_id = ? and code = ?", String.class, groupId, code); } @Test void 그룹마다_정렬순서가_중복되지_않는다() { Integer dup = jdbc.queryForObject( "select count(*) from (select group_id, sort_order from code " + " group by group_id, sort_order having count(*) > 1) t", Integer.class); assertThat(dup).isZero(); } }