fix(db): m008 test — backfill + archived sweep 실제 검증 + name COLLATE NOCASE
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@ export function up(db: Database.Database): void {
|
|||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
updated_at TEXT NOT NULL
|
updated_at TEXT NOT NULL
|
||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX idx_notebooks_name ON notebooks(name);
|
CREATE UNIQUE INDEX idx_notebooks_name ON notebooks(name COLLATE NOCASE);
|
||||||
|
|
||||||
ALTER TABLE notes ADD COLUMN notebook_id TEXT
|
ALTER TABLE notes ADD COLUMN notebook_id TEXT
|
||||||
REFERENCES notebooks(id) ON DELETE RESTRICT;
|
REFERENCES notebooks(id) ON DELETE RESTRICT;
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||||
import Database from 'better-sqlite3';
|
import Database from 'better-sqlite3';
|
||||||
import { runMigrations } from '../../src/main/db/migrations/index.js';
|
import { runMigrations } from '../../src/main/db/migrations/index.js';
|
||||||
|
import * as m001 from '../../src/main/db/migrations/m001_initial.js';
|
||||||
|
import * as m002 from '../../src/main/db/migrations/m002_due_date.js';
|
||||||
|
import * as m003 from '../../src/main/db/migrations/m003_soft_delete.js';
|
||||||
|
import * as m004 from '../../src/main/db/migrations/m004_status.js';
|
||||||
|
import * as m005 from '../../src/main/db/migrations/m005_ai_disabled.js';
|
||||||
|
import * as m006 from '../../src/main/db/migrations/m006_revisions.js';
|
||||||
|
import * as m007 from '../../src/main/db/migrations/m007_fts.js';
|
||||||
|
|
||||||
|
/** m001~m007 을 수동 적용하고 user_version=7 로 설정 (m008 만 미적용 상태) */
|
||||||
|
function applyUpTo7(db: Database.Database): void {
|
||||||
|
m001.up(db);
|
||||||
|
m002.up(db);
|
||||||
|
m003.up(db);
|
||||||
|
m004.up(db);
|
||||||
|
m005.up(db);
|
||||||
|
m006.up(db);
|
||||||
|
m007.up(db);
|
||||||
|
db.pragma('user_version = 7');
|
||||||
|
}
|
||||||
|
|
||||||
describe('m008 notebooks migration', () => {
|
describe('m008 notebooks migration', () => {
|
||||||
let db: Database.Database;
|
let db: Database.Database;
|
||||||
@@ -13,17 +32,32 @@ describe('m008 notebooks migration', () => {
|
|||||||
expect(row.name).toBe('기본');
|
expect(row.name).toBe('기본');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('기존 notes 가 default notebook 으로 마이그레이션 — fresh insert 는 NULL 유지', () => {
|
it('fresh insert 는 notebook_id NULL 유지 (migration UPDATE 는 migration 시점 행만 채움)', () => {
|
||||||
runMigrations(db);
|
runMigrations(db);
|
||||||
const defaultId = (db.prepare(`SELECT id FROM notebooks`).get() as { id: string }).id;
|
|
||||||
db.prepare(`INSERT INTO notes(id,raw_text,ai_status,created_at,updated_at,status) VALUES('n1','t','pending','2026-05-14','2026-05-14','active')`).run();
|
db.prepare(`INSERT INTO notes(id,raw_text,ai_status,created_at,updated_at,status) VALUES('n1','t','pending','2026-05-14','2026-05-14','active')`).run();
|
||||||
const r = db.prepare(`SELECT notebook_id FROM notes WHERE id='n1'`).get() as { notebook_id: string | null };
|
const r = db.prepare(`SELECT notebook_id FROM notes WHERE id='n1'`).get() as { notebook_id: string | null };
|
||||||
expect(r.notebook_id).toBeNull(); // m008 의 UPDATE 는 migration 시점의 NULL 만 채움
|
expect(r.notebook_id).toBeNull(); // m008 의 UPDATE 는 migration 시점의 NULL 만 채움
|
||||||
});
|
});
|
||||||
|
|
||||||
it('archived 잔류 노트가 있다면 completed 로 통합', () => {
|
it('pre-migration 노트가 runMigrations 후 default notebook 으로 backfill 됨', () => {
|
||||||
|
applyUpTo7(db);
|
||||||
|
// m008 적용 전 상태에서 노트 삽입 (notebook_id 컬럼 없음)
|
||||||
|
db.prepare(`INSERT INTO notes(id,raw_text,ai_status,created_at,updated_at,status) VALUES('pre1','hello','pending','2026-05-14','2026-05-14','active')`).run();
|
||||||
|
// m008 만 적용
|
||||||
runMigrations(db);
|
runMigrations(db);
|
||||||
expect(() => db.prepare(`SELECT COUNT(*) FROM notes WHERE status='archived'`).get()).not.toThrow();
|
const defaultId = (db.prepare(`SELECT id FROM notebooks`).get() as { id: string }).id;
|
||||||
|
const r = db.prepare(`SELECT notebook_id FROM notes WHERE id='pre1'`).get() as { notebook_id: string | null };
|
||||||
|
expect(r.notebook_id).toBe(defaultId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pre-migration archived 노트가 runMigrations 후 completed 로 변환됨', () => {
|
||||||
|
applyUpTo7(db);
|
||||||
|
// m008 적용 전 상태에서 archived 노트 삽입
|
||||||
|
db.prepare(`INSERT INTO notes(id,raw_text,ai_status,created_at,updated_at,status) VALUES('arc1','bye','pending','2026-05-14','2026-05-14','archived')`).run();
|
||||||
|
// m008 만 적용
|
||||||
|
runMigrations(db);
|
||||||
|
const r = db.prepare(`SELECT status FROM notes WHERE id='arc1'`).get() as { status: string };
|
||||||
|
expect(r.status).toBe('completed');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('UNIQUE index 가 같은 이름 중복 INSERT 거부', () => {
|
it('UNIQUE index 가 같은 이름 중복 INSERT 거부', () => {
|
||||||
@@ -32,4 +66,13 @@ describe('m008 notebooks migration', () => {
|
|||||||
db.prepare(`INSERT INTO notebooks(id,name,created_at,updated_at) VALUES('x','기본','t','t')`).run()
|
db.prepare(`INSERT INTO notebooks(id,name,created_at,updated_at) VALUES('x','기본','t','t')`).run()
|
||||||
).toThrow();
|
).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('UNIQUE index 가 대소문자 다른 이름도 중복으로 거부 (COLLATE NOCASE)', () => {
|
||||||
|
runMigrations(db);
|
||||||
|
// 영문 notebook 추가 후 대소문자 변형 삽입 시도
|
||||||
|
db.prepare(`INSERT INTO notebooks(id,name,created_at,updated_at) VALUES('nb1','Default','t','t')`).run();
|
||||||
|
expect(() =>
|
||||||
|
db.prepare(`INSERT INTO notebooks(id,name,created_at,updated_at) VALUES('nb2','default','t','t')`).run()
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user