From ed8c9b1543444f4be606d1895d46245c833c2866 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 13 Oct 2026 11:18:40 +0800 Subject: [PATCH] test(selfop): verify every coverage bucket identity and pagination --- .../query/DashboardSqlSemanticTest.java | 74 +++++++++++++++---- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/DashboardSqlSemanticTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/DashboardSqlSemanticTest.java index 6d3206faa..bdf4eb095 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/DashboardSqlSemanticTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/selfoperated/analytics/query/DashboardSqlSemanticTest.java @@ -85,20 +85,55 @@ class DashboardSqlSemanticTest { assertTrue(rows.stream().filter(row->"LONG".equals(row.get("rank_code"))).allMatch(row->new BigDecimal(row.get("main_value").toString()).compareTo(new BigDecimal("30.04"))==0)); } @Test void sevenExclusiveBucketsConserveCandidatesAndEveryInventoryDrilldownMatches()throws Exception{ - fixture("NOOUT","100","100",null);fixture("FROZEN","50","0",null);fixture("UNKNOWN",null,null,null); - fixture("THIRTY","30","30","30");fixture("THIRTY04","30.04","30.04","30");fixture("ZERO","0","0",null); - var stats=stats();assertEquals(5,count(stats,"candidate_skus"));assertEquals(1,count(stats,"excluded_zero_skus")); - assertEquals(1,count(stats,"bucket_days_0_30"));assertEquals(1,count(stats,"bucket_days_30_60"));assertEquals(1,count(stats,"bucket_no_outbound_30d")); - assertEquals(1,count(stats,"bucket_no_available"));assertEquals(1,count(stats,"bucket_unknown")); - long sum=0;for(String bucket:CoverageDistributionPolicy.BUCKETS){long n=count(stats,"bucket_"+bucket.toLowerCase(Locale.ROOT));sum+=n; + Map> expected=new LinkedHashMap<>(); + for(String bucket:CoverageDistributionPolicy.BUCKETS)expected.put(bucket,new HashSet<>()); + // Quantities make coverage exactly available days. +/- 0.000001 must not be display-rounded across boundaries. + String[][] finite={ + {"THIRTY-BELOW","29.999999","DAYS_0_30"},{"THIRTY","30","DAYS_0_30"},{"ONE","1","DAYS_0_30"}, + {"THIRTY-ABOVE","30.000001","DAYS_30_60"},{"SIXTY-BELOW","59.999999","DAYS_30_60"},{"SIXTY","60","DAYS_30_60"}, + {"SIXTY-ABOVE","60.000001","DAYS_60_90"},{"NINETY-BELOW","89.999999","DAYS_60_90"},{"NINETY","90","DAYS_60_90"}, + {"NINETY-ABOVE","90.000001","DAYS_GT_90"},{"LONG-A","100","DAYS_GT_90"},{"LONG-B","100","DAYS_GT_90"}}; + for(var row:finite){fixture(row[0],row[1],row[1],"30");expected.get(row[2]).add("C/"+row[0]);} + for(int i=0;i<3;i++){ + fixture("NOOUT-"+i,"100","100",null);expected.get("NO_OUTBOUND_30D").add("C/NOOUT-"+i); + fixture("FROZEN-"+i,"50","0",null);expected.get("NO_AVAILABLE").add("C/FROZEN-"+i); + fixture("UNKNOWN-"+i,null,null,null);expected.get("UNKNOWN").add("C/UNKNOWN-"+i); + } + fixture("ZERO","0","0",null); + // Same SKU, different owner and bucket: identities must never collapse to SKU alone. + profile(11,"B","THIRTY","PCS");profile(22,"B","THIRTY","PCS");stock("B","THIRTY","75","75");daily("B","THIRTY",D.minusDays(1),"30","PCS"); + expected.get("DAYS_60_90").add("B/THIRTY"); + var stats=stats();assertEquals(1,count(stats,"excluded_zero_skus")); + var classified=query(DashboardReadMapper.class,"report",Map.of("p",request(),"o",options()),false,true); + Map> dashboardIdentities=new HashMap<>(); + for(Object value:classified){Map row=(Map)value;Map normalized=new HashMap<>();row.forEach((k,v)->normalized.put(k.toString().toLowerCase(Locale.ROOT),v)); + String bucket=(String)normalized.get("bucket_code");String key=normalized.get("customer_code")+"/"+normalized.get("sku_code"); + assertTrue(dashboardIdentities.computeIfAbsent(bucket,ignored->new HashSet<>()).add(key),"duplicate dashboard identity "+key); + } + assertEquals(expected,dashboardIdentities); + long sum=0;Set union=new HashSet<>(); + for(String bucket:CoverageDistributionPolicy.BUCKETS){ + long n=count(stats,"bucket_"+bucket.toLowerCase(Locale.ROOT));assertTrue(n>0,bucket+" must be populated");sum+=n; + assertEquals(expected.get(bucket).size(),n,bucket); var o=new AnalyticsInventoryOptions(null,null,null,null,bucket,CoverageDistributionPolicy.RULE_VERSION,null,null); assertEquals(n,((Number)query(AnalyticsReadMapper.class,"inventoryCount",Map.of("p",request(),"o",o)).get(0)).longValue(),bucket); - }assertEquals(5,sum); - var finite=new AnalyticsInventoryOptions(null,null,"COVERAGE_DAYS_DESC",null,null,null,"FINITE_POSITIVE",null); - assertEquals(count(stats,"long_eligible"),((Number)query(AnalyticsReadMapper.class,"inventoryCount",Map.of("p",request(),"o",finite)).get(0)).longValue()); - assertEquals(2,count(stats,"no_outbound_eligible")); - var ranked=ranks();assertEquals("THIRTY04",ranked.stream().filter(r->r.rankCode().equals("LONG")).max(Comparator.comparing(DashboardReadMapper.Ranked::mainValue)).orElseThrow().skuCode()); - assertTrue(ranked.stream().anyMatch(r->r.rankCode().equals("NO_OUTBOUND")&&r.skuCode().equals("FROZEN"))); + var all=query(AnalyticsReadMapper.class,"inventoryPage",Map.of("p",request(),"o",o)).stream().map(AnalyticsReadRows.Inventory.class::cast).map(row->row.customerCode()+"/"+row.skuCode()).toList(); + assertEquals(dashboardIdentities.get(bucket),new HashSet<>(all),bucket+" drilldown identities");assertEquals(n,all.size()); + List paged=new ArrayList<>();Set seen=new HashSet<>(); + for(long offset=0;offset<=n+1;offset+=2){ + var page=query(AnalyticsReadMapper.class,"inventoryPage",Map.of("p",pageRequest(offset,2),"o",o)).stream().map(AnalyticsReadRows.Inventory.class::cast).map(row->row.customerCode()+"/"+row.skuCode()).toList(); + int start=(int)Math.min(offset,n),end=(int)Math.min(offset+2,n);assertEquals(all.subList(start,end),page,bucket+" offset="+offset); + for(String key:page)assertTrue(seen.add(key),bucket+" repeated identity "+key);paged.addAll(page); + } + assertEquals(all,paged,bucket+" pages omit or reorder identities");assertEquals(dashboardIdentities.get(bucket),seen); + for(String key:seen)assertTrue(union.add(key),"identity belongs to multiple buckets: "+key); + } + assertEquals(count(stats,"candidate_skus"),sum);assertEquals(sum,union.size());assertFalse(union.contains("C/ZERO")); + var finiteSelection=new AnalyticsInventoryOptions(null,null,"COVERAGE_DAYS_DESC",null,null,null,"FINITE_POSITIVE",null); + assertEquals(13,count(stats,"long_eligible")); + assertEquals(count(stats,"long_eligible"),((Number)query(AnalyticsReadMapper.class,"inventoryCount",Map.of("p",request(),"o",finiteSelection)).get(0)).longValue()); + assertEquals(6,count(stats,"no_outbound_eligible")); + assertTrue(ranks().stream().anyMatch(row->row.rankCode().equals("NO_OUTBOUND")&&row.skuCode().equals("FROZEN-0"))); } @Test void exactNinetyBoundaryAndUnknownInventoryRemainDistinct()throws Exception{ fixture("NINETY","90","90","30");fixture("NINETY04","90.04","90.04","30");fixture("INFINITE","999","999",null); @@ -185,9 +220,11 @@ class DashboardSqlSemanticTest { p.setLong(1,run);p.setString(2,"demo");p.setString(3,"W");p.setString(4,owner);p.setString(5,sku);p.setString(6,sku);p.setString(7,unit);p.setString(8,run==11?"VALID_OUTBOUND":"INVENTORY_OBSERVATION");p.setLong(9,run);p.setObject(10,D.minusDays(40).atStartOfDay().minusHours(8));p.executeUpdate();}} private void daily(String owner,String sku,LocalDate day,String value,String unit)throws Exception{try(var p=connection.prepareStatement("INSERT INTO ll_selfop_sku_daily_sales VALUES(?,?,?,?,?,?,?,?,?,?)")){ p.setLong(1,++sequence);p.setLong(2,11);p.setString(3,"demo");p.setString(4,"W");p.setString(5,owner);p.setString(6,sku);p.setObject(7,day);p.setBigDecimal(8,value==null?null:new BigDecimal(value));p.setLong(9,1);p.setString(10,unit);p.executeUpdate();}} - private void stock(String sku,String physical,String available)throws Exception{try(var p=connection.prepareStatement("INSERT INTO ll_selfop_inventory_snapshot VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)")){ - p.setLong(1,22);p.setString(2,"demo");p.setString(3,"W");p.setString(4,"C");p.setString(5,sku);p.setInt(6,0);p.setBigDecimal(7,physical==null?null:new BigDecimal(physical));p.setBigDecimal(8,available==null?null:new BigDecimal(available));p.setBigDecimal(9,BigDecimal.ZERO);p.setBigDecimal(10,BigDecimal.ZERO);p.setObject(11,D.atStartOfDay());p.setString(12,physical==null?"UNKNOWN":"VALID");p.setString(13,physical==null?"FIELD_MISSING":null);p.executeUpdate();}} + private void stock(String sku,String physical,String available)throws Exception{stock("C",sku,physical,available);} + private void stock(String owner,String sku,String physical,String available)throws Exception{try(var p=connection.prepareStatement("INSERT INTO ll_selfop_inventory_snapshot VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)")){ + p.setLong(1,22);p.setString(2,"demo");p.setString(3,"W");p.setString(4,owner);p.setString(5,sku);p.setInt(6,0);p.setBigDecimal(7,physical==null?null:new BigDecimal(physical));p.setBigDecimal(8,available==null?null:new BigDecimal(available));p.setBigDecimal(9,BigDecimal.ZERO);p.setBigDecimal(10,BigDecimal.ZERO);p.setObject(11,D.atStartOfDay());p.setString(12,physical==null?"UNKNOWN":"VALID");p.setString(13,physical==null?"FIELD_MISSING":null);p.executeUpdate();}} private AnalyticsSqlRequest request(){return new AnalyticsSqlRequest("demo","W",null,null,11,22,D.minusDays(30),D,D,D.minusDays(30),D.minusDays(30),D,D.minusDays(30).atStartOfDay().minusHours(8),D.atStartOfDay(),true,true,true,true,true,0,20,"DASHBOARD");} + private AnalyticsSqlRequest pageRequest(long offset,int size){var p=request();return new AnalyticsSqlRequest(p.account(),p.warehouse(),p.customer(),p.sku(),p.salesRun(),p.inventoryRun(),p.from(),p.until(),p.anchor(),p.windowFrom(),p.completeFrom(),p.completeUntil(),p.coveredFrom(),p.coveredUntil(),p.salesKnown(),p.formalSales(),p.completeWindow(),p.complete30(),p.stockFresh(),offset,size,"INVENTORY");} private AnalyticsSqlRequest request(boolean complete,boolean fresh){var p=request();return new AnalyticsSqlRequest(p.account(),p.warehouse(),null,null,p.salesRun(),p.inventoryRun(),p.from(),p.until(),p.anchor(),p.windowFrom(),p.completeFrom(),p.completeUntil(),p.coveredFrom(),p.coveredUntil(),p.salesKnown(),p.formalSales(),complete,complete,fresh,p.offset(),p.size(),p.kind());} private static AnalyticsInventoryOptions options(){return new AnalyticsInventoryOptions(null,null,null,null,null,null,null,null);} @SuppressWarnings("unchecked") private List> report(){ @@ -201,7 +238,8 @@ class DashboardSqlSemanticTest { private List query(Class mapper,String method,Map args){ return query(mapper,method,args,false); } - private List query(Class mapper,String method,Map args,boolean originalAggregates){ + private List query(Class mapper,String method,Map args,boolean originalAggregates){return query(mapper,method,args,originalAggregates,false);} + private List query(Class mapper,String method,Map args,boolean originalAggregates,boolean classifiedIdentities){ var original=config.getMappedStatement(mapper.getName()+"."+method);var bound=original.getBoundSql(args);var meta=org.apache.ibatis.reflection.SystemMetaObject.forObject(args); StringBuilder sql=new StringBuilder();int index=0;boolean quoted=false; for(int i=0;i