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.
165 lines
4.9 KiB
Rust
165 lines
4.9 KiB
Rust
//! Smoke tests covering the 4 critical fixes consolidated in this crate.
|
|
|
|
use kei_atom_discovery::{
|
|
classify_wikilink, discover_atoms, parse_frontmatter, parse_wikilink, safe_join, AtomKind,
|
|
Error, WikilinkTarget, MAX_FRONTMATTER_BYTES,
|
|
};
|
|
use std::fs;
|
|
use std::path::Path;
|
|
use tempfile::tempdir;
|
|
|
|
const ATOM_OK: &str = r#"---
|
|
atom: kei-task::create
|
|
kind: command
|
|
version: "0.1.0"
|
|
input:
|
|
schema: schemas/create-input.json
|
|
output:
|
|
schema: schemas/create-output.json
|
|
side_effects:
|
|
- { op: write, domain: kei-task-db }
|
|
idempotent: false
|
|
stability: stable
|
|
keywords: [task, todo]
|
|
related:
|
|
- "[[kei-task::add-dependency]]"
|
|
- "[[rules/RULE 0.12]]"
|
|
---
|
|
# kei-task::create
|
|
Body text.
|
|
"#;
|
|
|
|
fn write_atom(root: &Path, crate_name: &str, verb: &str, body: &str) {
|
|
let atoms_dir = root.join(crate_name).join("atoms");
|
|
fs::create_dir_all(atoms_dir.join("schemas")).unwrap();
|
|
fs::write(atoms_dir.join(format!("{verb}.md")), body).unwrap();
|
|
fs::write(atoms_dir.join("schemas").join("create-input.json"), "{}").unwrap();
|
|
fs::write(atoms_dir.join("schemas").join("create-output.json"), "{}").unwrap();
|
|
}
|
|
|
|
// FIX 2 happy path — shared Frontmatter correctly parses and exposes typed kind
|
|
#[test]
|
|
fn discovery_returns_well_formed_atom_meta() {
|
|
let tmp = tempdir().unwrap();
|
|
write_atom(tmp.path(), "kei-task", "create", ATOM_OK);
|
|
let atoms = discover_atoms(tmp.path());
|
|
assert_eq!(atoms.len(), 1);
|
|
let a = &atoms[0];
|
|
assert_eq!(a.full_id, "kei-task::create");
|
|
assert_eq!(a.kind, AtomKind::Command);
|
|
assert_eq!(a.crate_name, "kei-task");
|
|
assert_eq!(a.verb, "create");
|
|
assert!(a.input_schema.is_some());
|
|
assert!(a.output_schema.is_some());
|
|
assert_eq!(a.side_effects.len(), 1);
|
|
assert_eq!(a.side_effects[0].op, "write");
|
|
assert_eq!(a.side_effects[0].domain, "kei-task-db");
|
|
assert!(a.body.contains("Body text"));
|
|
}
|
|
|
|
// FIX 1 — path traversal rejection via safe_join
|
|
#[test]
|
|
fn safe_join_rejects_parent_component() {
|
|
let tmp = tempdir().unwrap();
|
|
let err = safe_join(tmp.path(), "../etc/shadow").unwrap_err();
|
|
assert!(matches!(err, Error::PathParent(_)));
|
|
}
|
|
|
|
#[test]
|
|
fn safe_join_rejects_absolute_path() {
|
|
let tmp = tempdir().unwrap();
|
|
let err = safe_join(tmp.path(), "/etc/shadow").unwrap_err();
|
|
assert!(matches!(err, Error::PathAbsolute(_)));
|
|
}
|
|
|
|
#[test]
|
|
fn safe_join_accepts_plain_relative() {
|
|
let tmp = tempdir().unwrap();
|
|
let target = tmp.path().join("schemas");
|
|
fs::create_dir_all(&target).unwrap();
|
|
let joined = safe_join(tmp.path(), "schemas").unwrap();
|
|
assert!(joined.ends_with("schemas"));
|
|
}
|
|
|
|
// FIX 3 — YAML size cap enforced pre-parse
|
|
#[test]
|
|
fn frontmatter_size_cap_enforced() {
|
|
let huge = "x".repeat(MAX_FRONTMATTER_BYTES + 100);
|
|
let md = format!("---\n{huge}\n---\nbody\n");
|
|
let err = parse_frontmatter(&md).unwrap_err();
|
|
assert!(matches!(err, Error::FrontmatterTooLarge { .. }));
|
|
}
|
|
|
|
#[test]
|
|
fn frontmatter_missing_start_rejected() {
|
|
let err = parse_frontmatter("no fence\nbody\n").unwrap_err();
|
|
assert!(matches!(err, Error::FrontmatterMissingStart));
|
|
}
|
|
|
|
#[test]
|
|
fn frontmatter_missing_end_rejected() {
|
|
let err = parse_frontmatter("---\nkey: val\nno-end\n").unwrap_err();
|
|
assert!(matches!(err, Error::FrontmatterMissingEnd));
|
|
}
|
|
|
|
// FIX — symlink not followed (walkdir follow_links=false)
|
|
#[test]
|
|
fn discover_does_not_follow_symlinks() {
|
|
let tmp = tempdir().unwrap();
|
|
write_atom(tmp.path(), "kei-real", "create", ATOM_OK);
|
|
// Create a symlink named `kei-link` pointing at `kei-real`.
|
|
#[cfg(unix)]
|
|
{
|
|
let target = tmp.path().join("kei-real");
|
|
let link = tmp.path().join("kei-link");
|
|
std::os::unix::fs::symlink(&target, &link).unwrap();
|
|
}
|
|
let atoms = discover_atoms(tmp.path());
|
|
// Only 1 atom — symlinked tree is NOT walked.
|
|
assert_eq!(atoms.len(), 1, "symlink was traversed — follow_links must be false");
|
|
}
|
|
|
|
// Wikilink strictness
|
|
#[test]
|
|
fn wikilink_malformed_returns_none() {
|
|
assert_eq!(parse_wikilink("[[[foo]]"), None); // triple-bracket open
|
|
assert_eq!(parse_wikilink("foo"), None);
|
|
assert_eq!(parse_wikilink("[[ ]]"), None);
|
|
assert_eq!(
|
|
parse_wikilink("[[kei-task::create]]"),
|
|
Some("kei-task::create".to_string())
|
|
);
|
|
}
|
|
|
|
// classify_wikilink — 3 variants (Atom / Rule / Other)
|
|
#[test]
|
|
fn classify_atom_target() {
|
|
assert_eq!(
|
|
classify_wikilink("kei-task::create"),
|
|
WikilinkTarget::Atom("kei-task::create".into())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn classify_rule_targets() {
|
|
assert_eq!(
|
|
classify_wikilink("rules/RULE 0.12"),
|
|
WikilinkTarget::Rule("0.12".into())
|
|
);
|
|
assert_eq!(
|
|
classify_wikilink("rules/memory-protocol"),
|
|
WikilinkTarget::Rule("memory-protocol".into())
|
|
);
|
|
assert_eq!(
|
|
classify_wikilink("rule 0.12"),
|
|
WikilinkTarget::Rule("0.12".into())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn classify_other_target() {
|
|
assert_eq!(
|
|
classify_wikilink("random-note"),
|
|
WikilinkTarget::Other("random-note".into())
|
|
);
|
|
}
|