assay-cli 3.31.0

Policy-as-code gate for MCP agent tool calls, with verifiable evidence and Linux kernel enforcement.
use super::constants::{EVENT_SOURCE, EVENT_TYPE, SOURCE_SURFACE};
use super::{cmd_pydantic_case_result, PydanticCaseResultArgs};
use crate::exit_codes;
use anyhow::Result;
use assay_evidence::bundle::BundleReader;
use std::fs::{self, File};

#[test]
fn import_writes_verifiable_case_result_bundle() {
    let dir = tempfile::tempdir().unwrap();
    let input = dir.path().join("pydantic-case-results.jsonl");
    let output = dir.path().join("pydantic-case-results.tar.gz");
    fs::write(
        &input,
        concat!(
            r#"{"schema":"pydantic-evals.report-case-result.export.v1","framework":"pydantic_evals","surface":"evaluation_report.cases.case_result","case_name":"case-hello","source_case_name":"source-hello","source_ref":"fixture:pydantic-case-results","results":[{"kind":"assertion","evaluator_name":"EqualsExpected","passed":true},{"kind":"score","evaluator_name":"ExactScorePoints","score":1.0,"reason":"maximum points"}],"timestamp":"2026-05-02T08:00:00Z"}"#,
            "\n",
            r#"{"schema":"pydantic-evals.report-case-result.export.v1","framework":"pydantic_evals","surface":"evaluation_report.cases.case_result","case_name":"case-bye","results":[{"kind":"assertion","evaluator_name":"EqualsExpected","passed":false},{"kind":"score","evaluator_name":"ExactScorePoints","score":0.25}],"timestamp":"2026-05-02T08:05:00Z"}"#,
            "\n"
        ),
    )
    .unwrap();

    let code = cmd_pydantic_case_result(PydanticCaseResultArgs {
        input: input.clone(),
        bundle_out: output.clone(),
        source_artifact_ref: Some("pydantic-case-results.jsonl".to_string()),
        run_id: "pydantic_test".to_string(),
        import_time: Some("2026-05-03T12:00:00Z".to_string()),
    })
    .unwrap();
    assert_eq!(code, exit_codes::OK);

    let reader = BundleReader::open(File::open(output).unwrap()).unwrap();
    assert_eq!(reader.manifest().event_count, 2);
    let events = reader.events().collect::<Result<Vec<_>>>().unwrap();
    assert_eq!(events[0].type_, EVENT_TYPE);
    assert_eq!(events[0].source, EVENT_SOURCE);
    assert_eq!(events[0].payload["source_surface"], SOURCE_SURFACE);
    assert_eq!(events[0].payload["case_result"]["case_name"], "case-hello");
    assert_eq!(
        events[0].payload["case_result"]["source_case_name"],
        "source-hello"
    );
    assert_eq!(
        events[0].payload["case_result"]["results"][0]["passed"],
        true
    );
    assert_eq!(events[0].payload["case_result"]["results"][1]["score"], 1.0);
    assert_eq!(
        events[0].payload["case_result"]["timestamp"],
        "2026-05-02T08:00:00.000Z"
    );

    let serialized = serde_json::to_string(&events).unwrap();
    assert!(!serialized.contains("expected_output"));
    assert!(!serialized.contains("\"output\""));
    assert!(!serialized.contains("trace_id"));
    assert!(!serialized.contains("span_id"));
    assert!(!serialized.contains("logfire"));
}

#[test]
fn import_rejects_raw_reportcase_fields() {
    let dir = tempfile::tempdir().unwrap();
    let input = dir.path().join("pydantic-case-results.jsonl");
    let output = dir.path().join("pydantic-case-results.tar.gz");
    fs::write(
        &input,
        r#"{"schema":"pydantic-evals.report-case-result.export.v1","framework":"pydantic_evals","surface":"evaluation_report.cases.case_result","case_name":"case-leaky","expected_output":"secret","output":"completion","results":[{"kind":"assertion","evaluator_name":"EqualsExpected","passed":true}],"timestamp":"2026-05-02T08:00:00Z"}"#,
    )
    .unwrap();

    let err = cmd_pydantic_case_result(PydanticCaseResultArgs {
        input,
        bundle_out: output,
        source_artifact_ref: None,
        run_id: "pydantic_test".to_string(),
        import_time: Some("2026-05-03T12:00:00Z".to_string()),
    })
    .unwrap_err();
    assert!(err
        .to_string()
        .contains("unsupported top-level key \"expected_output\""));
}

