69 lines
5.0 KiB
JavaScript
69 lines
5.0 KiB
JavaScript
/** Real, disposable LOCAL PostgreSQL restore regression; never connects to production. */
|
|
import assert from 'node:assert/strict'
|
|
import { randomUUID } from 'node:crypto'
|
|
import { readFileSync } from 'node:fs'
|
|
import { spawnSync } from 'node:child_process'
|
|
import { PURGE_RESTORE_FUNCTION_EVIDENCE_SQL, PURGE_RESTORE_SET_SQL, PURGE_RESTORE_RESET_SQL, assertPurgeRestoreFunctions } from './create-verified-production-backup.mjs'
|
|
|
|
const container = 'gongxiang-detail-test-20261012'
|
|
function docker(args, options = {}) {
|
|
const result = spawnSync('docker', ['exec', '-i', container, ...args], { encoding: 'utf8', maxBuffer: 2 * 1024 * 1024, ...options })
|
|
if (options.allowFailure) return result
|
|
assert.equal(result.status, 0, result.stderr || 'Local PostgreSQL command failed')
|
|
return result.stdout.trim()
|
|
}
|
|
const inspect = spawnSync('docker', ['inspect', '--format', '{{json .NetworkSettings.Ports}}', container], { encoding: 'utf8' })
|
|
assert.equal(inspect.status, 0)
|
|
assert.deepEqual(JSON.parse(inspect.stdout)['5432/tcp'], [{ HostIp: '127.0.0.1', HostPort: '15437' }], 'Only the existing disposable local database is allowed')
|
|
const user = docker(['sh', '-c', 'printf "%s" "$POSTGRES_USER"']) || 'postgres'
|
|
assert.match(user, /^[a-zA-Z0-9_]+$/)
|
|
const suffix = randomUUID().replaceAll('-', '').slice(0, 12)
|
|
const source = `gongxiangp2_restore_purge_source_${suffix}`
|
|
const broken = `gongxiangp2_restore_purge_broken_${suffix}`
|
|
const restored = `gongxiangp2_restore_purge_passed_${suffix}`
|
|
const dump = `/tmp/purge-restore-${suffix}.dump`
|
|
const psql = (database, sql) => docker(['psql', '-X', '-A', '-t', '-v', 'ON_ERROR_STOP=1', '-U', user, '-d', database, '-c', sql])
|
|
const restore = (database, sections = [], allowFailure = false) => docker(['pg_restore', '--exit-on-error', '--no-owner', '--no-privileges', ...sections.map(section => `--section=${section}`), '-U', user, '--dbname', database, dump], { allowFailure })
|
|
const schema = `
|
|
CREATE TABLE materials ("previewUrl" text, "zipFilePath" text, "videoSourceUrl" text, "videoPreview540Url" text, "videoPreviewClipUrl" text, "videoCoverUrl" text, "displayImages" text[]);
|
|
CREATE TABLE material_assets (file_url text);
|
|
CREATE TABLE material_skus (file_path text, preview_url text);
|
|
CREATE TABLE brand_knowledge (file_path text, preview_image_path text, preview_image_slices text[]);
|
|
CREATE TABLE system_settings ("headerLogoUrl" text, "loginLogoUrl" text);
|
|
CREATE TABLE performance_survey_work_items (image_urls jsonb);
|
|
CREATE TABLE work_review_versions (image_urls jsonb);
|
|
CREATE TABLE product_package_upload_jobs (assets jsonb, status text);
|
|
CREATE TABLE task_detail_change_requests (operations jsonb, status text);
|
|
INSERT INTO brand_knowledge VALUES ('uploads/%E4%BD%A0%E5%A5%BD.png', NULL, '{}');
|
|
INSERT INTO work_review_versions VALUES ('{"nested":["uploads/%E4%BD%A0%E5%A5%BD.png"]}');
|
|
`
|
|
const migration = readFileSync(new URL('../prisma/migrations/20261009_material_purge_reference_indexes/migration.sql', import.meta.url), 'utf8')
|
|
try {
|
|
for (const database of [source, broken, restored]) psql('postgres', `CREATE DATABASE ${database};`)
|
|
psql(source, schema)
|
|
psql(source, migration)
|
|
docker(['pg_dump', '--format=custom', '--no-owner', '--no-privileges', '-U', user, '--dbname', source, '--file', dump])
|
|
const originalHash = docker(['sha256sum', dump]).split(' ')[0]
|
|
const failed = restore(broken, [], true)
|
|
assert.notEqual(failed.status, 0, 'Original restore unexpectedly accepted the unqualified legacy functions')
|
|
assert.match(failed.stderr, /purge_decode_reference.*does not exist/)
|
|
restore(restored, ['pre-data'])
|
|
const before = JSON.parse(psql(restored, PURGE_RESTORE_FUNCTION_EVIDENCE_SQL))
|
|
assertPurgeRestoreFunctions(before)
|
|
psql(restored, PURGE_RESTORE_SET_SQL)
|
|
restore(restored, ['data', 'post-data'])
|
|
psql(restored, PURGE_RESTORE_RESET_SQL)
|
|
const after = JSON.parse(psql(restored, PURGE_RESTORE_FUNCTION_EVIDENCE_SQL))
|
|
assertPurgeRestoreFunctions(after)
|
|
assert.deepEqual(after, before, 'Function definition or final configuration changed')
|
|
const indexes = Number(psql(restored, "SELECT count(*) FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class c ON c.oid=i.indexrelid JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace WHERE n.nspname='public' AND c.relname LIKE '%purge_references_idx' AND i.indisvalid AND i.indisready;"))
|
|
assert.equal(indexes, 9, 'Every original GIN index must be restored and valid')
|
|
assert.equal(psql(restored, 'SELECT count(*) FROM brand_knowledge;'), '1')
|
|
assert.equal(psql(restored, 'SELECT count(*) FROM work_review_versions;'), '1')
|
|
assert.equal(docker(['sha256sum', dump]).split(' ')[0], originalHash, 'Original dump changed')
|
|
console.log(JSON.stringify({ passed: ['original empty-search-path restore failure reproduced', 'all pre-data/data/post-data restored', '9 original GIN indexes valid', 'original function definitions/configuration restored', 'dump hash unchanged'], productionWrites: 0 }))
|
|
} finally {
|
|
for (const database of [source, broken, restored]) psql('postgres', `DROP DATABASE IF EXISTS ${database} WITH (FORCE);`)
|
|
docker(['rm', '-f', dump])
|
|
}
|