//! Regression test for the twin-file idempotency bug. //! //! Identical-content files at different workspace paths share one //! `assets` row (`asset_id` = blake3 content hash, PRIMARY KEY). The //! old UPSERT `ON CONFLICT(asset_id) DO UPDATE SET workspace_path = //! excluded.workspace_path` made each twin overwrite the other's path //! on every ingest, so `get_asset_by_workspace_path(path1)` returned //! None (or the wrong twin) → re-process every time. //! //! Fix: `try_skip_unchanged` now uses `get_document_by_workspace_path` //! instead. `documents.workspace_path` is UNIQUE (V001) so each twin //! has its own stable document row. //! //! Assertion contract: //! 1st ingest → 2 New (one per twin) //! 2nd ingest → 0 New, 0 Updated, 2 Unchanged mod common; use common::TestEnv; use kebab_app::ingest_with_config; use kebab_core::IngestItemKind; #[test] fn twin_files_second_ingest_is_unchanged() { let env = TestEnv::lexical_only(); // Write two files with identical content at different paths. let pkg_a = env.workspace_root.join("pkg_a"); let pkg_b = env.workspace_root.join("pkg_b"); std::fs::create_dir_all(&pkg_a).unwrap(); std::fs::create_dir_all(&pkg_b).unwrap(); let content = b"# shared\nThis content is identical in both files.\n"; std::fs::write(pkg_a.join("__init__.py"), content).unwrap(); std::fs::write(pkg_b.join("__init__.py"), content).unwrap(); // First ingest — both files must be New. let first = ingest_with_config(env.config.clone(), env.scope(), false) .expect("first ingest must succeed"); assert_eq!(first.errors, 0, "first ingest: no errors; report={first:?}"); let items = first.items.as_ref().expect("items must be present"); let twin_items: Vec<_> = items .iter() .filter(|i| i.doc_path.0.ends_with("__init__.py")) .collect(); assert_eq!( twin_items.len(), 2, "first ingest: expected exactly 2 __init__.py items; items={items:?}" ); for item in &twin_items { assert_eq!( item.kind, IngestItemKind::New, "first ingest: each twin must be New; item={item:?}" ); } // Second ingest — same files, same content → both must be Unchanged. let second = ingest_with_config(env.config.clone(), env.scope(), false) .expect("second ingest must succeed"); assert_eq!( second.errors, 0, "second ingest: no errors; report={second:?}" ); assert_eq!( second.new, 0, "second ingest: no new docs; report={second:?}" ); assert_eq!( second.updated, 0, "second ingest: no updated docs (twin-file bug would set this to 2); report={second:?}" ); let second_items = second.items.as_ref().expect("items must be present"); let twin_items2: Vec<_> = second_items .iter() .filter(|i| i.doc_path.0.ends_with("__init__.py")) .collect(); assert_eq!( twin_items2.len(), 2, "second ingest: expected exactly 2 __init__.py items; items={second_items:?}" ); for item in &twin_items2 { assert_eq!( item.kind, IngestItemKind::Unchanged, "second ingest: each twin must be Unchanged; item={item:?}" ); } }