Add notes backup management
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { Hono } from 'hono';
|
||||
@@ -10,11 +11,12 @@ process.env.MEDIA_CACHE_PINNED_MAX_BYTES = '1000';
|
||||
const db = await import('./db.js');
|
||||
const { registerAdminToolsRoutes } = await import('./admin-tools.js');
|
||||
const app = new Hono();
|
||||
const backupDir = join(root, 'backups');
|
||||
const auth = async (c, next) => c.req.header('x-admin') === 'yes' ? next() : c.json({ ok: false }, 401);
|
||||
|
||||
beforeAll(async () => {
|
||||
await db.initDb();
|
||||
registerAdminToolsRoutes(app, { db, adminAuth: auth, backfillStep: async () => 3 });
|
||||
registerAdminToolsRoutes(app, { db, adminAuth: auth, backupDir, backfillStep: async () => 3 });
|
||||
await db.db.execute({ sql: 'INSERT INTO video_meta(id,card,hay,seen,updated_at) VALUES(?,?,?,?,?)', args: ['QualMiss001', JSON.stringify({ title: 'Missing lyric sample', channel: 'Choir' }), 'missing lyric sample choir', 1, Date.now()] });
|
||||
await db.db.execute({ sql: 'INSERT INTO video_meta(id,card,hay,seen,updated_at) VALUES(?,?,?,?,?)', args: ['Untimely001', JSON.stringify({ title: 'Untimed sample', channel: 'Band' }), 'untimed sample band', 1, Date.now()] });
|
||||
await db.db.execute({ sql: "INSERT INTO video_notes(video_id,kind,data,rev,source) VALUES(?, 'lyrics', ?, 1, 'user')", args: ['Untimely001', JSON.stringify({ lines: [{ t: null, text: 'A line' }, { t: 4, text: 'Next' }] })] });
|
||||
@@ -70,4 +72,25 @@ describe('lyrics quality queue', () => {
|
||||
expect(clean.removed).toBeGreaterThanOrEqual(1);
|
||||
expect((await db.db.execute({ sql: "SELECT COUNT(*) AS n FROM video_meta WHERE id='OldOrphan01'" })).rows[0].n).toBe(0);
|
||||
});
|
||||
|
||||
test('backup list verifies, previews, downloads, safely restores and exports a SQLite dump', async () => {
|
||||
const headers = { 'x-admin': 'yes', 'content-type': 'application/json' };
|
||||
await db.saveNote({ videoId: 'BackupSong01', kind: 'lyrics', data: { lines: [{ t: 1, text: 'Before' }] }, force: true, source: 'user' });
|
||||
const created = await (await app.request('/api/admin/backups', { method: 'POST', headers, body: '{}' })).json();
|
||||
expect(created.ok).toBe(true);
|
||||
await db.saveNote({ videoId: 'BackupSong01', kind: 'lyrics', data: { lines: [{ t: 1, text: 'After' }] }, force: true, source: 'user' });
|
||||
const list = await (await app.request('/api/admin/backups', { headers })).json();
|
||||
expect(list.backups.find(item => item.date === created.date)).toMatchObject({ valid: true, notes: created.notes });
|
||||
const preview = await (await app.request(`/api/admin/backups/${created.date}`, { headers })).json();
|
||||
expect(preview.diff.changed).toBe(1);
|
||||
const download = await app.request(`/api/admin/backups/${created.date}/download`, { headers });
|
||||
expect(download.status).toBe(200);
|
||||
const restored = await (await app.request(`/api/admin/backups/${created.date}/restore`, { method: 'POST', headers, body: JSON.stringify({ videoId: 'BackupSong01' }) })).json();
|
||||
expect(restored.ok).toBe(true);
|
||||
expect((await db.getNotes('BackupSong01')).lyrics.data.lines[0].text).toBe('Before');
|
||||
expect(existsSync(restored.safetySnapshot)).toBe(true);
|
||||
const dump = await app.request('/api/admin/backups/export/sqlite-dump', { headers: { 'x-admin': 'yes' } });
|
||||
expect(dump.status).toBe(200);
|
||||
expect(await dump.text()).toContain('CREATE TABLE');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user