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>
138 lines
5.2 KiB
Rust
138 lines
5.2 KiB
Rust
//! Integration test for `kebab reset --orphans-only`.
|
|
//!
|
|
//! Verifies that stored docs outside the current walker scope are purged
|
|
//! from the store without removing any files from the filesystem.
|
|
//!
|
|
//! Test outline:
|
|
//! 1. Ingest 3 .rs files (a.rs, b.rs, c.rs) — all New.
|
|
//! 2. Narrow the config `include` to `["a.rs"]` only; b.rs and c.rs are
|
|
//! still on disk but outside the walker scope.
|
|
//! 3. Run `execute(ResetScope::OrphansOnly, &cfg)` — report must show
|
|
//! `orphans_purged == 2` and `purged_paths` contains b.rs + c.rs.
|
|
//! 4. `list docs` must show only a.rs.
|
|
//! 5. b.rs and c.rs must still exist on disk (no filesystem removal).
|
|
//! 6. Second reset → `orphans_purged == 0` (idempotent).
|
|
|
|
mod common;
|
|
|
|
use common::TestEnv;
|
|
use kebab_app::IngestOpts;
|
|
use kebab_app::reset::{ResetScope, execute};
|
|
use kebab_core::{DocFilter, DocumentStore, SourceScope};
|
|
|
|
/// Open the SqliteStore and list all `workspace_path` values.
|
|
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 reset_orphans_only_purges_out_of_scope_docs() {
|
|
let env = TestEnv::lexical_only();
|
|
|
|
// Write three .rs files into the workspace.
|
|
let a_path = env.workspace_root.join("a.rs");
|
|
let b_path = env.workspace_root.join("b.rs");
|
|
let c_path = env.workspace_root.join("c.rs");
|
|
std::fs::write(&a_path, "// file a\nfn alpha() {}\n").unwrap();
|
|
std::fs::write(&b_path, "// file b\nfn bravo() {}\n").unwrap();
|
|
std::fs::write(&c_path, "// file c\nfn charlie() {}\n").unwrap();
|
|
|
|
// Ingest all three with a wide scope.
|
|
let wide_scope = SourceScope {
|
|
root: env.workspace_root.clone(),
|
|
include: vec!["**/*.rs".to_string()],
|
|
exclude: env.config.workspace.exclude.clone(),
|
|
};
|
|
let first = kebab_app::ingest_with_config_opts(
|
|
env.config.clone(),
|
|
wide_scope,
|
|
false,
|
|
IngestOpts::default(),
|
|
)
|
|
.expect("first ingest must succeed");
|
|
// The fixture workspace may contain other .rs files — just assert we
|
|
// got at least 3 new docs (our a.rs, b.rs, c.rs).
|
|
assert!(first.new >= 3, "expected at least 3 new docs: {first:?}");
|
|
assert_eq!(first.errors, 0, "no errors on first ingest");
|
|
|
|
// Narrow config to include only a.rs; b.rs + c.rs are still on disk.
|
|
let mut narrow_cfg = env.config.clone();
|
|
narrow_cfg.workspace.exclude.clear();
|
|
// Re-point workspace root (already correct) and restrict include via
|
|
// the SourceScope in the connector. The config's `workspace.root` is
|
|
// used by `enumerate_orphans` to build its scope — we keep that
|
|
// pointing at the workspace root. We simulate narrowing by setting a
|
|
// glob that only matches a.rs.
|
|
//
|
|
// NOTE: `kebab_config::WorkspaceCfg` does not have an `include` field
|
|
// (it was removed in p9-fb-25). We narrow the scope via the walker
|
|
// exclude list: exclude b.rs and c.rs explicitly.
|
|
narrow_cfg.workspace.exclude = vec!["b.rs".to_string(), "c.rs".to_string()];
|
|
|
|
// Run orphans-only reset.
|
|
let report =
|
|
execute(ResetScope::OrphansOnly, &narrow_cfg).expect("orphans-only reset must succeed");
|
|
|
|
assert_eq!(
|
|
report.orphans_purged, 2,
|
|
"expected 2 orphans purged (b.rs + c.rs): {report:?}"
|
|
);
|
|
|
|
let mut purged: Vec<String> = report.purged_paths.iter().map(|p| p.0.clone()).collect();
|
|
purged.sort();
|
|
assert_eq!(
|
|
purged,
|
|
vec!["b.rs".to_string(), "c.rs".to_string()],
|
|
"purged_paths must list b.rs and c.rs in sorted order: {purged:?}"
|
|
);
|
|
|
|
// list docs must show only a.rs (and any pre-existing fixture files
|
|
// that are not excluded by the narrow config).
|
|
let doc_paths = list_doc_paths(&env);
|
|
// The narrow_cfg excludes b.rs + c.rs — they must no longer be in store.
|
|
assert!(
|
|
!doc_paths.iter().any(|p| p == "b.rs"),
|
|
"b.rs must be gone from store after orphans-only reset; got: {doc_paths:?}"
|
|
);
|
|
assert!(
|
|
!doc_paths.iter().any(|p| p == "c.rs"),
|
|
"c.rs must be gone from store after orphans-only reset; got: {doc_paths:?}"
|
|
);
|
|
assert!(
|
|
doc_paths.iter().any(|p| p == "a.rs"),
|
|
"a.rs must still be in store; got: {doc_paths:?}"
|
|
);
|
|
|
|
// Both b.rs and c.rs must still exist on the filesystem — no file
|
|
// removal is performed by orphans-only.
|
|
assert!(
|
|
b_path.exists(),
|
|
"b.rs must still be on disk after orphans-only reset"
|
|
);
|
|
assert!(
|
|
c_path.exists(),
|
|
"c.rs must still be on disk after orphans-only reset"
|
|
);
|
|
|
|
// Second reset must be idempotent: nothing left to purge.
|
|
let second = execute(ResetScope::OrphansOnly, &narrow_cfg)
|
|
.expect("second orphans-only reset must succeed");
|
|
assert_eq!(
|
|
second.orphans_purged, 0,
|
|
"second reset must be idempotent (orphans_purged == 0): {second:?}"
|
|
);
|
|
assert!(
|
|
second.purged_paths.is_empty(),
|
|
"second reset purged_paths must be empty: {:?}",
|
|
second.purged_paths
|
|
);
|
|
}
|