//! FC-65: argv invariants are enforced at admission, before any VM is spawned.
//!
//! Three failure modes are exercised:
//!   1. argv contains an empty string token (existing check)
//!   2. argv contains a NUL byte (would be silently truncated by execve)
//!   3. argv contains a non-UTF-8 byte (rejected at the serde_json boundary,
//!      not at admission — `String` is UTF-8 by Rust's type system).

use cellos_core::{
    validate_execution_cell_document, AuthorityBundle, ExecutionCellDocument, ExecutionCellSpec,
    Lifetime, RunSpec, SecretDeliveryMode,
};

fn doc_with_argv(argv: Vec<String>) -> ExecutionCellDocument {
    ExecutionCellDocument {
        api_version: "cellos.io/v1".into(),
        kind: "ExecutionCell".into(),
        spec: ExecutionCellSpec {
            id: "fc65-cell".into(),
            correlation: None,
            ingress: None,
            environment: None,
            placement: None,
            policy: None,
            identity: None,
            run: Some(RunSpec {
                argv,
                working_directory: None,
                timeout_ms: None,
                limits: None,
                secret_delivery: SecretDeliveryMode::Env,
            }),
            authority: AuthorityBundle::default(),
            lifetime: Lifetime { ttl_seconds: 1 },
            export: None,
            telemetry: None,
        },
    }
}

#[test]
fn argv_empty_string_rejected() {
    // argv = [""] — the existing empty-token check fires.
    let doc = doc_with_argv(vec!["".into()]);
    let err = validate_execution_cell_document(&doc).expect_err("empty argv must reject");
    let msg = format!("{err}");
    assert!(
        msg.contains("argv"),
        "error should mention argv; got: {msg}"
    );
}

#[test]
fn argv_with_nul_byte_rejected() {
    // argv = ["sh", "-c", "echo \0hidden"] — embedded NUL would be silently
    // truncated by execve, so admission must reject before any VM is spawned.
    let doc = doc_with_argv(vec!["sh".into(), "-c".into(), "echo \0hidden".into()]);
    let err = validate_execution_cell_document(&doc).expect_err("NUL in argv must reject");
    let msg = format!("{err}");
    assert!(
        msg.contains("NUL byte") && msg.contains("argv[2]"),
        "error should identify the NUL byte and the offending index; got: {msg}"
    );
}

#[test]
fn argv_non_utf8_impossible_at_serde_boundary() {
    // A non-UTF-8 byte (0xFF) inside a JSON string is rejected at the
    // serde_json deserialize boundary — admission never sees it because
    // `Vec<String>` is UTF-8 by Rust's type system.
    //
    // Construct raw bytes for a minimally-valid spec whose argv[0] contains 0xFF.
    let mut bytes: Vec<u8> = Vec::new();
    bytes.extend_from_slice(
        br#"{"apiVersion":"cellos.io/v1","kind":"ExecutionCell","spec":{"id":"x","authority":{"secretRefs":[],"egressRules":[]},"lifetime":{"ttlSeconds":1},"run":{"argv":[""#,
    );
    bytes.push(0xFF); // invalid UTF-8 byte inside the JSON string
    bytes.extend_from_slice(br#""]}}}"#);

    let result: Result<ExecutionCellDocument, _> = serde_json::from_slice(&bytes);
    assert!(
        result.is_err(),
        "serde_json must reject non-UTF-8 argv at the deserialize boundary"
    );
}
