KeiSeiKit-1.0/_primitives/_rust/kei-refactor-engine/tests/integration.rs
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

131 lines
4.5 KiB
Rust

//! Integration tests for kei-refactor-engine.
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
fn bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_kei-refactor-engine"))
}
fn sample_json(extra_manual: bool) -> String {
let mut items = vec![serde_json::json!({
"category": "blocks",
"severity": "medium",
"files": ["_blocks/a.md", "_blocks/b.md"],
"evidence": "shingle-Jaccard 72% overlap",
"suggested_fix": "keep better-cited",
"auto_resolvable": true
})];
if extra_manual {
items.push(serde_json::json!({
"category": "rules",
"severity": "high",
"files": ["rules/x.md", "rules/y.md"],
"evidence": "contradictory directive on 'push'",
"suggested_fix": "review both",
"auto_resolvable": false
}));
}
serde_json::json!({ "hit_count": items.len(), "conflicts": items }).to_string()
}
#[test]
fn plan_only_prints_markdown() {
let tmp = TempDir::new().unwrap();
let input = tmp.path().join("c.json");
fs::write(&input, sample_json(false)).unwrap();
let out = std::process::Command::new(bin())
.args(["--input"])
.arg(&input)
.output()
.unwrap();
assert!(out.status.success());
let md = String::from_utf8(out.stdout).unwrap();
assert!(md.contains("# Deep-sleep refactor plan"));
assert!(md.contains("Auto-apply"));
}
#[test]
fn manual_items_listed_but_not_in_autoresolve() {
let tmp = TempDir::new().unwrap();
let input = tmp.path().join("c.json");
let plan_out = tmp.path().join("plan.md");
let patch_out = tmp.path().join("plan-autoresolve.md");
fs::write(&input, sample_json(true)).unwrap();
let out = std::process::Command::new(bin())
.args(["--input"])
.arg(&input)
.args(["--apply-to-branch", "deep-sleep/test", "--plan-out"])
.arg(&plan_out)
.args(["--patch-out"])
.arg(&patch_out)
.output()
.unwrap();
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
let md = fs::read_to_string(&plan_out).unwrap();
assert!(md.contains("Requires human decision"));
let autoresolve = fs::read_to_string(&patch_out).unwrap();
// autoresolve must NOT reference rules/x.md from the manual item
assert!(!autoresolve.contains("rules/x.md"), "autoresolve leaked manual item: {}", autoresolve);
assert!(autoresolve.contains("_blocks/a.md"));
// And it must NOT claim to be a unified diff.
assert!(!autoresolve.contains("--- a/"));
assert!(!autoresolve.contains("+++ b/"));
}
#[test]
fn empty_conflicts_produce_valid_plan() {
let tmp = TempDir::new().unwrap();
let input = tmp.path().join("c.json");
fs::write(&input, r#"{"hit_count": 0, "conflicts": []}"#).unwrap();
let out = std::process::Command::new(bin())
.args(["--input"])
.arg(&input)
.output()
.unwrap();
assert!(out.status.success());
let md = String::from_utf8(out.stdout).unwrap();
assert!(md.contains("Total conflicts: **0**"));
}
#[test]
fn stdin_input_works() {
let tmp = TempDir::new().unwrap();
let _ = tmp; // kept for parity
let mut child = std::process::Command::new(bin())
.args(["--input", "-"])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.unwrap();
{
use std::io::Write;
let stdin = child.stdin.as_mut().unwrap();
stdin.write_all(sample_json(false).as_bytes()).unwrap();
}
let out = child.wait_with_output().unwrap();
assert!(out.status.success());
assert!(String::from_utf8(out.stdout).unwrap().contains("refactor plan"));
}
#[test]
fn autoresolve_header_shows_counts() {
let tmp = TempDir::new().unwrap();
let input = tmp.path().join("c.json");
let patch_out = tmp.path().join("plan-autoresolve.md");
fs::write(&input, sample_json(true)).unwrap();
std::process::Command::new(bin())
.args(["--input"])
.arg(&input)
.args(["--apply-to-branch", "deep-sleep/a", "--patch-out"])
.arg(&patch_out)
.output()
.unwrap();
let autoresolve = fs::read_to_string(&patch_out).unwrap();
assert!(autoresolve.contains("Auto-apply candidates: 1"));
assert!(autoresolve.contains("Human-decision items"));
// Retraction check: no unified-diff headers.
assert!(!autoresolve.contains("--- a/"));
assert!(!autoresolve.contains("+++ b/"));
}