Files
baiyue-firstleg/integration-service/src/test/java/com/baiyue/integration/provider/ZimTrackingClientTest.java
T
2026-08-21 16:04:16 +08:00

153 lines
7.4 KiB
Java

package com.baiyue.integration.provider;
import com.baiyue.integration.config.IntegrationProperties;
import com.baiyue.integration.dto.CarrierScheduleDtos.QueryRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class ZimTrackingClientTest {
private HttpServer server;
@AfterEach
void stopServer() { if (server != null) server.stop(0); }
@Test
void usesOAuthTokenCacheAndContainerOnlyTracingPath() throws Exception {
Credentials credentials = Credentials.random();
String accessToken = UUID.randomUUID().toString();
AtomicInteger authCalls = new AtomicInteger();
AtomicReference<String> authForm = new AtomicReference<>();
AtomicReference<String> tracingPath = new AtomicReference<>();
server = server();
server.createContext("/authorize/v1", exchange -> {
authCalls.incrementAndGet(); authForm.set(body(exchange));
respond(exchange, 200, tokenBody(accessToken));
});
server.createContext("/tracing/v2", exchange -> {
tracingPath.set(exchange.getRequestURI().getPath());
assertThat(exchange.getRequestHeaders().getFirst("Authorization")).isEqualTo("bearer " + accessToken);
assertThat(exchange.getRequestHeaders().getFirst("Ocp-Apim-Subscription-Key"))
.isEqualTo(credentials.subscriptionKey());
respond(exchange, 200, successBody());
});
server.start();
ZimTrackingClient client = client(credentials);
client.query(query()); client.query(query());
assertThat(authCalls).hasValue(1);
assertThat(authForm.get()).contains("grant_type=client_credentials", "scope=tracing")
.contains("client_id=", "client_secret=");
assertThat(tracingPath.get()).isEqualTo("/tracing/v2/ZCSU7881970");
}
@Test
void unauthorizedRefreshesTokenAndRetriesTracingExactlyOnce() throws Exception {
Credentials credentials = Credentials.random();
String firstToken = UUID.randomUUID().toString();
String secondToken = UUID.randomUUID().toString();
AtomicInteger authCalls = new AtomicInteger();
AtomicInteger tracingCalls = new AtomicInteger();
server = server();
server.createContext("/authorize/v1", exchange -> {
int call = authCalls.incrementAndGet(); respond(exchange, 200, tokenBody(call == 1 ? firstToken : secondToken));
});
server.createContext("/tracing/v2", exchange -> {
int call = tracingCalls.incrementAndGet();
respond(exchange, call == 1 ? 401 : 200, call == 1 ? "{}" : successBody());
});
server.start();
client(credentials).query(query());
assertThat(authCalls).hasValue(2);
assertThat(tracingCalls).hasValue(2);
}
@Test
void forbiddenIsManualAuthorizationFailureAndDoesNotEchoSecrets() throws Exception {
Credentials credentials = Credentials.random();
server = server();
server.createContext("/authorize/v1", exchange -> respond(exchange, 200, tokenBody(UUID.randomUUID().toString())));
server.createContext("/tracing/v2", exchange -> respond(exchange, 403, "<html>forbidden</html>"));
server.start();
assertThatThrownBy(() -> client(credentials).query(query()))
.isInstanceOfSatisfying(ZimTrackingClient.ZimTrackingException.class, value -> {
assertThat(value.outcome()).isEqualTo("MANUAL_REQUIRED");
assertThat(value.code()).isEqualTo("ZIM_HTTP_403");
assertThat(value.getMessage()).doesNotContain(credentials.clientSecret())
.doesNotContain(credentials.subscriptionKey()).doesNotContain("ZIM_NOT_FOUND");
});
}
@Test
void emptyOrMissingContainerListRequiresManualHandling() throws Exception {
Credentials credentials = Credentials.random();
AtomicReference<String> businessBody = new AtomicReference<>(
"{\"bkBlDetails\":{\"consContainerList\":[]}}");
server = server();
server.createContext("/authorize/v1", exchange -> respond(exchange, 200, tokenBody(UUID.randomUUID().toString())));
server.createContext("/tracing/v2", exchange -> respond(exchange, 200, businessBody.get()));
server.start();
ZimTrackingClient client = client(credentials);
assertThatThrownBy(() -> client.query(query())).isInstanceOfSatisfying(
ZimTrackingClient.ZimTrackingException.class, value -> assertThat(value.code()).isEqualTo("ZIM_NOT_FOUND"));
businessBody.set("{}");
assertThatThrownBy(() -> client.query(query())).isInstanceOfSatisfying(
ZimTrackingClient.ZimTrackingException.class,
value -> assertThat(value.code()).isEqualTo("ZIM_EMPTY_RESULT"));
}
private HttpServer server() throws IOException { return HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); }
private QueryRequest query() { return new QueryRequest("ZIM", "CONTAINER_NO", "ZCSU7881970"); }
private ZimTrackingClient client(Credentials credentials) {
String root = "http://127.0.0.1:" + server.getAddress().getPort();
var zim = new IntegrationProperties.CarrierSchedule.Zim(true, URI.create(root + "/authorize/v1"),
URI.create(root + "/tracing/v2"), credentials.clientId(), credentials.clientSecret(),
credentials.subscriptionKey(), Duration.ofSeconds(2), Duration.ofSeconds(2), 10);
ZimTrackingClient client = new ZimTrackingClient();
ReflectionTestUtils.setField(client, "properties", new IntegrationProperties(null, null,
new IntegrationProperties.CarrierSchedule(null, null, null, zim, null)));
ReflectionTestUtils.setField(client, "objectMapper", new ObjectMapper()); client.initialize(); return client;
}
private static String body(HttpExchange exchange) throws IOException {
return new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
}
private static String tokenBody(String token) {
return "{\"access_token\":\"" + token + "\",\"expires_in\":3600}";
}
private static String successBody() {
return "{\"bkBlDetails\":{\"consContainerList\":[{\"containerNumber\":\" ZCSU7881970 \"}]}}";
}
private static void respond(HttpExchange exchange, int status, String body) throws IOException {
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().add("Content-Type", "application/json; charset=UTF-8");
exchange.sendResponseHeaders(status, bytes.length); exchange.getResponseBody().write(bytes); exchange.close();
}
private record Credentials(String clientId, String clientSecret, String subscriptionKey) {
static Credentials random() {
return new Credentials(UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString());
}
}
}