/// End-to-end integration test using a real Brittle repository. /// /// Exercises the full workflow: create repo, add references with authors, /// organize in libraries, export BibTeX, create a snapshot, modify state, /// restore the snapshot, and verify everything reverted correctly. use brittle_core::{ AnnotationType, Brittle, BrittleError, Color, EntryType, Person, TextMarkupType, ValidationError, }; #[test] fn full_workflow() { let tmp = tempfile::tempdir().unwrap(); let mut db = Brittle::create(tmp.path()).unwrap(); // ---- Create references ---- let mut turing = db .create_reference("turing1950", EntryType::Article) .unwrap(); turing.authors.push(Person { family: "Turing".into(), given: Some("Alan M.".into()), prefix: None, suffix: None, }); turing.fields.insert( "title".into(), "Computing Machinery and Intelligence".into(), ); turing.fields.insert("journal".into(), "Mind".into()); turing.fields.insert("year".into(), "1950".into()); let turing = db.update_reference(turing).unwrap(); let mut knuth = db.create_reference("knuth1984", EntryType::Book).unwrap(); knuth.authors.push(Person::new("Knuth")); knuth.fields.insert("title".into(), "The TeXbook".into()); knuth .fields .insert("publisher".into(), "Addison-Wesley".into()); knuth.fields.insert("year".into(), "1984".into()); let knuth = db.update_reference(knuth).unwrap(); // ---- Organize in libraries ---- let cs = db.create_library("Computer Science", None).unwrap(); let ai = db.create_library("AI", Some(cs.id)).unwrap(); db.add_to_library(cs.id, turing.id).unwrap(); db.add_to_library(ai.id, turing.id).unwrap(); // multi-membership db.add_to_library(cs.id, knuth.id).unwrap(); // Both references are in CS. let cs_refs = db.list_library_references(cs.id).unwrap(); assert_eq!(cs_refs.len(), 2); // Turing is in both CS and AI. let turing_libs = db.list_reference_libraries(turing.id).unwrap(); assert_eq!(turing_libs.len(), 2); // ---- BibTeX export ---- let (bibtex, errors) = db.export_library_bibtex(cs.id).unwrap(); assert!(errors.is_empty(), "unexpected BibTeX errors: {errors:?}"); assert!(bibtex.contains("@article{turing1950,")); assert!(bibtex.contains("Turing, Alan M.")); assert!(bibtex.contains("Computing Machinery and Intelligence")); assert!(bibtex.contains("@book{knuth1984,")); // ---- Annotations ---- let ann = db .create_annotation( turing.id, 0, AnnotationType::TextMarkup { markup_type: TextMarkupType::Highlight, quads: vec![], color: Color::YELLOW, selected_text: Some("The Imitation Game".into()), }, Some("Key concept".into()), ) .unwrap(); let annotations = db.get_annotations(turing.id).unwrap(); assert_eq!(annotations.len(), 1); assert_eq!(annotations[0].content.as_deref(), Some("Key concept")); // ---- Snapshot ---- let snap = db .create_snapshot("Baseline with Turing and Knuth") .unwrap(); assert!(!snap.id.is_empty()); let snapshots = db.list_snapshots().unwrap(); // At least our named snapshot + the initial "Initialize Brittle repository" commit. assert!(snapshots.len() >= 2); assert!( snapshots .iter() .any(|s| s.message == "Baseline with Turing and Knuth") ); // ---- Modify state after snapshot ---- db.delete_reference(knuth.id).unwrap(); assert!(db.get_reference(knuth.id).is_err()); let cs_refs_after = db.list_library_references(cs.id).unwrap(); assert_eq!( cs_refs_after.len(), 1, "Knuth should have been removed from library" ); // ---- Restore snapshot ---- // The knuth deletion is written to disk but not committed — verify this. assert!(db.has_uncommitted_changes().unwrap()); // restore_snapshot errors on uncommitted changes; use discard_changes instead. db.discard_changes().unwrap(); // After restore, Knuth should be back. let knuth_restored = db.get_reference(knuth.id).unwrap(); assert_eq!(knuth_restored.cite_key, "knuth1984"); // CS library should have 2 members again. let cs_refs_restored = db.list_library_references(cs.id).unwrap(); assert_eq!(cs_refs_restored.len(), 2); // Inspect git log to verify history is human-readable. let snapshots_after = db.list_snapshots().unwrap(); assert!(!snapshots_after.is_empty()); } #[test] fn get_pdf_path_returns_error_when_no_pdf_attached() { let tmp = tempfile::tempdir().unwrap(); let mut db = Brittle::create(tmp.path()).unwrap(); let r = db.create_reference("nopdf2024", EntryType::Misc).unwrap(); let err = db.get_pdf_path(r.id).unwrap_err(); assert!( matches!( err, BrittleError::Validation(ValidationError::NoPdfAttached { .. }) ), "expected NoPdfAttached, got {err}" ); } #[test] fn get_pdf_path_returns_path_after_attach() { let tmp = tempfile::tempdir().unwrap(); let mut db = Brittle::create(tmp.path()).unwrap(); let r = db.create_reference("withpdf2024", EntryType::Misc).unwrap(); // Write a dummy PDF file. let source = tmp.path().join("dummy.pdf"); std::fs::write(&source, b"%PDF-1.4 dummy").unwrap(); db.attach_pdf(r.id, &source).unwrap(); let path = db.get_pdf_path(r.id).unwrap(); assert!(path.exists(), "PDF path {path:?} should exist on disk"); assert_eq!(path.extension().and_then(|e| e.to_str()), Some("pdf")); }