270 lines
8.6 KiB
Rust
270 lines
8.6 KiB
Rust
//! Integration tests for the Tauri command layer.
|
|
//!
|
|
//! These tests exercise `AppState` and the command logic end-to-end using a
|
|
//! real `Brittle<FsStore>` repository in a temp directory. Tauri IPC is not
|
|
//! involved — the functions under test are plain Rust.
|
|
|
|
use brittle_app::state::AppState;
|
|
use brittle_core::{Brittle, EntryType, Person};
|
|
|
|
fn open_state() -> (AppState, tempfile::TempDir) {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let state = AppState::new();
|
|
let brittle = Brittle::create(tmp.path()).unwrap();
|
|
*state.brittle.lock().unwrap() = Some(brittle);
|
|
(state, tmp)
|
|
}
|
|
|
|
// ── AppState ─────────────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn no_repo_open_returns_error() {
|
|
let state = AppState::new();
|
|
let err = state.with_repo(|_| Ok(())).unwrap_err();
|
|
assert_eq!(err, "no repository open");
|
|
}
|
|
|
|
#[test]
|
|
fn with_repo_propagates_brittle_errors() {
|
|
let (state, _tmp) = open_state();
|
|
// Trying to get a non-existent reference propagates the StoreError.
|
|
let err = state
|
|
.with_repo_read(|b| {
|
|
use brittle_core::model::ids::ReferenceId;
|
|
b.get_reference(ReferenceId::new())
|
|
})
|
|
.unwrap_err();
|
|
assert!(!err.is_empty());
|
|
}
|
|
|
|
// ── Repository lifecycle ──────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn create_and_reopen_repository() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let state = AppState::new();
|
|
|
|
// Create
|
|
{
|
|
let brittle = Brittle::create(tmp.path()).unwrap();
|
|
*state.brittle.lock().unwrap() = Some(brittle);
|
|
}
|
|
|
|
// Close
|
|
*state.brittle.lock().unwrap() = None;
|
|
assert!(state.with_repo_read(|_| Ok(())).is_err());
|
|
|
|
// Reopen
|
|
{
|
|
let brittle = Brittle::open(tmp.path()).unwrap();
|
|
*state.brittle.lock().unwrap() = Some(brittle);
|
|
}
|
|
assert!(state.with_repo_read(|_| Ok(())).is_ok());
|
|
}
|
|
|
|
// ── Reference CRUD ───────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn create_and_list_references() {
|
|
let (state, _tmp) = open_state();
|
|
|
|
state
|
|
.with_repo(|b| b.create_reference("turing1950", EntryType::Article))
|
|
.unwrap();
|
|
state
|
|
.with_repo(|b| b.create_reference("knuth1984", EntryType::Book))
|
|
.unwrap();
|
|
|
|
let refs = state.with_repo_read(|b| b.list_references()).unwrap();
|
|
assert_eq!(refs.len(), 2);
|
|
let keys: Vec<&str> = refs.iter().map(|r| r.cite_key.as_str()).collect();
|
|
assert!(keys.contains(&"turing1950"));
|
|
assert!(keys.contains(&"knuth1984"));
|
|
}
|
|
|
|
#[test]
|
|
fn delete_reference_removes_it() {
|
|
let (state, _tmp) = open_state();
|
|
|
|
let r = state
|
|
.with_repo(|b| b.create_reference("gone2024", EntryType::Misc))
|
|
.unwrap();
|
|
|
|
state.with_repo(|b| b.delete_reference(r.id)).unwrap();
|
|
|
|
let refs = state.with_repo_read(|b| b.list_references()).unwrap();
|
|
assert!(refs.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn set_and_remove_field() {
|
|
let (state, _tmp) = open_state();
|
|
|
|
let r = state
|
|
.with_repo(|b| b.create_reference("fields2024", EntryType::Article))
|
|
.unwrap();
|
|
|
|
state
|
|
.with_repo(|b| b.set_field(r.id, "title", "A Test Title"))
|
|
.unwrap();
|
|
|
|
let fetched = state.with_repo_read(|b| b.get_reference(r.id)).unwrap();
|
|
assert_eq!(
|
|
fetched.fields.get("title").map(String::as_str),
|
|
Some("A Test Title")
|
|
);
|
|
|
|
state.with_repo(|b| b.remove_field(r.id, "title")).unwrap();
|
|
|
|
let fetched2 = state.with_repo_read(|b| b.get_reference(r.id)).unwrap();
|
|
assert!(!fetched2.fields.contains_key("title"));
|
|
}
|
|
|
|
#[test]
|
|
fn search_references_filters_by_query() {
|
|
let (state, _tmp) = open_state();
|
|
|
|
state
|
|
.with_repo(|b| b.create_reference("turing1950", EntryType::Article))
|
|
.unwrap();
|
|
state
|
|
.with_repo(|b| b.create_reference("knuth1984", EntryType::Book))
|
|
.unwrap();
|
|
|
|
let results = state
|
|
.with_repo_read(|b| b.search_references("turing"))
|
|
.unwrap();
|
|
assert_eq!(results.len(), 1);
|
|
assert_eq!(results[0].cite_key, "turing1950");
|
|
}
|
|
|
|
// ── Library ───────────────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn create_nested_libraries_and_query_hierarchy() {
|
|
let (state, _tmp) = open_state();
|
|
|
|
let root = state.with_repo(|b| b.create_library("Root", None)).unwrap();
|
|
let child = state
|
|
.with_repo(|b| b.create_library("Child", Some(root.id)))
|
|
.unwrap();
|
|
|
|
let roots = state.with_repo_read(|b| b.list_root_libraries()).unwrap();
|
|
assert_eq!(roots.len(), 1);
|
|
assert_eq!(roots[0].id, root.id);
|
|
|
|
let children = state
|
|
.with_repo_read(|b| b.list_child_libraries(root.id))
|
|
.unwrap();
|
|
assert_eq!(children.len(), 1);
|
|
assert_eq!(children[0].id, child.id);
|
|
|
|
let ancestors = state
|
|
.with_repo_read(|b| b.get_library_ancestors(child.id))
|
|
.unwrap();
|
|
assert_eq!(ancestors.len(), 1);
|
|
assert_eq!(ancestors[0].id, root.id);
|
|
}
|
|
|
|
#[test]
|
|
fn add_reference_to_library_and_query() {
|
|
let (state, _tmp) = open_state();
|
|
|
|
let r = state
|
|
.with_repo(|b| b.create_reference("member2024", EntryType::Article))
|
|
.unwrap();
|
|
let lib = state.with_repo(|b| b.create_library("Lib", None)).unwrap();
|
|
|
|
state.with_repo(|b| b.add_to_library(lib.id, r.id)).unwrap();
|
|
|
|
let members = state
|
|
.with_repo_read(|b| b.list_library_references(lib.id))
|
|
.unwrap();
|
|
assert_eq!(members.len(), 1);
|
|
assert_eq!(members[0].id, r.id);
|
|
}
|
|
|
|
#[test]
|
|
fn force_delete_library_removes_subtree() {
|
|
let (state, _tmp) = open_state();
|
|
|
|
let root = state.with_repo(|b| b.create_library("Root", None)).unwrap();
|
|
state
|
|
.with_repo(|b| b.create_library("Child", Some(root.id)))
|
|
.unwrap();
|
|
|
|
state
|
|
.with_repo(|b| b.force_delete_library(root.id))
|
|
.unwrap();
|
|
|
|
let all = state.with_repo_read(|b| b.list_root_libraries()).unwrap();
|
|
assert!(all.is_empty());
|
|
}
|
|
|
|
// ── BibTeX export ─────────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn export_library_bibtex_contains_entries() {
|
|
let (state, _tmp) = open_state();
|
|
|
|
let mut r = state
|
|
.with_repo(|b| b.create_reference("turing1950", EntryType::Article))
|
|
.unwrap();
|
|
r.authors.push(Person::new("Turing"));
|
|
r.fields.insert(
|
|
"title".into(),
|
|
"Computing Machinery and Intelligence".into(),
|
|
);
|
|
r.fields.insert("journal".into(), "Mind".into());
|
|
r.fields.insert("year".into(), "1950".into());
|
|
let r = state.with_repo(|b| b.update_reference(r)).unwrap();
|
|
|
|
let lib = state.with_repo(|b| b.create_library("CS", None)).unwrap();
|
|
state.with_repo(|b| b.add_to_library(lib.id, r.id)).unwrap();
|
|
|
|
let (bibtex, errors) = state
|
|
.with_repo_read(|b| b.export_library_bibtex(lib.id))
|
|
.unwrap();
|
|
|
|
assert!(errors.is_empty());
|
|
assert!(bibtex.contains("@article{turing1950,"));
|
|
assert!(bibtex.contains("Computing Machinery and Intelligence"));
|
|
}
|
|
|
|
// ── Snapshot ──────────────────────────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn snapshot_and_discard_changes() {
|
|
let (state, _tmp) = open_state();
|
|
|
|
state
|
|
.with_repo(|b| b.create_reference("snap2024", EntryType::Misc))
|
|
.unwrap();
|
|
|
|
let snap = state.with_repo(|b| b.create_snapshot("baseline")).unwrap();
|
|
assert!(!snap.id.is_empty());
|
|
|
|
let snapshots = state.with_repo_read(|b| b.list_snapshots()).unwrap();
|
|
assert!(snapshots.iter().any(|s| s.message == "baseline"));
|
|
|
|
// Delete the reference (uncommitted change).
|
|
let r_id = state
|
|
.with_repo_read(|b| b.list_references())
|
|
.unwrap()
|
|
.into_iter()
|
|
.next()
|
|
.unwrap()
|
|
.id;
|
|
state.with_repo(|b| b.delete_reference(r_id)).unwrap();
|
|
|
|
assert!(state
|
|
.with_repo_read(|b| b.has_uncommitted_changes())
|
|
.unwrap());
|
|
|
|
// Discard → reference comes back.
|
|
state.with_repo(|b| b.discard_changes()).unwrap();
|
|
|
|
let refs = state.with_repo_read(|b| b.list_references()).unwrap();
|
|
assert_eq!(refs.len(), 1);
|
|
}
|