feat: prepare lingxing browser auth and sku measurement inbox

This commit is contained in:
2026-09-21 21:20:32 +08:00
parent 656f197737
commit 03374c83f6
14 changed files with 446 additions and 9 deletions
@@ -0,0 +1,54 @@
package com.baiyue.firstleg.lingxing;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
class LingxingAuthManagerTest {
private final LingxingProviderConfig config = new LingxingProviderConfig(
"LINGXING_01", "http://127.0.0.1:9222", true, 172800, 300);
@Test
void concurrentRequestsUseOneBrowserRefresh() {
LingxingBrowserSessionReader browser = mock(LingxingBrowserSessionReader.class);
LingxingAuthState state = new LingxingAuthState("token", "sid=1", Long.MAX_VALUE, "ua", "browser_session");
when(browser.read(config, "https://oms.xlwms.com")).thenAnswer(invocation -> {
Thread.sleep(40); return state;
});
LingxingAuthManager manager = new LingxingAuthManager(browser, Clock.systemUTC(), Runnable::run);
CompletableFuture<LingxingAuthState> first = manager.getAuthState(config, "https://oms.xlwms.com");
CompletableFuture<LingxingAuthState> second = manager.getAuthState(config, "https://oms.xlwms.com");
assertThat(first.join()).isSameAs(state);
assertThat(second.join()).isSameAs(state);
verify(browser).read(config, "https://oms.xlwms.com");
}
@Test
void secondaryVerificationBlocksRefreshUntilCooldownExpires() {
LingxingBrowserSessionReader browser = mock(LingxingBrowserSessionReader.class);
Clock clock = Clock.fixed(Instant.parse("2026-09-21T12:00:00Z"), ZoneOffset.UTC);
LingxingAuthManager manager = new LingxingAuthManager(browser, clock, Runnable::run);
manager.markSecondaryVerificationRequired(config.providerKey(), 1800);
assertThatThrownBy(() -> manager.getAuthState(config, "https://oms.xlwms.com").join())
.hasRootCauseInstanceOf(LingxingSecondaryVerificationException.class);
}
@Test
void jwtExpiryUsesSafetyWindow() {
String token = "eyJhbGciOiJub25lIn0.eyJleHAiOjE3OTAwMDAwMDB9.signature";
long expected = 1790000000000L - 300_000L;
assertThat(LingxingBrowserSessionReader.expiresAt(token, config)).isEqualTo(expected);
}
}
@@ -119,11 +119,47 @@ class MojiaMeasurementOpenApiServiceTest {
assertThat(receipt.getValue().getBoxCode()).isEqualTo("PCHK2608140001#5-1");
assertThat(receipt.getValue().getScannedShipmentId()).isNull();
assertThat(receipt.getValue().getLengthCm()).isEqualByComparingTo("60.1");
assertThat(receipt.getValue().getWeightKg()).isEqualByComparingTo("15.13");
assertThat(receipt.getValue().getImageFileId()).isEqualTo(3L);
assertThat(receipt.getValue().getStatus()).isEqualTo("UNMATCHED");
assertThat(receipt.getValue().getTenantId()).isZero();
}
@Test
void skuWithoutHashIsAcceptedIntoSkuSyncInboxAndDoesNotEnterCartonFlow() {
MeasurementRequestAuthenticator authenticator = mock(MeasurementRequestAuthenticator.class);
CartonMeasurementService cartons = mock(CartonMeasurementService.class);
MeasurementImageClient images = mock(MeasurementImageClient.class);
MojiaMeasurementReceiptRepository receipts = mock(MojiaMeasurementReceiptRepository.class);
InboundCartonLabelService labels = mock(InboundCartonLabelService.class);
var service = new MojiaMeasurementOpenApiService(authenticator, cartons,
new ObjectMapper().findAndRegisterModules(), Validation.buildDefaultValidatorFactory().getValidator(),
images, receipts, labels);
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
MeasurementRequestHeaders headers = new MeasurementRequestHeaders("mojia", null, null, null, null);
String requestJson = json().replace("PCHK2608140001#5-1", "SKU-NEW-001");
when(authenticator.authenticate("POST", MojiaMeasurementOpenApiService.CALLBACK_PATH,
headers, requestJson, false)).thenReturn(new AuthenticatedMeasurementApp(
"mojia", 0L, 90001L, Map.of("MJ-01", 30001L)));
when(images.uploadReceipt(eq("internal-token"), eq(90001L), eq(0L), any(Long.TYPE), any()))
.thenReturn(ApiResponse.success(new MeasurementImageClient.UploadResult(
9L, "mojia.jpg", "image/jpeg", 4L, "NOT_REQUIRED", null, null, null, null)));
UploadResponse result = service.receive(headers, requestJson);
assertThat(result.accepted()).isTrue();
assertThat(result.matched()).isFalse();
verify(labels, org.mockito.Mockito.never()).resolvePlatformByBoxCode(any());
verify(cartons, org.mockito.Mockito.never()).upsert(any(Long.TYPE), any(Long.TYPE), any());
ArgumentCaptor<MojiaMeasurementReceiptEntity> receipt =
ArgumentCaptor.forClass(MojiaMeasurementReceiptEntity.class);
verify(receipts).insert(receipt.capture());
assertThat(receipt.getValue().getMeasurementKind()).isEqualTo("SKU");
assertThat(receipt.getValue().getSyncStatus()).isEqualTo("PENDING");
assertThat(receipt.getValue().getStatus()).isEqualTo("SKU_PENDING_SYNC");
assertThat(receipt.getValue().getBoxCode()).isEqualTo("SKU-NEW-001");
}
@Test
void platformMachineCannotWriteAnInboundFromAnotherWarehouse() {
MeasurementRequestAuthenticator authenticator = mock(MeasurementRequestAuthenticator.class);
@@ -153,7 +189,7 @@ class MojiaMeasurementOpenApiServiceTest {
private String json() {
return """
{"boxCode":"PCHK2608140001#5-1","shipment_id":"FBA19L5KMY2CU000002",
"length":60.1,"width":40.2,"height":30.3,
"length":60.1,"width":40.2,"height":30.3,"weightKg":15.13,
"machineNo":"MJ-01","picture":"data:image/jpeg;base64,/9j/2Q=="}
""";
}