@@ -1,14 +1,8 @@
|
||||
package com.cloudhandson.vpdbackoffice.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.config.CatalogProperties;
|
||||
import com.cloudhandson.vpdbackoffice.config.MaskingProperties;
|
||||
import com.cloudhandson.vpdbackoffice.mapper.MaskingRuleMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
class MaskingPolicySynchronizerTest {
|
||||
|
||||
@@ -29,29 +23,8 @@ class MaskingPolicySynchronizerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void hmmEmployeePolicyComesFromEnvironmentPolicyCatalog() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
var dataCatalog = new EnvironmentDataCatalog(
|
||||
new CatalogProperties("ADMIN", """
|
||||
[{"key":"employees","tableName":"HMM_HR_EMPLOYEES","objectType":"TABLE",
|
||||
"businessName":"직원","description":"직원"}]
|
||||
"""),
|
||||
objectMapper);
|
||||
var policyCatalog = new EnvironmentMaskingPolicyCatalog(
|
||||
new MaskingProperties("""
|
||||
[{"objectName":"HMM_HR_EMPLOYEES","policyName":"HMM_EMPLOYEE_PII_REDACT"}]
|
||||
"""),
|
||||
objectMapper);
|
||||
var synchronizer = new MaskingPolicySynchronizer(
|
||||
mock(JdbcTemplate.class),
|
||||
mock(MaskingRuleMapper.class),
|
||||
dataCatalog,
|
||||
policyCatalog);
|
||||
|
||||
assertThat(synchronizer.owner()).isEqualTo("ADMIN");
|
||||
assertThat(synchronizer.managedPolicyName("HMM_HR_EMPLOYEES"))
|
||||
.isEqualTo("HMM_EMPLOYEE_PII_REDACT");
|
||||
assertThat(synchronizer.managedObjectNames())
|
||||
.doesNotContain("KB_CUSTOMERS", "KB_CLAIMS", "KB_CONTRACTS");
|
||||
void smilegateSalesTransactionPolicyIsManagedByBackoffice() {
|
||||
assertThat(MaskingPolicySynchronizer.managedPolicyName("COMN_SALES_TXN"))
|
||||
.isEqualTo("SG_SALES_TXN_REDACT");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,208 +1,71 @@
|
||||
package com.cloudhandson.vpdbackoffice.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.config.BackofficeProperties;
|
||||
import com.cloudhandson.vpdbackoffice.config.McpProperties;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class McpSseServiceTest {
|
||||
|
||||
private static final String HMM_TOOLS = """
|
||||
[
|
||||
{
|
||||
"name":"resolve_hr_term",
|
||||
"label":"HMM HR 용어 표준화",
|
||||
"description":"휴가·근태 표현을 표준 용어와 코드로 변환합니다.",
|
||||
"argumentName":"term",
|
||||
"argumentDescription":"확인할 휴가·근태 용어입니다.",
|
||||
"executionType":"AGENT_TOOL",
|
||||
"targetName":"HMM_HR_TERM_RESOLVER",
|
||||
"targetParameterName":"P_TERM"
|
||||
},
|
||||
{
|
||||
"name":"search_hr_data",
|
||||
"label":"HMM HR 데이터 조회",
|
||||
"description":"조직, 직원, 휴가, 근태 데이터를 조회합니다.",
|
||||
"argumentName":"query",
|
||||
"argumentDescription":"완전한 자연어 질문입니다.",
|
||||
"executionType":"AGENT_TOOL",
|
||||
"targetName":"HMM_HR_NORMALIZED_DATA_SEARCH",
|
||||
"targetParameterName":"P_QUERY"
|
||||
},
|
||||
{
|
||||
"name":"search_hr_policy",
|
||||
"label":"HMM HR 규정 검색",
|
||||
"description":"HR 규정 PDF를 검색합니다.",
|
||||
"argumentName":"query",
|
||||
"argumentDescription":"정책에 대한 자연어 질문입니다.",
|
||||
"executionType":"AGENT_TOOL",
|
||||
"targetName":"HMM_HR_POLICY_SEARCH",
|
||||
"targetParameterName":"P_QUERY"
|
||||
}
|
||||
]
|
||||
""";
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final McpProperties mcpProperties =
|
||||
new McpProperties(
|
||||
"https://example.com/mcp",
|
||||
"hmm-hr-backoffice",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
HMM_TOOLS);
|
||||
private final McpToolCatalog toolCatalog =
|
||||
new EnvironmentMcpToolCatalog(mcpProperties, objectMapper);
|
||||
private final CapturingHmmAiAgentToolRunner agentToolRunner =
|
||||
new CapturingHmmAiAgentToolRunner();
|
||||
private final HmmMcpBearerAuthenticator bearerAuthenticator =
|
||||
token -> new HmmMcpPrincipal(1L, "E1001", 1L);
|
||||
private final SmilegateSelectAiService selectAiService = new CapturingSmilegateSelectAiService();
|
||||
private final McpSseService service = new McpSseService(
|
||||
agentToolRunner,
|
||||
mock(SelectAiService.class),
|
||||
bearerAuthenticator,
|
||||
toolCatalog,
|
||||
mcpProperties,
|
||||
new BackofficeProperties(null, null, null, null, null),
|
||||
objectMapper);
|
||||
selectAiService,
|
||||
objectMapper
|
||||
);
|
||||
|
||||
@Test
|
||||
void listsEnvironmentConfiguredTermDataAndPolicyTools() {
|
||||
ObjectNode response =
|
||||
service.handle("default", request(1, "tools/list"), "valid-token");
|
||||
void listsOnlyVpdSelectAiToolWithPromptInput() {
|
||||
ObjectNode response = service.handle("default", request(1, "tools/list"));
|
||||
|
||||
var tools = response.path("result").path("tools");
|
||||
assertThat(tools).hasSize(3);
|
||||
assertThat(tools).extracting(node -> node.path("name").asText())
|
||||
.containsExactly("resolve_hr_term", "search_hr_data", "search_hr_policy");
|
||||
assertThat(tools.get(0).path("inputSchema").path("required"))
|
||||
.extracting(JsonNode::asText)
|
||||
.containsExactly("term");
|
||||
assertThat(tools.get(1).path("inputSchema").path("required"))
|
||||
.extracting(JsonNode::asText)
|
||||
.containsExactly("query");
|
||||
assertThat(tools).hasSize(1);
|
||||
var selectAi = tools.get(0);
|
||||
assertThat(selectAi.path("name").asText()).isEqualTo("oracle.select_ai.smilegate_game_text2sql");
|
||||
assertThat(selectAi.path("inputSchema").path("required"))
|
||||
.extracting(node -> node.asText())
|
||||
.contains("prompt");
|
||||
assertThat(selectAi.path("inputSchema").path("properties").has("bearerToken")).isFalse();
|
||||
assertThat(selectAi.path("inputSchema").path("properties").has("limit")).isFalse();
|
||||
assertThat(selectAi.path("inputSchema").path("properties").has("conversationId")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void callsConfiguredAgentToolWithItsDeclaredInputName() {
|
||||
void callsVpdSelectAiThroughOrdsService() {
|
||||
ObjectNode request = request(2, "tools/call");
|
||||
ObjectNode params = request.putObject("params");
|
||||
params.put("name", "resolve_hr_term");
|
||||
params.putObject("arguments").put("term", "연차 이월");
|
||||
ObjectNode params = (ObjectNode) request.putObject("params");
|
||||
params.put("name", "oracle.select_ai.smilegate_game_text2sql");
|
||||
ObjectNode arguments = params.putObject("arguments");
|
||||
arguments.put("prompt", "카제나 AU를 조회해 줘");
|
||||
|
||||
ObjectNode response = service.handle("default", request, "valid-token");
|
||||
ObjectNode response = service.handle("default", request, "user-bearer");
|
||||
|
||||
assertThat(agentToolRunner.toolName).isEqualTo("HMM_HR_TERM_RESOLVER");
|
||||
assertThat(agentToolRunner.input.path("P_TERM").asText()).isEqualTo("연차 이월");
|
||||
assertThat(agentToolRunner.bearerToken).isEqualTo("valid-token");
|
||||
CapturingSmilegateSelectAiService agentService =
|
||||
(CapturingSmilegateSelectAiService) selectAiService;
|
||||
assertThat(agentService.bearerToken).isEqualTo("user-bearer");
|
||||
assertThat(agentService.prompt).isEqualTo("카제나 AU를 조회해 줘");
|
||||
assertThat(response.path("error").isMissingNode()).isTrue();
|
||||
assertThat(response.path("result").path("isError").asBoolean()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void routesSelectAiToolThroughTheVpdAwareExecutor() {
|
||||
String selectAiTools = """
|
||||
[
|
||||
{
|
||||
"name":"search_hr_data",
|
||||
"label":"HMM HR 데이터 조회",
|
||||
"description":"VPD가 적용된 HMM HR 데이터를 조회합니다.",
|
||||
"argumentName":"query",
|
||||
"argumentDescription":"완전한 자연어 질문입니다.",
|
||||
"executionType":"SELECT_AI"
|
||||
}
|
||||
]
|
||||
""";
|
||||
McpProperties selectAiProperties =
|
||||
new McpProperties("", "", "", "", "", "", selectAiTools);
|
||||
SelectAiService selectAiService = mock(SelectAiService.class);
|
||||
when(selectAiService.generateAndExecute(
|
||||
"valid-token", "내 휴가 신청 내역을 보여줘"))
|
||||
.thenReturn(objectMapper.createObjectNode().put("vpdEnforced", true));
|
||||
McpSseService selectAiMcp = new McpSseService(
|
||||
agentToolRunner,
|
||||
selectAiService,
|
||||
bearerAuthenticator,
|
||||
new EnvironmentMcpToolCatalog(selectAiProperties, objectMapper),
|
||||
selectAiProperties,
|
||||
new BackofficeProperties(null, null, null, null, null),
|
||||
objectMapper);
|
||||
ObjectNode request = request(5, "tools/call");
|
||||
request.putObject("params")
|
||||
.put("name", "search_hr_data")
|
||||
.putObject("arguments")
|
||||
.put("query", "내 휴가 신청 내역을 보여줘");
|
||||
|
||||
ObjectNode response =
|
||||
selectAiMcp.handle("default", request, "valid-token");
|
||||
|
||||
verify(selectAiService).generateAndExecute(
|
||||
"valid-token", "내 휴가 신청 내역을 보여줘");
|
||||
assertThat(response.path("result").path("isError").asBoolean()).isFalse();
|
||||
assertThat(response.path("result").path("content").get(0).path("text").asText())
|
||||
.contains("\"vpdEnforced\" : true");
|
||||
.contains("SGMP_POC_HAIKU45")
|
||||
.contains("SELECT 1 FROM DUAL");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDiscoveryWhenBearerAuthenticationFails() {
|
||||
McpSseService rejectingService = new McpSseService(
|
||||
agentToolRunner,
|
||||
mock(SelectAiService.class),
|
||||
token -> {
|
||||
throw new McpUnauthorizedException();
|
||||
},
|
||||
toolCatalog,
|
||||
mcpProperties,
|
||||
new BackofficeProperties(null, null, null, null, null),
|
||||
objectMapper);
|
||||
|
||||
assertThatThrownBy(() ->
|
||||
rejectingService.handle("default", request(4, "tools/list"), "invalid-token"))
|
||||
.isInstanceOf(McpUnauthorizedException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUnknownToolsWithoutCallingTheRunner() {
|
||||
void returnsToolLevelDeniedResultWhenVpdTokenIsMissing() {
|
||||
ObjectNode request = request(3, "tools/call");
|
||||
ObjectNode params = request.putObject("params");
|
||||
params.put("name", "unconfigured.tool");
|
||||
params.putObject("arguments").put("prompt", "query");
|
||||
ObjectNode params = (ObjectNode) request.putObject("params");
|
||||
params.put("name", "oracle.select_ai.smilegate_game_text2sql");
|
||||
params.putObject("arguments").put("prompt", "카제나 AU를 조회해 줘");
|
||||
|
||||
ObjectNode response = service.handle("default", request, "valid-token");
|
||||
ObjectNode response = service.handle("default", request, "");
|
||||
|
||||
assertThat(response.path("result").isMissingNode()).isTrue();
|
||||
assertThat(response.path("error").path("message").asText())
|
||||
.contains("등록되지 않은 MCP tool");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDuplicateToolNamesAtStartup() {
|
||||
String duplicate = """
|
||||
[
|
||||
{"name":"same","label":"A","description":"A","argumentName":"query",
|
||||
"argumentDescription":"A","executionType":"AGENT_TOOL",
|
||||
"targetName":"TOOL_A","targetParameterName":"P_QUERY"},
|
||||
{"name":"same","label":"B","description":"B","argumentName":"query",
|
||||
"argumentDescription":"B","executionType":"AGENT_TOOL",
|
||||
"targetName":"TOOL_B","targetParameterName":"P_QUERY"}
|
||||
]
|
||||
""";
|
||||
McpProperties duplicateProperties =
|
||||
new McpProperties("", "", "", "", "", "", duplicate);
|
||||
|
||||
assertThatThrownBy(() ->
|
||||
new EnvironmentMcpToolCatalog(duplicateProperties, objectMapper))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessageContaining("BACKOFFICE_MCP_TOOLS");
|
||||
assertThat(response.path("error").isMissingNode()).isTrue();
|
||||
assertThat(response.path("result").path("isError").asBoolean()).isTrue();
|
||||
assertThat(response.path("result").path("content").get(0).path("text").asText())
|
||||
.contains("VPD_TOKEN_DENIED")
|
||||
.contains("권한이 없습니다");
|
||||
}
|
||||
|
||||
private ObjectNode request(int id, String method) {
|
||||
@@ -213,23 +76,22 @@ class McpSseServiceTest {
|
||||
return request;
|
||||
}
|
||||
|
||||
private final class CapturingHmmAiAgentToolRunner
|
||||
implements HmmAiAgentToolRunner {
|
||||
private static final class CapturingSmilegateSelectAiService extends SmilegateSelectAiService {
|
||||
|
||||
private String toolName;
|
||||
private ObjectNode input;
|
||||
private String bearerToken;
|
||||
private String prompt;
|
||||
|
||||
private CapturingSmilegateSelectAiService() {
|
||||
super(null, null, null, new ObjectMapper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode run(
|
||||
String requestedToolName,
|
||||
ObjectNode requestedInput,
|
||||
String bearerToken
|
||||
) {
|
||||
toolName = requestedToolName;
|
||||
input = requestedInput.deepCopy();
|
||||
public JsonNode generateShowSql(String bearerToken, String prompt) {
|
||||
this.bearerToken = bearerToken;
|
||||
return objectMapper.createObjectNode().put("status", "ok");
|
||||
this.prompt = prompt;
|
||||
return new ObjectMapper().createObjectNode()
|
||||
.put("profile", "SGMP_POC_HAIKU45")
|
||||
.put("generatedSql", "SELECT 1 FROM DUAL");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,51 +3,30 @@ package com.cloudhandson.vpdbackoffice.service;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.config.SecuritySqlScriptProperties;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class SecuritySqlScriptServiceTest {
|
||||
|
||||
private final SecuritySqlScriptService service = new SecuritySqlScriptService(
|
||||
new SecuritySqlScriptProperties("""
|
||||
[{
|
||||
"scriptId":"hmm-leave-vpd",
|
||||
"category":"HMM / VPD",
|
||||
"fileName":"72_hmm_leave_team_vpd.sql",
|
||||
"title":"HMM 휴가 팀 접근 정책",
|
||||
"description":"팀장과 팀원 휴가 행 접근 정책"
|
||||
}]
|
||||
"""),
|
||||
new ObjectMapper());
|
||||
private final SecuritySqlScriptService service = new SecuritySqlScriptService();
|
||||
|
||||
@Test
|
||||
void exposesOnlyEnvironmentAllowlistedBundledScripts() {
|
||||
void exposesOnlyTheCuratedGitTrackedSecurityScripts() {
|
||||
assertThat(service.list())
|
||||
.extracting(item -> item.fileName())
|
||||
.containsExactly("72_hmm_leave_team_vpd.sql");
|
||||
.containsExactly(
|
||||
"70_sg_tool_user.sql",
|
||||
"71_sg_identity_administration.sql"
|
||||
);
|
||||
|
||||
assertThat(service.find("hmm-leave-vpd").source())
|
||||
.contains("HMM_ACCESS_CTX_PKG")
|
||||
.contains("HMM_LEAVE_VPD_FILTER");
|
||||
assertThat(service.find("smilegate-identity-administration").source())
|
||||
.contains("create table sg_app_user")
|
||||
.contains("DATA_AI_TF");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUnknownScriptIdsInsteadOfResolvingARequestPath() {
|
||||
void rejectsUnknownScriptIdsInsteadOfResolvingAPathFromRequestInput() {
|
||||
assertThatThrownBy(() -> service.find("../../etc/passwd"))
|
||||
.isInstanceOf(AppException.class)
|
||||
.hasMessageContaining("조회할 수 없는");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUnsafeResourcePathsAtStartup() {
|
||||
assertThatThrownBy(() -> new SecuritySqlScriptService(
|
||||
new SecuritySqlScriptProperties("""
|
||||
[{"scriptId":"bad","category":"x","fileName":"../../etc/passwd.sql",
|
||||
"title":"x","description":"x"}]
|
||||
"""),
|
||||
new ObjectMapper()))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessageContaining("BACKOFFICE_SECURITY_SQL_SCRIPTS");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,72 +4,31 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.config.CatalogProperties;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
class StructuredDataServiceTest {
|
||||
|
||||
private static final String HMM_OBJECTS = """
|
||||
[
|
||||
{"key":"teams","tableName":"HMM_ORG_TEAMS","objectType":"TABLE",
|
||||
"businessName":"조직 원장","description":"조직 정보"},
|
||||
{"key":"employees","tableName":"HMM_HR_EMPLOYEES","objectType":"TABLE",
|
||||
"businessName":"직원 원장","description":"직원 정보"},
|
||||
{"key":"leave-balances","tableName":"HMM_LEAVE_BALANCES","objectType":"VIEW",
|
||||
"businessName":"휴가 잔여 원장","description":"휴가 잔여 정보"}
|
||||
]
|
||||
""";
|
||||
|
||||
private final DataCatalog catalog = new EnvironmentDataCatalog(
|
||||
new CatalogProperties("ADMIN", HMM_OBJECTS), new ObjectMapper());
|
||||
private final StructuredDataService service =
|
||||
new StructuredDataService(mock(JdbcTemplate.class), catalog);
|
||||
private final StructuredDataService service = new StructuredDataService(mock(JdbcTemplate.class));
|
||||
|
||||
@Test
|
||||
void exposesOnlyEnvironmentConfiguredTablesAndViews() {
|
||||
assertThat(service.owner()).isEqualTo("ADMIN");
|
||||
void exposesOnlyTheSevenApprovedSmilegateStructuredTables() {
|
||||
assertThat(service.tables())
|
||||
.extracting(table -> table.tableName())
|
||||
.containsExactly(
|
||||
"HMM_ORG_TEAMS",
|
||||
"HMM_HR_EMPLOYEES",
|
||||
"HMM_LEAVE_BALANCES");
|
||||
assertThat(service.requireTable("leave-balances").objectType()).isEqualTo("VIEW");
|
||||
assertThat(service.previewSql(service.requireTable("employees")))
|
||||
.isEqualTo("SELECT * FROM \"ADMIN\".\"HMM_HR_EMPLOYEES\" WHERE ROWNUM <= ?");
|
||||
"CZN_COMN_USER_MST",
|
||||
"CZN_COMN_CHARACTER_MST",
|
||||
"COMN_SALES_TXN",
|
||||
"COMN_REFUND_TXN",
|
||||
"COMN_SALES_PRODUCT_DISP_BAS",
|
||||
"COMN_GAME_SERVER_BAS",
|
||||
"COMN_GAME_ALIAS_BAS");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsAnyObjectOutsideTheServerSideAllowlist() {
|
||||
assertThatThrownBy(() -> service.requireTable("KB_SECURITY_AUDIT_LOG"))
|
||||
void rejectsAnyTableOutsideTheServerSideAllowlist() {
|
||||
assertThatThrownBy(() -> service.requireTable("security-audit-log"))
|
||||
.isInstanceOf(AppException.class)
|
||||
.hasMessage("선택할 수 없는 카탈로그 객체입니다.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUnsafeOracleIdentifiers() {
|
||||
assertThatThrownBy(() -> new EnvironmentDataCatalog(
|
||||
new CatalogProperties("ADMIN; DROP USER X", HMM_OBJECTS), new ObjectMapper()))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessageContaining("BACKOFFICE_CATALOG_OWNER");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDuplicateObjectNamesEvenWhenKeysDiffer() {
|
||||
String duplicate = """
|
||||
[
|
||||
{"key":"employees","tableName":"HMM_HR_EMPLOYEES","objectType":"TABLE",
|
||||
"businessName":"직원","description":"직원"},
|
||||
{"key":"workers","tableName":"HMM_HR_EMPLOYEES","objectType":"VIEW",
|
||||
"businessName":"직원 뷰","description":"직원 뷰"}
|
||||
]
|
||||
""";
|
||||
|
||||
assertThatThrownBy(() -> new EnvironmentDataCatalog(
|
||||
new CatalogProperties("ADMIN", duplicate), new ObjectMapper()))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessageContaining("BACKOFFICE_CATALOG_OBJECTS");
|
||||
.hasMessage("선택할 수 없는 정형 데이터 테이블입니다.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +35,25 @@ class GuidedFlowTemplateTest {
|
||||
.doesNotContain("dashboard-workflow")
|
||||
.doesNotContain(">01<", ">02<");
|
||||
assertThat(layout)
|
||||
.contains("Data & AI Backoffice")
|
||||
.contains("사용자")
|
||||
.contains("접근 그룹")
|
||||
.contains("역할")
|
||||
.contains("토큰")
|
||||
.contains("운영 사용자 관리")
|
||||
.contains("접근 제어")
|
||||
.contains("보호·검증")
|
||||
.contains("연동 도구")
|
||||
.contains("운영")
|
||||
.contains("관리자")
|
||||
.contains("조회 연동")
|
||||
.contains("정형 데이터 조회")
|
||||
.contains("MCP 서비스")
|
||||
.contains("컬럼 마스킹")
|
||||
.contains("컬럼 원문 표시 허용")
|
||||
.contains("행 접근 필터 구조")
|
||||
.contains("보안 SQL 스크립트")
|
||||
.contains("고급 접근 조건")
|
||||
.contains("시스템 설정")
|
||||
.contains("DB 준비 상태")
|
||||
.contains("data-submenu-trigger")
|
||||
.contains("data-submenu-bar")
|
||||
.contains("data-submenu-panel")
|
||||
.contains("backoffice-can-mutate")
|
||||
.contains("backoffice-read-only")
|
||||
.doesNotContain("VPD Backoffice");
|
||||
@@ -108,7 +122,7 @@ class GuidedFlowTemplateTest {
|
||||
.contains("data-wizard-target=\"2\">대상")
|
||||
.doesNotContain(">1 역할<", ">2 객체<", "wizard-step-number");
|
||||
assertThat(tokens)
|
||||
.contains("직원 토큰 발급", "검증 세션 발급")
|
||||
.contains("도구 사용자 토큰 발급", "검증 세션 발급")
|
||||
.doesNotContain("1. 검증할 사용자 선택", "2. 검증 세션 발급");
|
||||
assertThat(probe)
|
||||
.contains("접근 검증 실행", "<strong>검증 결과</strong>")
|
||||
@@ -184,8 +198,7 @@ class GuidedFlowTemplateTest {
|
||||
String javascript = Files.readString(Path.of("src/main/resources/static/js/app.js"));
|
||||
|
||||
assertThat(html)
|
||||
.contains("value=\"MANAGED_TEAM\">본인 및 직접 보고 팀원")
|
||||
.contains("value=\"SELF\">본인 직원 행")
|
||||
.contains("value=\"OWN_CUSTOMER\">담당 게임 사용자")
|
||||
.contains("value=\"STATIC_SQL\">정적 SQL 조건식")
|
||||
.contains("HMM_LEAVE_VPD_FILTER")
|
||||
.contains("행 규칙의 두 가지 적용 방식 보기")
|
||||
@@ -209,6 +222,7 @@ class GuidedFlowTemplateTest {
|
||||
String filters = template("vpd-filter-policies.html");
|
||||
|
||||
assertThat(roles)
|
||||
.contains("스마일게이트 Data & AI PoC 운영 역할")
|
||||
.contains("데이터 취급 등급");
|
||||
assertThat(tokens)
|
||||
.contains("회수·만료 포함")
|
||||
|
||||
@@ -31,26 +31,26 @@ class MaskingRuleTemplateRenderTest {
|
||||
var engine = new SpringTemplateEngine();
|
||||
engine.setTemplateResolver(resolver);
|
||||
|
||||
var object = new ProtectedObject(7L, "POC_2", "KB_CUSTOMERS", "kb/customers", "Y");
|
||||
var contractObject = new ProtectedObject(8L, "POC_2", "KB_CONTRACTS", "kb/contracts", "Y");
|
||||
var column = new ProtectedColumn(11L, 7L, "RRN_MASKED", "Y", null, "RESTRICTED", "NULLIFY");
|
||||
var rule = new MaskingRule(1L, "KB_RRN_STANDARD", "주민번호 기본 마스킹", "RRN_PARTIAL", "테스트", "Y");
|
||||
var columnRule = new ColumnMaskingRule(11L, 7L, "POC_2", "KB_CUSTOMERS", "RRN_MASKED",
|
||||
1L, "KB_RRN_STANDARD", "주민번호 기본 마스킹", "RRN_PARTIAL", "Y");
|
||||
var object = new ProtectedObject(7L, "SGMP_POC", "CZN_COMN_USER_MST", "sgmp-poc/czn-users", "Y");
|
||||
var transactionObject = new ProtectedObject(8L, "SGMP_POC", "COMN_SALES_TXN", "sgmp-poc/sales", "Y");
|
||||
var column = new ProtectedColumn(11L, 7L, "GUID", "Y", null, "RESTRICTED", "NULLIFY");
|
||||
var rule = new MaskingRule(1L, "GAME_GUID_MASK", "게임 사용자 식별자 기본 마스킹", "TEXT_PARTIAL", "테스트", "Y");
|
||||
var columnRule = new ColumnMaskingRule(11L, 7L, "SGMP_POC", "CZN_COMN_USER_MST", "GUID",
|
||||
1L, "GAME_GUID_MASK", "게임 사용자 식별자 기본 마스킹", "TEXT_PARTIAL", "Y");
|
||||
var context = new Context(Locale.KOREAN);
|
||||
context.setVariable("_csrf", new CsrfFixture("_csrf", "test-token"));
|
||||
context.setVariable("templates", List.of(MaskingTemplate.values()));
|
||||
context.setVariable("rules", List.of(rule));
|
||||
context.setVariable("columnRules", List.of(columnRule));
|
||||
var status = new MaskingPolicyStatus(
|
||||
"POC_2", "KB_CUSTOMERS", "KB_CUSTOMER_PII_REDACT", "YES", 1, 1, 0, 0
|
||||
"SGMP_POC", "CZN_COMN_USER_MST", "SG_CZN_USER_REDACT", "YES", 1, 1, 0, 0
|
||||
);
|
||||
context.setVariable("policyStatuses", List.of(status));
|
||||
context.setVariable("policyStatusByObjectName", Map.of("KB_CUSTOMERS", status));
|
||||
context.setVariable("objects", List.of(object, contractObject));
|
||||
context.setVariable("maskingTargetObjects", List.of(object, contractObject));
|
||||
context.setVariable("policyStatusByObjectName", Map.of("CZN_COMN_USER_MST", status));
|
||||
context.setVariable("objects", List.of(object, transactionObject));
|
||||
context.setVariable("maskingTargetObjects", List.of(object, transactionObject));
|
||||
context.setVariable("sensitiveColumnsByObject", Map.of(7L, List.of(column), 8L, List.of()));
|
||||
context.setVariable("availableMaskingColumnsByObject", Map.of(7L, List.of("CUST_NM"), 8L, List.of("PREMIUM")));
|
||||
context.setVariable("availableMaskingColumnsByObject", Map.of(7L, List.of("GUID"), 8L, List.of("GUID")));
|
||||
|
||||
String rulesPage = engine.process("masking-rules", context);
|
||||
|
||||
@@ -64,13 +64,13 @@ class MaskingRuleTemplateRenderTest {
|
||||
.contains("DB Redaction 컬럼")
|
||||
.contains("레거시 VPD 컬럼 제어")
|
||||
.contains("마스킹 대상 컬럼 추가")
|
||||
.contains("POC_2.KB_CONTRACTS.PREMIUM")
|
||||
.contains("KB_CUSTOMER_PII_REDACT")
|
||||
.contains("SGMP_POC.COMN_SALES_TXN.GUID")
|
||||
.contains("SG_CZN_USER_REDACT")
|
||||
.contains("적용됨");
|
||||
|
||||
context.setVariable("users", List.of(new AppUser(2L, "KB_VPD_ADMIN", "ADMIN", "SEC", "N", "Y")));
|
||||
context.setVariable("userRules", List.of(new UserMaskingRule(2L, "KB_VPD_ADMIN", 11L,
|
||||
"POC_2", "KB_CUSTOMERS", "RRN_MASKED", "주민번호 기본 마스킹", "RRN_PARTIAL", "UNMASK", "Y")));
|
||||
context.setVariable("users", List.of(new AppUser(2L, "sg-teamlead", "SG-001", "DATA_AI", "N", "Y")));
|
||||
context.setVariable("userRules", List.of(new UserMaskingRule(2L, "sg-teamlead", 11L,
|
||||
"SGMP_POC", "CZN_COMN_USER_MST", "GUID", "게임 사용자 식별자 기본 마스킹", "TEXT_PARTIAL", "UNMASK", "Y")));
|
||||
String userPage = engine.process("user-masking-rules", context);
|
||||
|
||||
assertThat(userPage)
|
||||
|
||||
Reference in New Issue
Block a user