Les colonnes metadata_source_config et automation_settings sont désormais ajoutées par ALTER TABLE conditionnels sur les bases déjà initialisées, l'index books_isbn13_idx est créé via le chemin de migration, et un test couvre la base de données existante. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
110 lines
4.1 KiB
TypeScript
110 lines
4.1 KiB
TypeScript
import Database from "better-sqlite3";
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { DatabaseService } from "./database.service.js";
|
|
|
|
const previousDatabasePath = process.env.DATABASE_PATH;
|
|
const previousStorageDir = process.env.STORAGE_DIR;
|
|
const tempDirs: string[] = [];
|
|
|
|
afterEach(() => {
|
|
process.env.DATABASE_PATH = previousDatabasePath;
|
|
process.env.STORAGE_DIR = previousStorageDir;
|
|
for (const dir of tempDirs.splice(0)) {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
describe("database migrations", () => {
|
|
it("adds metadata columns to an existing comic-capable books table before creating dependent indexes", () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "readabook-migration-"));
|
|
tempDirs.push(dir);
|
|
const databasePath = join(dir, "readabook.sqlite");
|
|
const storageDir = join(dir, "storage");
|
|
|
|
const legacy = new Database(databasePath);
|
|
legacy.exec(`
|
|
CREATE TABLE users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
email TEXT NOT NULL UNIQUE,
|
|
name TEXT,
|
|
password_hash TEXT NOT NULL,
|
|
role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('admin','user')),
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE libraries (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
path TEXT NOT NULL,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE books (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
library_id INTEGER NOT NULL REFERENCES libraries(id) ON DELETE CASCADE,
|
|
title TEXT NOT NULL,
|
|
author TEXT,
|
|
description TEXT,
|
|
isbn TEXT,
|
|
language TEXT,
|
|
publisher TEXT,
|
|
published_date TEXT,
|
|
format TEXT NOT NULL CHECK (format IN ('epub','pdf','cbz','cbr')),
|
|
file_path TEXT NOT NULL UNIQUE,
|
|
cover_path TEXT,
|
|
file_size INTEGER NOT NULL,
|
|
file_mtime TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE progress (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
book_id INTEGER NOT NULL REFERENCES books(id) ON DELETE CASCADE,
|
|
locator TEXT NOT NULL,
|
|
percent INTEGER NOT NULL CHECK (percent >= 0 AND percent <= 100),
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
UNIQUE(user_id, book_id)
|
|
);
|
|
|
|
CREATE TABLE jobs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
type TEXT NOT NULL,
|
|
status TEXT NOT NULL CHECK (status IN ('queued','running','succeeded','failed')),
|
|
detail TEXT,
|
|
error TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
`);
|
|
legacy.close();
|
|
|
|
process.env.DATABASE_PATH = databasePath;
|
|
process.env.STORAGE_DIR = storageDir;
|
|
|
|
const database = new DatabaseService();
|
|
const bookColumns = database.sqlite.prepare("PRAGMA table_info(books)").all() as Array<{ name: string }>;
|
|
const bookIndexes = database.sqlite.prepare("PRAGMA index_list(books)").all() as Array<{ name: string }>;
|
|
const metadataSources = database.sqlite.prepare("SELECT provider FROM metadata_source_config ORDER BY priority").all() as Array<{ provider: string }>;
|
|
const automationSettings = database.sqlite.prepare("SELECT id, isbn_priority_enabled FROM automation_settings").get() as
|
|
| { id: number; isbn_priority_enabled: number }
|
|
| undefined;
|
|
|
|
expect(bookColumns.map((column) => column.name)).toContain("isbn13");
|
|
expect(bookColumns.map((column) => column.name)).toContain("identifiers_json");
|
|
expect(bookIndexes.map((index) => index.name)).toContain("books_isbn13_idx");
|
|
expect(metadataSources.map((source) => source.provider)).toEqual(["local", "openlibrary", "googlebooks", "bnf"]);
|
|
expect(automationSettings).toMatchObject({ id: 1, isbn_priority_enabled: 1 });
|
|
|
|
database.onModuleDestroy();
|
|
});
|
|
});
|