Files
UmbraAudio/tests/test_multi_project.py
2026-10-18 19:01:04 -07:00

137 lines
4.1 KiB
Python

# Copyright 2026 Umbra Tools (umbratools.com)
# SPDX-License-Identifier: Apache-2.0
"""Tests for multi-project readiness (Phase C2 of MULTI_PROJECT_PLAN.md).
Covers:
- dynamic active_project_id (A1) instead of hard-coded project_id=1
- WAL journal mode enabled on project + registry DBs (A2)
- bootstrap of a brand-new isolated project from a manuscript (B1)
- list_projects() excludes the legacy root .umbra (C1)
"""
from __future__ import annotations
import sqlite3
from pathlib import Path
import pytest
@pytest.fixture
def manuscript(temp_dir: Path) -> Path:
p = temp_dir / "Second_Book.txt"
p.write_text(
"# Chapter One\n\nHello world.\n\n"
"# Chapter Two\n\nGoodbye moon.\n\n",
encoding="utf-8",
)
return p
async def test_active_project_id_returns_first_row(temp_dir: Path) -> None:
from umbraaudio.database import Database
db = Database(temp_dir / "project.db")
await db.connect()
await db.create_project("Book A", "a.txt", "txt")
pid = await db.active_project_id()
assert pid == 1
await db.close()
def test_database_enables_wal(temp_dir: Path) -> None:
import asyncio
asyncio.run(_open_wal(temp_dir / "project.db"))
conn = sqlite3.connect(str(temp_dir / "project.db"))
assert conn.execute("PRAGMA journal_mode").fetchone()[0] == "wal"
conn.close()
async def _open_wal(path: Path) -> None:
from umbraaudio.database import Database
db = Database(path)
await db.connect()
await db.close()
def test_registry_enables_wal(registry_db_path: Path) -> None:
import asyncio
from umbraaudio.voices.registry import VoiceRegistry
async def _open() -> None:
reg = VoiceRegistry(registry_db_path)
await reg.connect()
await reg.close()
asyncio.run(_open())
conn = sqlite3.connect(str(registry_db_path))
assert conn.execute("PRAGMA journal_mode").fetchone()[0] == "wal"
conn.close()
def test_bootstrap_creates_isolated_project(manuscript: Path, monkeypatch) -> None:
import shutil
import tempfile
from umbraaudio import config, project_bootstrap
base = Path(tempfile.mkdtemp(prefix="umbra_base_"))
monkeypatch.setattr(config, "PROJECT_BASE", base)
result = project_bootstrap.create_project_from_manuscript(
manuscript, project_name="Second_Book"
)
assert result["slug"] == "Second_Book"
project_dir = base / "Second_Book"
assert (project_dir / ".umbra" / "project.db").exists()
# One project row, and default isolated chapters in the new DB.
conn = sqlite3.connect(str(project_dir / ".umbra" / "project.db"))
project_rows = conn.execute("SELECT id, name FROM project").fetchall()
assert len(project_rows) == 1
assert project_rows[0][1] == "Second_Book"
ch_count = conn.execute("SELECT COUNT(*) FROM chapters").fetchone()[0]
conn.close()
assert ch_count > 0
# Do not leave the temp base behind.
shutil.rmtree(base, ignore_errors=True)
def test_list_projects_excludes_root_umbra(monkeypatch) -> None:
import asyncio
import tempfile
from umbraaudio import config
base = Path(tempfile.mkdtemp(prefix="umbra_base_"))
# Create a legacy root .umbra with a project.db (the C1 case).
legacy = base / ".umbra"
legacy.mkdir(parents=True)
asyncio.run(_make_db(legacy / "project.db", name="DealHunterShip_4.0e_whole-to-audio.docx"))
# Create a real project dir.
real = base / "DealHunterShip_4.0e_whole-to-audio"
real.mkdir(parents=True)
asyncio.run(_make_db(real / ".umbra" / "project.db", name="Deal Hunter"))
monkeypatch.setattr(config, "PROJECT_BASE", base)
projects = config.list_projects()
slugs = {p["slug"] for p in projects}
assert "DealHunterShip_4.0e_whole-to-audio" in slugs
assert ".umbra" not in slugs
assert len(slugs) == 1
async def _make_db(path: Path, name: str) -> None:
from umbraaudio.database import Database
path.parent.mkdir(parents=True, exist_ok=True)
db = Database(path)
await db.connect()
await db.create_project(name, f"{name}.txt", "txt")
await db.close()