File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
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<String> 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<String> applied = jdbc.queryForList(
"select code from code where group_id = 'STAGE' and attrs->>'progress' = 'applied'",
String.class);
List<String> 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("유형 확인 필요")
.doesNotContain("제4유형");
assertThat(koglScope("process")).hasSize(6).doesNotContain("보류");
}
@Test
void 기존_부착유형에는_조사표에서_온_값도_들어간다() {
// 운영 DB 실측(2026-07-28): kogl_type에 '유형 확인 필요' 1,858건이 저장돼 있었다.
// 코드로 등록해 두지 않으면 상세 화면에서 선택 안 됨으로 보인다.
// '제4유형'은 '4유형'으로 통일했으므로 선택지에서는 빠진다(V18).
assertThat(koglScope("prior")).containsExactly(
"0유형", "1유형", "2유형", "3유형", "4유형", "유형 확인 필요");
}
/**
* 「제4유형」은 지웠으면 안 된다. 기관이 옛 표기 그대로 엑셀을 다시 올릴 수 있고,
* 그때 sameAs를 보고 「4유형」으로 바꿔 넣어야 한다.
*/
@Test
void 통일된_옛_표기는_별칭으로_남아_있다() {
String active = jdbc.queryForObject(
"select active::text from code where group_id = 'KOGL_TYPE' and code = '제4유형'",
String.class);
assertThat(active).isEqualTo("false");
assertThat(attr("KOGL_TYPE", "제4유형", "sameAs")).isEqualTo("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<String> koglScope(String scope) {
return jdbc.queryForList(
"select code from code c where c.group_id = 'KOGL_TYPE' and c.active "
+ " 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();
}
}