task-46: 跳过空品牌批次的无效远程品牌检查请求
新增 CollectDataBrandBatchFilter 接管品牌检查批次切分与分类:空品牌 批次不发起 checkAll 远程调用(行直接归 rejected),远程抛错整组降级 queryFailed 可恢复。service 委托查询器只做计数与无效 ASIN 落库,语义 与旧 filterByBrandCheck 完全等价。8 个测试覆盖默认/批量/幂等/空输入/ 单元素/超限/非法参数/依赖失败,全量回归 720 通过。
This commit is contained in:
+197
@@ -0,0 +1,197 @@
|
||||
package com.nanri.aiimage.modules.collectdata.util;
|
||||
|
||||
import com.nanri.aiimage.modules.brand.client.BrandCheckClient;
|
||||
import com.nanri.aiimage.modules.collectdata.model.vo.CollectDataResultRowVo;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Task 46:跳过空品牌批次的无效远程品牌检查请求。
|
||||
* CollectDataBrandBatchFilter 按批次做品牌检查,批次内品牌集合为空的批次
|
||||
* 完全不发起 checkAll 远程调用(行直接归入 rejected),非空批次正常检查并
|
||||
* 分类(失败品牌 rejected / 查询失败 queryFailed / 其余 accepted),
|
||||
* 分类语义与 CollectDataService 原 filterByBrandCheck 完全等价。
|
||||
*/
|
||||
class CollectDataBrandBatchFilterTest {
|
||||
|
||||
private BrandCheckClient brandCheckClient;
|
||||
private CollectDataBrandBatchFilter filter;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
brandCheckClient = mock(BrandCheckClient.class);
|
||||
filter = new CollectDataBrandBatchFilter(brandCheckClient, 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_046_brand_normal_default_path() {
|
||||
// 正常输入:非空品牌批次发起检查,失败品牌行 rejected、其余 accepted。
|
||||
when(brandCheckClient.checkAll(anyList(), any())).thenReturn(
|
||||
new BrandCheckClient.BrandCheckBatchResult(
|
||||
List.of(), List.of("Zara"), List.of()));
|
||||
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome outcome = filter.filter(List.of(
|
||||
row("B000000001", "Zara"),
|
||||
row("B000000002", "Nike"),
|
||||
row("B000000003", "H&M")
|
||||
));
|
||||
|
||||
assertEquals(1, outcome.rejected().size(), "失败品牌行进入 rejected");
|
||||
assertEquals("B000000001", outcome.rejected().get(0).getAsin());
|
||||
assertEquals(2, outcome.accepted().size(), "其余行 accepted");
|
||||
assertEquals("B000000002", outcome.accepted().get(0).getAsin(), "accepted 顺序稳定");
|
||||
assertEquals(0, outcome.queryFailed().size(), "无查询失败行");
|
||||
verify(brandCheckClient).checkAll(anyList(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_046_brand_normal_multiple_items() {
|
||||
// 批量场景:120 行(12 批次),每批次检查一次,分类跨批次不丢失、顺序稳定。
|
||||
List<CollectDataResultRowVo> rows = new ArrayList<>();
|
||||
for (int i = 0; i < 120; i++) {
|
||||
rows.add(row("B" + String.format("%09d", i + 1), i % 10 == 0 ? "bad-brand" : "good-" + (i / 10)));
|
||||
}
|
||||
when(brandCheckClient.checkAll(anyList(), any())).thenReturn(
|
||||
new BrandCheckClient.BrandCheckBatchResult(
|
||||
List.of(), List.of("bad-brand"), List.of()));
|
||||
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome outcome = filter.filter(rows);
|
||||
|
||||
assertEquals(12, outcome.rejected().size(), "每批次 1 个失败品牌行");
|
||||
assertEquals(108, outcome.accepted().size(), "其余行 accepted");
|
||||
assertEquals("B000000001", outcome.rejected().get(0).getAsin(), "rejected 顺序稳定");
|
||||
assertEquals("B000000002", outcome.accepted().get(0).getAsin(), "accepted 顺序稳定");
|
||||
verify(brandCheckClient, times(12)).checkAll(anyList(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_046_brand_normal_repeated_operation_is_idempotent() {
|
||||
// 幂等:同一输入重复执行结果一致,不产生重复对象。
|
||||
when(brandCheckClient.checkAll(anyList(), any())).thenReturn(
|
||||
new BrandCheckClient.BrandCheckBatchResult(
|
||||
List.of(), List.of("Nike"), List.of()));
|
||||
List<CollectDataResultRowVo> rows = List.of(
|
||||
row("B000000001", "Nike"),
|
||||
row("B000000002", "Adidas")
|
||||
);
|
||||
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome first = filter.filter(rows);
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome second = filter.filter(rows);
|
||||
|
||||
assertEquals(first.rejected(), second.rejected(), "重复执行 rejected 一致");
|
||||
assertEquals(first.accepted(), second.accepted(), "重复执行 accepted 一致");
|
||||
verify(brandCheckClient, times(2)).checkAll(anyList(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_046_brand_boundary_empty_input() {
|
||||
// 空输入:空列表返回空结果;全空品牌批次不发起远程检查,行全部 rejected。
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome empty = filter.filter(List.of());
|
||||
assertEquals(0, empty.rejected().size() + empty.accepted().size() + empty.queryFailed().size(),
|
||||
"空列表返回空结果");
|
||||
verify(brandCheckClient, never()).checkAll(anyList(), any());
|
||||
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome blankBatch = filter.filter(List.of(
|
||||
row("B000000001", ""), row("B000000002", null), row("B000000003", " ")
|
||||
));
|
||||
assertEquals(3, blankBatch.rejected().size(), "空品牌批次行全部 rejected");
|
||||
assertEquals(0, blankBatch.accepted().size(), "空品牌批次无 accepted");
|
||||
verify(brandCheckClient, never()).checkAll(anyList(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_046_brand_boundary_single_item() {
|
||||
// 单元素:单行单品牌批次正常检查;单行空品牌批次不发远程调用。
|
||||
when(brandCheckClient.checkAll(anyList(), any())).thenReturn(
|
||||
new BrandCheckClient.BrandCheckBatchResult(
|
||||
List.of(), List.of(), List.of()));
|
||||
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome single = filter.filter(List.of(
|
||||
row("B000000001", "solo")
|
||||
));
|
||||
assertEquals(1, single.accepted().size(), "单行未命中失败归 accepted");
|
||||
verify(brandCheckClient).checkAll(anyList(), any());
|
||||
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome blank = filter.filter(List.of(
|
||||
row("B000000002", "")
|
||||
));
|
||||
assertEquals(1, blank.rejected().size(), "单行空品牌归 rejected");
|
||||
verify(brandCheckClient, times(1)).checkAll(anyList(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_046_brand_boundary_limit_and_overflow() {
|
||||
// 上限/超限:5000 行非空品牌 = 500 次检查;混合空品牌行时不产生无效远程调用。
|
||||
List<CollectDataResultRowVo> rows = new ArrayList<>();
|
||||
for (int i = 0; i < 5000; i++) {
|
||||
rows.add(row("B" + String.format("%09d", i + 1), "brand-" + (i % 100)));
|
||||
}
|
||||
rows.add(row("B999999999", " "));
|
||||
when(brandCheckClient.checkAll(anyList(), any())).thenReturn(
|
||||
new BrandCheckClient.BrandCheckBatchResult(
|
||||
List.of(), List.of(), List.of()));
|
||||
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome outcome = filter.filter(rows);
|
||||
|
||||
assertEquals(1, outcome.rejected().size(), "空品牌行归 rejected");
|
||||
assertEquals(5000, outcome.accepted().size(), "非空品牌行全 accepted");
|
||||
verify(brandCheckClient, times(500)).checkAll(anyList(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_046_brand_invalid_input_rejected() {
|
||||
// 非法参数:checkAll 返回 null 按无失败处理;null 行安全跳过不计数。
|
||||
when(brandCheckClient.checkAll(anyList(), any())).thenReturn(null);
|
||||
|
||||
List<CollectDataResultRowVo> rows = new ArrayList<>();
|
||||
rows.add(null);
|
||||
rows.add(row("B000000001", "Nike"));
|
||||
rows.add(row("B000000002", "Zara"));
|
||||
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome outcome = filter.filter(rows);
|
||||
|
||||
assertEquals(2, outcome.accepted().size(), "null 结果按无失败处理,行全 accepted");
|
||||
assertEquals(0, outcome.rejected().size(), "null 行不计数");
|
||||
verify(brandCheckClient).checkAll(anyList(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_046_brand_dependency_failure_releases_resources() {
|
||||
// 依赖失败:远程检查抛错时批次整组降级 queryFailed,可恢复;恢复后重新检查成功。
|
||||
when(brandCheckClient.checkAll(anyList(), any()))
|
||||
.thenThrow(new RuntimeException("brand service down"))
|
||||
.thenReturn(new BrandCheckClient.BrandCheckBatchResult(
|
||||
List.of(), List.of("Zara"), List.of()));
|
||||
List<CollectDataResultRowVo> rows = List.of(
|
||||
row("B000000001", "Zara"), row("B000000002", "Nike"));
|
||||
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome failed = filter.filter(rows);
|
||||
assertEquals(2, failed.queryFailed().size(), "依赖失败批次整组降级 queryFailed");
|
||||
assertEquals(0, failed.accepted().size() + failed.rejected().size(), "降级后无其他分类");
|
||||
|
||||
CollectDataBrandBatchFilter.BrandBatchOutcome recovered = filter.filter(rows);
|
||||
assertEquals(1, recovered.rejected().size(), "恢复后失败品牌重新分类");
|
||||
assertEquals(1, recovered.accepted().size(), "恢复后其余行 accepted");
|
||||
assertTrue(recovered.queryFailed().isEmpty(), "恢复后无残留 queryFailed");
|
||||
}
|
||||
|
||||
private static CollectDataResultRowVo row(String asin, String brand) {
|
||||
CollectDataResultRowVo row = new CollectDataResultRowVo();
|
||||
row.setAsin(asin);
|
||||
row.setBrand(brand);
|
||||
return row;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user