Files
FabledCurator/frontend/test/components/backupTestSection.spec.js
T
bvandeusenandClaude Opus 5.5 75008ad048
CI and images / lint (push) Successful in 2s
CI and images / extension-version (push) Successful in 2s
CI and images / frontend-build (push) Successful in 22s
CI and images / extension-test (push) Successful in 24s
CI and images / backend-lint-and-test (push) Failing after 47s
CI and images / integration (push) Failing after 2m42s
CI and images / sign-extension (push) Skipped
CI and images / build-web (push) Skipped
CI and images / smoke-web (push) Skipped
CI and images / promote (push) Skipped
CI and images / refresh-dev (push) Skipped
CI and images / unattended-failure (push) Skipped
CI and images / build-agent (push) Skipped
feat: test a database backup without touching the install
"Test a backup" on the Backups card loads a dump into a separate, temporary
database on the same Postgres server, the same way a restore does, checks every
table against the row counts recorded with the dump, brings the copy up to this
version, and drops it. The live database is only read.

Every dump now records its row counts, read in the snapshot pg_dump dumps from
(pg_export_snapshot + --snapshot), so the comparison is exact. Off-site the
counts sit beside the dump as <name>.counts.json; locally they go in the
manifest file. A dump taken before this is compared with the live database
instead.

A test cut off by a restart is ended, and its database dropped, when the long
maintenance lane starts. A test doesn't block backups; a restore and a second
test wait for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:21:25 -05:00

60 lines
2.2 KiB
JavaScript

// @vitest-environment happy-dom
import { describe, it, expect } from 'vitest'
import BackupTestSection from '../../src/components/settings/BackupTestSection.vue'
import { useBackupStore } from '../../src/stores/backup.js'
import { mountWithStore } from '../support/mountComponent.js'
const FINISHED = {
id: 9, kind: 'test', status: 'error', started_at: new Date().toISOString(),
finished_at: new Date().toISOString(), duration_seconds: 300, size_bytes: 2048,
error: '1 table(s) came back different: post (7 in the dump, 6 restored).',
offsite: null,
manifest: {
phase: 'done',
dump: { ts: '20261107T030000Z' },
summary: '1 table(s) came back different: post (7 in the dump, 6 restored).',
counted: true,
mismatched: ['post'],
alembic_dump: '0120', alembic_now: '0121',
tables: [
{ table: 'artist', dump: 10, restored: 10, live: 12 },
{ table: 'post', dump: 7, restored: 6, live: 7 },
],
},
}
function mount(runs) {
return mountWithStore(BackupTestSection, () => {
useBackupStore().testRuns = runs
}, { props: { bucket: 'curator' } })
}
describe('BackupTestSection', () => {
it('leads with the last test\'s verdict and how its schema was brought up', () => {
const t = mount([FINISHED]).text()
expect(t).toContain('Last test')
expect(t).toContain('post (7 in the dump, 6 restored)')
expect(t).toContain('brought up from 0120 to 0121')
expect(t).not.toContain('artist')
})
it('lists every table on request, with the dump, restored and live counts', async () => {
const w = mount([FINISHED])
const btn = w.findAll('v-btn').find((b) => b.text().includes('each table'))
await btn.trigger('click')
const rows = w.findAll('tr').filter((r) => r.text().includes('post'))
expect(rows.some((r) => r.classes().includes('text-error'))).toBe(true)
expect(w.text()).toContain('artist')
})
it('says what a running test is doing', () => {
const running = {
...FINISHED, id: 10, status: 'running', finished_at: null, error: null,
manifest: { phase: 'loading', dump: { ts: '20261107T030000Z' } },
}
const t = mount([running, FINISHED]).text()
expect(t).toContain('loading it into a temporary database')
})
})