You've already forked FrameTour-BE
fix(core): 修复 StageResult 中 nextStages 的不可变性问题
- 将 nextStages 初始化改为使用 Collections.unmodifiableList 包装 - 防止外部代码修改 nextStages 列表内容 - 保证 StageResult 的不可变性和线程安全性 - 添加完整的单元测试覆盖各种构造场景
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
package com.ycwl.basic.image.pipeline.core;
|
||||
|
||||
import com.ycwl.basic.image.pipeline.enums.ImageSource;
|
||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||
import com.ycwl.basic.image.pipeline.enums.PipelineScene;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* PhotoProcessContext 单元测试
|
||||
*/
|
||||
class PhotoProcessContextTest {
|
||||
|
||||
@Test
|
||||
void testBuilder_WithRequiredFields() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
assertNotNull(context);
|
||||
assertEquals("https://example.com/test.jpg", context.getOriginalUrl());
|
||||
assertEquals(123L, context.getScenicId());
|
||||
assertNotNull(context.getProcessId());
|
||||
assertEquals(ImageType.NORMAL_PHOTO, context.getImageType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuilder_WithAllFields() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.processId("test-123")
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(456L)
|
||||
.imageType(ImageType.PUZZLE)
|
||||
.source(ImageSource.IPC)
|
||||
.scene(PipelineScene.IMAGE_PRINT)
|
||||
.build();
|
||||
|
||||
assertEquals("test-123", context.getProcessId());
|
||||
assertEquals("https://example.com/test.jpg", context.getOriginalUrl());
|
||||
assertEquals(456L, context.getScenicId());
|
||||
assertEquals(ImageType.PUZZLE, context.getImageType());
|
||||
assertEquals(ImageSource.IPC, context.getSource());
|
||||
assertEquals(PipelineScene.IMAGE_PRINT, context.getScene());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuilder_MissingOriginalUrl_ShouldThrow() {
|
||||
PhotoProcessContext.Builder builder = PhotoProcessContext.builder()
|
||||
.scenicId(123L);
|
||||
|
||||
Exception exception = assertThrows(IllegalArgumentException.class, builder::build);
|
||||
|
||||
assertEquals("originalUrl is required", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuilder_MissingScenicId_ShouldThrow() {
|
||||
PhotoProcessContext.Builder builder = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg");
|
||||
|
||||
Exception exception = assertThrows(IllegalArgumentException.class, builder::build);
|
||||
|
||||
assertEquals("scenicId is required", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuilder_AutoGenerateProcessId() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
assertNotNull(context.getProcessId());
|
||||
assertFalse(context.getProcessId().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForSuperResolution() {
|
||||
PhotoProcessContext context = PhotoProcessContext.forSuperResolution(
|
||||
12345L,
|
||||
"https://example.com/photo.jpg",
|
||||
678L
|
||||
);
|
||||
|
||||
assertEquals("12345", context.getProcessId());
|
||||
assertEquals("https://example.com/photo.jpg", context.getOriginalUrl());
|
||||
assertEquals(678L, context.getScenicId());
|
||||
assertEquals(ImageType.NORMAL_PHOTO, context.getImageType());
|
||||
assertEquals(ImageSource.IPC, context.getSource());
|
||||
assertEquals(PipelineScene.SOURCE_PHOTO_SUPER_RESOLUTION, context.getScene());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testResultUrlCallback() {
|
||||
AtomicReference<String> capturedUrl = new AtomicReference<>();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.resultUrlCallback(capturedUrl::set)
|
||||
.build();
|
||||
|
||||
context.setResultUrl("https://cdn.example.com/result.jpg");
|
||||
|
||||
assertEquals("https://cdn.example.com/result.jpg", context.getResultUrl());
|
||||
assertEquals("https://cdn.example.com/result.jpg", capturedUrl.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testResultUrlCallback_WithoutCallback() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
assertDoesNotThrow(() -> {
|
||||
context.setResultUrl("https://cdn.example.com/result.jpg");
|
||||
});
|
||||
|
||||
assertEquals("https://cdn.example.com/result.jpg", context.getResultUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetCurrentFile_NoProcessedFile() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
File originalFile = new File("original.jpg");
|
||||
context.setOriginalFile(originalFile);
|
||||
|
||||
assertEquals(originalFile, context.getCurrentFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetCurrentFile_WithProcessedFile() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
File originalFile = new File("original.jpg");
|
||||
File processedFile = new File("processed.jpg");
|
||||
|
||||
context.setOriginalFile(originalFile);
|
||||
context.setProcessedFile(processedFile);
|
||||
|
||||
assertEquals(processedFile, context.getCurrentFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadStageConfig() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
Map<String, Boolean> stageConfig = new HashMap<>();
|
||||
stageConfig.put("watermark", false);
|
||||
stageConfig.put("enhance", true);
|
||||
|
||||
context.loadStageConfig(null, stageConfig);
|
||||
|
||||
assertFalse(context.isStageEnabled("watermark", true));
|
||||
assertTrue(context.isStageEnabled("enhance", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsStageEnabled_WithDefault() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
assertTrue(context.isStageEnabled("unknown_stage", true));
|
||||
assertFalse(context.isStageEnabled("unknown_stage", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsStageEnabled_WithConfig() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
Map<String, Boolean> config = new HashMap<>();
|
||||
config.put("watermark", false);
|
||||
context.loadStageConfig(null, config);
|
||||
|
||||
assertFalse(context.isStageEnabled("watermark", true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTempFileManager() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.processId("test-temp")
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
assertNotNull(context.getTempFileManager());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultImageType() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
assertEquals(ImageType.NORMAL_PHOTO, context.getImageType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetImageType() {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.PUZZLE)
|
||||
.build();
|
||||
|
||||
assertEquals(ImageType.PUZZLE, context.getImageType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.ycwl.basic.image.pipeline.core;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* PipelineBuilder 单元测试
|
||||
*/
|
||||
class PipelineBuilderTest {
|
||||
|
||||
@Test
|
||||
void testBuildEmptyPipeline_ShouldThrowException() {
|
||||
PipelineBuilder<PhotoProcessContext> builder = new PipelineBuilder<>();
|
||||
|
||||
Exception exception = assertThrows(IllegalStateException.class, builder::build);
|
||||
|
||||
assertEquals("管线至少需要一个Stage", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildWithDefaultName() {
|
||||
PipelineBuilder<PhotoProcessContext> builder = new PipelineBuilder<>();
|
||||
builder.addStage(new MockStage("stage1"));
|
||||
|
||||
Pipeline<PhotoProcessContext> pipeline = builder.build();
|
||||
|
||||
assertEquals("DefaultPipeline", pipeline.getName());
|
||||
assertEquals(1, pipeline.getStageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildWithCustomName() {
|
||||
Pipeline<PhotoProcessContext> pipeline = new PipelineBuilder<PhotoProcessContext>("CustomPipeline")
|
||||
.addStage(new MockStage("stage1"))
|
||||
.build();
|
||||
|
||||
assertEquals("CustomPipeline", pipeline.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddStage() {
|
||||
PipelineBuilder<PhotoProcessContext> builder = new PipelineBuilder<>();
|
||||
builder.addStage(new MockStage("stage1"));
|
||||
builder.addStage(new MockStage("stage2"));
|
||||
|
||||
Pipeline<PhotoProcessContext> pipeline = builder.build();
|
||||
|
||||
assertEquals(2, pipeline.getStageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddStageWithNull_ShouldIgnore() {
|
||||
PipelineBuilder<PhotoProcessContext> builder = new PipelineBuilder<>();
|
||||
builder.addStage(new MockStage("stage1"));
|
||||
builder.addStage(null);
|
||||
builder.addStage(new MockStage("stage2"));
|
||||
|
||||
Pipeline<PhotoProcessContext> pipeline = builder.build();
|
||||
|
||||
assertEquals(2, pipeline.getStageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddStageIf_WhenTrue() {
|
||||
PipelineBuilder<PhotoProcessContext> builder = new PipelineBuilder<>();
|
||||
builder.addStageIf(true, new MockStage("stage1"));
|
||||
|
||||
Pipeline<PhotoProcessContext> pipeline = builder.build();
|
||||
|
||||
assertEquals(1, pipeline.getStageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddStageIf_WhenFalse() {
|
||||
PipelineBuilder<PhotoProcessContext> builder = new PipelineBuilder<>();
|
||||
builder.addStage(new MockStage("required"));
|
||||
builder.addStageIf(false, new MockStage("optional"));
|
||||
|
||||
Pipeline<PhotoProcessContext> pipeline = builder.build();
|
||||
|
||||
assertEquals(1, pipeline.getStageCount());
|
||||
assertTrue(pipeline.getStageNames().contains("MockStage-required"));
|
||||
assertFalse(pipeline.getStageNames().contains("MockStage-optional"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFluentAPI() {
|
||||
Pipeline<PhotoProcessContext> pipeline = new PipelineBuilder<PhotoProcessContext>()
|
||||
.name("FluentPipeline")
|
||||
.addStage(new MockStage("stage1"))
|
||||
.addStage(new MockStage("stage2"))
|
||||
.addStageIf(true, new MockStage("stage3"))
|
||||
.build();
|
||||
|
||||
assertEquals("FluentPipeline", pipeline.getName());
|
||||
assertEquals(3, pipeline.getStageCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock Stage for testing
|
||||
*/
|
||||
private static class MockStage extends AbstractPipelineStage<PhotoProcessContext> {
|
||||
private final String id;
|
||||
|
||||
MockStage(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "MockStage-" + id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StageResult doExecute(PhotoProcessContext context) {
|
||||
return StageResult.success();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package com.ycwl.basic.image.pipeline.core;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Pipeline 单元测试
|
||||
*/
|
||||
class PipelineTest {
|
||||
|
||||
@Test
|
||||
void testPipelineExecution_AllSuccess() {
|
||||
Pipeline<PhotoProcessContext> pipeline = new PipelineBuilder<PhotoProcessContext>("TestPipeline")
|
||||
.addStage(new MockStage("stage1", StageResult.success("Stage1完成")))
|
||||
.addStage(new MockStage("stage2", StageResult.success("Stage2完成")))
|
||||
.addStage(new MockStage("stage3", StageResult.success("Stage3完成")))
|
||||
.build();
|
||||
|
||||
PhotoProcessContext context = createTestContext();
|
||||
|
||||
boolean success = pipeline.execute(context);
|
||||
|
||||
assertTrue(success);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPipelineExecution_WithSkippedStage() {
|
||||
Pipeline<PhotoProcessContext> pipeline = new PipelineBuilder<PhotoProcessContext>("TestPipeline")
|
||||
.addStage(new MockStage("stage1", StageResult.success()))
|
||||
.addStage(new ConditionalStage("stage2", false))
|
||||
.addStage(new MockStage("stage3", StageResult.success()))
|
||||
.build();
|
||||
|
||||
PhotoProcessContext context = createTestContext();
|
||||
|
||||
boolean success = pipeline.execute(context);
|
||||
|
||||
assertTrue(success);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPipelineExecution_WithDegradedStage() {
|
||||
Pipeline<PhotoProcessContext> pipeline = new PipelineBuilder<PhotoProcessContext>("TestPipeline")
|
||||
.addStage(new MockStage("stage1", StageResult.success()))
|
||||
.addStage(new MockStage("stage2", StageResult.degraded("降级执行")))
|
||||
.addStage(new MockStage("stage3", StageResult.success()))
|
||||
.build();
|
||||
|
||||
PhotoProcessContext context = createTestContext();
|
||||
|
||||
boolean success = pipeline.execute(context);
|
||||
|
||||
assertTrue(success);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPipelineExecution_WithFailedStage() {
|
||||
Pipeline<PhotoProcessContext> pipeline = new PipelineBuilder<PhotoProcessContext>("TestPipeline")
|
||||
.addStage(new MockStage("stage1", StageResult.success()))
|
||||
.addStage(new MockStage("stage2", StageResult.failed("执行失败")))
|
||||
.addStage(new MockStage("stage3", StageResult.success()))
|
||||
.build();
|
||||
|
||||
PhotoProcessContext context = createTestContext();
|
||||
|
||||
boolean success = pipeline.execute(context);
|
||||
|
||||
assertFalse(success);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPipelineExecution_WithDynamicStageAddition() {
|
||||
MockStage dynamicStage = new MockStage("dynamic", StageResult.success("动态Stage完成"));
|
||||
|
||||
Pipeline<PhotoProcessContext> pipeline = new PipelineBuilder<PhotoProcessContext>("TestPipeline")
|
||||
.addStage(new MockStage("stage1", StageResult.success()))
|
||||
.addStage(new MockStage("stage2", StageResult.successWithNext("添加动态Stage", dynamicStage)))
|
||||
.addStage(new MockStage("stage3", StageResult.success()))
|
||||
.build();
|
||||
|
||||
PhotoProcessContext context = createTestContext();
|
||||
|
||||
boolean success = pipeline.execute(context);
|
||||
|
||||
assertTrue(success);
|
||||
assertEquals(4, pipeline.getStageCount()); // 原3个 + 动态添加1个
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPipelineName() {
|
||||
Pipeline<PhotoProcessContext> pipeline = new PipelineBuilder<PhotoProcessContext>("CustomPipeline")
|
||||
.addStage(new MockStage("stage1", StageResult.success()))
|
||||
.build();
|
||||
|
||||
assertEquals("CustomPipeline", pipeline.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetStageCount() {
|
||||
Pipeline<PhotoProcessContext> pipeline = new PipelineBuilder<PhotoProcessContext>()
|
||||
.addStage(new MockStage("stage1", StageResult.success()))
|
||||
.addStage(new MockStage("stage2", StageResult.success()))
|
||||
.addStage(new MockStage("stage3", StageResult.success()))
|
||||
.build();
|
||||
|
||||
assertEquals(3, pipeline.getStageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetStageNames() {
|
||||
Pipeline<PhotoProcessContext> pipeline = new PipelineBuilder<PhotoProcessContext>()
|
||||
.addStage(new MockStage("stage1", StageResult.success()))
|
||||
.addStage(new MockStage("stage2", StageResult.success()))
|
||||
.build();
|
||||
|
||||
var names = pipeline.getStageNames();
|
||||
|
||||
assertEquals(2, names.size());
|
||||
assertTrue(names.contains("MockStage-stage1"));
|
||||
assertTrue(names.contains("MockStage-stage2"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建测试用的 Context
|
||||
*/
|
||||
private PhotoProcessContext createTestContext() {
|
||||
return PhotoProcessContext.builder()
|
||||
.processId("test-pipeline")
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock Stage for testing
|
||||
*/
|
||||
private static class MockStage extends AbstractPipelineStage<PhotoProcessContext> {
|
||||
private final String id;
|
||||
private final StageResult result;
|
||||
|
||||
MockStage(String id, StageResult result) {
|
||||
this.id = id;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "MockStage-" + id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StageResult doExecute(PhotoProcessContext context) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditional Stage for testing
|
||||
*/
|
||||
private static class ConditionalStage extends AbstractPipelineStage<PhotoProcessContext> {
|
||||
private final String id;
|
||||
private final boolean shouldExecute;
|
||||
|
||||
ConditionalStage(String id, boolean shouldExecute) {
|
||||
this.id = id;
|
||||
this.shouldExecute = shouldExecute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ConditionalStage-" + id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldExecuteByBusinessLogic(PhotoProcessContext context) {
|
||||
return shouldExecute;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StageResult doExecute(PhotoProcessContext context) {
|
||||
return StageResult.success();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.ycwl.basic.image.pipeline.core;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* StageResult 单元测试
|
||||
*/
|
||||
class StageResultTest {
|
||||
|
||||
@Test
|
||||
void testSuccess() {
|
||||
StageResult result = StageResult.success();
|
||||
|
||||
assertEquals(StageResult.Status.SUCCESS, result.getStatus());
|
||||
assertTrue(result.isSuccess());
|
||||
assertFalse(result.isFailed());
|
||||
assertFalse(result.isSkipped());
|
||||
assertFalse(result.isDegraded());
|
||||
assertTrue(result.canContinue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccessWithMessage() {
|
||||
StageResult result = StageResult.success("处理完成");
|
||||
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals("处理完成", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccessWithNextStages() {
|
||||
MockStage nextStage1 = new MockStage("next1");
|
||||
MockStage nextStage2 = new MockStage("next2");
|
||||
|
||||
StageResult result = StageResult.successWithNext("添加后续Stage", nextStage1, nextStage2);
|
||||
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals("添加后续Stage", result.getMessage());
|
||||
assertEquals(2, result.getNextStages().size());
|
||||
assertEquals("MockStage-next1", result.getNextStages().get(0).getName());
|
||||
assertEquals("MockStage-next2", result.getNextStages().get(1).getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccessWithNextStagesList() {
|
||||
List<PipelineStage<?>> stages = List.of(
|
||||
new MockStage("next1"),
|
||||
new MockStage("next2")
|
||||
);
|
||||
|
||||
StageResult result = StageResult.successWithNext("添加列表", stages);
|
||||
|
||||
assertEquals(2, result.getNextStages().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSkipped() {
|
||||
StageResult result = StageResult.skipped();
|
||||
|
||||
assertEquals(StageResult.Status.SKIPPED, result.getStatus());
|
||||
assertTrue(result.isSkipped());
|
||||
assertTrue(result.canContinue());
|
||||
assertEquals("条件不满足,跳过执行", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSkippedWithReason() {
|
||||
StageResult result = StageResult.skipped("图片类型不匹配");
|
||||
|
||||
assertTrue(result.isSkipped());
|
||||
assertEquals("图片类型不匹配", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailed() {
|
||||
StageResult result = StageResult.failed("下载失败");
|
||||
|
||||
assertEquals(StageResult.Status.FAILED, result.getStatus());
|
||||
assertTrue(result.isFailed());
|
||||
assertFalse(result.canContinue());
|
||||
assertEquals("下载失败", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailedWithException() {
|
||||
Exception exception = new RuntimeException("网络错误");
|
||||
StageResult result = StageResult.failed("下载失败", exception);
|
||||
|
||||
assertTrue(result.isFailed());
|
||||
assertEquals("下载失败", result.getMessage());
|
||||
assertEquals(exception, result.getException());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDegraded() {
|
||||
StageResult result = StageResult.degraded("使用备用方案");
|
||||
|
||||
assertEquals(StageResult.Status.DEGRADED, result.getStatus());
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.canContinue());
|
||||
assertEquals("使用备用方案", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCanContinue_Success() {
|
||||
assertTrue(StageResult.success().canContinue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCanContinue_Skipped() {
|
||||
assertTrue(StageResult.skipped().canContinue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCanContinue_Degraded() {
|
||||
assertTrue(StageResult.degraded("降级").canContinue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCanContinue_Failed() {
|
||||
assertFalse(StageResult.failed("失败").canContinue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNextStagesImmutable() {
|
||||
MockStage stage1 = new MockStage("stage1");
|
||||
StageResult result = StageResult.successWithNext("test", stage1);
|
||||
|
||||
List<PipelineStage<?>> nextStages = result.getNextStages();
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> {
|
||||
nextStages.add(new MockStage("stage2"));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock Stage for testing
|
||||
*/
|
||||
private static class MockStage extends AbstractPipelineStage<PhotoProcessContext> {
|
||||
private final String id;
|
||||
|
||||
MockStage(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "MockStage-" + id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StageResult doExecute(PhotoProcessContext context) {
|
||||
return StageResult.success();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.ycwl.basic.image.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
||||
import com.ycwl.basic.image.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* CleanupStage 单元测试
|
||||
*/
|
||||
class CleanupStageTest {
|
||||
|
||||
@Test
|
||||
void testGetName() {
|
||||
CleanupStage stage = new CleanupStage();
|
||||
|
||||
assertEquals("CleanupStage", stage.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetPriority() {
|
||||
CleanupStage stage = new CleanupStage();
|
||||
|
||||
assertEquals(999, stage.getPriority());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_AlwaysTrue() {
|
||||
CleanupStage stage = new CleanupStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
assertTrue(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success() {
|
||||
CleanupStage stage = new CleanupStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.processId("test-cleanup")
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isSuccess());
|
||||
assertNotNull(result.getMessage());
|
||||
assertTrue(result.getMessage().contains("已清理"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_ForAllImageTypes() {
|
||||
CleanupStage stage = new CleanupStage();
|
||||
|
||||
// 测试普通照片
|
||||
PhotoProcessContext normalContext = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
assertTrue(stage.execute(normalContext).isSuccess());
|
||||
|
||||
// 测试拼图
|
||||
PhotoProcessContext puzzleContext = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/puzzle.png")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.PUZZLE)
|
||||
.build();
|
||||
assertTrue(stage.execute(puzzleContext).isSuccess());
|
||||
|
||||
// 测试手机上传
|
||||
PhotoProcessContext mobileContext = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/mobile.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.MOBILE_UPLOAD)
|
||||
.build();
|
||||
assertTrue(stage.execute(mobileContext).isSuccess());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.ycwl.basic.image.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
||||
import com.ycwl.basic.image.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* ConditionalRotateStage 单元测试
|
||||
*/
|
||||
class ConditionalRotateStageTest {
|
||||
|
||||
@Test
|
||||
void testGetName() {
|
||||
ConditionalRotateStage stage = new ConditionalRotateStage();
|
||||
|
||||
assertEquals("ConditionalRotateStage", stage.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_NormalPhotoAndPortrait() {
|
||||
ConditionalRotateStage stage = new ConditionalRotateStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
context.setLandscape(false); // 竖图
|
||||
|
||||
assertTrue(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_NormalPhotoButLandscape_ShouldSkip() {
|
||||
ConditionalRotateStage stage = new ConditionalRotateStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
context.setLandscape(true); // 横图
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_Puzzle_ShouldSkip() {
|
||||
ConditionalRotateStage stage = new ConditionalRotateStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/puzzle.png")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.PUZZLE)
|
||||
.build();
|
||||
|
||||
context.setLandscape(false);
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NoCurrentFile_ShouldFail() {
|
||||
ConditionalRotateStage stage = new ConditionalRotateStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
context.setLandscape(false);
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isFailed());
|
||||
assertEquals("当前文件不存在", result.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
package com.ycwl.basic.image.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.image.enhancer.entity.BceEnhancerConfig;
|
||||
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
||||
import com.ycwl.basic.image.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.image.pipeline.enums.ImageSource;
|
||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* ImageEnhanceStage 单元测试
|
||||
*/
|
||||
class ImageEnhanceStageTest {
|
||||
|
||||
@Test
|
||||
void testGetName() {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage();
|
||||
|
||||
assertEquals("ImageEnhanceStage", stage.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultConstructor_ShouldSetDefaultConfig() {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage();
|
||||
|
||||
BceEnhancerConfig config = stage.getEnhancerConfig();
|
||||
|
||||
assertNotNull(config);
|
||||
assertEquals("119554288", config.getAppId());
|
||||
assertEquals("OX6QoijgKio3eVtA0PiUVf7f", config.getApiKey());
|
||||
assertEquals("dYatXReVriPeiktTjUblhfubpcmYfuMk", config.getSecretKey());
|
||||
assertEquals(1.0f, config.getQps());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConstructorWithConfig() {
|
||||
BceEnhancerConfig customConfig = new BceEnhancerConfig();
|
||||
customConfig.setAppId("custom-app-id");
|
||||
customConfig.setApiKey("custom-api-key");
|
||||
customConfig.setSecretKey("custom-secret-key");
|
||||
customConfig.setQps(2.0f);
|
||||
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage(customConfig);
|
||||
|
||||
BceEnhancerConfig config = stage.getEnhancerConfig();
|
||||
assertEquals("custom-app-id", config.getAppId());
|
||||
assertEquals("custom-api-key", config.getApiKey());
|
||||
assertEquals("custom-secret-key", config.getSecretKey());
|
||||
assertEquals(2.0f, config.getQps());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_IPCSource() {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.IPC)
|
||||
.build();
|
||||
|
||||
// 需要显式启用 image_enhance
|
||||
context.loadStageConfig(null, java.util.Map.of("image_enhance", true));
|
||||
|
||||
assertTrue(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_PhoneSource_ShouldSkip() {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.PHONE)
|
||||
.build();
|
||||
|
||||
context.loadStageConfig(null, java.util.Map.of("image_enhance", true));
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_CameraSource_ShouldSkip() {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.CAMERA)
|
||||
.build();
|
||||
|
||||
context.loadStageConfig(null, java.util.Map.of("image_enhance", true));
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_UnknownSource_ShouldSkip() {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.UNKNOWN)
|
||||
.build();
|
||||
|
||||
context.loadStageConfig(null, java.util.Map.of("image_enhance", true));
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_NullSource_ShouldSkip() {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
context.loadStageConfig(null, java.util.Map.of("image_enhance", true));
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_DisabledByConfig_ShouldSkip() {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.IPC)
|
||||
.build();
|
||||
|
||||
// 默认不启用,不加载配置
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NoCurrentFile_ShouldFail() {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.IPC)
|
||||
.build();
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isFailed());
|
||||
assertEquals("当前文件不存在", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_InvalidConfig_ShouldSkip() {
|
||||
// 创建无效配置(使用 TODO 占位符)
|
||||
BceEnhancerConfig invalidConfig = new BceEnhancerConfig();
|
||||
invalidConfig.setAppId("TODO_YOUR_APP_ID");
|
||||
invalidConfig.setApiKey("TODO_YOUR_API_KEY");
|
||||
invalidConfig.setSecretKey("TODO_YOUR_SECRET_KEY");
|
||||
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage(invalidConfig);
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.processId("test-invalid-config")
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.IPC)
|
||||
.build();
|
||||
|
||||
// 创建一个临时文件
|
||||
File tempFile = context.getTempFileManager().createTempFile("test", ".jpg");
|
||||
context.setOriginalFile(tempFile);
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isSkipped());
|
||||
assertEquals("配置不完整,跳过增强", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullConfig_ShouldSkip(@TempDir Path tempDir) throws Exception {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage(null);
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.processId("test-null-config")
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.IPC)
|
||||
.build();
|
||||
|
||||
// 创建一个临时文件
|
||||
File testFile = tempDir.resolve("test.jpg").toFile();
|
||||
try (FileOutputStream fos = new FileOutputStream(testFile)) {
|
||||
fos.write(new byte[]{1, 2, 3, 4, 5});
|
||||
}
|
||||
context.setOriginalFile(testFile);
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isSkipped());
|
||||
assertEquals("配置不完整,跳过增强", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullAppId_ShouldSkip(@TempDir Path tempDir) throws Exception {
|
||||
BceEnhancerConfig config = new BceEnhancerConfig();
|
||||
config.setAppId(null);
|
||||
config.setApiKey("valid-api-key");
|
||||
config.setSecretKey("valid-secret-key");
|
||||
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage(config);
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.processId("test-null-appid")
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.IPC)
|
||||
.build();
|
||||
|
||||
File testFile = tempDir.resolve("test.jpg").toFile();
|
||||
try (FileOutputStream fos = new FileOutputStream(testFile)) {
|
||||
fos.write(new byte[]{1, 2, 3, 4, 5});
|
||||
}
|
||||
context.setOriginalFile(testFile);
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isSkipped());
|
||||
assertEquals("配置不完整,跳过增强", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_TodoPlaceholderAppId_ShouldSkip(@TempDir Path tempDir) throws Exception {
|
||||
BceEnhancerConfig config = new BceEnhancerConfig();
|
||||
config.setAppId("TODO_APP_ID");
|
||||
config.setApiKey("valid-api-key");
|
||||
config.setSecretKey("valid-secret-key");
|
||||
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage(config);
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.processId("test-todo-appid")
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.IPC)
|
||||
.build();
|
||||
|
||||
File testFile = tempDir.resolve("test.jpg").toFile();
|
||||
try (FileOutputStream fos = new FileOutputStream(testFile)) {
|
||||
fos.write(new byte[]{1, 2, 3, 4, 5});
|
||||
}
|
||||
context.setOriginalFile(testFile);
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isSkipped());
|
||||
assertEquals("配置不完整,跳过增强", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAndGetEnhancerConfig() {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage();
|
||||
|
||||
BceEnhancerConfig newConfig = new BceEnhancerConfig();
|
||||
newConfig.setAppId("new-app-id");
|
||||
newConfig.setApiKey("new-api-key");
|
||||
newConfig.setSecretKey("new-secret-key");
|
||||
newConfig.setQps(3.0f);
|
||||
|
||||
stage.setEnhancerConfig(newConfig);
|
||||
|
||||
BceEnhancerConfig retrieved = stage.getEnhancerConfig();
|
||||
assertEquals("new-app-id", retrieved.getAppId());
|
||||
assertEquals("new-api-key", retrieved.getApiKey());
|
||||
assertEquals("new-secret-key", retrieved.getSecretKey());
|
||||
assertEquals(3.0f, retrieved.getQps());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_AllImageTypes_WithIPCSource(@TempDir Path tempDir) throws Exception {
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage();
|
||||
|
||||
// 测试普通照片
|
||||
testImageType(stage, tempDir, ImageType.NORMAL_PHOTO);
|
||||
|
||||
// 测试拼图
|
||||
testImageType(stage, tempDir, ImageType.PUZZLE);
|
||||
|
||||
// 测试手机上传
|
||||
testImageType(stage, tempDir, ImageType.MOBILE_UPLOAD);
|
||||
}
|
||||
|
||||
private void testImageType(ImageEnhanceStage stage, Path tempDir, ImageType imageType) throws Exception {
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.processId("test-" + imageType.getCode())
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(imageType)
|
||||
.source(ImageSource.IPC)
|
||||
.build();
|
||||
|
||||
File testFile = tempDir.resolve("test-" + imageType.getCode() + ".jpg").toFile();
|
||||
try (FileOutputStream fos = new FileOutputStream(testFile)) {
|
||||
fos.write(new byte[]{1, 2, 3, 4, 5});
|
||||
}
|
||||
context.setOriginalFile(testFile);
|
||||
|
||||
context.loadStageConfig(null, java.util.Map.of("image_enhance", true));
|
||||
|
||||
// 应该可以执行(因为 source 是 IPC)
|
||||
assertTrue(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConfigValidation_AllTodoFields() {
|
||||
BceEnhancerConfig config = new BceEnhancerConfig();
|
||||
config.setAppId("TODO_APP_ID");
|
||||
config.setApiKey("TODO_API_KEY");
|
||||
config.setSecretKey("TODO_SECRET_KEY");
|
||||
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage(config);
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.processId("test-all-todo")
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.IPC)
|
||||
.build();
|
||||
|
||||
File tempFile = context.getTempFileManager().createTempFile("test", ".jpg");
|
||||
context.setOriginalFile(tempFile);
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isSkipped());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConfigValidation_MixedTodoAndValid() {
|
||||
BceEnhancerConfig config = new BceEnhancerConfig();
|
||||
config.setAppId("valid-app-id");
|
||||
config.setApiKey("TODO_API_KEY");
|
||||
config.setSecretKey("valid-secret-key");
|
||||
|
||||
ImageEnhanceStage stage = new ImageEnhanceStage(config);
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.processId("test-mixed-todo")
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.source(ImageSource.IPC)
|
||||
.build();
|
||||
|
||||
File tempFile = context.getTempFileManager().createTempFile("test", ".jpg");
|
||||
context.setOriginalFile(tempFile);
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isSkipped());
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试说明:
|
||||
*
|
||||
* 由于 ImageEnhanceStage 依赖外部 API (BceImageEnhancer),
|
||||
* 以下测试场景需要 Mock 或集成测试环境:
|
||||
*
|
||||
* 1. testExecute_Success_ValidConfig()
|
||||
* - 模拟成功的图像增强调用
|
||||
* - 验证增强后文件大小变化
|
||||
* - 验证 context.updateProcessedFile() 被调用
|
||||
*
|
||||
* 2. testExecute_ApiReturnsNull_ShouldDegrade()
|
||||
* - 模拟 API 返回 null
|
||||
* - 验证返回 StageResult.degraded()
|
||||
*
|
||||
* 3. testExecute_ApiReturnsEmptyFile_ShouldDegrade()
|
||||
* - 模拟 API 返回空文件
|
||||
* - 验证返回 StageResult.degraded()
|
||||
*
|
||||
* 4. testExecute_ApiThrowsException_ShouldDegrade()
|
||||
* - 模拟 API 抛出异常
|
||||
* - 验证返回 StageResult.degraded() 而不是 failed()
|
||||
*
|
||||
* 5. testExecute_SaveFileFails_ShouldDegrade()
|
||||
* - 模拟文件保存失败
|
||||
* - 验证返回 StageResult.degraded()
|
||||
*
|
||||
* 这些测试需要使用 Mockito 或类似的 Mock 框架来模拟 BceImageEnhancer 的行为。
|
||||
*
|
||||
* 示例(使用 Mockito):
|
||||
*
|
||||
* @Test
|
||||
* void testExecute_Success_ValidConfig() {
|
||||
* // 创建 Mock 的 BceImageEnhancer
|
||||
* BceImageEnhancer mockEnhancer = mock(BceImageEnhancer.class);
|
||||
*
|
||||
* // 创建 Mock 的 MultipartFile
|
||||
* MultipartFile mockEnhancedImage = mock(MultipartFile.class);
|
||||
* when(mockEnhancedImage.isEmpty()).thenReturn(false);
|
||||
* when(mockEnhancedImage.getBytes()).thenReturn(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
|
||||
*
|
||||
* // 配置 Mock 行为
|
||||
* when(mockEnhancer.enhance(anyString())).thenReturn(mockEnhancedImage);
|
||||
*
|
||||
* // 测试逻辑...
|
||||
* }
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.ycwl.basic.image.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
||||
import com.ycwl.basic.image.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* ImageOrientationStage 单元测试
|
||||
*/
|
||||
class ImageOrientationStageTest {
|
||||
|
||||
@Test
|
||||
void testGetName() {
|
||||
ImageOrientationStage stage = new ImageOrientationStage();
|
||||
|
||||
assertEquals("ImageOrientationStage", stage.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_NormalPhoto() {
|
||||
ImageOrientationStage stage = new ImageOrientationStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
assertTrue(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_Puzzle_ShouldSkip() {
|
||||
ImageOrientationStage stage = new ImageOrientationStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/puzzle.png")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.PUZZLE)
|
||||
.build();
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_MobileUpload_ShouldSkip() {
|
||||
ImageOrientationStage stage = new ImageOrientationStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/mobile.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.MOBILE_UPLOAD)
|
||||
.build();
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NoCurrentFile_ShouldFail() {
|
||||
ImageOrientationStage stage = new ImageOrientationStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.build();
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isFailed());
|
||||
assertEquals("当前文件不存在", result.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.ycwl.basic.image.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
||||
import com.ycwl.basic.image.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* ImageQualityCheckStage 单元测试
|
||||
*/
|
||||
class ImageQualityCheckStageTest {
|
||||
|
||||
@Test
|
||||
void testGetName() {
|
||||
ImageQualityCheckStage stage = new ImageQualityCheckStage();
|
||||
|
||||
assertEquals("ImageQualityCheckStage", stage.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_NormalPhoto() {
|
||||
ImageQualityCheckStage stage = new ImageQualityCheckStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
// ImageQualityCheckStage 默认不启用,需要显式启用
|
||||
context.loadStageConfig(null, java.util.Map.of("quality_check", true));
|
||||
|
||||
assertTrue(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_Puzzle_ShouldSkip() {
|
||||
ImageQualityCheckStage stage = new ImageQualityCheckStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/puzzle.png")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.PUZZLE)
|
||||
.build();
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_MobileUpload_ShouldSkip() {
|
||||
ImageQualityCheckStage stage = new ImageQualityCheckStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/mobile.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.MOBILE_UPLOAD)
|
||||
.build();
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NoCurrentFile_ShouldFail() {
|
||||
ImageQualityCheckStage stage = new ImageQualityCheckStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isFailed());
|
||||
assertEquals("当前文件不存在", result.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.ycwl.basic.image.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
||||
import com.ycwl.basic.image.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* PuzzleBorderStage 单元测试
|
||||
*/
|
||||
class PuzzleBorderStageTest {
|
||||
|
||||
@Test
|
||||
void testGetName() {
|
||||
PuzzleBorderStage stage = new PuzzleBorderStage();
|
||||
|
||||
assertEquals("PuzzleBorderStage", stage.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_Puzzle() {
|
||||
PuzzleBorderStage stage = new PuzzleBorderStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/puzzle.png")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.PUZZLE)
|
||||
.build();
|
||||
|
||||
assertTrue(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_NormalPhoto_ShouldSkip() {
|
||||
PuzzleBorderStage stage = new PuzzleBorderStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_MobileUpload_ShouldSkip() {
|
||||
PuzzleBorderStage stage = new PuzzleBorderStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/mobile.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.MOBILE_UPLOAD)
|
||||
.build();
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NoCurrentFile_ShouldFail() {
|
||||
PuzzleBorderStage stage = new PuzzleBorderStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/puzzle.png")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.PUZZLE)
|
||||
.build();
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isFailed());
|
||||
assertEquals("当前文件不存在", result.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.ycwl.basic.image.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
||||
import com.ycwl.basic.image.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* RestoreOrientationStage 单元测试
|
||||
*/
|
||||
class RestoreOrientationStageTest {
|
||||
|
||||
@Test
|
||||
void testGetName() {
|
||||
RestoreOrientationStage stage = new RestoreOrientationStage();
|
||||
|
||||
assertEquals("RestoreOrientationStage", stage.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_NormalPhotoAndNeedRotation() {
|
||||
RestoreOrientationStage stage = new RestoreOrientationStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
context.setNeedRotation(true);
|
||||
|
||||
assertTrue(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_NormalPhotoButNoNeedRotation_ShouldSkip() {
|
||||
RestoreOrientationStage stage = new RestoreOrientationStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
context.setNeedRotation(false);
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_Puzzle_ShouldSkip() {
|
||||
RestoreOrientationStage stage = new RestoreOrientationStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/puzzle.png")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.PUZZLE)
|
||||
.build();
|
||||
|
||||
context.setNeedRotation(true);
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NoCurrentFile_ShouldFail() {
|
||||
RestoreOrientationStage stage = new RestoreOrientationStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
context.setNeedRotation(true);
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isFailed());
|
||||
assertEquals("当前文件不存在", result.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.ycwl.basic.image.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
||||
import com.ycwl.basic.image.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* WatermarkStage 单元测试
|
||||
*/
|
||||
class WatermarkStageTest {
|
||||
|
||||
@Test
|
||||
void testGetName() {
|
||||
WatermarkStage stage = new WatermarkStage();
|
||||
|
||||
assertEquals("WatermarkStage", stage.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_NormalPhoto() {
|
||||
WatermarkStage stage = new WatermarkStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
assertTrue(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_Puzzle_ShouldSkip() {
|
||||
WatermarkStage stage = new WatermarkStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/puzzle.png")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.PUZZLE)
|
||||
.build();
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldExecute_MobileUpload_ShouldSkip() {
|
||||
WatermarkStage stage = new WatermarkStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/mobile.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.MOBILE_UPLOAD)
|
||||
.build();
|
||||
|
||||
assertFalse(stage.shouldExecute(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NoWatermarkType_ShouldSkip() {
|
||||
WatermarkStage stage = new WatermarkStage();
|
||||
|
||||
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||
.originalUrl("https://example.com/test.jpg")
|
||||
.scenicId(123L)
|
||||
.imageType(ImageType.NORMAL_PHOTO)
|
||||
.build();
|
||||
|
||||
StageResult result = stage.execute(context);
|
||||
|
||||
assertTrue(result.isSkipped());
|
||||
assertEquals("未配置水印", result.getMessage());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user