Files
baiyue-firstleg/lastleg-service/src/test/java/com/baiyue/lastleg/security/LastlegSecurityContractTest.java
T

104 lines
7.0 KiB
Java
Raw Normal View History

2026-08-25 16:40:48 +08:00
package com.baiyue.lastleg.security;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class LastlegSecurityContractTest {
@Test void securityProjectionLivesInLastlegDatabaseWithReliableInbox() throws Exception {
String baseline = read("lastleg-service/src/main/resources/db/lastleg.sql");
String migration = read("deploy/sql/海外区域统一增量-MySQL8.sql");
String authSeed = read("auth/src/main/resources/db/phase1-admin-accounts.sql");
String service = read("lastleg-service/src/main/java/com/baiyue/lastleg/security/service/LastlegSecurityProjectionService.java");
String validation = read("lastleg-service/src/main/java/com/baiyue/lastleg/security/service/LastlegSecurityValidationService.java");
String validationDto = read("lastleg-service/src/main/java/com/baiyue/lastleg/security/dto/LastlegSecurityDtos.java");
String mirror = read("lastleg-service/src/main/java/com/baiyue/lastleg/security/service/LastlegSecurityProjectionRedisMirror.java");
assertThat(baseline).contains("CREATE TABLE IF NOT EXISTS ll_security_event_inbox",
"PRIMARY KEY (event_id)", "payload_hash CHAR(64)",
"CREATE TABLE IF NOT EXISTS ll_security_projection",
"PRIMARY KEY (aggregate_type,aggregate_id)", "aggregate_version BIGINT",
"permission_version BIGINT", "roles_json JSON", "authorities_json JSON");
2026-09-08 17:58:42 +08:00
assertThat(baseline).contains("table_name='ll_security_projection' AND column_name='roles_json'",
"table_name='ll_security_projection' AND column_name='authorities_json'",
"UPDATE ll_security_projection SET roles_json=JSON_ARRAY() WHERE roles_json IS NULL",
"UPDATE ll_security_projection SET authorities_json=JSON_ARRAY() WHERE authorities_json IS NULL");
assertThat(baseline).contains("SET @platform_admin_overseas_roles = JSON_ARRAY('PLATFORM_ADMIN')",
2026-09-09 23:46:21 +08:00
"SET @platform_admin_overseas_authorities = JSON_ARRAY(",
"('USER','1000001',0,'ACTIVE',0,JSON_ARRAY(),JSON_ARRAY(),0",
"('AUTHORIZATION','1000001',0,NULL,0,@platform_admin_overseas_roles,@platform_admin_overseas_authorities,0",
2026-08-25 16:40:48 +08:00
"baseline-platform-admin-user-v0",
2026-09-09 23:46:21 +08:00
"baseline-platform-admin-authorization-v0",
"platform:overseas-warehouse:last-mile-order:list",
"platform:overseas-warehouse:inbound-order:list",
"platform:overseas-warehouse:dispatch:list",
"platform:overseas-business:purchase:list",
"platform:overseas-master-data:quote:list",
"platform:finance:fee-source:review",
"AND aggregate_version=0 AND last_event_id='baseline-platform-admin-authorization-v0'")
.doesNotContain("('AUTHORIZATION','1000001',0,NULL,0,JSON_ARRAY(),JSON_ARRAY(),0",
"baseline-merchant-admin");
assertThat(migration).contains(
"CREATE TABLE ll_security_event_inbox (",
"CREATE TABLE ll_security_projection (",
"VALUES ('AUTHORIZATION','1000001',0,NULL,0,",
"VALUES ('USER','1000001',0,'ACTIVE',0,'[]','[]',0",
"PLATFORM_ADMIN",
"baseline-platform-admin-user-v0",
"baseline-platform-admin-authorization-v0",
"platform:overseas-warehouse:last-mile-order:list",
"platform:overseas-warehouse:inbound-order:list",
"platform:overseas-warehouse:dispatch:list",
"platform:overseas-business:purchase:list",
"platform:overseas-master-data:quote:list",
"platform:finance:fee-source:review")
.doesNotContain("SET @platform_admin_overseas_roles", "UPDATE ll_security_projection",
"baseline-merchant-admin");
2026-08-25 16:40:48 +08:00
assertThat(authSeed).contains("(1000001, 'platform_admin'", "default_tenant_id",
"default_app_code");
assertThat(migration).contains("USE baiyue_lastleg;")
2026-08-25 16:40:48 +08:00
.doesNotContain("CREATE DATABASE IF NOT EXISTS baiyue_overseas_security",
"USE baiyue_overseas_security;", "CREATE TABLE IF NOT EXISTS overseas_security_event_inbox",
"CREATE TABLE IF NOT EXISTS overseas_security_projection");
assertThat(service).contains("SecurityProjectionMonotonicity.decide", "Decision.APPLY");
assertThat(service).contains("EventPayloadHash.matches", "validatedStorageHash")
.doesNotContain("LoadingCompletedConsumer");
assertThat(mirror).contains("TransactionPhase.AFTER_COMMIT", "Redis 镜像写入失败");
assertThat(validation).contains("SELECT permission_version", "authorities_json", "tenant_id=?",
2026-08-25 16:40:48 +08:00
"SECURITY_PROJECTION_MISSING", "SECURITY_PROJECTION_NOT_READY", "USER_DISABLED",
"TENANT_DISABLED", "PERMISSION_VERSION_STALE");
assertThat(validationDto).contains("Long userId", "Long tenantId", "Long permissionVersion")
.doesNotContain("Long tokenVersion");
}
@Test void genericLastlegEventEndpointWhitelistsBusinessEventsOnly() throws Exception {
String controller = read("lastleg-service/src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java");
assertThat(controller).contains("FIRST_LEG_CONTAINER_SHIPPING_NODE_RECORDED",
"FIRST_LEG_LOADING_PLAN_COMPLETED", "Lastleg 不消费该事件类型")
.doesNotContain("IAM_USER_STATUS_CHANGED", "IAM_TENANT_STATUS_CHANGED",
"IAM_PERMISSION_VERSION_CHANGED");
}
@Test void internalControllerUsesLastlegOwnedPathsAndExistingTokenGuard() throws Exception {
String controller = read("lastleg-service/src/main/java/com/baiyue/lastleg/security/controller/LastlegSecurityController.java");
String guard = read("lastleg-service/src/main/java/com/baiyue/lastleg/security/InternalServiceTokenGuard.java");
String application = read("lastleg-service/src/main/resources/application.yml");
assertThat(controller).contains("@RequestMapping(\"/internal/lastleg/security/v1\")",
"@PostMapping(\"/events\")", "@PostMapping(\"/validate\")",
"com.baiyue.lastleg.security.InternalServiceTokenGuard")
.doesNotContain("/internal/overseas-security/v1");
assertThat(guard).contains("MessageDigest.isEqual", "SERVICE_UNAVAILABLE", "UNAUTHORIZED");
assertThat(application).contains("internal-service-token: ${INTERNAL_SERVICE_TOKEN:}")
.doesNotContain("phase1-local-internal-token");
}
private static String read(String relative) throws Exception {
Path path = Path.of(relative);
if (!Files.exists(path)) path = Path.of("..").resolve(relative);
return Files.readString(path, StandardCharsets.UTF_8);
}
}