refs #722: externalize backoffice customer configuration

This commit is contained in:
devmrko
2026-07-23 19:14:37 +09:00
parent a579501d6e
commit f452b05209
42 changed files with 449 additions and 618 deletions

View File

@@ -23,8 +23,9 @@ class MaskingPolicySynchronizerTest {
}
@Test
void smilegateSalesTransactionPolicyIsManagedByBackoffice() {
assertThat(MaskingPolicySynchronizer.managedPolicyName("COMN_SALES_TXN"))
.isEqualTo("SG_SALES_TXN_REDACT");
void redactionPolicyManagementIsNotBoundToAProductSpecificMapping() {
assertThat(MaskingPolicySynchronizer.class.getDeclaredFields())
.extracting(field -> field.getName())
.doesNotContain("MANAGED_POLICIES", "OWNER");
}
}

View File

@@ -2,6 +2,7 @@ package com.cloudhandson.vpdbackoffice.service;
import static org.assertj.core.api.Assertions.assertThat;
import com.cloudhandson.vpdbackoffice.config.BackofficeProperties;
import com.cloudhandson.vpdbackoffice.config.McpProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -10,7 +11,7 @@ import org.junit.jupiter.api.Test;
class McpSseServiceTest {
private final ObjectMapper objectMapper = new ObjectMapper();
private final SmilegateSelectAiService selectAiService = new CapturingSmilegateSelectAiService();
private final SelectAiService selectAiService = new CapturingSelectAiService();
private final McpSseService service = new McpSseService(
selectAiService,
objectMapper,
@@ -20,6 +21,12 @@ class McpSseServiceTest {
null,
null,
new BackofficeProperties.SelectAi("", "", "", "SGMP_POC_OCI_GPT54MINI")
),
new McpProperties(
"oracle.select_ai.test_data_text2sql",
"테스트 데이터 Text2SQL",
"테스트 데이터용 읽기 전용 SQL을 생성하고 실행합니다.",
"테스트 데이터의 조회 내용을 입력합니다."
)
);
@@ -30,7 +37,7 @@ class McpSseServiceTest {
var tools = response.path("result").path("tools");
assertThat(tools).hasSize(1);
var selectAi = tools.get(0);
assertThat(selectAi.path("name").asText()).isEqualTo("oracle.select_ai.smilegate_game_text2sql");
assertThat(selectAi.path("name").asText()).isEqualTo("oracle.select_ai.test_data_text2sql");
assertThat(selectAi.path("description").asText()).contains("SGMP_POC_OCI_GPT54MINI");
assertThat(selectAi.path("inputSchema").path("required"))
.extracting(node -> node.asText())
@@ -44,14 +51,13 @@ class McpSseServiceTest {
void callsVpdSelectAiThroughOrdsService() {
ObjectNode request = request(2, "tools/call");
ObjectNode params = (ObjectNode) request.putObject("params");
params.put("name", "oracle.select_ai.smilegate_game_text2sql");
params.put("name", "oracle.select_ai.test_data_text2sql");
ObjectNode arguments = params.putObject("arguments");
arguments.put("prompt", "카제나 AU를 조회해 줘");
ObjectNode response = service.handle("default", request, "user-bearer");
CapturingSmilegateSelectAiService agentService =
(CapturingSmilegateSelectAiService) selectAiService;
CapturingSelectAiService agentService = (CapturingSelectAiService) selectAiService;
assertThat(agentService.bearerToken).isEqualTo("user-bearer");
assertThat(agentService.prompt).isEqualTo("카제나 AU를 조회해 줘");
assertThat(response.path("error").isMissingNode()).isTrue();
@@ -69,7 +75,7 @@ class McpSseServiceTest {
void returnsToolLevelDeniedResultWhenVpdTokenIsMissing() {
ObjectNode request = request(3, "tools/call");
ObjectNode params = (ObjectNode) request.putObject("params");
params.put("name", "oracle.select_ai.smilegate_game_text2sql");
params.put("name", "oracle.select_ai.test_data_text2sql");
params.putObject("arguments").put("prompt", "카제나 AU를 조회해 줘");
ObjectNode response = service.handle("default", request, "");
@@ -89,12 +95,12 @@ class McpSseServiceTest {
return request;
}
private static final class CapturingSmilegateSelectAiService extends SmilegateSelectAiService {
private static final class CapturingSelectAiService extends SelectAiService {
private String bearerToken;
private String prompt;
private CapturingSmilegateSelectAiService() {
private CapturingSelectAiService() {
super(null, null, null, new ObjectMapper());
}

View File

@@ -29,10 +29,12 @@ class SchemaMetadataServiceTest {
void setUp() {
mapper = mock(SchemaMetadataMapper.class);
StructuredDataService structuredDataService = mock(StructuredDataService.class);
DataCatalog catalog = mock(DataCatalog.class);
when(catalog.owner()).thenReturn("SGMP_POC");
when(structuredDataService.requireTable("game-users")).thenReturn(GAME_USERS);
when(structuredDataService.tables()).thenReturn(List.of(GAME_USERS));
when(structuredDataService.defaultKey()).thenReturn("game-users");
service = new SchemaMetadataService(mapper, structuredDataService);
service = new SchemaMetadataService(mapper, structuredDataService, catalog);
}
@Test

View File

@@ -3,22 +3,31 @@ 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();
private final SecuritySqlScriptService service = new SecuritySqlScriptService(
new SecuritySqlScriptProperties("""
[{"scriptId":"tool-users","category":"사용자","fileName":"legacy/smilegate/70_tool_user.sql",
"title":"도구 사용자","description":"도구 사용자 초기 데이터"},
{"scriptId":"identity-administration","category":"권한","fileName":"legacy/smilegate/71_identity_administration.sql",
"title":"권한 관리","description":"권한 메타데이터"}]
"""),
new ObjectMapper()
);
@Test
void exposesOnlyTheCuratedGitTrackedSecurityScripts() {
assertThat(service.list())
.extracting(item -> item.fileName())
.containsExactly(
"70_sg_tool_user.sql",
"71_sg_identity_administration.sql"
"legacy/smilegate/70_tool_user.sql", "legacy/smilegate/71_identity_administration.sql"
);
assertThat(service.find("smilegate-identity-administration").source())
assertThat(service.find("identity-administration").source())
.contains("create table sg_app_user")
.contains("DATA_AI_TF");
}

View File

@@ -6,29 +6,30 @@ import static org.mockito.Mockito.mock;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.doThrow;
class StructuredDataServiceTest {
private final StructuredDataService service = new StructuredDataService(mock(JdbcTemplate.class));
private final DataCatalog catalog = mock(DataCatalog.class);
private final StructuredDataService service = new StructuredDataService(mock(JdbcTemplate.class), catalog);
@Test
void exposesOnlyTheSevenApprovedSmilegateStructuredTables() {
when(catalog.objects()).thenReturn(List.of(
new com.cloudhandson.vpdbackoffice.domain.structured.StructuredDataTable("sales", "COMN_SALES_TXN", "판매 거래", "게임 상품 판매 거래")));
assertThat(service.tables())
.extracting(table -> table.tableName())
.containsExactly(
"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");
.containsExactly("COMN_SALES_TXN");
}
@Test
void rejectsAnyTableOutsideTheServerSideAllowlist() {
doThrow(new AppException("선택할 수 없는 카탈로그 객체입니다."))
.when(catalog).require("security-audit-log");
assertThatThrownBy(() -> service.requireTable("security-audit-log"))
.isInstanceOf(AppException.class)
.hasMessage("선택할 수 없는 정형 데이터 테이블입니다.");
.hasMessage("선택할 수 없는 카탈로그 객체입니다.");
}
}