Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions pgpm/core/__tests__/export/export-meta.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* Tests for export-meta.ts configuration validation
*
* These tests validate that the export-meta config uses correct table names
* and includes all required fields for exporting meta_public and collections_public data.
*/

import { readFileSync } from 'fs';
import { join } from 'path';

describe('Export Meta Config Validation', () => {
let exportMetaSource: string;

beforeAll(() => {
// Read the source file to validate the config
const filePath = join(__dirname, '../../src/export/export-meta.ts');
exportMetaSource = readFileSync(filePath, 'utf-8');
});

describe('table name validation', () => {
it('should use singular table name "database_extension" (not plural "database_extensions")', () => {
// The actual table in collections_public is database_extension (singular)
// This test ensures the config uses the correct table name

// Check that the config defines the table correctly
expect(exportMetaSource).toContain("table: 'database_extension'");

// Ensure we're not using the incorrect plural form in the table definition
// Note: We check specifically in the config section, not the query section
const configSection = exportMetaSource.split('const config:')[1]?.split('interface ExportMetaParams')[0] || '';
expect(configSection).not.toContain("table: 'database_extensions'");
});

it('should query the correct table name in collections_public.database_extension', () => {
// The query should use the correct singular table name
expect(exportMetaSource).toContain('FROM collections_public.database_extension');
});

it('should include field table in queries', () => {
// The field table should be queried (it was missing before)
expect(exportMetaSource).toContain("queryAndParse('field'");
expect(exportMetaSource).toContain('FROM collections_public.field');
});
});

describe('collections_public tables', () => {
it('should include all required collections_public tables in config', () => {
const requiredTables = [
'database',
'database_extension',
'schema',
'table',
'field'
];

for (const table of requiredTables) {
expect(exportMetaSource).toContain(`queryAndParse('${table}'`);
}
});
});

describe('meta_public tables', () => {
it('should include all required meta_public tables in config', () => {
const requiredTables = [
'domains',
'sites',
'apis',
'apps',
'site_modules',
'site_themes',
'api_modules',
'api_extensions',
'api_schemata',
'rls_module',
'user_auth_module'
];

for (const table of requiredTables) {
expect(exportMetaSource).toContain(`queryAndParse('${table}'`);
}
});
});

describe('query order validation', () => {
it('should query database before dependent tables', () => {
// database should be queried first as other tables depend on it
const databaseQueryIndex = exportMetaSource.indexOf("queryAndParse('database',");
const schemaQueryIndex = exportMetaSource.indexOf("queryAndParse('schema',");
const tableQueryIndex = exportMetaSource.indexOf("queryAndParse('table',");

expect(databaseQueryIndex).toBeLessThan(schemaQueryIndex);
expect(schemaQueryIndex).toBeLessThan(tableQueryIndex);
});
});
});

