use pushkin_core::envelope::Decision;
use pushkin_core::manifest::Manifest;
use pushkin_core::pipeline::{check_write, WriteRequest};
use pushkin_daemon::warm::WarmState;
const MANIFEST: &str = r#"
version = 1
canonical = "json-schema-2020-12"
authoring = "zod"
[[contracts]]
name = "user"
source = "contracts/user.zod.ts"
emit = ["zod"]
[[mappings]]
glob = "app/api/**/*.ts"
contracts = ["user"]
require = "boundary-validation"
[gates]
protected_paths = ["pushkin.toml"]
"#;
const NONCONFORMING: &str = "export async function POST(req: Request) {\n\
const body = await req.json();\n\
return Response.json({ name: body.name });\n\
}\n";
const CONFORMING: &str = "import { UserCreateSchema } from \"contracts/user.zod\";\n\
export async function POST(req: Request) {\n\
const body = UserCreateSchema.parse(await req.json());\n\
return Response.json(body);\n\
}\n";
const HANDLER_PATH: &str = "app/api/users/route.ts";
fn request(content: &str) -> WriteRequest {
WriteRequest {
file_path: HANDLER_PATH.to_owned(),
content: content.to_owned(),
}
}
#[test]
fn warm_check_result_matches_cold_check_result() {
let manifest = Manifest::parse(MANIFEST).unwrap();
let warm = WarmState::new(Manifest::parse(MANIFEST).unwrap());
for content in [NONCONFORMING, CONFORMING] {
let cold = check_write(&manifest, &request(content));
for pass in ["miss", "hit"] {
let warm_result = warm.check(&request(content));
assert_eq!(
warm_result.decision, cold.decision,
"{pass}: warm and cold must agree on decision"
);
assert_eq!(
warm_result.violations.len(),
cold.violations.len(),
"{pass}: same violation count"
);
for (w, c) in warm_result.violations.iter().zip(cold.violations.iter()) {
assert_eq!(w.rule, c.rule, "{pass}: same rule");
assert_eq!(w.line, c.line, "{pass}: same line");
assert_eq!(w.fix_hint, c.fix_hint, "{pass}: same fix hint");
}
}
}
}
#[test]
fn warm_repeat_check_is_served_from_memory() {
let warm = WarmState::new(Manifest::parse(MANIFEST).unwrap());
let first = warm.check(&request(NONCONFORMING));
assert_eq!(first.decision, Decision::Block);
assert_eq!(warm.hits(), 0, "first check is a miss");
let second = warm.check(&request(NONCONFORMING));
assert_eq!(second.decision, Decision::Block);
assert_eq!(warm.hits(), 1, "identical repeat must hit the memo");
let third = warm.check(&request(CONFORMING));
assert_eq!(third.decision, Decision::Allow);
assert_eq!(warm.hits(), 1, "changed content must miss");
}
#[test]
fn manifest_swap_invalidates_every_entry() {
let warm = WarmState::new(Manifest::parse(MANIFEST).unwrap());
let _ = warm.check(&request(NONCONFORMING));
let _ = warm.check(&request(NONCONFORMING));
assert_eq!(warm.hits(), 1);
let unmapped = MANIFEST.replace("app/api/**/*.ts", "elsewhere/**/*.ts");
warm.swap_manifest(Manifest::parse(&unmapped).unwrap());
let result = warm.check(&request(NONCONFORMING));
assert_eq!(
result.decision,
Decision::Allow,
"post-swap verdicts come from the new manifest, not the memo"
);
assert_eq!(warm.hits(), 1, "swap must clear the memo (this was a miss)");
}
#[test]
fn path_invalidation_is_keyed_not_global() {
let warm = WarmState::new(Manifest::parse(MANIFEST).unwrap());
let other = WriteRequest {
file_path: "app/api/orders/route.ts".to_owned(),
content: NONCONFORMING.to_owned(),
};
let _ = warm.check(&request(NONCONFORMING));
let _ = warm.check(&other);
warm.invalidate_path(HANDLER_PATH);
let _ = warm.check(&other);
assert_eq!(warm.hits(), 1, "untouched path stays warm");
let _ = warm.check(&request(NONCONFORMING));
assert_eq!(warm.hits(), 1, "invalidated path re-computes");
}
#[test]
fn watcher_invalidates_changed_file_state() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("pushkin.toml"), MANIFEST).unwrap();
let warm = WarmState::new(Manifest::parse(MANIFEST).unwrap());
let _watcher = warm.watch(dir.path()).unwrap();
let _ = warm.check(&request(NONCONFORMING));
let _ = warm.check(&request(NONCONFORMING));
assert_eq!(warm.hits(), 1, "memo warm before the disk change");
let unmapped = MANIFEST.replace("app/api/**/*.ts", "elsewhere/**/*.ts");
std::fs::write(dir.path().join("pushkin.toml"), &unmapped).unwrap();
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
let mut invalidated = false;
while std::time::Instant::now() < deadline {
let result = warm.check(&request(NONCONFORMING));
if result.decision == Decision::Allow {
invalidated = true;
break;
}
std::thread::sleep(std::time::Duration::from_millis(50));
}
assert!(
invalidated,
"the watcher must reload the manifest and drop stale verdicts"
);
}