feat(self-operated): expose private runtime configuration snapshots
This commit is contained in:
+48
@@ -0,0 +1,48 @@
|
||||
package com.baiyue.integration.controller;
|
||||
|
||||
import com.baiyue.common.web.GlobalExceptionHandler;
|
||||
import com.baiyue.integration.config.LingxingWmsProperties;
|
||||
import com.baiyue.integration.dto.lingxing.LingxingEgressRuntimeSnapshot;
|
||||
import com.baiyue.integration.provider.lingxing.LingxingEgressGate;
|
||||
import com.baiyue.integration.security.InternalServiceTokenGuard;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
class LingxingEgressRuntimeControllerTest {
|
||||
@Test void internalTokenIsRequiredBeforeGateInspectionAndPublicPathDoesNotExist() throws Exception {
|
||||
String token = UUID.randomUUID().toString();var guard = new InternalServiceTokenGuard();
|
||||
ReflectionTestUtils.setField(guard, "token", token);ReflectionTestUtils.invokeMethod(guard, "initializeToken");
|
||||
var gate = mock(LingxingEgressGate.class);var provider = new LingxingWmsProperties(false,null,null,null,null,null);
|
||||
var converter = new MappingJackson2HttpMessageConverter(Jackson2ObjectMapperBuilder.json()
|
||||
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build());
|
||||
var mvc = MockMvcBuilders.standaloneSetup(new LingxingEgressRuntimeController(guard,gate,provider))
|
||||
.setMessageConverters(converter).setControllerAdvice(new GlobalExceptionHandler()).build();
|
||||
String path = "/internal/integrations/lingxing/wms/egress/runtime";
|
||||
for (String bad : new String[] {"", "wrong", "Bearer user-jwt"}) {
|
||||
mvc.perform(get(path).header("X-Internal-Service-Token",bad)).andExpect(status().isOk()).andExpect(jsonPath("$.code").value("A0401"));
|
||||
}
|
||||
verifyNoInteractions(gate);
|
||||
mvc.perform(get("/api/integrations/lingxing/wms/egress/runtime")).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(org.hamcrest.Matchers.not("200")))
|
||||
.andExpect(jsonPath("$.data.service").doesNotExist());
|
||||
verifyNoInteractions(gate);
|
||||
var snapshot = new LingxingEgressRuntimeSnapshot(1,"integration",Instant.parse("2026-10-13T14:00:00Z"),
|
||||
"OFF","test",false,null,false,null,5000,30,false,null);
|
||||
when(gate.runtimeSnapshot(false)).thenReturn(snapshot);
|
||||
mvc.perform(get(path).header("X-Internal-Service-Token",token)).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value("200")).andExpect(jsonPath("$.data.service").value("integration"))
|
||||
.andExpect(jsonPath("$.data.observedAt").value("2026-10-13T14:00:00Z"))
|
||||
.andExpect(jsonPath("$.data.mode").value("OFF")).andExpect(jsonPath("$.data.providerEnabled").value(false))
|
||||
.andExpect(jsonPath("$.data.verified").doesNotExist()).andExpect(jsonPath("$.data.grant").doesNotExist());
|
||||
verify(gate).runtimeSnapshot(false);verifyNoMoreInteractions(gate);
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.baiyue.integration.provider.lingxing;
|
||||
|
||||
import com.baiyue.integration.config.LingxingEgressProperties;
|
||||
import com.baiyue.integration.dto.lingxing.LingxingEgressDtos.CollectionMode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.*;
|
||||
import java.util.HexFormat;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class LingxingEgressRuntimeSnapshotTest {
|
||||
private static final Instant NOW = Instant.parse("2026-10-13T14:00:00Z");
|
||||
private final LingxingBudgetAuthority authority = mock(LingxingBudgetAuthority.class);
|
||||
private LingxingEgressGate gate(CollectionMode mode, String account, String grant, URI uri) {
|
||||
return new LingxingEgressGate(new LingxingEgressProperties(mode, "test", account, grant, null, uri,
|
||||
mode == CollectionMode.VALIDATION ? Duration.ofSeconds(5) : Duration.ofMillis(1200), null, true),
|
||||
authority, Clock.fixed(NOW, ZoneOffset.UTC), millis -> { throw new AssertionError("DIAGNOSTIC_MUST_NOT_WAIT"); });
|
||||
}
|
||||
private static String hash(String domain, String value) throws Exception {
|
||||
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
|
||||
.digest(("selfop-runtime:v1:" + domain + "\n" + value).getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
@Test void sameGateReturnsActualBindingsWithDomainSeparatedExactRawFingerprints() throws Exception {
|
||||
var snapshot = gate(CollectionMode.BUSINESS, "same-raw-value", "same-raw-value", URI.create("http://authority:9303")).runtimeSnapshot(true);
|
||||
assertThat(snapshot.schemaVersion()).isEqualTo(1);assertThat(snapshot.service()).isEqualTo("integration");
|
||||
assertThat(snapshot.observedAt()).isEqualTo(NOW);assertThat(snapshot.mode()).isEqualTo("BUSINESS");
|
||||
assertThat(snapshot.environment()).isEqualTo("test");assertThat(snapshot.continuousEnabled()).isTrue();
|
||||
assertThat(snapshot.providerEnabled()).isTrue();assertThat(snapshot.grantPresent()).isTrue();
|
||||
assertThat(snapshot.intervalMs()).isEqualTo(1200);assertThat(snapshot.permitMaxSeconds()).isEqualTo(30);
|
||||
assertThat(snapshot.sourceAccountFingerprint()).isEqualTo(hash("source-account", "same-raw-value"));
|
||||
assertThat(snapshot.grantFingerprint()).isEqualTo(hash("grant", "same-raw-value")).isNotEqualTo(snapshot.sourceAccountFingerprint());
|
||||
assertThat(snapshot.budgetAuthorityFingerprint()).isEqualTo(hash("budget-authority", "http://authority:9303"));
|
||||
assertThat(gate(CollectionMode.BUSINESS, " raw ", " raw ", null).runtimeSnapshot(false).grantFingerprint())
|
||||
.isEqualTo(hash("grant", " raw ")).isNotEqualTo(hash("grant", "raw"));
|
||||
verifyNoInteractions(authority);
|
||||
}
|
||||
@Test void offAndValidationAreObservableAndAbsentOrBlankValuesAreNotHashed() {
|
||||
for (String value : new String[] {null, "", " "}) {
|
||||
var s = gate(CollectionMode.OFF, value, value, null).runtimeSnapshot(false);
|
||||
assertThat(s.mode()).isEqualTo("OFF");assertThat(s.sourceAccountFingerprint()).isNull();
|
||||
assertThat(s.grantPresent()).isFalse();assertThat(s.grantFingerprint()).isNull();
|
||||
assertThat(s.budgetAuthorityFingerprint()).isNull();assertThat(s.providerEnabled()).isFalse();
|
||||
}
|
||||
assertThat(gate(CollectionMode.VALIDATION, null, null, null).runtimeSnapshot(false).intervalMs()).isEqualTo(5000);
|
||||
verifyNoInteractions(authority);
|
||||
}
|
||||
@Test void serializedSnapshotContainsNoRawIdentityAddressCredentialOrVerifiedClaim() throws Exception {
|
||||
var raw = new ObjectMapper().findAndRegisterModules().writeValueAsString(
|
||||
gate(CollectionMode.BUSINESS, "private-account", "private-grant", URI.create("http://private-host:9303")).runtimeSnapshot(true));
|
||||
assertThat(raw).doesNotContain("private-account", "private-grant", "private-host", "http://", "verified", "appSecret", "internalServiceToken");
|
||||
verifyNoInteractions(authority);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user