完善任务存储、权限及货源查询流程
Build Backend JAR / build (push) Has been cancelled

This commit is contained in:
supernijia
2026-08-14 22:49:15 +08:00
parent 5b1ccad40e
commit 7a7f1dfa21
21 changed files with 2584 additions and 566 deletions
@@ -0,0 +1,208 @@
package com.nanri.aiimage.modules.task.service;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nanri.aiimage.modules.task.mapper.TaskScopeStateMapper;
import com.nanri.aiimage.modules.task.model.entity.TaskScopeStateEntity;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.SimpleTransactionStatus;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class TaskScopePayloadStorageServiceTest {
@BeforeAll
static void initializeMybatisMetadata() {
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""),
TaskScopeStateEntity.class);
}
@Test
void uploadsBeforeOpeningTransactionAndCleansReplacedPayloadAfterCommit() {
TaskScopeStateMapper mapper = mock(TaskScopeStateMapper.class);
TransientPayloadStorageService payloadStorage = mock(TransientPayloadStorageService.class);
PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
TaskScopePayloadStorageService service = new TaskScopePayloadStorageService(
mapper, new ObjectMapper(), payloadStorage, transactionManager);
List<String> events = new ArrayList<>();
AtomicBoolean transactionActive = new AtomicBoolean();
TaskScopeStateEntity existing = new TaskScopeStateEntity();
existing.setId(9L);
existing.setTaskId(7L);
existing.setModuleType("PRICE_TRACK");
existing.setScopeHash("scope-hash");
existing.setStateJson("rustfs:old.json");
when(payloadStorage.storeScopePayloadVersioned(
eq("PRICE_TRACK"), eq(7L), any(), any(), eq(true)))
.thenAnswer(invocation -> {
assertFalse(transactionActive.get());
events.add("upload");
return "rustfs:new.json";
});
when(payloadStorage.extractPointer("rustfs:new.json")).thenReturn("rustfs:new.json");
when(payloadStorage.extractPointer("rustfs:old.json")).thenReturn("rustfs:old.json");
when(transactionManager.getTransaction(any(TransactionDefinition.class)))
.thenAnswer(invocation -> {
events.add("begin");
transactionActive.set(true);
return new SimpleTransactionStatus();
});
when(mapper.selectOne(any())).thenAnswer(invocation -> {
LambdaQueryWrapper<?> query = invocation.getArgument(0);
assertTrue(query.getSqlSegment().endsWith("limit 1 FOR UPDATE"));
return existing;
});
when(mapper.update(any(), any())).thenReturn(1);
doAnswer(invocation -> {
events.add("commit");
transactionActive.set(false);
return null;
}).when(transactionManager).commit(any(TransactionStatus.class));
doAnswer(invocation -> {
assertFalse(transactionActive.get());
events.add("cleanup");
return null;
}).when(payloadStorage).deleteReplacedPayloadIfNeeded("rustfs:old.json", "rustfs:new.json");
service.saveScopePayload(7L, "PRICE_TRACK", "shop-a", new Payload("ok"));
assertEquals(List.of("upload", "begin", "commit", "cleanup"), events);
verify(mapper).update(any(), any());
}
@Test
void deletesUploadedPayloadAfterRollbackWhenDatabaseConfirmsItIsUnreferenced() {
TaskScopeStateMapper mapper = mock(TaskScopeStateMapper.class);
TransientPayloadStorageService payloadStorage = mock(TransientPayloadStorageService.class);
PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
TaskScopePayloadStorageService service = new TaskScopePayloadStorageService(
mapper, new ObjectMapper(), payloadStorage, transactionManager);
List<String> events = new ArrayList<>();
AtomicBoolean transactionActive = new AtomicBoolean();
TaskScopeStateEntity existing = new TaskScopeStateEntity();
existing.setId(9L);
existing.setTaskId(7L);
existing.setModuleType("PRICE_TRACK");
existing.setScopeHash("scope-hash");
existing.setStateJson("rustfs:old.json");
when(payloadStorage.storeScopePayloadVersioned(
eq("PRICE_TRACK"), eq(7L), any(), any(), eq(true)))
.thenAnswer(invocation -> {
assertFalse(transactionActive.get());
events.add("upload");
return "rustfs:new.json";
});
when(payloadStorage.extractPointer("rustfs:new.json")).thenReturn("rustfs:new.json");
when(payloadStorage.extractPointer("rustfs:old.json")).thenReturn("rustfs:old.json");
when(transactionManager.getTransaction(any(TransactionDefinition.class)))
.thenAnswer(invocation -> {
events.add("begin");
transactionActive.set(true);
return new SimpleTransactionStatus();
});
when(mapper.selectOne(any())).thenAnswer(invocation -> {
events.add(transactionActive.get() ? "transaction-read" : "cleanup-read");
return existing;
});
when(mapper.update(any(), any())).thenThrow(new IllegalStateException("database write failed"));
doAnswer(invocation -> {
events.add("rollback");
transactionActive.set(false);
return null;
}).when(transactionManager).rollback(any(TransactionStatus.class));
doAnswer(invocation -> {
assertFalse(transactionActive.get());
events.add("delete");
return null;
}).when(payloadStorage).deletePayloadIfPresent("rustfs:new.json");
assertThrows(IllegalStateException.class,
() -> service.saveScopePayload(7L, "PRICE_TRACK", "shop-a", new Payload("ok")));
assertEquals(List.of("upload", "begin", "transaction-read", "rollback", "cleanup-read", "delete"), events);
verify(payloadStorage).deletePayloadIfPresent("rustfs:new.json");
}
@Test
void keepsUploadedPayloadWhenCommitThrowsButDatabaseReferencesIt() {
TaskScopeStateMapper mapper = mock(TaskScopeStateMapper.class);
TransientPayloadStorageService payloadStorage = mock(TransientPayloadStorageService.class);
PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
TaskScopePayloadStorageService service = new TaskScopePayloadStorageService(
mapper, new ObjectMapper(), payloadStorage, transactionManager);
List<String> events = new ArrayList<>();
AtomicBoolean transactionActive = new AtomicBoolean();
TaskScopeStateEntity previous = new TaskScopeStateEntity();
previous.setId(9L);
previous.setTaskId(7L);
previous.setModuleType("PRICE_TRACK");
previous.setScopeHash("scope-hash");
previous.setStateJson("rustfs:old.json");
TaskScopeStateEntity committed = new TaskScopeStateEntity();
committed.setId(9L);
committed.setTaskId(7L);
committed.setModuleType("PRICE_TRACK");
committed.setScopeHash("scope-hash");
committed.setStateJson("rustfs:new.json");
when(payloadStorage.storeScopePayloadVersioned(
eq("PRICE_TRACK"), eq(7L), any(), any(), eq(true)))
.thenAnswer(invocation -> {
assertFalse(transactionActive.get());
events.add("upload");
return "rustfs:new.json";
});
when(payloadStorage.extractPointer("rustfs:new.json")).thenReturn("rustfs:new.json");
when(payloadStorage.extractPointer("rustfs:old.json")).thenReturn("rustfs:old.json");
when(transactionManager.getTransaction(any(TransactionDefinition.class)))
.thenAnswer(invocation -> {
events.add("begin");
transactionActive.set(true);
return new SimpleTransactionStatus();
});
when(mapper.selectOne(any())).thenAnswer(invocation -> {
events.add(transactionActive.get() ? "transaction-read" : "cleanup-read");
return transactionActive.get() ? previous : committed;
});
when(mapper.update(any(), any())).thenReturn(1);
doAnswer(invocation -> {
events.add("commit");
transactionActive.set(false);
throw new IllegalStateException("commit outcome unknown");
}).when(transactionManager).commit(any(TransactionStatus.class));
IllegalStateException error = assertThrows(IllegalStateException.class,
() -> service.saveScopePayload(7L, "PRICE_TRACK", "shop-a", new Payload("ok")));
assertEquals("commit outcome unknown", error.getMessage());
assertEquals(List.of("upload", "begin", "transaction-read", "commit", "cleanup-read"), events);
verify(payloadStorage, never()).deletePayloadIfPresent("rustfs:new.json");
verify(payloadStorage, never()).deleteReplacedPayloadIfNeeded(any(), any());
}
private record Payload(String value) {
}
}