#[test]
fn import_rejects_non_boolean_assertion_value() {
    let dir = tempfile::tempdir().unwrap();
    let input = dir.path().join("pydantic-case-results.jsonl");
    let output = dir.path().join("pydantic-case-results.tar.gz");
    fs::write(
        &input,
        r#"{"schema":"pydantic-evals.report-case-result.export.v1","framework":"pydantic_evals","surface":"evaluation_report.cases.case_result","case_name":"case-hello","results":[{"kind":"assertion","evaluator_name":"EqualsExpected","passed":"true"}],"timestamp":"2026-05-02T08:00:00Z"}"#,
    )
    .unwrap();

    let err = cmd_pydantic_case_result(PydanticCaseResultArgs {
        input,
        bundle_out: output,
        source_artifact_ref: None,
        run_id: "pydantic_test".to_string(),
        import_time: Some("2026-05-03T12:00:00Z".to_string()),
    })
    .unwrap_err();
    assert!(err.to_string().contains("passed must be a boolean"));
}

#[test]
fn import_rejects_null_optional_fields() {
    let dir = tempfile::tempdir().unwrap();
    let input = dir.path().join("pydantic-case-results.jsonl");
    let output = dir.path().join("pydantic-case-results.tar.gz");
    fs::write(
        &input,
        r#"{"schema":"pydantic-evals.report-case-result.export.v1","framework":"pydantic_evals","surface":"evaluation_report.cases.case_result","case_name":"case-hello","source_ref":null,"results":[{"kind":"assertion","evaluator_name":"EqualsExpected","passed":true}],"timestamp":"2026-05-02T08:00:00Z"}"#,
    )
    .unwrap();

    let err = cmd_pydantic_case_result(PydanticCaseResultArgs {
        input,
        bundle_out: output,
        source_artifact_ref: None,
        run_id: "pydantic_test".to_string(),
        import_time: Some("2026-05-03T12:00:00Z".to_string()),
    })
    .unwrap_err();
    assert!(err
        .to_string()
        .contains("source_ref must be a string when present"));
}

#[test]
fn import_rejects_synthetic_case_id_ref() {
    let dir = tempfile::tempdir().unwrap();
    let input = dir.path().join("pydantic-case-results.jsonl");
    let output = dir.path().join("pydantic-case-results.tar.gz");
    fs::write(
        &input,
        r#"{"schema":"pydantic-evals.report-case-result.export.v1","framework":"pydantic_evals","surface":"evaluation_report.cases.case_result","case_name":"case-hello","case_id_ref":"case:synthetic","results":[{"kind":"assertion","evaluator_name":"EqualsExpected","passed":true}],"timestamp":"2026-05-02T08:00:00Z"}"#,
    )
    .unwrap();

    let err = cmd_pydantic_case_result(PydanticCaseResultArgs {
        input,
        bundle_out: output,
        source_artifact_ref: None,
        run_id: "pydantic_test".to_string(),
        import_time: Some("2026-05-03T12:00:00Z".to_string()),
    })
    .unwrap_err();
    assert!(err
        .to_string()
        .contains("unsupported top-level key \"case_id_ref\""));
}

#[test]
fn import_rejects_score_with_passed_field() {
    let dir = tempfile::tempdir().unwrap();
    let input = dir.path().join("pydantic-case-results.jsonl");
    let output = dir.path().join("pydantic-case-results.tar.gz");
    fs::write(
        &input,
        r#"{"schema":"pydantic-evals.report-case-result.export.v1","framework":"pydantic_evals","surface":"evaluation_report.cases.case_result","case_name":"case-hello","results":[{"kind":"score","evaluator_name":"ExactScorePoints","score":1.0,"passed":true}],"timestamp":"2026-05-02T08:00:00Z"}"#,
    )
    .unwrap();

    let err = cmd_pydantic_case_result(PydanticCaseResultArgs {
        input,
        bundle_out: output,
        source_artifact_ref: None,
        run_id: "pydantic_test".to_string(),
        import_time: Some("2026-05-03T12:00:00Z".to_string()),
    })
    .unwrap_err();
    assert!(err
        .to_string()
        .contains("score result must not include passed"));
}