package jnpf.dmsService; import jnpf.dmsEntity.kanwu.DmsXiudingKanwuRow; import jnpf.dmsMapper.DmsXiudingKanwuMapper; import jnpf.dmsIntegration.audit.DmsBusinessChangeAudit; import jnpf.exception.DataException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.*; import org.mockito.junit.jupiter.MockitoExtension; import java.util.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class DmsXiudingKanwuServiceTest { @Mock DmsXiudingKanwuMapper mapper; @Mock DmsBusinessChangeAudit audit; @InjectMocks DmsXiudingKanwuService service; DmsXiudingKanwuRow row(String id) { DmsXiudingKanwuRow row = new DmsXiudingKanwuRow(); row.setKanwuZixiangId(id); row.setKanwuShenqingId("request"); row.setWenjianId("file"); row.setBanbenId("base"); row.setKanwuNeirong("content"); row.setChuliJielun("yixiugai_queren"); row.setChuliYuanyin("已核对并修订"); row.setChuliRenId("drafter"); row.setChuliShijian(new Date(1000)); return row; } void arrange(List handled, List approved) { when(mapper.selectHandlingForUpdate("tenant", "audit")).thenReturn(handled); when(mapper.selectApprovedForUpdate("tenant", "file", "base")).thenReturn(approved); } List load() { return service.loadAndValidate("tenant", "audit", "file", "base"); } @Test void acceptsEmptyScope() { arrange(List.of(), List.of()); assertTrue(load().isEmpty()); } @Test void sortsWithoutMutatingMapperListAndNormalizesFiles() { DmsXiudingKanwuRow handled = row("a"), source = row("a"); handled.setFujian("[{\"url\":\"x\",\"name\":\"n\"}]"); source.setFujian("[{\"name\":\"n\",\"url\":\"x\"}]"); DmsXiudingKanwuRow empty = row("b"); empty.setFujian("[]"); arrange(List.of(empty, handled), List.of(source, row("b"))); assertEquals(List.of("a", "b"), load().stream().map(DmsXiudingKanwuRow::getKanwuZixiangId).toList()); } @ParameterizedTest @ValueSource(strings={"yixiugai_queren", "bucaina"}) void bothOutcomesRequireReasonAndAuditFields(String outcome) { DmsXiudingKanwuRow handled = row("a"); handled.setChuliJielun(outcome); arrange(List.of(handled), List.of(row("a"))); assertEquals(outcome, load().get(0).getChuliJielun()); handled.setChuliYuanyin(" "); assertThrows(DataException.class, this::load); handled.setChuliYuanyin("reason"); handled.setChuliRenId(null); assertThrows(DataException.class, this::load); handled.setChuliRenId("drafter"); handled.setChuliShijian(null); assertThrows(DataException.class, this::load); } @Test void rejectsMissingNewApprovedNote() { arrange(List.of(row("a")), List.of(row("a"), row("b"))); assertThrows(DataException.class, this::load); } @Test void rejectsUnapprovedOrOutOfScopeNote() { arrange(List.of(row("other")), List.of(row("a"))); assertThrows(DataException.class, this::load); } @Test void rejectsDuplicateHandling() { arrange(List.of(row("a"), row("a")), List.of(row("a"), row("b"))); assertThrows(DataException.class, this::load); } @Test void rejectsChangedContentOrFiles() { DmsXiudingKanwuRow handled = row("a"); arrange(List.of(handled), List.of(row("a"))); handled.setKanwuNeirong("changed"); assertThrows(DataException.class, this::load); handled.setKanwuNeirong("content"); handled.setFujian("[{}]"); assertThrows(DataException.class, this::load); handled.setFujian("{}"); assertThrows(DataException.class, this::load); handled.setFujian("invalid"); assertThrows(DataException.class, this::load); } @Test void rejectsUnknownOutcome() { DmsXiudingKanwuRow handled=row("a");handled.setChuliJielun("yes");arrange(List.of(handled),List.of(row("a")));assertThrows(DataException.class,this::load); } @Test void preservesFirstProcessingAuditAndOnlyClosesUnprocessedRows() { DmsXiudingKanwuRow processed = row("a"); processed.setShifouYichuli("yes"); arrange(List.of(row("a"), row("b")), List.of(processed, row("b"))); List verified = load(); when(mapper.markProcessed("tenant", verified.get(1))).thenReturn(1); Date writtenAt = new Date(9000); Map before = DmsBusinessChangeAudit.values("shifou_yichuli", null, "chuliren", "previous", "chuli_shijian", null); Map after = DmsBusinessChangeAudit.values("shifou_yichuli", "yes", "chuliren", "drafter", "chuli_shijian", writtenAt); when(mapper.selectProcessingState("tenant", "b")).thenReturn(before, after); service.complete("tenant", "audit", verified); verify(mapper, never()).markProcessed("tenant", verified.get(0)); verify(mapper).markProcessed("tenant", verified.get(1)); verify(mapper, never()).selectProcessingState("tenant", "a"); verify(audit).record(eq("tenant"), eq("DMS_REVISION_ERRATA_PROCESSED"), eq("修订生效后回写勘误处理状态"), eq("dms_wenjian_kanwu_shenqing_zixiang"), eq("b"), eq(before), eq(after), eq("已核对并修订"), argThat(context -> "audit".equals(context.get("xiuding_shenhe_id")) && "request".equals(context.get("kanwu_shenqing_id")) && "yixiugai_queren".equals(context.get("chuli_jielun")) && new Date(1000).equals(context.get("chuli_shijian")))); verifyNoMoreInteractions(audit); } @Test void emptyOrAlreadyProcessedRowsDoNotCreateAuditEvents() { service.complete("tenant", "audit", List.of()); DmsXiudingKanwuRow processed = row("a"); processed.setAlreadyProcessed(true); service.complete("tenant", "audit", List.of(processed)); verifyNoInteractions(mapper, audit); } @Test void updateFailurePropagatesToEffectiveTransaction() { when(mapper.selectProcessingState("tenant", "a")).thenReturn(Map.of("id", "a")); assertThrows(DataException.class, () -> service.complete("tenant", "audit", List.of(row("a")))); verify(mapper).markProcessed(eq("tenant"), any()); verifyNoInteractions(audit); } @Test void missingSnapshotDoesNotFabricateAuditValues() { when(mapper.selectProcessingState("tenant", "a")).thenReturn(null); assertThrows(DataException.class, () -> service.complete("tenant", "audit", List.of(row("a")))); verify(mapper, never()).markProcessed(anyString(), any()); verifyNoInteractions(audit); } }