feat: expose reliable collection activity and stage estimates

This commit is contained in:
2026-10-13 16:50:53 +08:00
parent 03601ab0aa
commit 146d468ff4
15 changed files with 557 additions and 46 deletions
@@ -0,0 +1,90 @@
package com.baiyue.lastleg.selfoperated.analytics.model;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static com.baiyue.lastleg.selfoperated.analytics.model.SelfopSupport.*;
class SelfopStageEstimateTest {
private final Instant start=Instant.parse("2026-10-13T00:00:00Z");
private Map<String,Object> progress(){
var p=new LinkedHashMap<String,Object>();p.put("taskNo","LXCOL-ETA");p.put("attemptNo",1);
p.put("phase","DETAIL");p.put("scanRound",1);p.put("progressScope","ACCOUNT_SHARED");
p.put("unit","ORDER");p.put("countBasis","TASK_CUMULATIVE");p.put("total",1000L);p.put("totalKnown",true);
p.put("sliceFrom","2026-10-12T00:00:00Z");p.put("sliceUntil","2026-10-13T00:00:00Z");return p;
}
private void commit(Map<String,Object> p,long seconds,long completed,boolean real){
p.put("completed",completed);SelfopStageEstimate.checkpoint(p,start.plusSeconds(seconds),real);
}
private Map<String,Object> ready(){var p=progress();for(int i=0;i<=3;i++)commit(p,i*20,1+i*20,true);return p;}
private SelfopStageEstimate.Estimate estimate(Map<String,Object> p,long second){return SelfopStageEstimate.estimate(p,start.plusSeconds(second),true,false,false);}
@Test void sufficientRealAdvancesEstimateCurrentStageAndIncludeIdleTime(){
var p=ready();var e=estimate(p,60);assertEquals("AVAILABLE",e.status());assertEquals(60L,e.completedDelta());
assertEquals(0,new BigDecimal("1").compareTo(e.ratePerSecond()));assertEquals(939L,e.remainingSeconds());
assertEquals(start,e.sampleFrom());assertEquals(start.plusSeconds(60),e.sampleUntil());assertEquals(start.plusSeconds(70),e.validUntil());
var idle=estimate(p,90);assertEquals("AVAILABLE",idle.status());assertEquals(new BigDecimal("0.666666"),idle.ratePerSecond());
assertEquals(1409L,idle.remainingSeconds());assertEquals("NO_RECENT_PROGRESS",estimate(p,120).unavailableReason());
}
@Test void shortOrInsufficientAdvanceWindowsNeverEstimate(){
var p=progress();commit(p,0,1,true);commit(p,20,2,true);commit(p,40,3,true);
assertEquals("SAMPLING",estimate(p,59).unavailableReason());assertEquals("SAMPLING",estimate(p,60).unavailableReason());
commit(p,60,4,true);assertEquals("AVAILABLE",estimate(p,60).status());
}
@Test void reuseBeforeInsideAndAfterWindowRestartsBaselineInsteadOfInflatingRate(){
var p=progress();commit(p,0,500,false);commit(p,20,501,true);commit(p,40,502,true);commit(p,60,503,true);
assertEquals(3L,estimate(p,60).completedDelta());
commit(p,70,900,false);assertEquals("SAMPLING",estimate(p,70).unavailableReason());
commit(p,90,901,true);commit(p,110,902,true);commit(p,130,903,true);assertEquals(3L,estimate(p,130).completedDelta());
commit(p,400,950,false);assertEquals("SAMPLING",estimate(p,400).unavailableReason());
}
@Test void eachIdentityDimensionInvalidatesEarlierEstimateAndCheckpointRebuildsIt(){
for(String key:List.of("taskNo","attemptNo","phase","scanRound","progressScope","unit","countBasis","sliceFrom","sliceUntil","total")){
var p=ready();String old=SelfopStageEstimate.identity(p);
Object value=p.get(key);p.put(key,value instanceof Number?((Number)value).longValue()+1:"changed");
assertNotEquals(old,SelfopStageEstimate.identity(p));assertEquals("SAMPLING",estimate(p,61).unavailableReason());
commit(p,61,62,true);assertEquals("SAMPLING",estimate(p,61).unavailableReason());
}
}
@Test void pausedUnhealthyTerminalUnknownAndCompleteNeverReturnActiveNumbers(){
var p=ready();
var paused=SelfopStageEstimate.estimate(p,start.plusSeconds(60),true,true,false);assertEquals("PAUSED",paused.status());assertNull(paused.ratePerSecond());
var terminal=SelfopStageEstimate.estimate(p,start.plusSeconds(60),true,true,true);assertEquals("UNAVAILABLE",terminal.status());assertEquals("TERMINAL",terminal.unavailableReason());
assertEquals("HEALTH_UNCONFIRMED",SelfopStageEstimate.estimate(p,start.plusSeconds(60),false,false,false).unavailableReason());
p.put("totalKnown",false);assertEquals("TOTAL_UNKNOWN",estimate(p,60).unavailableReason());
p.put("totalKnown",true);p.put("completed",1000L);assertEquals("COUNT_COMPLETE",estimate(p,60).unavailableReason());
commit(p,61,1000,true);assertFalse(p.containsKey("etaSamples"));
}
@Test void zeroAndFullCountsNeverInferStageCompletionOrPublication(){
for(long total:List.of(0L,1000L)){
var p=progress();p.put("total",total);p.put("completed",total);p.put("stageFinishedAt",null);
var result=estimate(p,60);
assertEquals("UNAVAILABLE",result.status());assertEquals("COUNT_COMPLETE",result.unavailableReason());
assertNull(result.remainingSeconds());assertNull(result.ratePerSecond());assertNull(result.validUntil());
assertNull(p.get("stageFinishedAt"));assertFalse(p.containsKey("publishedAt"));
assertEquals("DETAIL",p.get("phase"));
}
}
@Test void bucketBoundaryHighFrequencyAndLongHistoryStayBounded(){
var p=progress();p.put("total",100000L);
for(int second=0;second<=1000;second++)commit(p,second,second,true);
var sample=(Map<?,?>)p.get("etaSamples");var points=(List<?>)sample.get("p");assertTrue(points.size()<=20);
assertTrue(json(sample).getBytes(StandardCharsets.UTF_8).length<=1024);assertEquals("AVAILABLE",estimate(p,1000).status());
long first=((Number)((List<?>)points.get(0)).get(0)).longValue();assertTrue(start.plusSeconds(1000).getEpochSecond()-first<=180);
var boundary=progress();commit(boundary,9,1,true);commit(boundary,10,2,true);
assertEquals(2,((List<?>)((Map<?,?>)boundary.get("etaSamples")).get("p")).size());
}
@Test void sixtySecondGapAndCounterRegressionRestartSampling(){
var p=ready();commit(p,120,62,true);assertEquals("SAMPLING",estimate(p,120).unavailableReason());
commit(p,140,1,true);assertEquals("SAMPLING",estimate(p,140).unavailableReason());
}
@Test void corruptFractionalOutOfOrderOrOversizedSamplesFailClosed(){
var p=ready();p.put("completed",new BigDecimal("61.5"));assertEquals("INVALID_PROGRESS",estimate(p,60).unavailableReason());
p=ready();var sample=new LinkedHashMap<>((Map<String,Object>)p.get("etaSamples"));
sample.put("p",List.of(List.of(start.getEpochSecond()+40,1L,0L),List.of(start.getEpochSecond()+20,2L,1L)));p.put("etaSamples",sample);
assertEquals("INVALID_SAMPLE",estimate(p,60).unavailableReason());
p=ready();var future=estimate(p,50);assertEquals("INVALID_PROGRESS",future.unavailableReason());
}
}
@@ -25,6 +25,78 @@ class CollectionQueryServiceTest {
CollectionQuery q(Integer page,LocalDateTime upper,Long id) {
return new CollectionQuery("primary","A",null,null,null,null,null,null,page,null,upper==null?null:upper.toInstant(ZoneOffset.UTC),id);
}
private Task activity(long id,String status,Long upstream,boolean account,LocalDateTime created,String progress) {
return new Task(id,"LXCOL-"+id,"primary",account?null:"A","OUTBOUND",account?"ACCOUNT_DISCOVERY":"WAREHOUSE_SYNC","BACKFILL","BUSINESS",
"hash",status,"NOT_STARTED",1,now.minusDays(1),now,null,upstream,null,null,progress,now,null,null,false,created,created);
}
@Test void currentActivityWinsOverNewerHistoryAndIsBoundedToEight() {
var candidates=new ArrayList<Task>();
candidates.add(activity(99,"SUCCEEDED",null,false,now,null));
for(int i=0;i<10;i++)candidates.add(activity(10+i,"WAITING_SOURCE",null,false,now.minusMinutes(i+1),null));
candidates.add(activity(1,"RUNNING",null,false,now.minusDays(1),null));
when(mapper.currentTasks(eq("primary"),anyList())).thenReturn(candidates);
var row=service.overview(q(null,null,null)).warehouses().get(0);
assertEquals("LXCOL-1",row.currentTask().taskNo());assertEquals(8,row.activeTasks().size());
assertEquals("LXCOL-1",row.activeTasks().get(0).taskNo());
assertTrue(row.activeTasks().stream().noneMatch(t->"SUCCEEDED".equals(t.taskStatus())));
}
@Test void overlappingAccountActivityDoesNotProveWarehouseDependency() {
var child=activity(7,"WAITING_SOURCE",null,false,now,null);
var account=activity(8,"RUNNING",null,true,now,null);
when(mapper.currentTasks(eq("primary"),anyList())).thenReturn(List.of(child));
when(mapper.sharedActiveAccount("primary")).thenReturn(account);
when(mapper.task("primary","A",child.taskNo())).thenReturn(child);
var result=service.overview(q(null,null,null));var row=result.warehouses().get(0);
assertEquals(account.taskNo(),result.accountActivity().taskNo());assertNull(row.sharedUpstream());
assertEquals("WAITING_UNCONFIRMED",row.currentTask().dependencyState());
var detail=service.taskDetail(child.taskNo(),q(null,null,null));
assertNull(detail.sharedUpstream());assertEquals(row.currentTask().dependencyState(),detail.task().dependencyState());
}
@Test void directDependenciesAreConsistentInOverviewAndDetailIncludingFailureAndCompletion() {
enableBusiness("production");
var child=activity(7,"WAITING_SOURCE",8L,false,now,null);
when(mapper.currentTasks(eq("primary"),anyList())).thenReturn(List.of(child));
when(mapper.task("primary","A",child.taskNo())).thenReturn(child);
for(var pair:Map.of("RUNNING","WAITING_CONFIRMED","SUCCEEDED","SATISFIED","DEAD","FAILED").entrySet()) {
var parent=activity(8,pair.getKey(),null,true,now.minusHours(1),null);
when(mapper.tasksByIds(eq("primary"),anyList())).thenReturn(List.of(parent));
var row=service.overview(q(null,null,null)).warehouses().get(0);
var detail=service.taskDetail(child.taskNo(),q(null,null,null));
assertEquals(parent.taskNo(),row.sharedUpstream().taskNo());
assertEquals(parent.taskNo(),detail.sharedUpstream().taskNo());
assertEquals(pair.getValue(),row.currentTask().dependencyState());
assertEquals(pair.getValue(),detail.task().dependencyState());
if("RUNNING".equals(pair.getKey())) {
assertEquals(parent.taskNo(),detail.task().progressSourceTaskNo());
assertEquals("UPSTREAM_NOT_READY",detail.task().blockingReason());
assertEquals("TASK_NOT_RETRYABLE",detail.task().actionBlockReason());
}
}
when(mapper.tasksByIds(eq("primary"),anyList())).thenReturn(List.of());
assertEquals("WAITING_UNCONFIRMED",service.taskDetail(child.taskNo(),q(null,null,null)).task().dependencyState());
}
@Test void stageEstimateUsesCommittedSamplesWithoutExposingThemAndRejectsAttemptMismatch() throws Exception {
enableBusiness("production");
var p=new LinkedHashMap<String,Object>();
p.put("taskNo","LXCOL-7");p.put("attemptNo",1);p.put("phase","DETAIL");p.put("scanRound",1);
p.put("progressScope","WAREHOUSE");p.put("unit","ORDER");p.put("countBasis","TASK_CUMULATIVE");p.put("total",100);p.put("totalKnown",true);
Instant at=now.toInstant(ZoneOffset.UTC);
for(int i=0;i<4;i++) {
p.put("completed",10+i*10);
com.baiyue.lastleg.selfoperated.analytics.model.SelfopStageEstimate.checkpoint(p,at.minusSeconds(70-i*20),true);
}
var t=activity(7,"RUNNING",null,false,now.minusMinutes(2),new ObjectMapper().writeValueAsString(p));
when(mapper.task("primary","A",t.taskNo())).thenReturn(t);
when(mapper.latestAttempts(eq("primary"),anyList())).thenReturn(List.of(run(1,"FETCHING",null)));
var result=service.taskDetail(t.taskNo(),q(null,null,null)).task();
assertEquals("AVAILABLE",result.stageEstimate().status());
assertEquals(result.progress().get("completed"),result.stageEstimate().completed());
assertEquals(result.progress().get("total"),result.stageEstimate().total());
assertFalse(result.progress().containsKey("etaSamples"));
when(mapper.latestAttempts(eq("primary"),anyList())).thenReturn(List.of(run(2,"FETCHING",null)));
result=service.taskDetail(t.taskNo(),q(null,null,null)).task();
assertEquals("UNAVAILABLE",result.stageEstimate().status());assertNull(result.stageEstimate().remainingSeconds());
}
@Test void controlledTestBusinessProjectsEnabledInventoryPublicationWithoutAutomaticSchedule() {
config.setCollectionMode("BUSINESS");config.setEnvironment("test");config.setQuantityUnit("PCS");
config.setExclusiveHandoffVerified(true);config.setPriorEgressClosed(true);config.setInflightDrained(true);config.setTransferredBudgetVerified(true);
@@ -279,7 +351,7 @@ class CollectionQueryServiceTest {
"publishedRuns","currentTasks","latestValidationTasks","lastSuccessfulTasks","sharedActiveAccount","barrierWarehouses",
"inventoryDailySamples","inventoryAccumulationStarts")) {
String sql=configuration.getMappedStatement(CollectionReadMapper.class.getName()+"."+method).getBoundSql(parameters).getSql();
assertFalse(sql.contains("AS routing_manifest_json"));assertFalse(sql.contains("ll_selfop_sku_daily_sales"));assertFalse(sql.contains("ll_selfop_inventory_snapshot"));
assertFalse(sql.contains("routing_manifest_json"));assertFalse(sql.contains("JSON_TABLE"));assertFalse(sql.contains("ll_selfop_sku_daily_sales"));assertFalse(sql.contains("ll_selfop_inventory_snapshot"));
assertFalse(sql.contains("ll_selfop_sync_stage"));
}
String runSql=configuration.getMappedStatement(CollectionReadMapper.class.getName()+".runs").getBoundSql(parameters).getSql();
@@ -289,11 +361,15 @@ class CollectionQueryServiceTest {
assertTrue(sql.contains("b.source_account=t.source_account"));
assertTrue(sql.contains("b.execution_purpose='BUSINESS' AND b.business_root_task_id=b.id"));
assertTrue(sql.contains("AS business_local_work_ready"));
assertTrue(sql.contains("'$.objects'))='OBJECT'"));
assertTrue(sql.contains("'$.objects.*'"));
assertTrue(sql.contains("COALESCE(JSON_TYPE(JSON_EXTRACT(manifest_item.entry,'$.effectiveStageId')),'NULL')"));
assertTrue(sql.contains("BETWEEN 1 AND 9223372036854775807) IS NOT TRUE"));
assertTrue(sql.contains("OCTET_LENGTH(t.progress_json) BETWEEN 0 AND 8192"));
assertTrue(sql.contains("'$.localWorkReady')=CAST('true' AS JSON)"));
assertTrue(sql.contains("'$.localWorkReadyScopeHash'))=t.scope_hash"));
assertTrue(sql.contains("'$.localWorkReadyUpstreamTaskId'))=CAST(t.upstream_task_id AS CHAR)"));
}
String currentSql=configuration.getMappedStatement(CollectionReadMapper.class.getName()+".currentTasks").getBoundSql(parameters).getSql();
assertTrue(currentSql.contains("ranked.rn <= 8"));
assertTrue(currentSql.contains("CASE WHEN status='RUNNING' THEN 0"));
assertTrue(currentSql.contains("source_account=? AND task_role='WAREHOUSE_SYNC'"));
assertTrue(runSql.contains("r.created_at <"));assertTrue(runSql.contains("r.id <="));assertTrue(runSql.contains("LIMIT ? OFFSET ?"));
String samplesSql=configuration.getMappedStatement(CollectionReadMapper.class.getName()+".inventoryDailySamples").getBoundSql(parameters).getSql();
assertTrue(samplesSql.contains("r.inventory_daily_sample_date >= ?"));
@@ -225,6 +225,10 @@ class SelfopEgressServiceTest {
assertThat(tasks).contains("business_root_task_id=? AND source_account=? AND execution_purpose='BUSINESS'", "next_retry_at_utc=NULL", "status IN ('WAITING_SOURCE','READY','RUNNING','RETRY_WAIT')");
assertThat(runs).contains("business_root_task_id=? AND source_account=? AND execution_purpose='BUSINESS'", "error_code=?", "status IN ('FETCHING','VALIDATED','YIELDED','READY','BUILDING')");
assertThat(tasks).doesNotContain("SUCCEEDED");assertThat(runs).doesNotContain("PUBLISHED","REUSED");
assertThat(runs).contains("JSON_SET(JSON_REMOVE", "'$.etaSamples'", "'$.summaryAsOf'", "DATE_FORMAT(?,", "JSON_EXTRACT(progress_json,'$.attemptNo')=attempt_no");
args.put("status","FAILED");
String validation=configuration.getMappedStatement(prefix+"terminateValidationRuns").getBoundSql(args).getSql();
assertThat(validation).contains("'$.etaSamples'", "'$.summaryAsOf'", "DATE_FORMAT(?,", "validation_root_task_id=?");
}
@Test void committedCompletionWithLostReplyStaysPausedAcrossIntegrationRestart(){
business();var c=reserve(Operation.WAREHOUSES,1,null,null);service.authorize(new AuthorizationRequest(c,Operation.WAREHOUSES,PROVIDER));
@@ -167,6 +167,8 @@ class SelfopDiscoveryServiceTest {
service.collect(task,run,lease);
var warehouseA=children.get("route:1:A");var warehouseB=children.get("route:1:B");
assertEquals("WAITING_SOURCE",warehouseA.getStatus());assertEquals("WAITING_SOURCE",warehouseB.getStatus());
assertEquals(false,read(warehouseA.getProgressJson(),Map.class).get("localWorkReady"));
assertEquals(false,read(warehouseB.getProgressJson(),Map.class).get("localWorkReady"));
assertTrue(read(warehouseB.getRoutingManifestJson(),SelfopRoutingManifest.class).getObjects().isEmpty());
var partial=read(task.getRoutingManifestJson(),SelfopRoutingManifest.class);
assertTrue(partial.isDirectoryComplete());assertEquals(1,partial.getObjects().values().stream().filter(e->e.getEffectiveStageId()!=null).count());
@@ -175,6 +177,11 @@ class SelfopDiscoveryServiceTest {
account.setRequestBudget(10);service.collect(task,run,lease);
assertEquals("READY",warehouseA.getStatus());assertEquals("READY",warehouseB.getStatus());
for(var child:List.of(warehouseA,warehouseB)){
var proof=read(child.getProgressJson(),Map.class);assertEquals(true,proof.get("localWorkReady"));
assertEquals(child.getScopeHash(),proof.get("localWorkReadyScopeHash"));
assertEquals(task.getId().toString(),proof.get("localWorkReadyUpstreamTaskId"));
}
var destinationManifest=read(warehouseB.getRoutingManifestJson(),SelfopRoutingManifest.class);
String movedKey=hash(List.of("demo","customer","two"));assertEquals(Set.of(movedKey),destinationManifest.getObjects().keySet());
var moved=destinationManifest.getObjects().get(movedKey);assertNotNull(moved.getEffectiveStageId());assertEquals("B",moved.getWarehouseCode());
@@ -69,6 +69,27 @@ class SelfopInventoryRecoveryTest {
assertEquals(7L, resumed.getFenceToken()); assertEquals("FETCHING", resumed.getStatus()); assertNull(resumed.getFinishedAtUtc());
verify(runs, never()).insert(any(SelfopSyncRun.class));
}
@Test void resumedInventoryReplacesYieldSnapshotWithFinalProgressWithoutChangingHistoricalAttempt(){
original.setFenceToken(lease.fence());
SelfopProgressProjection.applyCheckpoint(task,original,"INVENTORY","PAGE",1,4L,1);
service.yield(1L,20L,lease);
var yielded=read(original.getProgressSnapshotJson(),java.util.Map.class);
assertEquals(1,yielded.get("completed"));assertNotNull(yielded.get("summaryAsOf"));
var history=new SelfopSyncRun();history.setId(19L);history.setAttemptNo(0);
String historical="{\"attemptNo\":0,\"completed\":9,\"summaryAsOf\":\"2026-10-12T00:00:00Z\"}";
history.setProgressSnapshotJson(historical);
when(runs.selectOne(any())).thenReturn(null,original);
var resumed=service.begin(1L,lease);assertSame(original,resumed);assertNull(resumed.getProgressSnapshotJson());
var update=ArgumentCaptor.forClass(com.baomidou.mybatisplus.core.conditions.Wrapper.class);
verify(runs).update(isNull(),update.capture());
assertTrue(((com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<?>)update.getValue()).getSqlSet().contains("progress_snapshot_json"));
SelfopProgressProjection.applyCheckpoint(task,resumed,"INVENTORY","PAGE",4,4L,1);
java.time.Instant before=java.time.Instant.now();SelfopProgressProjection.freezeRun(task,resumed);
java.time.Instant after=java.time.Instant.now();var finished=read(resumed.getProgressSnapshotJson(),java.util.Map.class);
assertEquals(4,finished.get("completed"));var asOf=java.time.Instant.parse((String)finished.get("summaryAsOf"));
assertFalse(asOf.isBefore(before));assertFalse(asOf.isAfter(after));assertNotEquals(yielded,finished);
SelfopProgressProjection.freezeRun(task,history);assertEquals(historical,history.getProgressSnapshotJson());
}
@Test void genuineFailureClearsCursorAndNewAttemptStartsAtPageOneWithItsOwnStageBatch() {
original.setFenceToken(lease.fence());
@@ -62,4 +62,57 @@ class SelfopProgressServiceTest {
verify(tasks,never()).updateById(any(SelfopSyncTask.class));verify(runs,never()).updateById(any(SelfopSyncRun.class));
var order=inOrder(leases,locks);order.verify(leases).verify(lease);order.verify(locks).lockTask(1);order.verify(locks).lockRun(2);
}
@Test void terminalSnapshotHasActualFreezeTimeAndResumeCannotKeepPausedEtaBaseline(){
var t=task();var r=run();SelfopProgressProjection.applyCheckpoint(t,r,"DETAIL","ORDER",4,10L,1);
assertTrue(read(t.getProgressJson(),Map.class).containsKey("etaSamples"));
Instant before=Instant.now();SelfopProgressProjection.freezeRun(t,r);Instant after=Instant.now();
var snapshot=read(r.getProgressSnapshotJson(),Map.class);Instant asOf=Instant.parse((String)snapshot.get("summaryAsOf"));
assertFalse(asOf.isBefore(before));assertFalse(asOf.isAfter(after));assertFalse(snapshot.containsKey("etaSamples"));
assertFalse(read(t.getProgressJson(),Map.class).containsKey("etaSamples"));
SelfopProgressProjection.applyCheckpoint(t,r,"DETAIL","ORDER",5,10L,1);
var points=(List<?>)((Map<?,?>)read(t.getProgressJson(),Map.class).get("etaSamples")).get("p");assertEquals(1,points.size());
var fallback=run();SelfopProgressProjection.freezeRun(task(),fallback);
assertNotNull(read(fallback.getProgressSnapshotJson(),Map.class).get("summaryAsOf"));
}
@Test void etaIsFirstToBeDroppedAtEightKiBWhileCandidateAndAuditEvidenceRemain(){
var t=task();var r=run();SelfopProgressProjection.applyCheckpoint(t,r,"DETAIL","ORDER",1,100L,1);
var p=new java.util.LinkedHashMap<String,Object>(read(t.getProgressJson(),Map.class));p.remove("etaSamples");
p.put("candidateBuild",Map.of("committedBatches",7));
p.put("audit", "x".repeat(Math.max(0,8192-json(p).getBytes(StandardCharsets.UTF_8).length-80)));
t.setProgressJson(json(p));SelfopProgressProjection.applyCheckpoint(t,r,"DETAIL","ORDER",2,100L,1);
var result=read(t.getProgressJson(),Map.class);assertFalse(result.containsKey("etaSamples"));
assertEquals(Map.of("committedBatches",7),result.get("candidateBuild"));assertEquals(p.get("audit"),result.get("audit"));
assertTrue(t.getProgressJson().getBytes(StandardCharsets.UTF_8).length<=8192);
}
@Test void localWorkProofIsBoundToTheCommittedScopeAndUpstream(){
var t=task();t.setScopeHash("scope-1");t.setUpstreamTaskId(99L);
SelfopProgressProjection.localWorkReady(t,true);var p=read(t.getProgressJson(),Map.class);
assertEquals(true,p.get("localWorkReady"));assertEquals("scope-1",p.get("localWorkReadyScopeHash"));assertEquals("99",p.get("localWorkReadyUpstreamTaskId"));
SelfopProgressProjection.applyCheckpoint(t,run(),"MERGE","ORDER",0,4L,0);
assertEquals(true,read(t.getProgressJson(),Map.class).get("localWorkReady"));
var next=run();next.setAttemptNo(2);SelfopProgressProjection.applyCheckpoint(t,next,"MERGE","ORDER",1,4L,0);
assertEquals(true,read(t.getProgressJson(),Map.class).get("localWorkReady"));
t.setScopeHash("scope-2");next.setAttemptNo(3);SelfopProgressProjection.applyCheckpoint(t,next,"MERGE","ORDER",1,4L,0);
assertFalse(read(t.getProgressJson(),Map.class).containsKey("localWorkReady"));
SelfopProgressProjection.localWorkReady(t,false);assertEquals(false,read(t.getProgressJson(),Map.class).get("localWorkReady"));
}
@Test void exactLegacyEightKiBCheckpointFreezesWithoutLosingBusinessOrAuditCounts(){
var t=task();var r=run();SelfopProgressProjection.applyCheckpoint(t,r,"DETAIL","ORDER",4,10L,1);
var p=new java.util.LinkedHashMap<String,Object>(read(t.getProgressJson(),Map.class));p.remove("etaSamples");
p.put("candidateBuild",Map.of("committedBatches",7));p.put("audit","");
p.put("audit","x".repeat(8192-json(p).getBytes(StandardCharsets.UTF_8).length));t.setProgressJson(json(p));
assertEquals(8192,t.getProgressJson().getBytes(StandardCharsets.UTF_8).length);
assertDoesNotThrow(()->SelfopProgressProjection.freezeRun(t,r));var frozen=read(r.getProgressSnapshotJson(),Map.class);
assertNotNull(frozen.get("summaryAsOf"));assertEquals(4,frozen.get("completed"));assertEquals(10,frozen.get("total"));
assertEquals(p.get("candidateBuild"),frozen.get("candidateBuild"));assertEquals(p.get("audit"),frozen.get("audit"));
assertTrue(r.getProgressSnapshotJson().getBytes(StandardCharsets.UTF_8).length<=8192);
var stage=(Map<?,?>)((List<?>)frozen.get("stages")).get(0);assertEquals(4,stage.get("completed"));assertEquals(10,stage.get("total"));
}
@Test void minimalFullSizeLegacyCheckpointNeverFailsBusinessEndForOptionalTimestamp(){
var t=task();var r=run();var p=new java.util.LinkedHashMap<String,Object>();
p.put("attemptNo",1);p.put("completed",4);p.put("total",10);p.put("audit","");
p.put("audit","x".repeat(8192-json(p).getBytes(StandardCharsets.UTF_8).length));t.setProgressJson(json(p));
assertDoesNotThrow(()->SelfopProgressProjection.freezeRun(t,r));var frozen=read(r.getProgressSnapshotJson(),Map.class);
assertEquals(p,frozen);assertEquals(8192,r.getProgressSnapshotJson().getBytes(StandardCharsets.UTF_8).length);
}
}