package jnpf.dmsMapper; import org.apache.ibatis.builder.xml.XMLMapperBuilder; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; import org.apache.ibatis.session.Configuration; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import java.io.InputStream; import java.sql.*; import java.util.*; import static org.junit.jupiter.api.Assertions.*; /** Production SQL against read-only CTE fixtures and the actual schema; never writes business data. */ @EnabledIfEnvironmentVariable(named = "DMS_LEDGER_TEST_JDBC_URL", matches = ".+") class DmsWenjianBufaPrintMapperPostgresTest { @Test void preservesInvalidSourceRowsButNeverResolvesWrongTenantDeletedOrMismatchedVersions() throws Exception { String fixture = """ WITH details AS ( SELECT 'detail-'||n f_id, CASE WHEN n=8 THEN 'other' ELSE 'tenant-a' END f_tenant_id, CASE WHEN n=9 THEN 1 ELSE NULL END f_delete_mark, CASE WHEN n=10 THEN 'other-request' ELSE 'request' END f_foreign_id, 'file-'||n wenjian_id, 'version-'||n banben_id, 'V1'::text banben_hao, '3'::text bufa_fenshu, 'purpose'::text yongtu, 'APPROVED-DOC'::text wenjian_bianhao, 'Approved document'::text wenjian_mingcheng FROM generate_series(1,10) n ), resources AS ( SELECT 'file-'||n f_id, CASE WHEN n=2 THEN 'other' ELSE 'tenant-a' END f_tenant_id, CASE WHEN n=3 THEN 1 ELSE NULL END f_delete_mark, 'yes'::text shifou_shoukong FROM generate_series(1,10) n ), versions AS ( SELECT 'version-'||n f_id, CASE WHEN n=4 THEN 'other' ELSE 'tenant-a' END f_tenant_id, CASE WHEN n=5 THEN 1 ELSE NULL END f_delete_mark, CASE WHEN n=6 THEN 'wrong-file' ELSE 'file-'||n END wenjian_id, CASE WHEN n=7 THEN 'V2' ELSE 'V1' END banbenhao, 'DOC'::text wenjian_bianhao, 'Document'::text wenjian_mingcheng, now() shengxiao_shijian, NULL::text shifou_shoukong FROM generate_series(1,10) n ) """; List> rows = query("selectDetails", sql -> fixture + sql .replace("public.dms_wenjian_bufa_shenqing_zixiang", "details") .replace("public.dms_wenjian_ziyuan", "resources") .replace("public.dms_wenjian_banben", "versions"), "request"); assertEquals(7, rows.size()); assertEquals("version-1", rows.get(0).get("resolved_version_id")); assertEquals("yes", rows.get(0).get("shifou_shoukong")); assertEquals("APPROVED-DOC", rows.get(0).get("wenjian_bianhao")); assertEquals("Approved document", rows.get(0).get("wenjian_mingcheng")); for (int i=1;i sql, "__codex_nonexistent_bufa_request__").isEmpty()); // EXPLAIN checks syntax/types without acquiring a row lock in the read-only transaction. assertFalse(query("lockRequests", sql -> "EXPLAIN " + sql, "__codex_nonexistent_bufa_request__").isEmpty()); } private List> query(String method, java.util.function.UnaryOperator transform, String requestId) throws Exception { Configuration config = new Configuration(); String resource = "mapper/DmsWenjianBufaPrintMapper.xml"; try (InputStream xml = getClass().getResourceAsStream("/" + resource)) { new XMLMapperBuilder(xml, config, resource, config.getSqlFragments()).parse(); } MappedStatement statement = config.getMappedStatement("jnpf.dmsMapper.DmsWenjianBufaPrintMapper." + method); Map parameters = new HashMap<>(); parameters.put("tenantId", "tenant-a"); parameters.put("requestId", requestId); parameters.put("taskId", requestId); BoundSql bound = statement.getBoundSql(parameters); try (Connection connection = DriverManager.getConnection(System.getenv("DMS_LEDGER_TEST_JDBC_URL"), System.getenv("DMS_LEDGER_TEST_DB_USER"), System.getenv("DMS_LEDGER_TEST_DB_PASSWORD"))) { connection.setReadOnly(true); connection.setAutoCommit(false); try (PreparedStatement ps = connection.prepareStatement(transform.apply(bound.getSql()))) { ps.setQueryTimeout(20); new DefaultParameterHandler(statement, parameters, bound).setParameters(ps); try (ResultSet rs = ps.executeQuery()) { List> result = new ArrayList<>(); while (rs.next()) { Map row = new HashMap<>(); for (int i=1;i<=rs.getMetaData().getColumnCount();i++) { row.put(rs.getMetaData().getColumnLabel(i), rs.getObject(i)); } result.add(row); } return result; } } finally { connection.rollback(); } } } }