182 lines
6.9 KiB
Java
182 lines
6.9 KiB
Java
package com.cloudhandson.vpdbackoffice.web;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
class GuidedFlowTemplateTest {
|
|
|
|
@Test
|
|
void dashboardExplainsTheFourStepPermissionJourney() throws IOException {
|
|
String html = template("dashboard.html");
|
|
|
|
assertThat(html)
|
|
.contains("1. 권한 설계")
|
|
.contains("2. DB 보호 연결")
|
|
.contains("3. 검증 세션")
|
|
.contains("4. 결과 확인");
|
|
}
|
|
|
|
@Test
|
|
void probeUsesOneTokenInputAndHidesTechnicalExchangeByDefault() throws IOException {
|
|
String probe = template("probe.html");
|
|
String result = template("fragments/probe-result.html");
|
|
|
|
assertThat(probe)
|
|
.contains("name=\"bearerToken\"")
|
|
.contains("검증 세션 사용자")
|
|
.contains("벡터 검색어 (평문)")
|
|
.contains("임베딩 방식")
|
|
.doesNotContain("벡터 검색 요청 본문 (JSON)")
|
|
.doesNotContain("name=\"tokenKeyId\"");
|
|
assertThat(result)
|
|
.contains("적용된 사용자와 권한")
|
|
.contains("토큰 적용 후 SQL")
|
|
.contains("set_vpd_context 사용자 컨텍스트")
|
|
.contains("CB_AGENT_CTX.USER_ID")
|
|
.contains("벡터 검색 Top-K")
|
|
.contains("벡터 거리 (SCORE)")
|
|
.contains("vpd_predicate")
|
|
.contains("다음에 할 일")
|
|
.contains("<details")
|
|
.contains("기술 상세");
|
|
}
|
|
|
|
@Test
|
|
void vpdDefaultApplyAndAdvancedCustomFiltersAreVisuallySeparated() throws IOException {
|
|
String policies = template("vpd-policies.html");
|
|
String filters = template("vpd-filter-policies.html");
|
|
|
|
assertThat(policies)
|
|
.contains("action=\"/vpd-policies/default\"")
|
|
.contains("권한체계 자동 적용")
|
|
.contains("객체만 선택");
|
|
assertThat(filters)
|
|
.contains("기본 동작: 권한체계 자동 반영")
|
|
.contains("고급: 별도 Filter 만들기")
|
|
.contains("fail-closed")
|
|
.contains("CB_AGENT_DOC_VPD_FILTER");
|
|
}
|
|
|
|
@Test
|
|
void ordsObjectPageSeparatesAccessFromColumnDisplayProtection() throws IOException {
|
|
String html = template("objects.html");
|
|
|
|
assertThat(html)
|
|
.contains("누가 어떤 행을 볼 수 있는지는 권한 규칙과 VPD에서 결정")
|
|
.contains("지식 검색 연결 흐름")
|
|
.contains("VPD context 설정과 기본 SELECT")
|
|
.contains("전용 벡터 Handler 사용")
|
|
.contains("원문 표시 허용 컬럼")
|
|
.doesNotContain("표시 보호 등급", "마스킹 방식");
|
|
}
|
|
|
|
@Test
|
|
void permissionWizardExplainsTagRulesAndOrSemantics() throws IOException {
|
|
String html = template("permissions.html");
|
|
|
|
assertThat(html)
|
|
.contains("value=\"TAG\">특정 기술 태그")
|
|
.contains("TECH_TAG")
|
|
.contains("태그를 여러 개 추가하면");
|
|
}
|
|
|
|
@Test
|
|
void roleTokenAndVpdPagesExplainTheirBoundaries() throws IOException {
|
|
String roles = template("roles.html");
|
|
String tokens = template("tokens.html");
|
|
String policies = template("vpd-policies.html");
|
|
String filters = template("vpd-filter-policies.html");
|
|
|
|
assertThat(roles)
|
|
.contains("표시 보호 등급 상한")
|
|
.contains("행 접근 권한은 권한 규칙과 VPD에서 결정합니다");
|
|
assertThat(tokens)
|
|
.contains("회수·만료 포함")
|
|
.contains("현재 사용 가능한 토큰만");
|
|
assertThat(policies)
|
|
.contains("어느 객체의 어떤 SQL에 어떤 Filter function을 붙일지")
|
|
.contains("이 policy가 하는 일");
|
|
assertThat(filters)
|
|
.contains("이 Filter가 보호하는 내용")
|
|
.contains("기술 태그 비교 예시");
|
|
}
|
|
|
|
@Test
|
|
void mcpPagesDescribeReasoningAndProtocolBoundaries() throws IOException {
|
|
String chatbot = template("mcp-chatbot.html");
|
|
String reasoning = template("mcp-reasoning.html");
|
|
String client = template("mcp-client-demo.html");
|
|
String sse = template("mcp-sse.html");
|
|
|
|
assertThat(chatbot).contains("검증 세션 사용자").contains("태그 벡터 검색");
|
|
assertThat(reasoning).contains("MCP tool을 고르고").contains("검증 세션 사용자").contains("MCP Tool");
|
|
assertThat(client).contains("tool 선택과 호출은 reasoning 결과").doesNotContain("Context Path");
|
|
assertThat(sse).contains("Instruction / parameter mapping").contains("bearerToken");
|
|
}
|
|
|
|
@Test
|
|
void vectorKnowledgePageShowsIngestPermissionAndSearchFlow() throws IOException {
|
|
String vector = template("vector-knowledge.html");
|
|
String result = template("fragments/vector-search-result.html");
|
|
|
|
assertThat(vector)
|
|
.contains("지식자료 등록")
|
|
.contains("ALLOW TAG SPRING_BOOT")
|
|
.contains("DENY TAG ORDS")
|
|
.contains("권한 기반 지식 검색")
|
|
.contains("로컬 임베딩(개발용)")
|
|
.contains("/vector-knowledge/ingest")
|
|
.contains("/vector-knowledge/search");
|
|
assertThat(result)
|
|
.contains("TECH_TAG")
|
|
.contains("검색 기술 상세 보기");
|
|
}
|
|
|
|
@Test
|
|
void probeResultExplainsVectorEffectiveSqlWhenTheHandlerReturnsTrace() throws IOException {
|
|
String result = template("fragments/probe-result.html");
|
|
String vectorSql = Files.readString(Path.of("sql/adb/29_agent_ords_vector_search_ords.sql"));
|
|
|
|
assertThat(result)
|
|
.contains("토큰 적용 후 SQL")
|
|
.contains("권한 적용 SQL")
|
|
.contains("VPD가 추가한 WHERE 조건");
|
|
assertThat(vectorSql)
|
|
.contains("VECTOR_DISTANCE(d.embedding, TO_VECTOR(:embedding), COSINE)")
|
|
.contains("WHERE d.embedding IS NOT NULL AND (' || v_vpd_predicate || ')");
|
|
}
|
|
|
|
@Test
|
|
void vectorOrdsHandlerReadsBodyStreamOnlyOnce() throws IOException {
|
|
String sql = Files.readString(Path.of("sql/adb/29_agent_ords_vector_search_ords.sql"));
|
|
|
|
assertThat(sql)
|
|
.contains("v_body_text CLOB")
|
|
.contains("v_body_text := :body_text")
|
|
.contains("Duplicate stream parameter")
|
|
.doesNotContain("JSON_QUERY(\n :body_text")
|
|
.doesNotContain("JSON_VALUE(\n :body_text");
|
|
assertThat(sql.split("v_body_text := :body_text", -1)).hasSize(2);
|
|
assertThat(sql).contains("vpd_predicate").contains("effective_sql");
|
|
}
|
|
|
|
@Test
|
|
void existingOrdsSetupCanReturnTheVpdPredicateTrace() throws IOException {
|
|
String setup = Files.readString(Path.of("sql/adb/22_agent_ords_security_ords_handler_setup.sql"));
|
|
String grant = Files.readString(Path.of("sql/adb/33_agent_ords_sql_trace_grant.sql"));
|
|
|
|
assertThat(setup)
|
|
.contains("admin.cb_agent_doc_vpd_filter('ADMIN', 'CB_V_SEARCH_DOCUMENTS')")
|
|
.contains("APEX_JSON.WRITE('effective_sql', v_effective_sql)");
|
|
assertThat(grant).contains("GRANT EXECUTE ON cb_agent_doc_vpd_filter TO cb_ords");
|
|
}
|
|
|
|
private String template(String relativePath) throws IOException {
|
|
return Files.readString(Path.of("src/main/resources/templates").resolve(relativePath));
|
|
}
|
|
}
|