96 lines
3.6 KiB
TypeScript
96 lines
3.6 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import Database from 'better-sqlite3';
|
|
import { up } from '../../src/main/db/migrations/m006_revisions.js';
|
|
|
|
describe('m006 migration — note_revisions table', () => {
|
|
let db: Database.Database;
|
|
|
|
beforeEach(() => {
|
|
db = new Database(':memory:');
|
|
db.pragma('foreign_keys = ON');
|
|
db.exec(`
|
|
CREATE TABLE notes (
|
|
id TEXT PRIMARY KEY,
|
|
raw_text TEXT NOT NULL,
|
|
ai_title TEXT,
|
|
ai_summary TEXT,
|
|
ai_status TEXT NOT NULL
|
|
CHECK (ai_status IN ('pending','done','failed','disabled')),
|
|
ai_error TEXT,
|
|
ai_provider TEXT,
|
|
ai_generated_at TEXT,
|
|
title_edited_by_user INTEGER NOT NULL DEFAULT 0,
|
|
summary_edited_by_user INTEGER NOT NULL DEFAULT 0,
|
|
user_intent TEXT,
|
|
intent_prompted_at TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
due_date TEXT,
|
|
due_date_edited_by_user INTEGER NOT NULL DEFAULT 0,
|
|
deleted_at TEXT,
|
|
last_recalled_at TEXT,
|
|
recall_dismissed_at TEXT,
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
status_changed_at TEXT,
|
|
move_reason TEXT
|
|
);
|
|
INSERT INTO notes (id, raw_text, ai_status, created_at, updated_at)
|
|
VALUES ('a', 'first text', 'done', '2026-05-01T00:00:00Z', '2026-05-01T00:00:00Z'),
|
|
('b', 'second text', 'done', '2026-05-02T00:00:00Z', '2026-05-02T00:00:00Z');
|
|
`);
|
|
});
|
|
|
|
afterEach(() => { db.close(); });
|
|
|
|
it('creates note_revisions table with required columns', () => {
|
|
up(db);
|
|
const cols = db.prepare(`PRAGMA table_info(note_revisions)`).all() as Array<{ name: string }>;
|
|
const names = cols.map((c) => c.name);
|
|
expect(names).toEqual(
|
|
expect.arrayContaining(['rev_id', 'note_id', 'raw_text', 'edited_at', 'edited_by'])
|
|
);
|
|
});
|
|
|
|
it('creates idx_note_revisions_note_id index', () => {
|
|
up(db);
|
|
const idx = db.prepare(`PRAGMA index_list(note_revisions)`).all() as Array<{ name: string }>;
|
|
expect(idx.map((i) => i.name)).toContain('idx_note_revisions_note_id');
|
|
});
|
|
|
|
it('cascades on note delete (FK ON DELETE CASCADE)', () => {
|
|
up(db);
|
|
db.prepare(
|
|
`INSERT INTO note_revisions (note_id, raw_text, edited_at, edited_by)
|
|
VALUES ('a', 'manual rev', '2026-05-03T00:00:00Z', 'user')`
|
|
).run();
|
|
db.prepare(`DELETE FROM notes WHERE id=?`).run('a');
|
|
const rows = db.prepare(`SELECT * FROM note_revisions WHERE note_id=?`).all('a');
|
|
expect(rows).toHaveLength(0);
|
|
});
|
|
|
|
it("backfills existing notes as edited_by='capture' revisions", () => {
|
|
up(db);
|
|
const rows = db
|
|
.prepare(`SELECT note_id, raw_text, edited_at, edited_by FROM note_revisions ORDER BY note_id`)
|
|
.all() as Array<{ note_id: string; raw_text: string; edited_at: string; edited_by: string }>;
|
|
expect(rows).toHaveLength(2);
|
|
expect(rows[0]).toEqual({
|
|
note_id: 'a',
|
|
raw_text: 'first text',
|
|
edited_at: '2026-05-01T00:00:00Z',
|
|
edited_by: 'capture'
|
|
});
|
|
expect(rows[1]).toEqual({
|
|
note_id: 'b',
|
|
raw_text: 'second text',
|
|
edited_at: '2026-05-02T00:00:00Z',
|
|
edited_by: 'capture'
|
|
});
|
|
});
|
|
|
|
it('exports version=6', async () => {
|
|
const mod = await import('../../src/main/db/migrations/m006_revisions.js');
|
|
expect(mod.version).toBe(6);
|
|
});
|
|
});
|