newt-core 0.8.0

Newt-Agent core types, errors, and the NeMoCode-style tier router
use super::*;

#[tokio::test]
async fn generation_probe_requires_an_authenticated_valid_chat_response() {
    let server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v1/chat/completions"))
        .and(header("authorization", "Bearer secret-token"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "choices": [{"message": {"content": "hi"}}]
        })))
        .mount(&server)
        .await;

    let result = verify_generation(
        &reqwest::Client::new(),
        BackendKind::Openai,
        Some(OpenAiApiSurface::ChatCompletions),
        &server.uri(),
        "selected-model",
        Some("secret-token"),
    )
    .await;

    assert_eq!(
        result,
        GenerationCheck::Accepted(Some(OpenAiApiSurface::ChatCompletions))
    );
    let requests = server.received_requests().await.expect("journal");
    let body: serde_json::Value = serde_json::from_slice(&requests[0].body).unwrap();
    assert_eq!(body["model"], "selected-model");
    assert_eq!(body["messages"][0]["content"], "Reply with OK.");
    assert_eq!(body["max_tokens"], 8);
    assert!(body.get("max_completion_tokens").is_none());
    assert_eq!(body["stream"], false);
}

#[tokio::test]
async fn generation_probe_rejects_auth_and_malformed_success_envelopes() {
    let auth = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v1/chat/completions"))
        .respond_with(ResponseTemplate::new(403))
        .mount(&auth)
        .await;
    assert_eq!(
        verify_generation(
            &reqwest::Client::new(),
            BackendKind::Openai,
            Some(OpenAiApiSurface::ChatCompletions),
            &auth.uri(),
            "m",
            None,
        )
        .await,
        GenerationCheck::Rejected(403)
    );

    let malformed = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v1/chat/completions"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "object": "chat.completion"
        })))
        .mount(&malformed)
        .await;
    assert!(matches!(
        verify_generation(
            &reqwest::Client::new(),
            BackendKind::Openai,
            Some(OpenAiApiSurface::ChatCompletions),
            &malformed.uri(),
            "m",
            None,
        )
        .await,
        GenerationCheck::Unverified(GenerationFailure::InvalidEnvelope)
    ));
}

#[tokio::test]
async fn generation_probe_negotiates_the_responses_surface_after_chat_404() {
    let server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v1/chat/completions"))
        .respond_with(ResponseTemplate::new(404))
        .mount(&server)
        .await;
    Mock::given(method("POST"))
        .and(path("/v1/responses"))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "status": "completed",
            "output": [{
                "type": "message",
                "content": [{"type": "output_text", "text": "hi"}]
            }]
        })))
        .mount(&server)
        .await;

    assert_eq!(
        verify_generation(
            &reqwest::Client::new(),
            BackendKind::Openai,
            None,
            &server.uri(),
            "responses-model",
            None,
        )
        .await,
        GenerationCheck::Accepted(Some(OpenAiApiSurface::Responses))
    );
    let requests = server.received_requests().await.expect("journal");
    let responses: serde_json::Value = serde_json::from_slice(&requests[1].body).unwrap();
    assert_eq!(responses["store"], false);
}

#[test]
fn incomplete_responses_probe_requires_clean_recognized_partial_output() {
    let partial = serde_json::json!({
        "status": "incomplete",
        "incomplete_details": {"reason": "max_output_tokens"},
        "output": [{
            "type": "message",
            "content": [{"type": "output_text", "text": "partial"}]
        }]
    });
    assert_eq!(
        classify_responses_generation(
            reqwest::StatusCode::OK,
            &serde_json::to_vec(&partial).unwrap(),
        ),
        GenerationCheck::Accepted(Some(OpenAiApiSurface::Responses))
    );

    let mut with_error = partial.clone();
    with_error["error"] = serde_json::json!({"message": "provider failure"});
    assert!(matches!(
        classify_responses_generation(
            reqwest::StatusCode::OK,
            &serde_json::to_vec(&with_error).unwrap(),
        ),
        GenerationCheck::Unverified(GenerationFailure::InvalidResponsesPayload)
    ));

    let refusal = serde_json::json!({
        "status": "incomplete",
        "incomplete_details": {"reason": "max_output_tokens"},
        "output": [{
            "type": "message",
            "content": [{"type": "refusal", "refusal": "declined"}]
        }]
    });
    assert!(matches!(
        classify_responses_generation(
            reqwest::StatusCode::OK,
            &serde_json::to_vec(&refusal).unwrap(),
        ),
        GenerationCheck::Unverified(GenerationFailure::InvalidResponsesPayload)
    ));

    let unrecognized = serde_json::json!({
        "status": "incomplete",
        "incomplete_details": {"reason": "max_output_tokens"},
        "output": [{"type": "reasoning", "summary": []}]
    });
    assert!(matches!(
        classify_responses_generation(
            reqwest::StatusCode::OK,
            &serde_json::to_vec(&unrecognized).unwrap(),
        ),
        GenerationCheck::Unverified(GenerationFailure::InvalidResponsesPayload)
    ));
}

#[tokio::test]
async fn responses_probe_never_returns_provider_text_or_bearer_material() {
    const BEARER_SENTINEL: &str = "probe-secret-must-not-escape";
    const BODY_SENTINEL: &str = "provider-body-must-not-escape";
    let escape = char::from(27);
    let bell = char::from(7);
    let server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v1/responses"))
        .and(header("authorization", format!("Bearer {BEARER_SENTINEL}")))
        .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
            "status": "completed",
            "error": {"message": format!(
                "{BEARER_SENTINEL} {BODY_SENTINEL} {escape}[31mred{bell}"
            )},
            "output": [{
                "type": "message",
                "content": [{
                    "type": "refusal",
                    "refusal": format!("{BODY_SENTINEL} {escape}[2J")
                }]
            }]
        })))
        .mount(&server)
        .await;

    let result = verify_generation(
        &reqwest::Client::new(),
        BackendKind::Openai,
        Some(OpenAiApiSurface::Responses),
        &server.uri(),
        "model",
        Some(BEARER_SENTINEL),
    )
    .await;
    let GenerationCheck::Unverified(reason) = result else {
        panic!("provider error/refusal must fail closed: {result:?}");
    };
    let rendered = reason.to_string();

    assert_eq!(reason, GenerationFailure::InvalidResponsesPayload);
    assert!(!rendered.contains(BEARER_SENTINEL));
    assert!(!rendered.contains(BODY_SENTINEL));
    assert!(!rendered.contains('\u{1b}'));
    assert!(!rendered.chars().any(char::is_control));
}