184 lines
9.8 KiB
Java
184 lines
9.8 KiB
Java
package com.baiyue.firstleg.controller;
|
|
|
|
import org.junit.jupiter.api.DisplayName;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.core.io.ClassPathResource;
|
|
import org.springframework.security.access.prepost.PreAuthorize;
|
|
import org.springframework.web.bind.annotation.RequestParam;
|
|
import org.yaml.snakeyaml.Yaml;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.lang.reflect.Method;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
@DisplayName("FBA计费OpenAPI契约")
|
|
class FbaPricingContractTest {
|
|
@Test
|
|
@DisplayName("确认和发送费用兼容新的组合权限与原有权限")
|
|
void allowsCombinedConfirmSendPermissionAndLegacyPermissions() {
|
|
assertPricingAuthority("preview", "hasAnyAuthority('firstleg:inbound:confirm-send-fee', 'firstleg:fba-pricing:calculate')");
|
|
assertPricingAuthority("confirm", "hasAnyAuthority('firstleg:inbound:confirm-send-fee',"
|
|
+ " 'firstleg:fba-pricing:confirm')");
|
|
assertPricingAuthority("send", "hasAnyAuthority('firstleg:inbound:confirm-send-fee',"
|
|
+ " 'firstleg:inbound:send-fee')");
|
|
assertPricingAuthority("adjustPublished", "hasAnyAuthority('firstleg:fba-pricing:adjust',"
|
|
+ " 'firstleg:inbound:confirm-send-fee')");
|
|
assertPricingAuthority("manualQuote", "hasAuthority('firstleg:inbound:confirm-send-fee')");
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("客户规则保留客户参数,船司价格使用平台公共作用域")
|
|
void keepsCustomerRuleScopeAndRemovesCarrierRateTenantScope() {
|
|
Method rules = method("rules");
|
|
assertThat(rules.getParameterTypes()[1]).isEqualTo(Long.class);
|
|
assertThat(rules.getParameters()[1].getAnnotation(RequestParam.class).required()).isFalse();
|
|
|
|
Method rates = method("rates");
|
|
assertThat(rates.getParameterTypes()).doesNotContain(Long.class);
|
|
|
|
for (String scopedMethod : new String[] {"rule", "createRule", "retireRule", "setDefaultRule"}) {
|
|
Method method = method(scopedMethod);
|
|
assertThat(method.getParameterTypes()[1]).isEqualTo(long.class);
|
|
assertThat(method.getParameters()[1].getAnnotation(RequestParam.class).required()).isTrue();
|
|
}
|
|
|
|
for (String publicMethod : new String[] {"rate", "createRate", "retireRate"}) {
|
|
assertThat(java.util.Arrays.stream(method(publicMethod).getParameters())
|
|
.map(parameter -> parameter.getAnnotation(RequestParam.class)))
|
|
.allMatch(annotation -> annotation == null);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("契约声明二期边界计费、人工费用调整和幂等头")
|
|
void documentsPricingLifecycle() throws Exception {
|
|
String openapi = new ClassPathResource("openapi/fba-pricing-v1.yaml")
|
|
.getContentAsString(StandardCharsets.UTF_8);
|
|
assertThat(openapi)
|
|
.contains("/api/platform/v1/fba-pricing/customer-rules/versions:")
|
|
.contains("/api/platform/v1/inbounds/{inboundId}/fees/preview:")
|
|
.contains("不生成费用版本")
|
|
.contains("/api/platform/v1/fba-pricing/customer-rules/{id}/default:")
|
|
.contains("/api/platform/v1/fba-pricing/carrier-rates/versions:")
|
|
.contains("/api/platform/v1/inbounds/{inboundId}/fees/calculate:")
|
|
.contains("/api/platform/v1/inbounds/{inboundId}/fees/manual-quote:")
|
|
.contains("/api/platform/v1/inbounds/{inboundId}/fees/confirm:")
|
|
.contains("/api/platform/v1/inbounds/{inboundId}/fees/send:")
|
|
.contains("/api/platform/v1/inbounds/{inboundId}/fees/published:")
|
|
.contains("Idempotency-Key")
|
|
.contains("主流程已到 WAIT_SPLIT,也可独立试算且不回退主流程状态")
|
|
.contains("区域阶梯渠道可按平台 FBA 仓或海外中转仓的邮编区域")
|
|
.contains("不再以 carrierId、业务 customerId 或客户计费规则作为前置")
|
|
.contains("仅此动作将费用置为 SENT")
|
|
.contains("写入 Finance Outbox")
|
|
.contains("不回退 WAIT_SPLIT 状态")
|
|
.contains("expectedFeeVersion")
|
|
.contains("chargeableQuantity")
|
|
.contains("unitPrice")
|
|
.doesNotContain("required: [subtotalAmount, additionalAmount, reason]")
|
|
.doesNotContain("待客户确认");
|
|
String customerRules = openapi.substring(
|
|
openapi.indexOf("/api/platform/v1/fba-pricing/customer-rules:"),
|
|
openapi.indexOf("/api/platform/v1/fba-pricing/customer-rules/versions:"));
|
|
assertThat(customerRules)
|
|
.contains("OptionalTenantId")
|
|
.contains("name: ruleCode")
|
|
.doesNotContain("#/components/parameters/Page")
|
|
.doesNotContain("#/components/parameters/Size")
|
|
.doesNotContain("name: customerName")
|
|
.doesNotContain("name: chargeMode")
|
|
.doesNotContain("name: status");
|
|
String carrierRates = openapi.substring(
|
|
openapi.indexOf("/api/platform/v1/fba-pricing/carrier-rates:"),
|
|
openapi.indexOf("/api/platform/v1/fba-pricing/carrier-rates/versions:"));
|
|
assertThat(carrierRates)
|
|
.doesNotContain("OptionalTenantId")
|
|
.contains("name: rateCode");
|
|
assertThat(openapi)
|
|
.contains("OptionalTenantId: {name: tenantId, in: query, required: false")
|
|
.contains("schema: {type: string, pattern: '^[1-9][0-9]*$'}")
|
|
.doesNotContain("format: int64");
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("费用快照强制返回实际换算与计费量舍入参数")
|
|
void requiresActualConversionAndRoundingMetadata() throws Exception {
|
|
String source = new ClassPathResource("openapi/fba-pricing-v1.yaml")
|
|
.getContentAsString(StandardCharsets.UTF_8);
|
|
Map<String, Object> schemas = map(map(new Yaml().load(source)).get("components"));
|
|
Map<String, Object> fee = map(map(schemas.get("schemas")).get("FeeSnapshotResponse"));
|
|
|
|
assertThat(list(fee.get("required"))).contains(
|
|
"conversionFactor", "roundingScale", "roundingMode");
|
|
Map<String, Object> properties = map(fee.get("properties"));
|
|
assertThat(map(properties.get("conversionFactor")).get("description").toString())
|
|
.contains("167", "363");
|
|
assertThat(list(map(properties.get("roundingScale")).get("enum"))).containsExactly(6);
|
|
assertThat(list(map(properties.get("roundingMode")).get("enum"))).containsExactly("HALF_UP");
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("客户规则列表响应锁定数组包络和实体核心字段")
|
|
void freezesCustomerRuleVersionListResponse() throws Exception {
|
|
String source = new ClassPathResource("openapi/fba-pricing-v1.yaml")
|
|
.getContentAsString(StandardCharsets.UTF_8);
|
|
Map<String, Object> api = map(new Yaml().load(source));
|
|
Map<String, Object> paths = map(api.get("paths"));
|
|
Map<String, Object> get = map(map(paths.get(
|
|
"/api/platform/v1/fba-pricing/customer-rules")).get("get"));
|
|
Map<String, Object> success = map(map(get.get("responses")).get("200"));
|
|
Map<String, Object> responseSchema = map(map(map(success.get("content"))
|
|
.get("application/json")).get("schema"));
|
|
assertThat(responseSchema).containsEntry(
|
|
"$ref", "#/components/schemas/CustomerRuleVersionListApiResponse");
|
|
|
|
Map<String, Object> schemas = map(map(api.get("components")).get("schemas"));
|
|
Map<String, Object> envelope = map(schemas.get("CustomerRuleVersionListApiResponse"));
|
|
assertThat(list(envelope.get("required"))).containsExactly("code", "message", "data", "timestamp");
|
|
Map<String, Object> envelopeProperties = map(envelope.get("properties"));
|
|
assertThat(envelopeProperties).containsOnlyKeys(
|
|
"code", "message", "data", "requestId", "traceId", "timestamp");
|
|
Map<String, Object> data = map(envelopeProperties.get("data"));
|
|
assertThat(data).containsEntry("type", "array");
|
|
assertThat(map(data.get("items"))).containsEntry(
|
|
"$ref", "#/components/schemas/CustomerRuleVersionResource");
|
|
|
|
Map<String, Object> resource = map(schemas.get("CustomerRuleVersionResource"));
|
|
assertThat(map(resource.get("properties"))).containsOnlyKeys(
|
|
"id", "tenantId", "ruleCode", "ruleVersion", "customerId", "customerName",
|
|
"businessType", "chargeMode", "taxMode", "conversionFactor", "roundingScale",
|
|
"roundingMode", "currency", "effectiveFrom", "effectiveTo", "status", "remark",
|
|
"createdBy", "createdAt", "updatedBy", "updatedAt", "deleted", "version");
|
|
for (String longField : List.of("id", "tenantId", "customerId", "createdBy", "updatedBy")) {
|
|
assertThat(map(map(resource.get("properties")).get(longField))).containsEntry("type", "string");
|
|
}
|
|
assertThat(source).doesNotContain("CustomerRuleVersionSummary");
|
|
}
|
|
|
|
private static Method method(String name) {
|
|
return java.util.Arrays.stream(PlatformFbaMasterDataController.class.getDeclaredMethods())
|
|
.filter(candidate -> candidate.getName().equals(name))
|
|
.findFirst().orElseThrow();
|
|
}
|
|
|
|
private static void assertPricingAuthority(String name, String expected) {
|
|
Method method = java.util.Arrays.stream(PlatformFbaPricingController.class.getDeclaredMethods())
|
|
.filter(candidate -> candidate.getName().equals(name))
|
|
.findFirst().orElseThrow();
|
|
assertThat(method.getAnnotation(PreAuthorize.class).value()).isEqualTo(expected);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static Map<String, Object> map(Object value) {
|
|
return (Map<String, Object>) value;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static List<Object> list(Object value) {
|
|
return (List<Object>) value;
|
|
}
|
|
}
|