package jnpf.dmsMapper; import jnpf.dmsDatabase.DmsUuidTypeHandler; import jnpf.dmsEntity.permission.entity.DmsFilePermissionEntity; import jnpf.dmsEntity.storage.approval.DmsApprovedItem; import jnpf.dmsPermission.DmsPermissionMatcher; import jnpf.dmsEntity.permission.model.DmsSubjectSet; 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.*; 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.util.*; import static org.junit.jupiter.api.Assertions.*; /** Real mapper/JDBC against connection-local tables. Never modifies customer rows or persistent DDL. */ @EnabledIfEnvironmentVariable(named="DMS_READ_TEST_URL",matches=".+") class DmsApprovedReadAccessPostgresTest { @Test void persistsSnapshotsReplacesOnlyOwnedReadAndRollsBackFailedReplacement() throws Exception { var ds=new UnpooledDataSource("org.postgresql.Driver",System.getenv("DMS_READ_TEST_URL"), System.getenv("DMS_READ_TEST_USER"),System.getenv("DMS_READ_TEST_PASSWORD")); var config=new Configuration(new Environment("read-test",new JdbcTransactionFactory(),ds)); config.setDefaultStatementTimeout(15); for (String name:List.of("DmsApprovedReadAccessMapper","DmsFilePermissionMapper","DmsApprovedBatchMapper")) { String path="mapper/"+name+".xml"; try(var input=getClass().getResourceAsStream("/"+path)) { String xml=new String(Objects.requireNonNull(input).readAllBytes(),StandardCharsets.UTF_8) .replace("public.dms_file_permissions","pg_temp.read_permissions") .replace("public.dms_approved_file_items","pg_temp.read_items") .replace("public.base_organize","pg_temp.read_departments"); 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 sql=connection.createStatement()) { sql.execute("CREATE TEMP TABLE read_permissions (LIKE public.dms_file_permissions INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES) ON COMMIT DROP"); sql.execute("ALTER TABLE pg_temp.read_permissions ADD COLUMN IF NOT EXISTS approved_org_scope TEXT"); sql.execute("CREATE TRIGGER permission_guard BEFORE UPDATE ON pg_temp.read_permissions FOR EACH ROW EXECUTE FUNCTION public.dms_permission_guard_permission_update()"); sql.execute("CREATE TEMP TABLE read_items (LIKE public.dms_approved_file_items INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES) ON COMMIT DROP"); sql.execute("ALTER TABLE pg_temp.read_items ADD COLUMN IF NOT EXISTS read_access_scope JSONB NOT NULL DEFAULT '[]'::jsonb"); sql.execute("CREATE TEMP TABLE read_departments (f_id text, f_tenant_id text, f_delete_mark integer, f_enabled_mark integer) ON COMMIT DROP"); sql.execute("INSERT INTO pg_temp.read_departments VALUES ('dept','tenant',NULL,1),('disabled','tenant',NULL,0),('foreign','other',NULL,1)"); } var mapper=session.getMapper(DmsApprovedReadAccessMapper.class); var permissions=session.getMapper(DmsFilePermissionMapper.class); assertEquals(List.of(new DmsApprovedReadAccessMapper.Department("dept")),mapper.departments("tenant",List.of("dept","disabled","foreign"))); var item=new DmsApprovedItem();item.setBatchId(UUID.randomUUID());item.setTenantId("tenant"); item.setSourceKey("audit:main");item.setChangeType("CREATE");item.setDocumentId(UUID.randomUUID());item.setFileVersionId(UUID.randomUUID()); item.setReadAccessScope("[\"dept--subOrg\"]"); var receipts=session.getMapper(DmsApprovedBatchMapper.class);receipts.insertItem(item); assertEquals(item.getReadAccessScope(),receipts.item("tenant","audit:main").getReadAccessScope()); // SQL interfaces return top-level arrays as text, while nested organizeSelect values stay arrays. try (var sql=connection.createStatement(); var result=sql.executeQuery( "SELECT read_access_scope::text AS scope, jsonb_build_array(jsonb_build_object('chayue_shouquan_fanwei_id',read_access_scope))::text AS rows FROM pg_temp.read_items WHERE source_key='audit:main'")) { assertTrue(result.next()); assertInstanceOf(String.class,result.getObject("scope")); assertInstanceOf(String.class,result.getObject("rows")); var json=new com.fasterxml.jackson.databind.ObjectMapper(); assertTrue(json.readTree((String)result.getObject("scope")).isArray()); assertEquals("dept--subOrg",json.readTree((String)result.getObject("rows")) .get(0).get("chayue_shouquan_fanwei_id").get(0).textValue()); } item.setSourceKey("audit:legacy");item.setReadAccessScope(null);receipts.insertItem(item); assertEquals("[]",receipts.item("tenant","audit:legacy").getReadAccessScope()); item.setSourceKey("audit:clear");item.setReadAccessScope("[]");receipts.insertItem(item); assertEquals("[]",receipts.item("tenant","audit:clear").getReadAccessScope()); var grant=new DmsFilePermissionEntity();grant.setTenantId("tenant");grant.setResourceId(item.getFileVersionId()); grant.setPrincipalId("dept");grant.setIncludeChildOrgs(false);grant.setApprovedOrgScope("CHILDREN"); grant.setSourceId("approved-read:"+item.getBatchId()+":"+item.getFileVersionId()); grant.setValidFrom(new Date(System.currentTimeMillis()-1000));grant.setGrantReason("read test"); var saved=mapper.insert(grant); assertNotNull(saved.getId());assertEquals("CHILDREN",saved.getApprovedOrgScope()); assertEquals("CHILDREN",permissions.selectByTenantAndId("tenant",saved.getId()).getApprovedOrgScope()); assertEquals("CHILDREN",permissions.selectByTenantAndIdForUpdate("tenant",saved.getId()).getApprovedOrgScope()); assertNull(permissions.selectByTenantAndId("other",saved.getId())); var candidates=permissions.selectCandidates("tenant",List.of("READ"),List.of(item.getFileVersionId())); assertEquals(1,candidates.size()); var subjects=new DmsSubjectSet();subjects.getImmediateParentOrgIds().add("dept"); assertTrue(new DmsPermissionMatcher().matchesPrincipal(candidates.getFirst(),subjects)); assertTrue(permissions.selectCandidates("tenant",List.of("DOWNLOAD"),List.of(item.getFileVersionId())).isEmpty()); // Unrelated manual permission on this exact version survives replacement. try(var sql=connection.createStatement()) { sql.execute("INSERT INTO pg_temp.read_permissions(id,tenant_id,resource_type,resource_id,resource_scope,principal_type,principal_id,include_child_orgs,action_code,source_type,source_id,valid_from,granted_by_type,granted_by_id) VALUES (uuidv7(),'tenant','FILE_VERSION','"+item.getFileVersionId()+"','SELF','ORG','dept',false,'READ','MANUAL','manual-test',CURRENT_TIMESTAMP,'USER','admin')"); } assertEquals(1,mapper.owned("tenant",item.getFileVersionId()).size()); var savepoint=connection.setSavepoint(); assertEquals(1,mapper.revoke("tenant",saved.getId())); grant.setSourceId("approved-read:replacement");grant.setPrincipalId(" "); assertThrows(Exception.class,()->mapper.insert(grant)); connection.rollback(savepoint);session.clearCache(); assertNull(mapper.owned("tenant",item.getFileVersionId()).getFirst().getRevokedAt()); assertEquals(1,mapper.revoke("tenant",saved.getId())); assertEquals(0,mapper.revoke("tenant",saved.getId())); assertEquals(1,permissions.selectCandidates("tenant",List.of("READ"),List.of(item.getFileVersionId())).size()); assertNotNull(mapper.owned("tenant",item.getFileVersionId()).getFirst().getRevokedAt()); } finally { session.rollback(true); } } } }