use super::manifest::offline_dependency_message;
use super::provenance::annotate_run_json_provenance;
use super::run_args::replay_run_args;
use crate::exit_codes::ExitCodeVersion;
use assay_core::replay::{ReplayCoverage, ReplayManifest};
use std::path::PathBuf;
#[test]
fn offline_dependency_message_present_when_incomplete() {
let mut manifest = ReplayManifest::minimal("2.15.0".to_string());
manifest.replay_coverage = Some(ReplayCoverage {
complete_tests: vec!["a".to_string()],
incomplete_tests: vec!["b".to_string()],
reason: Some(std::collections::BTreeMap::from([(
"b".to_string(),
"judge cache missing".to_string(),
)])),
});
let msg = offline_dependency_message(&manifest).expect("message expected");
assert!(msg.contains("incomplete"));
assert!(msg.contains("b"));
}
#[test]
fn annotate_run_json_provenance_adds_fields() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("run.json");
std::fs::write(&path, r#"{"exit_code":0,"reason_code":""}"#).unwrap();
annotate_run_json_provenance(&path, "sha256:abc", "offline", Some("123")).unwrap();
let value: serde_json::Value = serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap();
assert_eq!(value["provenance"]["replay"], true);
assert_eq!(value["provenance"]["bundle_digest"], "sha256:abc");
assert_eq!(value["provenance"]["replay_mode"], "offline");
assert_eq!(value["provenance"]["source_run_id"], "123");
}
#[test]
fn replay_run_args_overrides_and_inherits_defaults() {
let args = replay_run_args(
PathBuf::from("custom/eval.yaml"),
Some(PathBuf::from("custom/trace.jsonl")),
PathBuf::from("custom/eval.db"),
true,
ExitCodeVersion::V1,
);
assert_eq!(args.config, PathBuf::from("custom/eval.yaml"));
assert_eq!(args.trace_file, Some(PathBuf::from("custom/trace.jsonl")));
assert_eq!(args.db, PathBuf::from("custom/eval.db"));
assert_eq!(args.quarantine_mode, "off");
assert!(args.refresh_cache);
assert!(args.no_cache);
assert!(args.judge.no_judge);
assert!(args.replay_strict);
assert_eq!(args.exit_codes, ExitCodeVersion::V1);
assert_eq!(args.embedder, "none");
assert_eq!(args.embedding_model, "text-embedding-3-small");
assert!(!args.strict);
assert!(!args.redact_prompts);
assert!(!args.no_verify);
}
mod replay_limit_summary_contract {
use crate::cli::commands::run_output::summary_from_outcome;
use crate::exit_codes::{ExitCodeVersion, ReasonCode, RunOutcome};
use assay_common::limits::LimitKind;
use assay_core::replay::bundle::ReplayIngestError;
fn summary_for(refusal: &ReplayIngestError, digest: &str) -> serde_json::Value {
let reason = ReasonCode::EReplayLimitExceeded;
let message = format!("replay bundle refused by an ingest ceiling: {refusal}");
let mut outcome = RunOutcome::from_reason(reason, Some(message), None);
outcome.exit_code = reason.exit_code_for(ExitCodeVersion::default());
let summary = summary_from_outcome(&outcome, true)
.with_seeds(None, None)
.with_replay_provenance(digest.to_string(), "offline", None);
serde_json::to_value(&summary).expect("summary serializes")
}
fn every_variant() -> Vec<ReplayIngestError> {
vec![
ReplayIngestError::SourceCeiling {
kind: LimitKind::SourceBytes,
limit: 100 * 1024 * 1024,
},
ReplayIngestError::MemberCeiling {
kind: LimitKind::MemberBytes,
limit: 500 * 1024 * 1024,
},
ReplayIngestError::PathTooLong { limit: 256 },
ReplayIngestError::TooManyEntries { limit: 100_000 },
ReplayIngestError::ManifestTooDeep { limit: 64 },
]
}
#[test]
fn the_reason_code_reaches_the_summary_verbatim() {
for refusal in every_variant() {
let v = summary_for(&refusal, "sha256:abc");
assert_eq!(
v["reason_code"], "E_REPLAY_LIMIT_EXCEEDED",
"every ingest ceiling reports the same public code: {v}"
);
}
}
#[test]
fn a_ceiling_refusal_is_a_config_class_exit() {
let v = summary_for(&every_variant()[0], "sha256:abc");
assert_eq!(v["exit_code"], 2, "{v}");
}
#[test]
fn the_refusal_carries_no_verdict() {
let v = summary_for(&every_variant()[0], "sha256:abc");
for claim in ["passed", "failed", "total", "verdict", "score", "compliant"] {
assert!(
v.get(claim).is_none(),
"a ceiling refusal must not assert `{claim}`; no test ran: {v}"
);
}
}
#[test]
fn a_post_snapshot_refusal_publishes_the_digest() {
for refusal in every_variant()
.into_iter()
.filter(|r| !matches!(r, ReplayIngestError::SourceCeiling { .. }))
{
let v = summary_for(&refusal, "sha256:deadbeef");
assert!(
v.to_string().contains("sha256:deadbeef"),
"the source digest must reach the summary: {v}"
);
assert!(
!v.to_string().contains("sha256:unknown"),
"the digest must not degrade to unknown when we hold it: {v}"
);
}
}
#[test]
fn a_source_overflow_publishes_an_unknown_digest() {
let refusal = ReplayIngestError::SourceCeiling {
kind: LimitKind::SourceBytes,
limit: 100 * 1024 * 1024,
};
let v = summary_for(&refusal, "sha256:unknown");
assert_eq!(v["reason_code"], "E_REPLAY_LIMIT_EXCEEDED", "{v}");
assert!(
v.to_string().contains("sha256:unknown"),
"an unread source must be reported as unknown, not omitted: {v}"
);
}
#[test]
fn the_message_reports_the_ceiling_and_nothing_the_archive_chose() {
for refusal in every_variant() {
let rendered = refusal.to_string();
let v = summary_for(&refusal, "sha256:abc");
let message = v["message"].as_str().unwrap_or_default();
assert!(
message.contains(&rendered),
"the typed refusal must be what is rendered: {message}"
);
for archive_controlled in ["../", "\u{1b}", "payload", ".tar", "manifest.json"] {
assert!(
!message.contains(archive_controlled),
"archive-controlled text `{archive_controlled}` reached the message: {message}"
);
}
}
}
#[test]
fn the_next_step_points_at_the_budget() {
let v = summary_for(&every_variant()[0], "sha256:abc");
let next = v["next_step"].as_str().unwrap_or_default();
assert!(
next.contains("ceiling") || next.contains("smaller bundle"),
"next step must name the budget lever: {next}"
);
}
}
mod workspace_containment {
use super::super::fs_ops::write_entries;
#[test]
fn an_absolute_entry_is_refused_and_writes_nothing() {
let ws = tempfile::tempdir().expect("tmp");
let outside = tempfile::tempdir().expect("tmp");
let escape = outside.path().join("owned.txt");
let entries = vec![(escape.to_string_lossy().into_owned(), b"payload".to_vec())];
let err = write_entries(ws.path(), &entries).expect_err("must refuse an absolute entry");
assert!(
!escape.exists(),
"an absolute entry was materialized outside the workspace at {}",
escape.display()
);
assert!(
err.to_string().contains("workspace-relative"),
"unexpected refusal: {err}"
);
}
#[test]
fn a_traversal_entry_is_refused_and_writes_nothing() {
let ws = tempfile::tempdir().expect("tmp");
let parent = ws.path().parent().expect("workspace has a parent");
let escape = parent.join("traversed.txt");
let _ = std::fs::remove_file(&escape);
let entries = vec![("../traversed.txt".to_string(), b"payload".to_vec())];
let err = write_entries(ws.path(), &entries).expect_err("must refuse traversal");
assert!(
!escape.exists(),
"a traversal entry escaped to {}",
escape.display()
);
assert!(
err.to_string().contains("non-literal path component"),
"unexpected refusal: {err}"
);
}
#[test]
fn ordinary_entries_are_written_under_the_workspace() {
let ws = tempfile::tempdir().expect("tmp");
let entries = vec![
("files/trace.jsonl".to_string(), b"[]".to_vec()),
("outputs/nested/run.json".to_string(), b"{}".to_vec()),
];
write_entries(ws.path(), &entries).expect("ordinary entries are written");
for (rel, expected) in &entries {
let target = ws.path().join(rel);
assert!(target.starts_with(ws.path()), "{rel} left the workspace");
assert_eq!(
&std::fs::read(&target).expect("entry written"),
expected,
"{rel} round-trips"
);
}
}
#[test]
fn a_refusal_does_not_leave_earlier_entries_behind() {
let ws = tempfile::tempdir().expect("tmp");
let entries = vec![
("files/ok.txt".to_string(), b"ok".to_vec()),
("../escape.txt".to_string(), b"no".to_vec()),
];
let err = write_entries(ws.path(), &entries).expect_err("must refuse");
assert!(err.to_string().contains("non-literal"), "{err}");
assert!(
ws.path().join("files/ok.txt").exists(),
"the earlier entry is written before the refusal; this test pins that fact"
);
}
}