This commit is contained in:
2026-08-25 14:46:06 +08:00
parent 5abc7543ae
commit f325d57fc1
106 changed files with 3682 additions and 450 deletions
@@ -0,0 +1,51 @@
package com.baiyue.overseassecurity;
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 OverseasSecurityContractTest {
@Test void sqlFreezesReliableInboxProjectionAndRedisRecoveryBoundary() throws Exception {
String sql = read("overseas-security-service/src/main/resources/db/overseas-security.sql");
String service = read("overseas-security-service/src/main/java/com/baiyue/overseassecurity/service/SecurityProjectionService.java");
String validation = read("overseas-security-service/src/main/java/com/baiyue/overseassecurity/service/OverseasSecurityValidationService.java");
String mirror = read("overseas-security-service/src/main/java/com/baiyue/overseassecurity/service/SecurityProjectionRedisMirror.java");
assertThat(sql).contains("PRIMARY KEY (event_id)", "payload_hash CHAR(64)",
"PRIMARY KEY (aggregate_type, aggregate_id)", "aggregate_version BIGINT", "permission_version BIGINT");
assertThat(service).contains("ProjectionMonotonicity.decide", "Decision.APPLY");
assertThat(mirror).contains("TransactionPhase.AFTER_COMMIT", "Redis 镜像写入失败");
assertThat(validation).contains("SELECT MAX(permission_version)", "USER_DISABLED",
"TENANT_DISABLED", "PERMISSION_VERSION_STALE");
}
@Test void eventBridgeRoutesIamTypesWithoutInspectingTheirPayload() throws Exception {
String dispatcher = read("event-bridge-service/src/main/java/com/baiyue/eventbridge/service/OverseasEventDispatcher.java");
String client = read("event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpSecurityProjectionEventClient.java");
String lastleg = read("lastleg-service/src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java");
assertThat(dispatcher).contains("IAM_USER_STATUS_CHANGED", "IAM_TENANT_STATUS_CHANGED",
"IAM_PERMISSION_VERSION_CHANGED", "securityProjectionEventClient.deliver(event)");
assertThat(dispatcher).doesNotContain("IamSecurityEventsV1", "permissionVersion()", "status()");
assertThat(client).contains("path(\"data\").path(\"eventId\")", "row.getEventId().equals")
.doesNotContain("IamSecurityEventsV1");
assertThat(lastleg).contains("Lastleg 不消费该事件类型")
.doesNotContain("? shippingNodeConsumer.consume(event) : consumer.consume(event)");
}
@Test void internalOpenApiNeverAcceptsRawTokensOrCredentials() throws Exception {
String openapi = read("overseas-security-service/src/main/resources/openapi/internal-overseas-security-v1.yaml");
assertThat(openapi).contains("/internal/overseas-security/v1/events:",
"/internal/overseas-security/v1/validate:", "X-Internal-Service-Token",
"userId, tenantId, tokenVersion and permissionVersion");
assertThat(openapi).doesNotContain("accessToken", "refreshToken", "password:", "Authorization");
}
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);
}
}
@@ -0,0 +1,36 @@
package com.baiyue.overseassecurity.service;
import com.baiyue.overseassecurity.dto.OverseasSecurityDtos.FailureReason;
import com.baiyue.overseassecurity.dto.OverseasSecurityDtos.ValidationRequest;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class OverseasSecurityValidationServiceTest {
private final JdbcTemplate jdbc = mock(JdbcTemplate.class);
private final OverseasSecurityValidationService service = new OverseasSecurityValidationService(jdbc);
@Test void disabledUserAndTenantFailClosed() {
when(jdbc.query(anyString(), any(RowMapper.class), any(Object[].class)))
.thenReturn(List.of("DISABLED"));
assertThat(service.validate(new ValidationRequest(42L, 0L, 3L, 8L)).failureReason())
.isEqualTo(FailureReason.USER_DISABLED);
}
@Test void stalePermissionVersionFailsAndCurrentVersionPasses() {
when(jdbc.query(anyString(), any(RowMapper.class), any(Object[].class)))
.thenReturn(List.of(), List.of());
when(jdbc.queryForObject(anyString(), any(Class.class), any(Object[].class))).thenReturn(9L, 9L);
assertThat(service.validate(new ValidationRequest(42L, 0L, 3L, 8L)).failureReason())
.isEqualTo(FailureReason.PERMISSION_VERSION_STALE);
assertThat(service.validate(new ValidationRequest(42L, 0L, 3L, 9L)).valid()).isTrue();
}
}
@@ -0,0 +1,28 @@
package com.baiyue.overseassecurity.service;
import com.baiyue.common.core.exception.BizException;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class ProjectionMonotonicityTest {
@Test void appliesFirstAndNewerVersions() {
assertThat(ProjectionMonotonicity.decide(null, null, 1, "hash-1"))
.isEqualTo(ProjectionMonotonicity.Decision.APPLY);
assertThat(ProjectionMonotonicity.decide(1L, "hash-1", 2, "hash-2"))
.isEqualTo(ProjectionMonotonicity.Decision.APPLY);
}
@Test void ignoresOlderAndSameHashVersions() {
assertThat(ProjectionMonotonicity.decide(3L, "hash-3", 2, "hash-2"))
.isEqualTo(ProjectionMonotonicity.Decision.STALE);
assertThat(ProjectionMonotonicity.decide(3L, "hash-3", 3, "hash-3"))
.isEqualTo(ProjectionMonotonicity.Decision.DUPLICATE_VERSION);
}
@Test void rejectsSameVersionWithDifferentHash() {
assertThatThrownBy(() -> ProjectionMonotonicity.decide(3L, "hash-a", 3, "hash-b"))
.isInstanceOf(BizException.class).hasMessageContaining("payloadHash 冲突");
}
}