Phase C4 executor 의 마지막 `fix(test): clippy + fmt fixes` commit 이 test file 부분만 fmt 적용. workspace 전체 fmt 누락 발견 → cargo fmt --all 적용. 모든 import alphabetical reorder + line wrapping 정합. 추가 untracked artifact 동시 commit: - docs/superpowers/specs/2026-05-28-v0.20-ingest-log-spec.md (491 line, ACCEPT) - docs/superpowers/plans/2026-05-28-v0.20-ingest-log-plan.md (616 line, ACCEPT) workspace test: 1370 passed / 0 failed / 50 ignored, ingest_log_smoke green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95 lines
3.2 KiB
Rust
95 lines
3.2 KiB
Rust
//! 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:?}"
|
|
);
|
|
}
|
|
}
|