@@ -759,4 +759,159 @@ relocatable = false
759759 }
760760 } ) ;
761761 } ) ;
762+
763+ describe ( 'Export with excludeCategories' , ( ) => {
764+ const DATABASE_ID = 'a1b2c3d4-e5f6-4708-b250-000000000001' ;
765+ const OPEN_EXTENSION_NAME = 'pets-open' ;
766+ const OPEN_META_EXTENSION_NAME = 'pets-open-svc' ;
767+ const POLICY_DEPLOY = 'schemas/pets_public/tables/pets/policies/pets_policy' ;
768+ let exportError : Error | null = null ;
769+
770+ const scaffoldModule = ( name : string , description : string ) => {
771+ const moduleDir = join ( exportWorkspaceDir , 'packages' , name ) ;
772+ mkdirSync ( moduleDir , { recursive : true } ) ;
773+ writeFileSync ( join ( moduleDir , 'package.json' ) , JSON . stringify ( {
774+ name,
775+ version : '1.0.0' ,
776+ description
777+ } , null , 2 ) ) ;
778+ writeFileSync ( join ( moduleDir , `${ name } .control` ) , `# ${ name } extension
779+ comment = '${ description } '
780+ default_version = '1.0.0'
781+ relocatable = false
782+ ` ) ;
783+ writeFileSync ( join ( moduleDir , 'pgpm.plan' ) , `%syntax-version=1.0.0
784+ %project=${ name }
785+ %uri=https://github.com/test/${ name }
786+ ` ) ;
787+ } ;
788+
789+ beforeAll ( async ( ) => {
790+ // Add a category column (the real db_migrate.sql_actions has one) and
791+ // tag a policy action as 'security'
792+ await pg . query ( `ALTER TABLE db_migrate.sql_actions ADD COLUMN IF NOT EXISTS category text DEFAULT 'ddl'` ) ;
793+ await pg . query (
794+ `INSERT INTO db_migrate.sql_actions (name, database_id, deploy, deps, content, revert, verify, category)
795+ VALUES (
796+ 'Create pets policy',
797+ $1,
798+ $2,
799+ ARRAY['schemas/pets_public/tables/pets/table']::text[],
800+ 'CREATE POLICY pets_policy ON pets_public.pets FOR SELECT USING (true);',
801+ 'DROP POLICY pets_policy ON pets_public.pets;',
802+ 'SELECT 1;',
803+ 'security'
804+ )
805+ ON CONFLICT (database_id, deploy) DO NOTHING` ,
806+ [ DATABASE_ID , POLICY_DEPLOY ]
807+ ) ;
808+
809+ const schemaResult = await pg . query (
810+ 'SELECT schema_name FROM metaschema_public.schema WHERE database_id = $1' ,
811+ [ DATABASE_ID ]
812+ ) ;
813+ const schemaNames = schemaResult . rows . map ( ( r : any ) => r . schema_name ) ;
814+
815+ scaffoldModule ( OPEN_EXTENSION_NAME , 'Exported pets database schema (security excluded)' ) ;
816+ scaffoldModule ( OPEN_META_EXTENSION_NAME , 'Exported pets service metadata (security excluded)' ) ;
817+
818+ const project = new PgpmPackage ( exportWorkspaceDir ) ;
819+
820+ try {
821+ await exportMigrations ( {
822+ project,
823+ options : {
824+ pg : dbConfig
825+ } ,
826+ dbInfo : {
827+ dbname : dbConfig . database ,
828+ databaseName : 'pets' ,
829+ database_ids : [ DATABASE_ID ]
830+ } ,
831+ author : 'test <test@test.local>' ,
832+ outdir : join ( exportWorkspaceDir , 'packages' ) ,
833+ schema_names : schemaNames ,
834+ extensionName : OPEN_EXTENSION_NAME ,
835+ extensionDesc : 'Exported pets database schema (security excluded)' ,
836+ metaExtensionName : OPEN_META_EXTENSION_NAME ,
837+ metaExtensionDesc : 'Exported pets service metadata (security excluded)' ,
838+ excludeCategories : [ 'security' ]
839+ } ) ;
840+ } catch ( err ) {
841+ exportError = err as Error ;
842+ }
843+ } , 180000 ) ;
844+
845+ it ( 'should have completed export without errors' , ( ) => {
846+ if ( exportError ) {
847+ console . error ( 'Export error:' , exportError ) ;
848+ }
849+ expect ( exportError ) . toBeNull ( ) ;
850+ } ) ;
851+
852+ it ( 'should omit security-categorized actions from the plan' , ( ) => {
853+ const planPath = join ( exportWorkspaceDir , 'packages' , OPEN_EXTENSION_NAME , 'pgpm.plan' ) ;
854+ expect ( existsSync ( planPath ) ) . toBe ( true ) ;
855+
856+ // The replacer renames pets_public -> pets_open_public in the exported module
857+ const planContent = readFileSync ( planPath , 'utf-8' ) ;
858+ expect ( planContent ) . not . toContain ( '/policies/pets_policy' ) ;
859+ // Non-security actions (category 'ddl' via the column default) are kept
860+ expect ( planContent ) . toContain ( 'schemas/pets_open_public/tables/pets/table' ) ;
861+ } ) ;
862+
863+ it ( 'should not write deploy files for excluded actions' , ( ) => {
864+ const policyDeployPath = join (
865+ exportWorkspaceDir , 'packages' , OPEN_EXTENSION_NAME , 'deploy' ,
866+ 'schemas/pets_open_public/tables/pets/policies/pets_policy.sql'
867+ ) ;
868+ expect ( existsSync ( policyDeployPath ) ) . toBe ( false ) ;
869+
870+ const tableDeployPath = join (
871+ exportWorkspaceDir , 'packages' , OPEN_EXTENSION_NAME , 'deploy' , 'schemas/pets_open_public/tables/pets/table.sql'
872+ ) ;
873+ expect ( existsSync ( tableDeployPath ) ) . toBe ( true ) ;
874+ } ) ;
875+
876+ it ( 'should include security actions when excludeCategories is not passed' , async ( ) => {
877+ const FULL_EXTENSION_NAME = 'pets-full' ;
878+ const FULL_META_EXTENSION_NAME = 'pets-full-svc' ;
879+
880+ const schemaResult = await pg . query (
881+ 'SELECT schema_name FROM metaschema_public.schema WHERE database_id = $1' ,
882+ [ DATABASE_ID ]
883+ ) ;
884+ const schemaNames = schemaResult . rows . map ( ( r : any ) => r . schema_name ) ;
885+
886+ scaffoldModule ( FULL_EXTENSION_NAME , 'Exported pets database schema (full)' ) ;
887+ scaffoldModule ( FULL_META_EXTENSION_NAME , 'Exported pets service metadata (full)' ) ;
888+
889+ const project = new PgpmPackage ( exportWorkspaceDir ) ;
890+
891+ await exportMigrations ( {
892+ project,
893+ options : {
894+ pg : dbConfig
895+ } ,
896+ dbInfo : {
897+ dbname : dbConfig . database ,
898+ databaseName : 'pets' ,
899+ database_ids : [ DATABASE_ID ]
900+ } ,
901+ author : 'test <test@test.local>' ,
902+ outdir : join ( exportWorkspaceDir , 'packages' ) ,
903+ schema_names : schemaNames ,
904+ extensionName : FULL_EXTENSION_NAME ,
905+ extensionDesc : 'Exported pets database schema (full)' ,
906+ metaExtensionName : FULL_META_EXTENSION_NAME ,
907+ metaExtensionDesc : 'Exported pets service metadata (full)'
908+ } ) ;
909+
910+ const planContent = readFileSync (
911+ join ( exportWorkspaceDir , 'packages' , FULL_EXTENSION_NAME , 'pgpm.plan' ) ,
912+ 'utf-8'
913+ ) ;
914+ expect ( planContent ) . toContain ( 'schemas/pets_full_public/tables/pets/policies/pets_policy' ) ;
915+ } , 180000 ) ;
916+ } ) ;
762917} ) ;
0 commit comments