package jnpf.dmsMapper; import com.fasterxml.jackson.databind.ObjectMapper; import jnpf.dmsDatabase.DmsUuidTypeHandler; import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity; import jnpf.dmsService.DmsVersionPropertiesService; import org.apache.ibatis.builder.xml.XMLMapperBuilder; import org.apache.ibatis.datasource.unpooled.UnpooledDataSource; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.time.LocalDate; import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; /** Uses the target schema's real constraints/guard on a connection-local table; always rolls back. */ @EnabledIfEnvironmentVariable(named = "DMS_PROPERTIES_TEST_URL", matches = ".+") class DmsVersionPropertiesPostgresTest { @Test void insertsSnapshotAtomicallyWithoutRelaxingExistingVersionGuards() throws Exception { var dataSource = new UnpooledDataSource("org.postgresql.Driver", System.getenv("DMS_PROPERTIES_TEST_URL"), System.getenv("DMS_PROPERTIES_TEST_USER"), System.getenv("DMS_PROPERTIES_TEST_PASSWORD")); var config = new Configuration(new Environment("properties-test", new JdbcTransactionFactory(), dataSource)); config.getTypeHandlerRegistry().register(DmsUuidTypeHandler.class); config.setDefaultStatementTimeout(15); String path = "mapper/DmsVersionPropertiesMapper.xml"; try (var input = getClass().getResourceAsStream("/" + path)) { assertNotNull(input); String xml = new String(input.readAllBytes(), StandardCharsets.UTF_8) .replace("public.dms_file_versions", "pg_temp.dms_version_properties_test"); new XMLMapperBuilder(new StringReader(xml), config, path, config.getSqlFragments()).parse(); } try (var session = new SqlSessionFactoryBuilder().build(config).openSession(false)) { try { var connection = session.getConnection(); try (var statement = connection.createStatement()) { statement.execute("CREATE TEMP TABLE dms_version_properties_test " + "(LIKE public.dms_file_versions INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES) ON COMMIT DROP"); statement.execute("CREATE TRIGGER metadata_guard BEFORE UPDATE ON pg_temp.dms_version_properties_test " + "FOR EACH ROW EXECUTE FUNCTION public.dms_guard_file_version_metadata_completion()"); } var mapper = session.getMapper(DmsVersionPropertiesMapper.class); var service = new DmsVersionPropertiesService(mapper); var json = new ObjectMapper(); var properties = json.readTree("{\"prepared_by_name\":\"管理员\",\"reviewers\":[{\"user_id\":\"859053987238827589\",\"name\":\"\"}]}"); var row = version("018f0000-0000-7000-8000-000000000701", 1); service.insertVersion(row, properties); assertEquals(properties, mapper.find("properties-test", row.getId()).getExtendedProperties()); service.verifyRetry("properties-test", row.getId(), properties); assertNull(mapper.find("another-tenant", row.getId())); var legacy = version("018f0000-0000-7000-8000-000000000702", 2); service.insertVersion(legacy, null); assertNull(mapper.find("properties-test", legacy.getId()).getExtendedProperties()); try (var statement = connection.createStatement(); var result = statement.executeQuery("SELECT count(*) FROM pg_temp.dms_version_properties_test WHERE created_at IS NOT NULL")) { assertTrue(result.next()); assertEquals(2, result.getInt(1)); } // Both the previously failing snapshot-only update and identity mutation stay forbidden. for (String assignment : new String[]{"extended_properties = '{}'::jsonb", "file_name = 'changed.docx'"}) { var savepoint = connection.setSavepoint(); try (var statement = connection.createStatement()) { SQLException error = assertThrows(SQLException.class, () -> statement.executeUpdate( "UPDATE pg_temp.dms_version_properties_test SET " + assignment + " WHERE version_no = 1")); assertEquals("55000", error.getSQLState()); } finally { connection.rollback(savepoint); } } session.clearCache(); assertEquals(properties, mapper.find("properties-test", row.getId()).getExtendedProperties()); } finally { session.rollback(true); } } } private DmsFileVersionEntity version(String id, long number) { var row = new DmsFileVersionEntity(); row.setId(UUID.fromString(id)); row.setTenantId("properties-test"); row.setDocumentId(UUID.fromString("018f0000-0000-7000-8000-000000000700")); row.setVersionNo(number); row.setVersionLabel(number == 1 ? "D(00)" : "D(01)"); row.setFileName("test.docx"); row.setSizeBytes(10L); row.setChecksumSha256("a".repeat(64)); row.setPreparedBy("test-user"); row.setPreparedOn(LocalDate.of(2026, 10, 9)); row.setShifouShoukong("yes"); row.setCreatedBy("test-user"); return row; } }