Files
flow-center-service/src/test/java/com/yunhai/flowcenter/service/FormCenterSchemaClientTest.java
T
chenxing 7eeaea55f8 Enforce exact published form version contracts
Local review candidate only. Explicitly selected inherited prerequisites and prior repair patches; unrelated WIP and independent MQ fixes are excluded. No push or deployment authorized.
2026-10-23 10:51:47 +08:00

198 lines
9.1 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.yunhai.flowcenter.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunhai.platform.openapi.core.YschOpenCall;
import com.yunhai.platform.openapi.core.OpenApiException;
import com.yunhai.platform.openapi.core.OpenApiErrorCode;
import com.yunhai.platform.utils.exception.DException;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link FormCenterSchemaClient} 单元测试(2026-10-20 C1 第一刀)。<br>
* 覆盖:① SchemaView(data) → 快照节的 fields 扁平化(分组展开、enum 收 options 值域);
* ② audience <b>数组解析</b>(契约 v2 C5/G3 breaking)+ 旧单值串容错;③ 失败重试 2 次后转
* SchemaUnavailableException(发布阻断语义)。
*/
class FormCenterSchemaClientTest {
private YschOpenCall openCall;
private FormCenterSchemaClient client;
@BeforeEach
void setUp() {
openCall = mock(YschOpenCall.class);
client = new FormCenterSchemaClient(openCall, new ObjectMapper());
}
@SuppressWarnings("unchecked")
private Map<String, Object> schemaData(String uuid, int version, List<String> audience) {
Map<String, Object> data = new HashMap<>();
data.put("formUuid", uuid);
data.put("version", version);
data.put("code", "leave-form");
data.put("appCode", "svc-hall");
data.put("audience", audience);
data.put("groups", Arrays.asList(
map("key", "base", "label", "基本信息", "fields", Arrays.asList(
map("key", "days", "label", "天数", "type", "number", "required", true),
map("key", "grade", "label", "年级", "type", "select", "required", false,
"options", Arrays.asList(map("label", "高一", "value", "g1"),
map("label", "高二", "value", "g2"))))),
map("key", "extra", "label", "补充", "fields", Arrays.asList(
map("key", "reason", "label", "事由", "type", "text", "required", true)))));
return data;
}
private static Map<String, Object> map(Object... kv) {
Map<String, Object> m = new HashMap<>();
for (int i = 0; i + 1 < kv.length; i += 2) {
m.put((String) kv[i], kv[i + 1]);
}
return m;
}
@Test
@SuppressWarnings("unchecked")
void flattensGroupsIntoFieldsAndCollectsEnumFromOptions() {
when(openCall.get(anyString(), anyString(), anyMap(), eq(Map.class)))
.thenReturn(schemaData("U1", 3, Arrays.asList("STUDENT", "TEACHER")));
Map<String, Object> snap = client.fetchSchema(27L, "U1", 3);
assertEquals("U1", snap.get("formUuid"));
assertEquals(3, snap.get("formVersion"));
assertEquals(Arrays.asList("STUDENT", "TEACHER"), snap.get("audience"));
List<Map<String, Object>> fields = (List<Map<String, Object>>) snap.get("fields");
assertEquals(3, fields.size(), "两个分组的字段要扁平化到一处");
Map<String, Object> grade = fields.stream()
.filter(f -> "grade".equals(f.get("key"))).findFirst().orElseThrow(AssertionError::new);
assertEquals("select", grade.get("type"));
assertEquals(Boolean.FALSE, grade.get("required"));
assertEquals(Arrays.asList("g1", "g2"), grade.get("enum"), "options 值域收进 enum(域查③的输入)");
}
@Test
void legacySingleStringAudienceIsWrappedAsArray() {
Map<String, Object> data = schemaData("U1", 3, null);
data.put("audience", "STUDENT");
when(openCall.get(anyString(), anyString(), anyMap(), eq(Map.class))).thenReturn(data);
Map<String, Object> snap = client.fetchSchema(27L, "U1", 3);
assertEquals(Arrays.asList("STUDENT"), snap.get("audience"),
"C5 切换窗口的旧单值串按一元素数组容错");
}
@Test
void retriesTwiceThenThrowsUnavailable() {
when(openCall.get(anyString(), anyString(), anyMap(), eq(Map.class)))
.thenThrow(new RuntimeException("connect refused"));
assertThrows(FormCenterSchemaClient.SchemaUnavailableException.class,
() -> client.fetchSchema(27L, "U1", 3));
verify(openCall, times(FormCenterSchemaClient.MAX_ATTEMPTS))
.get(anyString(), anyString(), anyMap(), eq(Map.class));
}
@Test
@SuppressWarnings("unchecked")
void nullDataTreatedAsFailure() {
when(openCall.get(anyString(), anyString(), anyMap(), eq(Map.class))).thenReturn(null);
assertThrows(FormCenterSchemaClient.SchemaUnavailableException.class,
() -> client.fetchSchema(27L, "U1", 3));
verify(openCall, times(FormCenterSchemaClient.MAX_ATTEMPTS))
.get(anyString(), anyString(), anyMap(), eq(Map.class));
}
@ParameterizedTest
@NullSource
@ValueSource(strings = {"0", "-1", "4", "3.5", "wrong", "2147483648"})
void malformedOrWrongReturnedVersionIsContractFailureWithoutRetry(String returnedVersion) {
Map<String, Object> data = schemaData("U1", 3, Arrays.asList("STUDENT"));
data.put("version", returnedVersion);
when(openCall.get(anyString(), anyString(), anyMap(), eq(Map.class))).thenReturn(data);
assertThrows(FormCenterSchemaClient.SchemaContractViolationException.class,
() -> client.fetchSchema(27L, "U1", 3));
verify(openCall, times(1)).get(anyString(), anyString(), anyMap(), eq(Map.class));
}
@ParameterizedTest
@NullSource
@ValueSource(strings = {"", "U2"})
void missingOrWrongReturnedUuidIsContractFailureWithoutRetry(String returnedUuid) {
when(openCall.get(anyString(), anyString(), anyMap(), eq(Map.class)))
.thenReturn(schemaData(returnedUuid, 3, Arrays.asList("STUDENT")));
assertThrows(FormCenterSchemaClient.SchemaContractViolationException.class,
() -> client.fetchSchema(27L, "U1", 3));
verify(openCall, times(1)).get(anyString(), anyString(), anyMap(), eq(Map.class));
}
@ParameterizedTest
@ValueSource(ints = {0, -1})
void illegalRequestedVersionMakesNoRemoteCall(int version) {
assertThrows(IllegalArgumentException.class, () -> client.fetchSchema(27L, "U1", version));
org.mockito.Mockito.verifyNoInteractions(openCall);
}
@Test
void resourceRejectionUsesEnvelopeCodeAndDoesNotRetry() {
// SDK 实际携带 OpenApiErrorCode,不会转换成本地 ErrorCode 枚举;文案刻意无关键词。
when(openCall.get(anyString(), anyString(), anyMap(), eq(Map.class)))
.thenThrow(new DException(new OpenApiErrorCode(2, "opaque")));
assertThrows(FormCenterSchemaClient.SchemaResourceUnavailableException.class,
() -> client.fetchSchema(27L, "U1", 3));
verify(openCall, times(1)).get(anyString(), anyString(), anyMap(), eq(Map.class));
}
@ParameterizedTest
@ValueSource(ints = {3, 4})
void authRejectionDoesNotBlindlyRetry(int code) {
when(openCall.get(anyString(), anyString(), anyMap(), eq(Map.class)))
.thenThrow(new DException(new OpenApiErrorCode(code, "opaque")));
assertThrows(FormCenterSchemaClient.SchemaAccessDeniedException.class,
() -> client.fetchSchema(27L, "U1", 3));
verify(openCall, times(1)).get(anyString(), anyString(), anyMap(), eq(Map.class));
}
@Test
void http404DoesNotBlindlyRetry() {
when(openCall.get(anyString(), anyString(), anyMap(), eq(Map.class)))
.thenThrow(new OpenApiException("opaque", 404, ""));
assertThrows(FormCenterSchemaClient.SchemaResourceUnavailableException.class,
() -> client.fetchSchema(27L, "U1", 3));
verify(openCall, times(1)).get(anyString(), anyString(), anyMap(), eq(Map.class));
}
@Test
void internalRemoteFailureRemainsRetryable() {
when(openCall.get(anyString(), anyString(), anyMap(), eq(Map.class)))
.thenThrow(new DException(new OpenApiErrorCode(6, "opaque")));
assertThrows(FormCenterSchemaClient.SchemaUnavailableException.class,
() -> client.fetchSchema(27L, "U1", 3));
verify(openCall, times(FormCenterSchemaClient.MAX_ATTEMPTS))
.get(anyString(), anyString(), anyMap(), eq(Map.class));
}
}