test: 契约冻结 v1.3.0 api 侧收尾——四模块字节级快照同步 + community/media 契约矩阵入场
CI / backend-test (push) Successful in 5m4s
CI / backend-test (push) Successful in 5m4s
- 正典 v1.3.0(doc main@f848476)字节级复制为 pet/auth/community/user 四份 openapi-v1.3.0.yaml 快照(md5 与正典一致),删除旧 v1.2.0(守卫只认一份, 历史由 git 承载);pet/auth 守卫期望升版 1.3.0/31 路径/43 操作/72 schemas - CommunityContractConformanceTest:community 域 17 操作 64 单元格全响应矩阵 (Feed/帖子/评论/互动/关注,401/403/404/409/422 各格实证,零豁免) - MediaContractConformanceTest(user 模块):media 两步上传 2 操作 8 单元格 全矩阵(真实 MinIO 直传,零豁免) - ContractValidator 四副本加单分支 allOf 展平合并,修复 v1.3.0 nullable+allOf 模式(coverImage/replyToUser)被静默跳过的校验盲区, 定向 mutation 自证生效 - 实现与冻结契约零漂移;310 → 325 测试全绿;check-secrets --all 通过 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+6
-6
@@ -39,8 +39,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* T3-19(D3-8):auth 域 6 个 M1 操作补进契约一致性保障,机制与
|
||||
* patbond-pet 的 ContractConformanceTest 同构——对冻结契约 v1.2.0(快照
|
||||
* {@code src/test/resources/contract/openapi-v1.2.0.yaml},正典在 doc 仓
|
||||
* patbond-pet 的 ContractConformanceTest 同构——对冻结契约 v1.3.0(快照
|
||||
* {@code src/test/resources/contract/openapi-v1.3.0.yaml},正典在 doc 仓
|
||||
* {@code docs/api/openapi.yaml})逐操作真实发请求,用 {@link ContractValidator}
|
||||
* 严格校验响应结构,最后以全响应矩阵门禁兜底。
|
||||
*
|
||||
@@ -314,10 +314,10 @@ class AuthContractConformanceTest {
|
||||
@Test
|
||||
@Order(98)
|
||||
void frozenSnapshotIsTheExpectedContractVersion() {
|
||||
assertThat(CONTRACT.version()).isEqualTo("1.2.0");
|
||||
assertThat(CONTRACT.paths()).hasSize(18);
|
||||
assertThat(CONTRACT.operations()).hasSize(24);
|
||||
assertThat(CONTRACT.schemas()).hasSize(45);
|
||||
assertThat(CONTRACT.version()).isEqualTo("1.3.0");
|
||||
assertThat(CONTRACT.paths()).hasSize(31);
|
||||
assertThat(CONTRACT.operations()).hasSize(43);
|
||||
assertThat(CONTRACT.schemas()).hasSize(72);
|
||||
assertThat(CONTRACT.operationsTagged(Set.of("auth", "user", "analytics")))
|
||||
.containsExactlyInAnyOrderElementsOf(AUTH_OPERATIONS);
|
||||
}
|
||||
|
||||
+28
-1
@@ -10,6 +10,7 @@ import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -81,7 +82,7 @@ final class ContractValidator {
|
||||
}
|
||||
|
||||
private void validate(Map<String, Object> rawSchema, JsonNode node, String loc, List<String> errors) {
|
||||
Map<String, Object> schema = contract.resolve(rawSchema);
|
||||
Map<String, Object> schema = effectiveSchema(rawSchema);
|
||||
if (node == null || node.isMissingNode()) {
|
||||
errors.add(loc + ": 字段缺失");
|
||||
return;
|
||||
@@ -130,6 +131,32 @@ final class ContractValidator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves $refs and flattens the v1.3.0 {@code nullable + allOf: [$ref]}
|
||||
* pattern into one plain schema (branch keys first, sibling keys — e.g.
|
||||
* the outer {@code nullable} — win). The frozen contract only ever uses
|
||||
* single-branch allOf, so a shallow merge is exact; overlapping
|
||||
* {@code properties} across branches would need a deep merge and are not
|
||||
* supported.
|
||||
*/
|
||||
private Map<String, Object> effectiveSchema(Map<String, Object> rawSchema) {
|
||||
Map<String, Object> schema = contract.resolve(rawSchema);
|
||||
List<Object> allOf = list(schema, "allOf");
|
||||
if (allOf == null) {
|
||||
return schema;
|
||||
}
|
||||
Map<String, Object> merged = new LinkedHashMap<>();
|
||||
for (Object branch : allOf) {
|
||||
merged.putAll(effectiveSchema(cast(branch)));
|
||||
}
|
||||
schema.forEach((key, value) -> {
|
||||
if (!"allOf".equals(key)) {
|
||||
merged.put(key, value);
|
||||
}
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
|
||||
private void validateObject(Map<String, Object> schema, JsonNode node, String loc, List<String> errors) {
|
||||
if (!node.isObject()) {
|
||||
errors.add(loc + ": 应为 object,实际 " + node.getNodeType());
|
||||
|
||||
@@ -13,27 +13,30 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The frozen v1.2.0 OpenAPI contract, loaded from the test-resource snapshot
|
||||
* {@code /contract/openapi-v1.2.0.yaml}.
|
||||
* The frozen v1.3.0 OpenAPI contract, loaded from the test-resource snapshot
|
||||
* {@code /contract/openapi-v1.3.0.yaml}.
|
||||
*
|
||||
* <p><b>Sync discipline (T2-09, extended by T3-19)</b>: the canonical
|
||||
* contract lives in the doc repo at {@code docs/api/openapi.yaml}; this
|
||||
* snapshot is a byte-identical copy taken at freeze time, and this class is
|
||||
* the module-local copy of the pet module's contract framework (same
|
||||
* per-module duplication discipline as BearerAuthFilter). Whenever the
|
||||
* canonical contract changes, copy it here AND in patbond-pet under the new
|
||||
* version's file name and update both conformance tests (expected version +
|
||||
* snapshot counts). The guard test on {@code info.version} makes a forgotten
|
||||
* canonical contract changes, copy it into every framework-carrying module
|
||||
* (patbond-pet / patbond-auth / patbond-community / patbond-user) under the
|
||||
* new version's file name and update each conformance test (expected version
|
||||
* + snapshot counts). The guard test on {@code info.version} makes a forgotten
|
||||
* sync fail loudly in CI instead of silently testing against a stale
|
||||
* contract.
|
||||
*
|
||||
* <p>Only the subset of OpenAPI 3.0 this contract actually uses is supported:
|
||||
* local {@code #/} refs, plain types, {@code nullable}, {@code enum},
|
||||
* {@code required}, {@code properties}, {@code items} — no allOf/oneOf.
|
||||
* {@code required}, {@code properties}, {@code items}, and the v1.3.0
|
||||
* single-branch {@code nullable + allOf: [$ref]} pattern (merged in
|
||||
* {@link ContractValidator}) — no oneOf/anyOf.
|
||||
*/
|
||||
final class OpenApiContract {
|
||||
|
||||
static final String RESOURCE = "/contract/openapi-v1.2.0.yaml";
|
||||
static final String RESOURCE = "/contract/openapi-v1.3.0.yaml";
|
||||
|
||||
private static final Set<String> HTTP_METHODS =
|
||||
Set.of("get", "put", "post", "delete", "options", "head", "patch", "trace");
|
||||
|
||||
+1477
-3
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user