package com.label.integration; import com.label.AbstractIntegrationTest; import com.label.service.RedisService; import com.label.util.RedisKeyManager; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.*; import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; /** * 系统配置集成测试(US8)。 * * 测试场景: * 1. 公司专属配置覆盖全局默认 * 2. 未设置公司专属时,回退至全局默认 * 3. 未知配置键 → 400 UNKNOWN_CONFIG_KEY */ public class SysConfigIntegrationTest extends AbstractIntegrationTest { private static final String ADMIN_TOKEN = "test-admin-token-config"; @Autowired private TestRestTemplate restTemplate; @Autowired private RedisService redisService; private Long companyId; private Long adminUserId; @BeforeEach void setupToken() { companyId = jdbcTemplate.queryForObject( "SELECT id FROM sys_company WHERE company_code = 'DEMO'", Long.class); adminUserId = jdbcTemplate.queryForObject( "SELECT id FROM sys_user WHERE username = 'admin'", Long.class); // 伪造 Redis Token redisService.hSetAll(RedisKeyManager.tokenKey(ADMIN_TOKEN), Map.of("userId", adminUserId.toString(), "role", "ADMIN", "companyId", companyId.toString(), "username", "admin"), 3600L); } @AfterEach void cleanupTokens() { redisService.delete(RedisKeyManager.tokenKey(ADMIN_TOKEN)); } // ------------------------------------------------------------------ 测试 1: 公司配置覆盖全局 -- @Test @DisplayName("公司专属配置优先于全局默认(scope=COMPANY 覆盖 scope=GLOBAL)") void companyConfig_overridesGlobalDefault() { // 设置公司专属配置(覆盖全局 model_default) updateConfig("model_default", "glm-4-plus", "公司专属模型"); // 查询配置列表 ResponseEntity listResp = restTemplate.exchange( baseUrl("/api/config"), HttpMethod.GET, bearerRequest(ADMIN_TOKEN), Map.class); assertThat(listResp.getStatusCode()).isEqualTo(HttpStatus.OK); @SuppressWarnings("unchecked") List> configs = (List>) listResp.getBody().get("data"); assertThat(configs).isNotEmpty(); // 找到 model_default 配置 Map modelConfig = configs.stream() .filter(c -> "model_default".equals(c.get("configKey"))) .findFirst() .orElseThrow(() -> new AssertionError("model_default 配置不存在")); // 应返回公司专属配置值,scope=COMPANY assertThat(modelConfig.get("configValue")) .as("公司专属配置应覆盖全局默认") .isEqualTo("glm-4-plus"); assertThat(modelConfig.get("scope")) .as("scope 应标记为 COMPANY") .isEqualTo("COMPANY"); } // ------------------------------------------------------------------ 测试 2: 回退全局默认 -- @Test @DisplayName("未设置公司专属配置时,返回全局默认值(scope=GLOBAL)") void globalConfig_usedWhenNoCompanyOverride() { // 不设置公司专属,直接查询列表 ResponseEntity listResp = restTemplate.exchange( baseUrl("/api/config"), HttpMethod.GET, bearerRequest(ADMIN_TOKEN), Map.class); assertThat(listResp.getStatusCode()).isEqualTo(HttpStatus.OK); @SuppressWarnings("unchecked") List> configs = (List>) listResp.getBody().get("data"); // 至少包含 AbstractIntegrationTest.cleanData() 中插入的全局配置 assertThat(configs).isNotEmpty(); // 所有配置都应有 scope 字段 configs.forEach(cfg -> assertThat(cfg.containsKey("scope")).as("每条配置应含 scope 字段").isTrue()); // token_ttl_seconds 全局默认应为 7200 Map ttlConfig = configs.stream() .filter(c -> "token_ttl_seconds".equals(c.get("configKey"))) .findFirst() .orElse(null); if (ttlConfig != null) { assertThat(ttlConfig.get("configValue")).isEqualTo("7200"); assertThat(ttlConfig.get("scope")).isEqualTo("GLOBAL"); } } // ------------------------------------------------------------------ 测试 3: 未知配置键 -- @Test @DisplayName("更新未知配置键 → 400 UNKNOWN_CONFIG_KEY") void updateUnknownKey_returns400() { ResponseEntity resp = updateConfig("unknown_key_xyz", "someValue", null); assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); assertThat(resp.getBody().get("code")).isEqualTo("UNKNOWN_CONFIG_KEY"); } // ------------------------------------------------------------------ 测试 4: UPSERT 同键两次 -- @Test @DisplayName("同一配置键两次 PUT → 第二次更新而非重复插入") void updateSameKey_twice_upserts() { updateConfig("video_frame_interval", "60", "帧间隔 60s"); updateConfig("video_frame_interval", "120", "帧间隔 120s"); // 数据库中公司专属 video_frame_interval 应只有一条记录 Integer count = jdbcTemplate.queryForObject( "SELECT COUNT(*) FROM sys_config WHERE company_id = ? AND config_key = 'video_frame_interval'", Integer.class, companyId); assertThat(count).as("UPSERT:同键应只有一条公司专属记录").isEqualTo(1); // 值应为最后一次 PUT 的值 String value = jdbcTemplate.queryForObject( "SELECT config_value FROM sys_config WHERE company_id = ? AND config_key = 'video_frame_interval'", String.class, companyId); assertThat(value).isEqualTo("120"); } // ------------------------------------------------------------------ 工具方法 -- private ResponseEntity updateConfig(String key, String value, String description) { HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Bearer " + ADMIN_TOKEN); headers.setContentType(MediaType.APPLICATION_JSON); Map body = description != null ? Map.of("value", value, "description", description) : Map.of("value", value); return restTemplate.exchange( baseUrl("/api/config/" + key), HttpMethod.PUT, new HttpEntity<>(body, headers), Map.class); } private HttpEntity bearerRequest(String token) { HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Bearer " + token); return new HttpEntity<>(headers); } }