package cn.iocoder.yudao.module.mdm.service.agent; import cn.iocoder.yudao.module.mdm.controller.admin.agent.vo.MdmAgentImportCommitReqVO; import cn.iocoder.yudao.module.mdm.controller.admin.agent.vo.MdmAgentImportPreflightReqVO; import cn.iocoder.yudao.module.mdm.controller.admin.agent.vo.MdmAgentImportPreflightRespVO; import cn.iocoder.yudao.module.mdm.controller.admin.agent.vo.MdmAgentImportReceiptRespVO; import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemImportExcelVO; import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO; import cn.iocoder.yudao.module.mdm.dal.dataobject.agent.MdmAgentImportCommandDO; import cn.iocoder.yudao.module.mdm.dal.mysql.agent.MdmAgentImportCommandMapper; import cn.iocoder.yudao.module.mdm.dal.mysql.agent.MdmAgentImportCommandRowMapper; import cn.iocoder.yudao.module.mdm.service.item.MdmItemImportContext; import cn.iocoder.yudao.module.mdm.service.item.MdmItemRowResolution; import cn.iocoder.yudao.module.mdm.service.item.MdmItemService; import cn.iocoder.yudao.module.system.framework.agent.AgentBridgeException; import cn.iocoder.yudao.module.system.framework.agent.AgentPrincipal; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class MdmAgentImportCommandServiceTest { private static final String PREFLIGHT_HASH = "b".repeat(64); private static final String SOURCE_SHA = "a".repeat(64); @Mock private MdmAgentImportPreflightService preflightService; @Mock private MdmItemService itemService; @Mock private MdmAgentImportPolicy policy; @Mock private MdmAgentImportCommandMapper commandMapper; @Mock private MdmAgentImportCommandRowMapper commandRowMapper; @Mock private PlatformTransactionManager transactionManager; @InjectMocks private MdmAgentImportCommandService service; private final AgentPrincipal principal = new AgentPrincipal(7L, 0L, "live"); @BeforeEach void setUp() { when(policy.isWriteEnabled()).thenReturn(true); when(transactionManager.getTransaction(any(TransactionDefinition.class))) .thenReturn(mock(TransactionStatus.class)); when(itemService.buildImportContext(any())).thenReturn(new MdmItemImportContext( Map.of("RAW", 11L), Map.of("RAW", "原料"), Map.of("PCS", 22L), Map.of("PCS", "个"))); when(itemService.resolveImportRow(any(MdmItemImportExcelVO.class), any())) .thenAnswer(invocation -> MdmItemRowResolution.success(new MdmItemSaveReqVO())); when(itemService.getItemListByCode(any())).thenReturn(List.of()); when(itemService.createItem(any())).thenReturn(77L); when(commandRowMapper.selectListByCommandId(any())).thenReturn(List.of()); } @Test void writeDisabledRejects() { when(policy.isWriteEnabled()).thenReturn(false); AgentBridgeException ex = assertThrows(AgentBridgeException.class, () -> service.commit(request("物料A"), principal)); assertEquals("WRITE_DISABLED", ex.getErrorCode()); verify(itemService, never()).createItem(any()); } @Test void repeatedCommitCreatesItemOnceAndReplaysReceipt() { stubPreflight(PREFLIGHT_HASH); AtomicReference inserted = captureInsertedCommand(); MdmAgentImportReceiptRespVO first = service.commit(request("物料A"), principal); when(commandMapper.selectByCommandId(0L, 7L, "cmd-1")).thenReturn(inserted.get()); MdmAgentImportReceiptRespVO second = service.commit(request("物料A"), principal); assertEquals("SUCCEEDED", first.getStatus()); assertEquals("SUCCEEDED", second.getStatus()); assertEquals(first.getPreflightHash(), second.getPreflightHash()); verify(itemService, times(1)).createItem(any()); } @Test void sameCommandWithDifferentContentConflicts() { when(preflightService.preflight(any(), any())).thenReturn(preflight(PREFLIGHT_HASH)); AtomicReference inserted = captureInsertedCommand(); service.commit(request("物料A"), principal); when(commandMapper.selectByCommandId(0L, 7L, "cmd-1")).thenReturn(inserted.get()); when(commandMapper.selectByIdempotencyKey(0L, 7L, "idem-1")).thenReturn(null); AgentBridgeException ex = assertThrows(AgentBridgeException.class, () -> service.commit(request("物料B"), principal)); assertEquals("COMMAND_ID_CONFLICT", ex.getErrorCode()); } @Test void stalePreflightIsRejected() { stubPreflight("c".repeat(64)); AgentBridgeException ex = assertThrows(AgentBridgeException.class, () -> service.commit(request("物料A"), principal)); assertEquals("PLAN_STALE", ex.getErrorCode()); verify(commandMapper, never()).insert(any(MdmAgentImportCommandDO.class)); } @Test void missingCommandReturnsNotFound() { when(commandMapper.selectByCommandId(0L, 7L, "cmd-x")).thenReturn(null); AgentBridgeException ex = assertThrows(AgentBridgeException.class, () -> service.getCommand("cmd-x", principal)); assertEquals("COMMAND_NOT_FOUND", ex.getErrorCode()); } private void stubPreflight(String preflightHash) { when(preflightService.preflight(any(), any())).thenReturn(preflight(preflightHash)); } private static MdmAgentImportPreflightRespVO preflight(String hash) { return MdmAgentImportPreflightRespVO.builder() .preflightHash(hash) .canCommit(true) .rows(List.of(MdmAgentImportPreflightRespVO.Row.builder() .candidateId("c-0").action("CREATE").build())) .summary(MdmAgentImportPreflightRespVO.Summary.builder() .total(1).create(1).skip(0).blocked(0).build()) .build(); } private AtomicReference captureInsertedCommand() { AtomicReference holder = new AtomicReference<>(); doAnswer(invocation -> { MdmAgentImportCommandDO command = invocation.getArgument(0); command.setId(1L); holder.set(command); return 1; }).when(commandMapper).insert(any(MdmAgentImportCommandDO.class)); return holder; } private static MdmAgentImportCommitReqVO request(String name) { MdmAgentImportPreflightReqVO.Row row = new MdmAgentImportPreflightReqVO.Row(); row.setCandidateId("c-0"); row.setRowNo(1); row.setCode("AGENT-E2E-1"); row.setName(name); row.setCategoryCode("RAW"); row.setUnitMeasureCode("PCS"); MdmAgentImportCommitReqVO reqVO = new MdmAgentImportCommitReqVO(); reqVO.setCommandId("cmd-1"); reqVO.setIdempotencyKey("idem-1"); reqVO.setAttachmentId(5L); reqVO.setSessionId(21L); reqVO.setSourceSha256(SOURCE_SHA); reqVO.setPreflightHash(PREFLIGHT_HASH); reqVO.setProposalRevision("rev-1"); reqVO.setRows(List.of(row)); return reqVO; } }