describe('Export Meta Config Drift Detection', () => {
it('should document the expected table names in collections_public', () => {
// This test documents the expected table names that export-meta.ts should use
// If these change, the export-meta.ts config needs to be updated
const expectedCollectionsPublicTables = [
'database',
'database_extension', // NOT 'database_extensions' (plural)
'schema',
'table',
'field'
];

// Document the expected tables
expect(expectedCollectionsPublicTables).toContain('database_extension');
expect(expectedCollectionsPublicTables).not.toContain('database_extensions');
});

it('should document the expected table names in meta_public', () => {
// This test documents the expected table names that export-meta.ts should use
const expectedMetaPublicTables = [
'apis',
'api_extensions',
'api_modules',
'api_schemata',
'apps',
'domains',
'site_modules',
'site_themes',
'sites',
'rls_module',
'user_auth_module'
];

// Document the expected tables
expect(expectedMetaPublicTables.length).toBe(11);
});

it('should document the bug: config uses database_extensions but table is database_extension', () => {
// BUG DOCUMENTATION:
// In export-meta.ts, line 26, the config defines:
// table: 'database_extensions' (plural)
// But the actual table in db-meta-schema is:
// collections_public.database_extension (singular)
//
// This causes the Parser to generate INSERT statements with the wrong table name,
// which will fail when the exported SQL is replayed.
//
// FIX: Change line 26 in export-meta.ts from:
// table: 'database_extensions'
// to:
// table: 'database_extension'

const buggyConfigTableName = 'database_extensions';
const correctTableName = 'database_extension';

expect(buggyConfigTableName).not.toBe(correctTableName);
});
});
3 changes: 2 additions & 1 deletion pgpm/core/src/export/export-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const config: Record<string, TableConfig> = {
},
database_extension: {
schema: 'collections_public',
table: 'database_extensions',
table: 'database_extension',
fields: {
name: 'text',
database_id: 'uuid'
Expand Down Expand Up @@ -243,6 +243,7 @@ export const exportMeta = async ({ opts, dbname, database_id }: ExportMetaParams
await queryAndParse('database', `SELECT * FROM collections_public.database WHERE id = $1`);
await queryAndParse('schema', `SELECT * FROM collections_public.schema WHERE database_id = $1`);
await queryAndParse('table', `SELECT * FROM collections_public.table WHERE database_id = $1`);
await queryAndParse('field', `SELECT * FROM collections_public.field WHERE database_id = $1`);
await queryAndParse('domains', `SELECT * FROM meta_public.domains WHERE database_id = $1`);
await queryAndParse('apis', `SELECT * FROM meta_public.apis WHERE database_id = $1`);
await queryAndParse('sites', `SELECT * FROM meta_public.sites WHERE database_id = $1`);
Expand Down
114 changes: 114 additions & 0 deletions plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Export Meta Fix Plan

## Problem Statement

The `pgpm export` command's `meta_public` schema export is broken. The export functionality in `pgpm/core/src/export/export-meta.ts` has several issues that cause the generated SQL to fail when replayed.

## Issues Identified

### Critical Issues

1. **Table name mismatch (`database_extension`)**
- Location: `pgpm/core/src/export/export-meta.ts` line 26-27
- Config defines: `table: 'database_extensions'` (plural)
- Actual table: `collections_public.database_extension` (singular)
- Impact: Generated INSERT statements target non-existent table

2. **Missing columns in `user_auth_module` config**
- Actual table has columns not in export config:
- `audits_table_id`
- `audits_table_name`
- `verify_password_function`
- `check_password_function`
- Impact: Exported data is incomplete

3. **Missing `field` query**
- The `field` table is defined in config (lines 54-65) but never queried
- No `queryAndParse('field', ...)` call exists
- Impact: Field data is never exported

4. **Missing `site_metadata` table**
- Table exists in `meta_public` schema but not in export config
- Impact: Site metadata is never exported

### Secondary Issues

5. **Insert order vs FK constraints**
- Export queries `domains` before `apis`/`sites`
- `domains` has FK references to both `apis` and `sites`
- Code relies on `SET session_replication_role TO replica;` (requires superuser)
- Impact: May fail in non-superuser environments

6. **Type mismatches** (may or may not cause issues depending on csv-to-pg handling)
- `meta_public.sites.favicon`: actual `attachment`, config says `upload`
- `meta_public.domains.subdomain/domain`: actual `hostname`, config says `text`
- `meta_public.api_modules.data`: actual `pg_catalog.json`, config says `jsonb`
- `meta_public.site_modules.data`: actual `pg_catalog.json`, config says `jsonb`

## Proposed Solution

### Phase 1: Create Test Infrastructure

1. **Create a new test module** in `extensions/` directory
- Name: `@pgpm/export-meta-test` or similar
- Purpose: Test the export functionality with seed data

2. **Use `pgsql-test` for database testing**
- Leverage existing test infrastructure
- Transaction-isolated tests with automatic rollback

3. **Test approach**:
- Install `@pgpm/db-meta-schema` and `@pgpm/db-meta-modules`
- Seed representative data into all relevant tables
- Call `exportMeta()` to generate SQL
- Execute generated SQL in a fresh transaction
- Verify data integrity

### Phase 2: Fix Export Issues

1. Fix `database_extensions` -> `database_extension` table name
2. Add missing columns to `user_auth_module` config
3. Add `queryAndParse('field', ...)` call
4. Add `site_metadata` table to config and query
5. Fix insert order to respect FK dependencies
6. Update type mappings if needed

### Phase 3: Add Schema Drift Detection

1. Create a test that compares `information_schema.columns` against the config
2. Fail if config doesn't cover all required columns
3. Prevent future regressions when schema changes

## Test Module Structure

```
extensions/@pgpm/export-meta-test/
├── package.json
├── jest.config.js
├── __tests__/
│ ├── export-meta.test.ts # Main export tests
│ └── schema-drift.test.ts # Schema drift detection
└── seed/
└── seed-data.ts # Seed data helpers
```

## Dependencies

- `@pgpm/db-meta-schema` - Core schema definitions
- `@pgpm/db-meta-modules` - Module table definitions
- `pgsql-test` - Test infrastructure
- `@pgpmjs/core` - Export functionality to test

## Success Criteria

1. Export generates valid SQL that can be replayed
2. All tables in `meta_public` and `collections_public` are properly exported
3. FK constraints are respected (either via proper ordering or explicit handling)
4. Tests catch schema drift automatically
5. CI passes with new tests

## Questions to Resolve

1. Should this be a separate module or part of existing `db-meta-schema` tests?
2. Do we need to support non-superuser deployments (affects `session_replication_role` approach)?
3. Should we add the tests to `launchql-extensions` repo as well for parity?