KeiSeiKit-1.0/_primitives/_rust/kei-decompose/tests/normalizer_unified.rs
Parfii-bot 0be354a920 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

56 lines
2.1 KiB
Rust

//! Cross-parser normalization — every parser yields the same Action shape.
//!
//! This catches drift: if a new adapter starts emitting half-filled Actions
//! or skips the source_format tag, downstream emit/dispatch breaks silently.
use std::path::Path;
use kei_decompose::normalizer::Action;
use kei_decompose::parsers::{registry, FormatParser};
fn fixture(name: &str) -> std::path::PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures")
.join(name)
}
fn parser(name: &str) -> Box<dyn FormatParser> {
registry().into_iter().find(|p| p.name() == name).expect("parser present")
}
fn assert_action_shape(a: &Action, expected_format: &str) {
assert!(!a.id.is_empty(), "{}: id empty", expected_format);
assert!(!a.title.is_empty(), "{}: title empty", expected_format);
assert_eq!(a.source_format, expected_format, "source_format mismatch");
assert!(!a.source_path.is_empty(), "{}: source_path empty", expected_format);
assert!(a.source_line > 0, "{}: source_line zero", expected_format);
// Body should reference the source path (defensive marker for downstream).
assert!(a.body.contains(&a.source_path), "{}: body missing source ref", expected_format);
}
#[test]
fn all_parsers_emit_uniform_action_shape() {
let cases = [
("research", "sample-research.md"),
("audit", "sample-audit.md"),
("sleep", "sample-sleep.md"),
("architecture", "sample-arch.md"),
("new-project", "sample-new-project.md"),
];
for (fmt, file) in cases {
let actions = parser(fmt).parse(&fixture(file)).unwrap();
assert!(!actions.is_empty(), "{}: yielded zero actions", fmt);
for a in &actions {
assert_action_shape(a, fmt);
}
}
}
#[test]
fn action_serializes_to_json_round_trip() {
let actions = parser("research").parse(&fixture("sample-research.md")).unwrap();
let json = serde_json::to_string(&actions).unwrap();
let back: Vec<Action> = serde_json::from_str(&json).unwrap();
assert_eq!(actions.len(), back.len());
assert_eq!(actions[0].id, back[0].id);
}