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>
172 lines
5.9 KiB
Rust
172 lines
5.9 KiB
Rust
//! Dogfood: auto-purge stored docs for filesystem-deleted files.
|
|
//!
|
|
//! Two tests:
|
|
//!
|
|
//! 1. `file_deletion_auto_purge` — ingest 2 files, delete one, re-ingest.
|
|
//! The re-ingest must report `purged_deleted_files = 1`, the deleted
|
|
//! file must no longer appear in `list_docs`, and lexical search for
|
|
//! its unique content must return no hits.
|
|
//!
|
|
//! 2. `include_scope_narrowing_does_not_purge` — ingest 2 files under a
|
|
//! wide glob, narrow the walker scope to only one file, re-ingest.
|
|
//! The narrowed ingest must NOT purge the out-of-scope file because
|
|
//! the file is still on disk (just excluded from this run). Protects
|
|
//! users against accidental data loss via config edits.
|
|
|
|
mod common;
|
|
|
|
use common::TestEnv;
|
|
use kebab_app::IngestOpts;
|
|
use kebab_app::ingest_with_config_opts;
|
|
use kebab_core::{DocFilter, DocumentStore, SearchMode, SearchQuery, SourceScope};
|
|
|
|
/// Helper: open the store via `TestEnv` and run `list_documents`.
|
|
fn list_doc_paths(env: &TestEnv) -> Vec<String> {
|
|
use kebab_store_sqlite::SqliteStore;
|
|
let store = SqliteStore::open(&env.config).unwrap();
|
|
store.run_migrations().unwrap();
|
|
store
|
|
.list_documents(&DocFilter::default())
|
|
.unwrap()
|
|
.into_iter()
|
|
.map(|d| d.doc_path.0)
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn file_deletion_auto_purge() {
|
|
let env = TestEnv::lexical_only();
|
|
|
|
// Write two .rs files into the workspace.
|
|
let a_path = env.workspace_root.join("a.rs");
|
|
let b_path = env.workspace_root.join("b.rs");
|
|
std::fs::write(&a_path, "// file a\nfn alpha() {}\n").unwrap();
|
|
std::fs::write(&b_path, "// file b\nfn bravo() {}\n").unwrap();
|
|
|
|
// First ingest — both must be New.
|
|
let first = ingest_with_config_opts(
|
|
env.config.clone(),
|
|
env.scope(),
|
|
false,
|
|
IngestOpts::default(),
|
|
)
|
|
.expect("first ingest must succeed");
|
|
// Only count the .rs files we added (there may be fixture files too).
|
|
let first_new = first.new;
|
|
assert!(first_new >= 2, "expected at least 2 new docs: {first:?}");
|
|
assert_eq!(
|
|
first.purged_deleted_files, 0,
|
|
"no purges on first ingest: {first:?}"
|
|
);
|
|
assert_eq!(first.errors, 0, "no errors on first ingest: {first:?}");
|
|
|
|
// Delete one file from the filesystem.
|
|
std::fs::remove_file(&b_path).expect("remove b.rs");
|
|
|
|
// Second ingest — scanned count drops by 1; b.rs should be purged.
|
|
let second = ingest_with_config_opts(
|
|
env.config.clone(),
|
|
env.scope(),
|
|
false,
|
|
IngestOpts::default(),
|
|
)
|
|
.expect("second ingest must succeed");
|
|
|
|
assert_eq!(
|
|
second.purged_deleted_files, 1,
|
|
"exactly 1 file should be purged: {second:?}"
|
|
);
|
|
assert_eq!(second.new, 0, "no new docs after deletion: {second:?}");
|
|
assert_eq!(second.updated, 0, "no updated docs: {second:?}");
|
|
assert_eq!(second.errors, 0, "no errors: {second:?}");
|
|
|
|
// b.rs must no longer appear in list_docs.
|
|
let doc_paths = list_doc_paths(&env);
|
|
let b_ws_path = "b.rs";
|
|
assert!(
|
|
!doc_paths.iter().any(|p| p == b_ws_path),
|
|
"b.rs must be gone from list_docs; got: {doc_paths:?}"
|
|
);
|
|
// a.rs must still be present.
|
|
let a_ws_path = "a.rs";
|
|
assert!(
|
|
doc_paths.iter().any(|p| p == a_ws_path),
|
|
"a.rs must still be in list_docs; got: {doc_paths:?}"
|
|
);
|
|
|
|
// Lexical search for b.rs's unique content returns no hits.
|
|
let app = env.app();
|
|
let query = SearchQuery {
|
|
text: "bravo".to_string(),
|
|
mode: SearchMode::Lexical,
|
|
k: 10,
|
|
filters: kebab_core::SearchFilters::default(),
|
|
};
|
|
let hits = app.search(query).expect("search must not error");
|
|
assert!(
|
|
hits.is_empty(),
|
|
"search for deleted file's content must return no hits; got: {hits:?}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn include_scope_narrowing_does_not_purge() {
|
|
let env = TestEnv::lexical_only();
|
|
|
|
// Write two .rs files.
|
|
let a_path = env.workspace_root.join("a_narrow.rs");
|
|
let b_path = env.workspace_root.join("b_narrow.rs");
|
|
std::fs::write(&a_path, "// narrow a\nfn alpha_narrow() {}\n").unwrap();
|
|
std::fs::write(&b_path, "// narrow b\nfn bravo_narrow() {}\n").unwrap();
|
|
|
|
// Wide scope: first ingest — both must be New.
|
|
let wide_scope = SourceScope {
|
|
root: env.workspace_root.clone(),
|
|
include: vec!["**/*.rs".to_string()],
|
|
exclude: env.config.workspace.exclude.clone(),
|
|
};
|
|
let first =
|
|
ingest_with_config_opts(env.config.clone(), wide_scope, false, IngestOpts::default())
|
|
.expect("first ingest (wide) must succeed");
|
|
assert!(first.new >= 2, "expected at least 2 new docs: {first:?}");
|
|
assert_eq!(
|
|
first.purged_deleted_files, 0,
|
|
"no purges on first ingest: {first:?}"
|
|
);
|
|
|
|
// Narrow scope: only a_narrow.rs in include — b_narrow.rs is still
|
|
// on disk but excluded from the walker scope.
|
|
let narrow_scope = SourceScope {
|
|
root: env.workspace_root.clone(),
|
|
include: vec!["a_narrow.rs".to_string()],
|
|
exclude: env.config.workspace.exclude.clone(),
|
|
};
|
|
let second = ingest_with_config_opts(
|
|
env.config.clone(),
|
|
narrow_scope,
|
|
false,
|
|
IngestOpts::default(),
|
|
)
|
|
.expect("second ingest (narrow) must succeed");
|
|
|
|
// CRITICAL: b_narrow.rs is still on disk — must NOT be purged.
|
|
assert_eq!(
|
|
second.purged_deleted_files, 0,
|
|
"scope-narrowing must NOT purge on-disk files; got: {second:?}"
|
|
);
|
|
assert_eq!(second.errors, 0, "no errors: {second:?}");
|
|
|
|
// b_narrow.rs must still exist in the store.
|
|
let doc_paths = list_doc_paths(&env);
|
|
let b_ws_path = "b_narrow.rs";
|
|
assert!(
|
|
doc_paths.iter().any(|p| p == b_ws_path),
|
|
"b_narrow.rs must still be in list_docs after scope narrowing; got: {doc_paths:?}"
|
|
);
|
|
// And the file must still be on disk.
|
|
assert!(
|
|
b_path.exists(),
|
|
"b_narrow.rs must still be on disk (we didn't delete it)"
|
|
);
|
|
}
|