Phase A of dogfood verification for the Magnotia -> Lumotia rebrand
cascade. The existing in-crate unit tests prove the migration copies
bytes correctly; this commit closes the gaps an atomiser-grade review
would flag.
Phase A.1 — end-to-end integration test (crates/storage/tests/legacy_db_migration.rs):
Seeds a real on-disk magnotia.db via lumotia_storage::init (which runs
every schema migration head-to-tail), inserts a transcript via the
public API, drops the pool, runs migrate_legacy_data_dir_with_pairs,
then re-opens the migrated lumotia.db and asserts the transcript is
queryable. Three scenarios covered:
1. Legacy-only -> migrate -> reopen -> row survives. Also verifies a
non-DB companion file is carried along by the directory rename.
2. Idempotency: first boot migrates, user writes new data, second
boot is a no-op and BOTH rows survive.
3. Both-paths-present: refuses to merge, target's empty DB is
preserved, legacy retained on disk as a backup.
Wires the test surface by renaming the previously-private
migrate_legacy_data_dir_inner to pub migrate_legacy_data_dir_with_pairs
(mirroring migrate_tauri_app_data_dir_with_paths in the sibling
tauri_app_data_migration module).
Phase A.2a — copy_dir_recursive hardening (crates/core/src/paths.rs):
Pre-existing footgun: the fall-through branch called std::fs::copy()
on any DirEntry that was not a symlink or a directory. On Unix that
includes FIFOs, sockets, and char/block device nodes. Opening a FIFO
for read with no writer attached blocks forever — a stale debug FIFO
in the user's ~/.magnotia tree would silently hang first launch.
The branch now explicitly distinguishes is_file() (real regular file
-> copy) from anything else (-> Err with ErrorKind::Unsupported,
naming the offending path). Migration becomes re-runnable once the
user cleans up the offending node. Same-filesystem rename via
std::fs::rename is atomic and unaffected; only the EXDEV fallback path
touches the new guard.
Phase A.2b — three adversarial probes (crates/core/src/paths.rs tests):
- FIFO inside the legacy tree: copy_dir_recursive must return an
Unsupported error WITHOUT hanging. Test bounded by a 5s wall clock
+ a worker thread so a regression to the old fall-through would
surface as a panic, not a stalled CI job.
- Unreadable file (mode 0000): copy_dir_recursive must surface
PermissionDenied, not silently skip. Skips its core assertion under
euid 0 (root bypasses DAC permissions, would mask the regression).
- Dangling symlink (target nonexistent): symlink is recreated at
destination with link target preserved verbatim; the migration
does NOT try to dereference and does NOT abort the rest of the copy.
Verification:
- cargo fmt --check: clean
- cargo clippy --workspace --all-targets -- -D warnings: clean
- cargo test --workspace: 409 passed, 0 failed (up from 405 pre-commit;
3 storage integration tests + 3 paths adversarial + 1 net carry-over)
41 lines
1.6 KiB
TOML
41 lines
1.6 KiB
TOML
[package]
|
|
name = "lumotia-storage"
|
|
version = "0.1.0"
|
|
edition = "2021"
|
|
description = "SQLite persistence, BM25 search, and file storage for Lumotia"
|
|
|
|
[dependencies]
|
|
lumotia-core = { path = "../core" }
|
|
|
|
# SQLite with compile-time checked queries
|
|
# default-features = false strips sqlx's `any`, `macros`, `migrate`, `json` —
|
|
# none of which this crate uses (it calls sqlx::query() / query_scalar()
|
|
# directly and runs its own migration machinery). Cuts ~40% of sqlx's
|
|
# compile graph, most visibly on Windows MSVC where each proc-macro crate
|
|
# (which `macros` pulls in) becomes a slow .dll link.
|
|
sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "sqlite"] }
|
|
|
|
# Async runtime
|
|
tokio = { version = "1", features = ["rt", "sync", "macros"] }
|
|
|
|
# Serialisation (DailyCompletionCount exposed to frontend via Tauri commands)
|
|
serde = { version = "1", features = ["derive"] }
|
|
|
|
# Logging
|
|
log = "0.4"
|
|
|
|
# Structured error derivation for lumotia_storage::Error.
|
|
thiserror = "1"
|
|
|
|
# UUIDs for profile + profile_terms ids (v7 random).
|
|
uuid = { version = "1", features = ["v4"] }
|
|
|
|
[dev-dependencies]
|
|
# Real-file tempdirs for integration tests that need an on-disk DB (the
|
|
# in-memory `sqlite::memory:` connection in src/database.rs unit tests
|
|
# doesn't cover the rename-then-reopen path the rebrand migration exercises).
|
|
tempfile = "3"
|
|
# `rt-multi-thread` is required for the `#[tokio::test(flavor = "multi_thread")]`
|
|
# variants that drive blocking lumotia_core::paths migrations from async tests.
|
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "sync", "macros"] }
|