fix(selfop): allow controlled manual collection in test
This commit is contained in:
+2
-1
@@ -90,7 +90,8 @@ public final class LingxingEgressGate {
|
||||
}
|
||||
private void check(EgressContext context) {
|
||||
if (properties.collectionMode() == CollectionMode.OFF) throw failure("EGRESS_OFF");
|
||||
if (properties.collectionMode() == CollectionMode.BUSINESS && !"production".equals(properties.environmentLabel()))
|
||||
if (properties.collectionMode() == CollectionMode.BUSINESS
|
||||
&& !("production".equals(properties.environmentLabel()) || "test".equals(properties.environmentLabel())))
|
||||
throw failure("BUSINESS_ENVIRONMENT_REJECTED");
|
||||
if (paused && (!recoverableRatePause || properties.collectionMode() != CollectionMode.BUSINESS
|
||||
|| cooldownUntil == null || clock.instant().isBefore(cooldownUntil))) throw failure("ACCOUNT_PAUSED");
|
||||
|
||||
@@ -6,7 +6,7 @@ info:
|
||||
Single-page or single-order adapter. No scheduling, business persistence or public Gateway route.
|
||||
Source times remain uninterpreted strings; warehouse timeZone does not certify source timezone.
|
||||
Server-side collection mode defaults to OFF. VALIDATION requires an externally confirmed exclusive
|
||||
account window; BUSINESS requires environmentLabel exactly production. All five endpoints apply the same gate.
|
||||
account window; BUSINESS requires environmentLabel exactly production or test. TEST business task creation is restricted by Lastleg to controlled manual commands and their legitimate derived work; the adapter does not schedule work. All five endpoints apply the same gate.
|
||||
Missing exclusive proof, persistent account budget authorization or control context fails closed before egress.
|
||||
At most one account request is in flight and HTTP request starts are separated by at least 5 seconds.
|
||||
A JVM lock is only local protection and is not proof of cross-environment account exclusivity.
|
||||
|
||||
+33
-3
@@ -37,16 +37,46 @@ class LingxingEgressGateTest {
|
||||
assertThat(completion.sourceCode()).isNull();
|
||||
}
|
||||
}
|
||||
@Test void defaultOffAndNonProductionBusinessNeverAuthorize() {
|
||||
@Test void defaultOffAndUnknownBusinessEnvironmentsNeverAuthorize() {
|
||||
var defaults = new LingxingEgressProperties(null, null, null, null, null, null, null, null);
|
||||
assertThat(defaults.collectionMode()).isEqualTo(CollectionMode.OFF);
|
||||
assertThat(defaults.minStartInterval()).isEqualTo(Duration.ofSeconds(5));
|
||||
var off = new LingxingEgressGate(defaults, ledger, clock, clock::advanceMillis);
|
||||
assertThatThrownBy(() -> off.execute(context("a"), Operation.WAREHOUSES, HASH, h -> { h.startHttp(); return true; })).hasMessageContaining("EGRESS_OFF");
|
||||
var business = new LingxingEgressGate(props(CollectionMode.BUSINESS, "test"), ledger, clock, clock::advanceMillis);
|
||||
assertThatThrownBy(() -> business.execute(context("a"), Operation.WAREHOUSES, HASH, h -> true)).hasMessageContaining("BUSINESS_ENVIRONMENT_REJECTED");
|
||||
for(String environment:List.of("dev","gray","staging","prod","TEST","")) {
|
||||
var business = new LingxingEgressGate(props(CollectionMode.BUSINESS, environment), ledger, clock, clock::advanceMillis);
|
||||
assertThatThrownBy(() -> business.execute(businessContext("rejected"), Operation.WAREHOUSES, HASH, h -> true)).hasMessageContaining("BUSINESS_ENVIRONMENT_REJECTED");
|
||||
}
|
||||
assertThat(ledger.authorizations).isZero();
|
||||
}
|
||||
@Test void testAndProductionBusinessUseTheSameSingleConsumedPermitAndCompletionProtocol() {
|
||||
for(String environment:List.of("test","production")) {
|
||||
var gate=new LingxingEgressGate(props(CollectionMode.BUSINESS,environment),ledger,clock,clock::advanceMillis);
|
||||
var result=gate.execute(businessContext(environment+"-inventory"),Operation.PRODUCT_INVENTORY,HASH,handle->{handle.startHttp();return handle.metadata();});
|
||||
assertThat(result.actualHttpAttempts()).isEqualTo(1);assertThat(result.accountPaused()).isFalse();
|
||||
}
|
||||
assertThat(ledger.authorizations).isEqualTo(2);assertThat(ledger.consumed).hasSize(2);assertThat(ledger.completions).hasSize(2);
|
||||
assertThat(ledger.completions).allSatisfy(c->{assertThat(c.context().executionPurpose()).isEqualTo(CollectionMode.BUSINESS);assertThat(c.operation()).isEqualTo(Operation.PRODUCT_INVENTORY);assertThat(c.actualHttpAttempts()).isEqualTo(1);});
|
||||
}
|
||||
@Test void testBusinessStillRejectsPurposeAccountAndGrantMismatchBeforeAuthorization() {
|
||||
var gate=new LingxingEgressGate(props(CollectionMode.BUSINESS,"test"),ledger,clock,clock::advanceMillis);
|
||||
assertThatThrownBy(()->gate.execute(context("wrong-purpose"),Operation.WAREHOUSES,HASH,h->true)).hasMessageContaining("EGRESS_CONTEXT_INVALID");
|
||||
assertThatThrownBy(()->gate.execute(new EgressContext("other-account",CollectionMode.BUSINESS,"root","session","wrong-account","demo-grant"),Operation.WAREHOUSES,HASH,h->true)).hasMessageContaining("EGRESS_CONTEXT_INVALID");
|
||||
assertThatThrownBy(()->gate.execute(new EgressContext("demo-account",CollectionMode.BUSINESS,"root","session","wrong-grant","other-grant"),Operation.WAREHOUSES,HASH,h->true)).hasMessageContaining("EXCLUSIVE_WINDOW_UNCONFIRMED");
|
||||
clock.advanceMillis(3600000);
|
||||
assertThatThrownBy(()->gate.execute(businessContext("expired-window"),Operation.WAREHOUSES,HASH,h->true)).hasMessageContaining("EXCLUSIVE_WINDOW_UNCONFIRMED");
|
||||
assertThat(ledger.authorizations).isZero();assertThat(ledger.completions).isEmpty();
|
||||
}
|
||||
@Test void testBusinessUnconfirmedCompletionStillRequiresManualReviewAfterTimePasses() {
|
||||
var gate=new LingxingEgressGate(props(CollectionMode.BUSINESS,"test"),ledger,clock,clock::advanceMillis);
|
||||
ledger.failComplete=true;
|
||||
var failure=assertThrows(LingxingWmsException.class,()->gate.execute(businessContext("test-lost-ack"),Operation.PRODUCT_INVENTORY,HASH,h->{h.startHttp();return true;}));
|
||||
assertThat(failure.code()).isEqualTo("LINGXING_WMS_BUDGET_COMPLETION_UNCONFIRMED");
|
||||
assertThat(failure.egress().actualHttpAttempts()).isEqualTo(1);assertThat(failure.egress().accountPaused()).isTrue();
|
||||
clock.advanceMillis(600000);ledger.failComplete=false;
|
||||
assertThatThrownBy(()->gate.execute(businessContext("test-after-uncertainty"),Operation.PRODUCT_INVENTORY,HASH,h->{h.startHttp();return true;})).hasMessageContaining("ACCOUNT_PAUSED");
|
||||
assertThat(ledger.authorizations).isEqualTo(1);assertThat(ledger.completions).hasSize(1);
|
||||
}
|
||||
@Test void proofAndContextAreServerBoundBeforeAuthorization() {
|
||||
var contexts = Arrays.asList(null, new EgressContext("other", CollectionMode.VALIDATION, "root", "session", "a", "demo-grant"),
|
||||
new EgressContext("demo-account", CollectionMode.BUSINESS, "root", "session", "a", "demo-grant"),
|
||||
|
||||
Reference in New Issue
Block a user