153 lines
7.4 KiB
Java
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());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|