fix: scope shadow inputs to authoritative warehouse
This commit is contained in:
@@ -21,7 +21,7 @@ import java.util.function.Supplier;
|
||||
@Component
|
||||
public class ShadowPlanningEngine {
|
||||
private static final int INSTANCE_STALE_SECONDS = 120;
|
||||
private static final int DECISION_REASON_MAX_LENGTH = 2000;
|
||||
private static final int DECISION_REASON_MAX_LENGTH = 4000;
|
||||
private static final String PUBLISHED = "PUBLISHED";
|
||||
|
||||
private final SchedulingFacade schedulingFacade;
|
||||
@@ -128,9 +128,9 @@ public class ShadowPlanningEngine {
|
||||
+ "; request=" + observation.requestId()
|
||||
+ "; shadow=" + observation.resultCode()
|
||||
+ "; proposedSpace=" + nullSafe(observation.proposedSpaceId())
|
||||
+ "; shadowReason=" + nullSafe(observation.reason())
|
||||
+ "; legacy=" + nullSafe(legacyResultCode)
|
||||
+ "; legacySpace=" + nullSafe(legacySpaceId), DECISION_REASON_MAX_LENGTH));
|
||||
+ "; legacySpace=" + nullSafe(legacySpaceId)
|
||||
+ "; shadowReason=" + nullSafe(observation.reason()), DECISION_REASON_MAX_LENGTH));
|
||||
requireComparisonInserted(inserted);
|
||||
});
|
||||
}
|
||||
@@ -149,8 +149,8 @@ public class ShadowPlanningEngine {
|
||||
+ "; shadow=" + observation.resultCode()
|
||||
+ "; proposedStock=" + nullSafe(observation.proposedStockId())
|
||||
+ "; proposedSpace=" + nullSafe(observation.proposedSpaceId())
|
||||
+ "; shadowReason=" + nullSafe(observation.reason())
|
||||
+ "; legacy=" + nullSafe(legacyResultCode), DECISION_REASON_MAX_LENGTH));
|
||||
+ "; legacy=" + nullSafe(legacyResultCode)
|
||||
+ "; shadowReason=" + nullSafe(observation.reason()), DECISION_REASON_MAX_LENGTH));
|
||||
requireComparisonInserted(inserted);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.ruoyi.algorithm.repository.VersionToken;
|
||||
import com.ruoyi.algorithm.service.InboundPlanningResult;
|
||||
import com.ruoyi.algorithm.shadow.mapper.ShadowInputMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
@@ -207,20 +208,49 @@ class ShadowPlanningEngineTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void comparisonReasonIsBoundedToDecisionLogCapacity() {
|
||||
void inboundComparisonRetainsRequiredEvidenceWhenUnicodeReasonIsOversized() {
|
||||
ShadowInputMapper mapper = mock(ShadowInputMapper.class);
|
||||
when(mapper.insertComparison(any(), any(), any(), any(Long.class),
|
||||
any(Instant.class), any(), any(), any())).thenReturn(1);
|
||||
ShadowPlanningEngine engine = engine(mock(SchedulingFacade.class), mapper);
|
||||
String shadowDiagnostic = "snapshotGeneration=42; snapshotSync=2026-09-03T00:00:00Z"
|
||||
+ "; sourceSignature=ABC123; project=" + "中".repeat(255)
|
||||
+ "; shadowCandidates=17; reason=" + "长".repeat(5000);
|
||||
InboundShadowObservation observation = new InboundShadowObservation(
|
||||
true, 42L, "01", SNAPSHOT.synchronizedAt(), SNAPSHOT.sourceSignature(),
|
||||
"JOB-1", "READY", "SPACE-9", "X".repeat(2500));
|
||||
"JOB-1", "READY", "SPACE-9", shadowDiagnostic);
|
||||
|
||||
engine.compareInbound(observation, "READY", "SPACE-9");
|
||||
engine.compareInbound(observation, "READY", "LEGACY-SPACE-9");
|
||||
|
||||
ArgumentCaptor<String> reason = ArgumentCaptor.forClass(String.class);
|
||||
verify(mapper).insertComparison(any(), any(), any(), any(Long.class),
|
||||
any(Instant.class), any(), any(),
|
||||
argThat(reason -> reason.length() <= 2000));
|
||||
reason.capture());
|
||||
assertThat(reason.getValue()).hasSizeLessThanOrEqualTo(4000)
|
||||
.contains("sourceSignature=ABC123", "shadowCandidates=17",
|
||||
"legacy=READY", "legacySpace=LEGACY-SPACE-9");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundComparisonRetainsLegacyOutcomeAndCandidateEvidenceWhenReasonIsOversized() {
|
||||
ShadowInputMapper mapper = mock(ShadowInputMapper.class);
|
||||
when(mapper.insertComparison(any(), any(), any(), any(Long.class),
|
||||
any(Instant.class), any(), any(), any())).thenReturn(1);
|
||||
ShadowPlanningEngine engine = engine(mock(SchedulingFacade.class), mapper);
|
||||
String shadowDiagnostic = "snapshotGeneration=42; snapshotSync=2026-09-03T00:00:00Z"
|
||||
+ "; sourceSignature=ABC123; project=" + "中".repeat(255)
|
||||
+ "; shadowMatches=23; reason=" + "长".repeat(5000);
|
||||
OutboundShadowObservation observation = new OutboundShadowObservation(
|
||||
true, 42L, "01", SNAPSHOT.synchronizedAt(), SNAPSHOT.sourceSignature(),
|
||||
"DETAIL-1", "READY", "STOCK-1", "SPACE-9", shadowDiagnostic);
|
||||
|
||||
engine.compareOutbound(observation, "10");
|
||||
|
||||
ArgumentCaptor<String> reason = ArgumentCaptor.forClass(String.class);
|
||||
verify(mapper).insertComparison(any(), any(), any(), any(Long.class),
|
||||
any(Instant.class), any(), any(), reason.capture());
|
||||
assertThat(reason.getValue()).hasSizeLessThanOrEqualTo(4000)
|
||||
.contains("sourceSignature=ABC123", "shadowMatches=23", "legacy=10");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user