Files
kebab/crates/kebab-app/tests/reset_orphans.rs
altair823 685007789a style: cargo fmt --all (round 4 ingest log feature follow-up)
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>
2026-05-28 04:18:40 +00:00

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
);
}