package jnpf.limsService.impl; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import jnpf.audit.AuditConsts; import jnpf.audit.model.AuditEventDTO; import jnpf.audit.sdk.AuditClient; import jnpf.base.ModuleApi; import jnpf.limsEntity.print.LimsPrintAuditParam; import jnpf.limsEntity.print.LimsPrintAuditTarget; import jnpf.util.JsonUtil; import org.springframework.beans.factory.support.StaticListableBeanFactory; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; /** Lightweight checks for the one-click-to-one-print-event mapping. */ public class LimsPrintAuditServiceImplChecks { public static void main(String[] args) { checkCamelCaseDeserialization(); checkSinglePrint(); checkBatchPrintAndDeduplication(); checkBatchRecordsOnce(); checkOptionalTemplate(); checkMenuMappingAndFallback(); checkInvalidInputs(); } private static void checkCamelCaseDeserialization() { String json = "{\"requestId\":\"123e4567e89b12d3a456426614174000\"," + "\"printName\":\"仪器设备标签\",\"templateId\":\"template-1\"," + "\"recordTitle\":\"仪器设备标签\",\"targetTable\":\"lims_shebei_yiqi_xinxi\"," + "\"bizType\":\"DEVICE_LABEL\",\"source\":\"LIST_PRINT\"," + "\"targets\":[{\"id\":\"record-1\",\"bizCode\":\"SB-001\",\"title\":\"设备一\"}]}"; ObjectMapper mapper = new ObjectMapper(); mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); try { LimsPrintAuditParam param = mapper.readValue(json, LimsPrintAuditParam.class); require("123e4567e89b12d3a456426614174000".equals(param.getRequestId()), "camelCase requestId must override the global snake-case strategy"); require("仪器设备标签".equals(param.getPrintName()), "camelCase printName must be deserialized"); require("lims_shebei_yiqi_xinxi".equals(param.getTargetTable()), "camelCase targetTable must be deserialized"); require(param.getTargets() != null && param.getTargets().size() == 1, "camelCase targets must be deserialized"); require("SB-001".equals(param.getTargets().get(0).getBizCode()), "nested camelCase bizCode must be deserialized"); } catch (Exception e) { throw new AssertionError("camelCase print audit payload must be deserialized", e); } } private static void checkSinglePrint() { LimsPrintAuditParam param = baseParam(); param.setTargets(Collections.singletonList(target("record-1", "BG-001", "检验报告 BG-001"))); AuditEventDTO event = LimsPrintAuditServiceImpl.buildEvent(param); Map extra = extra(event); require(AuditConsts.TYPE_PRINT.equals(event.getEventType()), "event type must be PRINT"); require("PRINT".equals(event.getActionCode()), "action code must be PRINT"); require("发起打印".equals(event.getActionLabel()), "action label must describe initiation"); require(Integer.valueOf(3).equals(event.getSourceLayer()), "explicit print events must use layer 3"); require("PRINT-123e4567e89b12d3a456426614174000".equals(event.getClientEventId()), "request UUID must produce a stable client event id"); require(event.getClientEventId().equals(event.getOperationId()), "operation id must correlate the same click"); require("record-1".equals(event.getTargetId()), "single print must expose its target id"); require("BG-001".equals(event.getBizCode()), "single print must expose its business code"); require("SAMPLE_REPORT".equals(event.getBizType()), "frontend bizType must be retained"); require("SINGLE".equals(extra.get("printMode")), "single print mode is incorrect"); require(Integer.valueOf(1).equals(extra.get("printCount")), "single print count is incorrect"); } private static void checkBatchPrintAndDeduplication() { LimsPrintAuditParam param = baseParam(); param.setRecordTitle("检验报告"); param.setTargets(Arrays.asList( target("record-1", "BG-001", "第一份"), target("record-2", null, "第二份"), target("record-1", "SHOULD-NOT-REPLACE", "重复项"))); AuditEventDTO event = LimsPrintAuditServiceImpl.buildEvent(param); Map extra = extra(event); List targets = (List) extra.get("printTargets"); List subTitles = (List) extra.get("recordSubTitles"); require(event.getTargetId() == null, "batch print must not impersonate the first target"); require(event.getBizCode() == null, "batch print must not expose one target's business code"); require("检验报告(批量打印 2 条)".equals(event.getRecordTitle()), "batch title must use the deduplicated count"); require("BATCH".equals(extra.get("printMode")), "batch print mode is incorrect"); require(Integer.valueOf(2).equals(extra.get("printCount")), "duplicate ids must be counted once"); require(targets.size() == 2, "batch event must contain all deduplicated targets in one DTO"); require(Arrays.asList("BG-001", "第二份").equals(subTitles), "target summaries must preserve stable order"); } private static void checkOptionalTemplate() { LimsPrintAuditParam param = baseParam(); param.setTemplateId(null); param.setBizType(null); param.setTargets(Collections.singletonList(target("record-1", null, null))); AuditEventDTO event = LimsPrintAuditServiceImpl.buildEvent(param); Map extra = extra(event); require(!extra.containsKey("templateId"), "custom print must not require a template id"); require("LIMS_PRINT".equals(event.getBizType()), "missing bizType must use the print fallback"); } private static void checkBatchRecordsOnce() { CountingAuditClient client = new CountingAuditClient(); StaticListableBeanFactory factory = new StaticListableBeanFactory(); factory.addBean("auditClient", client); LimsPrintAuditServiceImpl service = new LimsPrintAuditServiceImpl( factory.getBeanProvider(AuditClient.class), factory.getBeanProvider(ModuleApi.class)); LimsPrintAuditParam param = baseParam(); param.setTargets(Arrays.asList( target("record-1", "BG-001", null), target("record-2", "BG-002", null))); service.record(param); require(client.recordCount == 1, "one batch click must invoke AuditClient.record exactly once"); require(client.event != null && "BATCH".equals(extra(client.event).get("printMode")), "the single submitted event must contain the complete batch"); } private static void checkMenuMappingAndFallback() { LimsPrintAuditParam param = baseParam(); param.setTargets(Collections.singletonList(target("record-1", null, null))); AuditEventDTO resolved = LimsPrintAuditServiceImpl.buildEvent( param, LimsPrintAuditServiceImpl.MenuContext.resolved("menu-1", "检验报告菜单")); Map resolvedExtra = extra(resolved); require("检验报告菜单".equals(resolved.getBizModule()), "resolved menu must become the business module"); require("MENU".equals(resolved.getEntryType()), "resolved menu must set entry type"); require("menu-1".equals(resolved.getEntryId()), "resolved menu must retain entry id"); require("检验报告菜单".equals(resolved.getEntryName()), "resolved menu must retain entry name"); require("RESOLVED".equals(resolvedExtra.get("menuResolution")), "menu resolution status is incorrect"); AuditEventDTO unresolved = LimsPrintAuditServiceImpl.buildEvent( param, LimsPrintAuditServiceImpl.MenuContext.unresolved("menu-2")); require("表单详情打印".equals(unresolved.getBizModule()), "unresolved menu must use the source label fallback"); require("MENU".equals(unresolved.getEntryType()), "an unresolved header must still identify a menu entry"); require("menu-2".equals(unresolved.getEntryId()), "an unresolved header must retain its menu id"); require(unresolved.getEntryName() == null, "unresolved menu must not invent an entry name"); AuditEventDTO missing = LimsPrintAuditServiceImpl.buildEvent(param); require("表单详情打印".equals(missing.getBizModule()), "missing menu must use the source label fallback"); require(missing.getEntryType() == null && missing.getEntryId() == null, "missing menu must leave entry fields empty"); } private static void checkInvalidInputs() { LimsPrintAuditParam invalidUuid = baseParam(); invalidUuid.setRequestId("not-a-uuid"); invalidUuid.setTargets(Collections.singletonList(target("record-1", null, null))); requireThrows(() -> LimsPrintAuditServiceImpl.buildEvent(invalidUuid), "invalid UUID must be rejected"); LimsPrintAuditParam invalidTable = baseParam(); invalidTable.setTargetTable("lims_report;drop table audit_events"); invalidTable.setTargets(Collections.singletonList(target("record-1", null, null))); requireThrows(() -> LimsPrintAuditServiceImpl.buildEvent(invalidTable), "invalid table name must be rejected"); } private static LimsPrintAuditParam baseParam() { LimsPrintAuditParam param = new LimsPrintAuditParam(); param.setRequestId("123e4567-e89b-12d3-a456-426614174000"); param.setPrintName("检验报告"); param.setTemplateId("report-template"); param.setTargetTable("lims_sample_report"); param.setBizType("SAMPLE_REPORT"); param.setSource("REPORT_VIEW"); return param; } private static LimsPrintAuditTarget target(String id, String bizCode, String title) { LimsPrintAuditTarget target = new LimsPrintAuditTarget(); target.setId(id); target.setBizCode(bizCode); target.setTitle(title); return target; } @SuppressWarnings("unchecked") private static Map extra(AuditEventDTO event) { return JsonUtil.getJsonToBean(event.getExtra(), Map.class); } private static void requireThrows(Runnable action, String message) { try { action.run(); throw new AssertionError(message); } catch (IllegalArgumentException ignored) { // Expected validation failure. } } private static void require(boolean condition, String message) { if (!condition) { throw new AssertionError(message); } } private static final class CountingAuditClient extends AuditClient { private AuditEventDTO event; private int recordCount; private CountingAuditClient() { super(null, null, false); } @Override public void record(AuditEventDTO dto) { recordCount++; event = dto; } } }