feat: update task routing and coze handling
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package com.nanri.aiimage.common.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class CozeGroupResultPropagatorTest {
|
||||
|
||||
@Test
|
||||
void doesNotTreatNoInfringementAsInfringementHit() {
|
||||
List<ParsedRow> parsedRows = List.of(
|
||||
new ParsedRow("20_1"),
|
||||
new ParsedRow("20_2")
|
||||
);
|
||||
List<ResultRow> resultRows = List.of(
|
||||
new ResultRow("无侵权"),
|
||||
new ResultRow("无侵权")
|
||||
);
|
||||
|
||||
int updatedRows = CozeGroupResultPropagator.propagateByGroup(
|
||||
parsedRows,
|
||||
ParsedRow::displayId,
|
||||
row -> resultRows.get(parsedRows.indexOf(row)),
|
||||
ResultRow::conclusion,
|
||||
ResultRow::setConclusion,
|
||||
List.of("已侵权", "侵权")
|
||||
);
|
||||
|
||||
assertEquals(0, updatedRows);
|
||||
assertEquals("无侵权", resultRows.get(0).conclusion());
|
||||
assertEquals("无侵权", resultRows.get(1).conclusion());
|
||||
}
|
||||
|
||||
@Test
|
||||
void propagatesWhenGroupHasRealInfringementHit() {
|
||||
List<ParsedRow> parsedRows = List.of(
|
||||
new ParsedRow("20_1"),
|
||||
new ParsedRow("20_2")
|
||||
);
|
||||
List<ResultRow> resultRows = List.of(
|
||||
new ResultRow("无侵权"),
|
||||
new ResultRow("侵权")
|
||||
);
|
||||
|
||||
int updatedRows = CozeGroupResultPropagator.propagateByGroup(
|
||||
parsedRows,
|
||||
ParsedRow::displayId,
|
||||
row -> resultRows.get(parsedRows.indexOf(row)),
|
||||
ResultRow::conclusion,
|
||||
ResultRow::setConclusion,
|
||||
List.of("已侵权", "侵权")
|
||||
);
|
||||
|
||||
assertEquals(1, updatedRows);
|
||||
assertEquals("侵权", resultRows.get(0).conclusion());
|
||||
assertEquals("侵权", resultRows.get(1).conclusion());
|
||||
}
|
||||
|
||||
private record ParsedRow(String displayId) {
|
||||
}
|
||||
|
||||
private static final class ResultRow {
|
||||
|
||||
private String conclusion;
|
||||
|
||||
private ResultRow(String conclusion) {
|
||||
this.conclusion = conclusion;
|
||||
}
|
||||
|
||||
private String conclusion() {
|
||||
return conclusion;
|
||||
}
|
||||
|
||||
private void setConclusion(String conclusion) {
|
||||
this.conclusion = conclusion;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.nanri.aiimage.modules.appearancepatent.client;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.config.AppearancePatentProperties;
|
||||
import com.nanri.aiimage.config.BrandCheckProperties;
|
||||
import com.nanri.aiimage.modules.brand.client.BrandCheckClient;
|
||||
import com.nanri.aiimage.modules.appearancepatent.model.dto.AppearancePatentResultRowDto;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
class AppearancePatentCozeClientTest {
|
||||
|
||||
private final AppearancePatentCozeClient client = new AppearancePatentCozeClient(
|
||||
new AppearancePatentProperties(),
|
||||
new ObjectMapper(),
|
||||
null,
|
||||
new BrandCheckClient(new BrandCheckProperties())
|
||||
);
|
||||
|
||||
@Test
|
||||
void titleRiskIsInfringementWhenBrandCheckHasFailedData() {
|
||||
BrandCheckClient.BrandCheckBatchResult result =
|
||||
new BrandCheckClient.BrandCheckBatchResult(List.of("阿凡达"), List.of("阿凡达"), List.of());
|
||||
|
||||
assertThat(client.buildTitleRisk("阿凡达", result)).isEqualTo("侵权");
|
||||
}
|
||||
|
||||
@Test
|
||||
void conclusionIsInfringementWhenTitleOrAppearanceIsInfringement() {
|
||||
assertThat(client.buildConclusion("侵权", "无侵权", "")).isEqualTo("侵权");
|
||||
assertThat(client.buildConclusion("无侵权", "侵权", "")).isEqualTo("侵权");
|
||||
assertThat(client.buildConclusion("无侵权", "无侵权", "")).isEqualTo("无侵权");
|
||||
}
|
||||
|
||||
@Test
|
||||
void skipBrandCheckWhenCozeTitleAndReasonAreNone() throws Exception {
|
||||
BrandCheckClient brandCheckClient = mock(BrandCheckClient.class);
|
||||
AppearancePatentCozeClient cozeClient = new AppearancePatentCozeClient(
|
||||
new AppearancePatentProperties(),
|
||||
new ObjectMapper(),
|
||||
null,
|
||||
brandCheckClient
|
||||
);
|
||||
AppearancePatentResultRowDto row = new AppearancePatentResultRowDto();
|
||||
row.setId("1");
|
||||
String dataText = """
|
||||
{"data":[{"row_id":"1","title":"无","title_reason":"无","appearance":"无侵权","result":"无侵权"}]}
|
||||
""";
|
||||
|
||||
List<AppearancePatentResultRowDto> merged = cozeClient.mergeRowsFromDataText(List.of(row), dataText);
|
||||
|
||||
assertThat(merged).hasSize(1);
|
||||
assertThat(merged.get(0).getTitleRisk()).isEqualTo("无侵权");
|
||||
assertThat(merged.get(0).getConclusion()).isEqualTo("无侵权");
|
||||
verify(brandCheckClient, never()).checkTitleText(anyString(), anyString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.nanri.aiimage.modules.brand.client;
|
||||
|
||||
import com.nanri.aiimage.config.BrandCheckProperties;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class BrandCheckClientTest {
|
||||
|
||||
@Test
|
||||
void splitTitleTextSupportsCommonSeparatorsAndQuotes() {
|
||||
BrandCheckClient client = new BrandCheckClient(new BrandCheckProperties());
|
||||
|
||||
List<String> brands = client.splitTitleText("'阿凡达,任天堂'; Disney、LEGO\nSony");
|
||||
|
||||
assertThat(brands).containsExactly("阿凡达", "任天堂", "Disney", "LEGO", "Sony");
|
||||
}
|
||||
|
||||
@Test
|
||||
void splitTitleTextDeduplicatesBlankValues() {
|
||||
BrandCheckClient client = new BrandCheckClient(new BrandCheckProperties());
|
||||
|
||||
List<String> brands = client.splitTitleText(" 任天堂, ,任天堂,Sony ");
|
||||
|
||||
assertThat(brands).containsExactly("任天堂", "Sony");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.nanri.aiimage.modules.similarasin.client;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.config.SimilarAsinProperties;
|
||||
import com.nanri.aiimage.modules.similarasin.model.dto.SimilarAsinResultRowDto;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class SimilarAsinCozeClientTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void buildParametersIncludesAlibabaPriceFromPythonPayload() throws Exception {
|
||||
List<SimilarAsinResultRowDto> rows = objectMapper.readValue("""
|
||||
[
|
||||
{
|
||||
"asin": "B0TEST123",
|
||||
"url": "https://m.media-amazon.com/images/I/main.jpg",
|
||||
"alibaba": [
|
||||
{"url": "https://cbu01.alicdn.com/img/ibank/a.jpg", "price": 12.80},
|
||||
{"url": "https://cbu01.alicdn.com/img/ibank/b.jpg", "price": 19.99}
|
||||
],
|
||||
"title": "Test title",
|
||||
"sku": "SKU-1"
|
||||
}
|
||||
]
|
||||
""", new TypeReference<>() {
|
||||
});
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null);
|
||||
|
||||
Method method = SimilarAsinCozeClient.class.getDeclaredMethod(
|
||||
"buildParameters", List.class, String.class, String.class, boolean.class);
|
||||
method.setAccessible(true);
|
||||
Map<String, Object> parameters = (Map<String, Object>) method.invoke(client, rows, "", "", true);
|
||||
|
||||
List<Map<String, Object>> items = (List<Map<String, Object>>) parameters.get("items");
|
||||
Map<String, Object> item = items.getFirst();
|
||||
List<Map<String, Object>> alibaba = (List<Map<String, Object>>) item.get("alibaba");
|
||||
|
||||
assertEquals("B0TEST123", item.get("asin"));
|
||||
assertEquals("https://m.media-amazon.com/images/I/main.jpg", item.get("url"));
|
||||
assertEquals("Test title", item.get("title"));
|
||||
assertEquals("SKU-1", item.get("sku"));
|
||||
assertEquals(2, alibaba.size());
|
||||
assertEquals("https://cbu01.alicdn.com/img/ibank/a.jpg", alibaba.get(0).get("url"));
|
||||
assertEquals(0, new BigDecimal("12.8").compareTo((BigDecimal) alibaba.get(0).get("price")));
|
||||
assertEquals("https://cbu01.alicdn.com/img/ibank/b.jpg", alibaba.get(1).get("url"));
|
||||
assertEquals(0, new BigDecimal("19.99").compareTo((BigDecimal) alibaba.get(1).get("price")));
|
||||
assertTrue(rows.getFirst().hasImageUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void buildParametersFallsBackAlibabaFromLegacyUrlsAndTopLevelPrice() throws Exception {
|
||||
SimilarAsinResultRowDto row = new SimilarAsinResultRowDto();
|
||||
row.setAsin("B0LEGACY1");
|
||||
row.setUrl("https://m.media-amazon.com/images/I/main.jpg");
|
||||
row.setUrls(List.of("https://cbu01.alicdn.com/img/ibank/legacy-a.jpg"));
|
||||
row.setPrice("8.50");
|
||||
row.setTitle("Legacy title");
|
||||
row.setSku("SKU-LEGACY");
|
||||
SimilarAsinCozeClient client = new SimilarAsinCozeClient(new SimilarAsinProperties(), objectMapper, null);
|
||||
|
||||
Method method = SimilarAsinCozeClient.class.getDeclaredMethod(
|
||||
"buildParameters", List.class, String.class, String.class, boolean.class);
|
||||
method.setAccessible(true);
|
||||
Map<String, Object> parameters = (Map<String, Object>) method.invoke(client, List.of(row), "", "", false);
|
||||
|
||||
List<Map<String, Object>> items = (List<Map<String, Object>>) parameters.get("items");
|
||||
List<Map<String, Object>> alibaba = (List<Map<String, Object>>) items.getFirst().get("alibaba");
|
||||
|
||||
assertEquals(1, alibaba.size());
|
||||
assertEquals("https://cbu01.alicdn.com/img/ibank/legacy-a.jpg", alibaba.getFirst().get("url"));
|
||||
assertEquals(0, new BigDecimal("8.5").compareTo((BigDecimal) alibaba.getFirst().get("price")));
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,16 @@ import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
@@ -154,4 +157,47 @@ class SimilarAsinImageEmbedderTest {
|
||||
assertEquals(12345678L, ex.size());
|
||||
assertTrue(ex.getMessage().contains("oversize"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void httpClientKeepsImageDownloadResilienceEnabled() {
|
||||
assertTrue(embedder.httpClient().retryOnConnectionFailure(), "OkHttp should retry transient connection failures");
|
||||
assertTrue(embedder.httpClient().followRedirects(), "image CDN redirects should be followed");
|
||||
assertTrue(embedder.httpClient().followSslRedirects(), "signed CDN downloads should follow browser-like SSL redirects");
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildImageRequestUsesBrowserLikeHeaders() {
|
||||
var req = SimilarAsinImageEmbedder.buildImageRequest("https://m.media-amazon.com/images/I/abc.jpg");
|
||||
|
||||
assertEquals("GET", req.method());
|
||||
assertTrue(req.header("User-Agent").contains("Chrome"), "User-Agent should look browser-like");
|
||||
assertTrue(req.header("Accept").contains("image/"), "Accept should prefer images");
|
||||
assertEquals("https://www.amazon.com/", req.header("Referer"));
|
||||
assertEquals("no-cache", req.header("Cache-Control"));
|
||||
assertNotNull(req.header("Accept-Language"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildImageRequestUsesDownloadHeadersForCozeSignedImages() {
|
||||
var req = SimilarAsinImageEmbedder.buildImageRequest(
|
||||
"https://lf9-bot-platform-tos-sign.coze.cn/bot-studio-bot-platform/bot_files/1/image/jpeg/2/merged_image.jpg?x-expires=1&x-signature=a");
|
||||
|
||||
assertEquals("GET", req.method());
|
||||
assertTrue(req.header("User-Agent").contains("Chrome"), "User-Agent should look browser-like");
|
||||
assertEquals("*/*", req.header("Accept"));
|
||||
assertNull(req.header("Referer"), "Coze signed download links should not carry an Amazon referer");
|
||||
assertEquals("no-cache", req.header("Cache-Control"));
|
||||
assertTrue(SimilarAsinImageEmbedder.isCozeSignedImageUrl(req.url().toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorSummaryDoesNotHideEmptyTimeoutMessage() {
|
||||
TimeoutException timeout = new TimeoutException();
|
||||
assertEquals("TimeoutException: <empty>", SimilarAsinImageEmbedder.errorSummary(timeout));
|
||||
|
||||
IOException io = new IOException("outer", new TimeoutException("inner"));
|
||||
String summary = SimilarAsinImageEmbedder.errorSummary(io);
|
||||
assertTrue(summary.contains("IOException: outer"));
|
||||
assertTrue(summary.contains("cause=TimeoutException: inner"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user