use super::*;
use crate::llm::api::{vm_call_completion_full, LlmCallOptions};
use std::io::Write;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
struct Cleanup;
impl Drop for Cleanup {
fn drop(&mut self) {
crate::reset_thread_local_state();
crate::llm_config::clear_user_overrides();
}
}
fn assert_completion_fixture(endpoint: Option<&str>) {
let _env = env_guard();
let _transport = allow_stubbed_llm_transport();
let _durable = ScopedEnvVar::set("HARN_LLM_RATE_LIMIT_DURABLE", "0");
crate::reset_thread_local_state();
let _cleanup = Cleanup;
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async {
let requests = Arc::new(AtomicUsize::new(0));
let seen = requests.clone();
let path = endpoint.unwrap_or("/chat/completions").to_owned();
let ollama = endpoint == Some("/api/generate");
let chat = endpoint.is_none();
let server = spawn_llm_stub_many("completion fixture control", 2, move |_, stream| {
let (headers, body) = read_http_request(stream);
assert!(headers.starts_with(&format!("POST {path} ")));
assert_eq!(body["model"], "completion-model");
if !chat {
assert_eq!(body["prompt"], "prefix");
assert_eq!(body["suffix"], "suffix");
}
seen.fetch_add(1, Ordering::SeqCst);
let response = if ollama {
serde_json::json!({"response":"provider-reached", "done":true,
"prompt_eval_count":3, "eval_count":1})
} else {
let choice = if chat {
serde_json::json!({"message":{"role":"assistant", "content":"provider-reached"}, "finish_reason":"stop"})
} else {
serde_json::json!({"text":"provider-reached", "finish_reason":"stop"})
};
serde_json::json!({"id":"completion-control", "model":"completion-model",
"choices":[choice], "usage":{"prompt_tokens":3, "completion_tokens":1}})
}.to_string();
write!(stream, "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{response}", response.len()).unwrap();
});
let provider = "completion-fixture";
let mut config = crate::llm_config::ProvidersConfig::default();
config.providers.insert(provider.into(), crate::llm_config::ProviderDef {
base_url: format!("http://{}", server.addr()),
chat_endpoint: "/chat/completions".into(),
completion_endpoint: endpoint.map(str::to_owned),
auth_style: "none".into(),
auth_env: crate::llm_config::AuthEnv::None,
..Default::default()
});
crate::llm_config::set_user_overrides(Some(config));
crate::llm::install_cli_llm_mock_fixture(crate::llm::parse_llm_mocks_jsonl(
"{\"schemaVersion\":1,\"strictScopes\":true}\n\
{\"id\":\"completion-1\",\"scope\":\"completion.fixture\",\"consume\":\"once\",\"match\":\"*prefix*\",\"text\":\"fixture-reached\",\"input_tokens\":7,\"output_tokens\":2}\n"
).unwrap());
let opts = LlmCallOptions {
provider: provider.into(),
model: "completion-model".into(),
mock_scope: Some("completion.fixture".into()),
max_tokens: 8,
stream: false,
..Default::default()
};
let fixture = vm_call_completion_full(&opts, "prefix", Some("suffix"))
.await
.unwrap();
assert_eq!(fixture.text, "fixture-reached");
assert_eq!((fixture.input_tokens, fixture.output_tokens), (7, 2));
assert_eq!(requests.load(Ordering::SeqCst), 0);
let exhausted = vm_call_completion_full(&opts, "prefix", Some("suffix"))
.await
.expect_err("an exhausted replay must not reach the provider");
assert!(exhausted.to_string().contains("No --llm-mock fixture matched"));
assert_eq!(requests.load(Ordering::SeqCst), 0);
crate::llm::clear_cli_llm_mock_mode();
crate::llm::enable_cli_llm_mock_recording();
let live = vm_call_completion_full(&opts, "prefix", Some("suffix"))
.await
.unwrap();
assert_eq!(live.text, "provider-reached");
assert_eq!(requests.load(Ordering::SeqCst), 1, "same route's positive control");
let recordings = crate::llm::take_cli_llm_recordings();
assert_eq!(recordings.len(), 1, "native and fallback results record once");
assert_eq!(recordings[0].text, "provider-reached");
assert_eq!(recordings[0].scope, "completion.fixture");
assert_eq!(recordings[0].input_tokens, Some(3));
assert_eq!(recordings[0].output_tokens, Some(1));
crate::llm::clear_cli_llm_mock_mode();
crate::llm::install_cli_llm_mock_fixture(crate::llm::parse_llm_mocks_jsonl(
"{\"schemaVersion\":1,\"strictScopes\":true,\"liveAfterCalls\":1}\n\
{\"id\":\"prefix-1\",\"scope\":\"completion.fixture\",\"consume\":\"once\",\"text\":\"prefix-reached\"}\n"
).unwrap());
let prefix = vm_call_completion_full(&opts, "prefix", Some("suffix"))
.await
.unwrap();
assert_eq!(prefix.text, "prefix-reached");
assert_eq!(requests.load(Ordering::SeqCst), 1);
assert_eq!(
prefix.telemetry.llm_mock_prefix.as_deref(),
Some(&crate::llm::mock::LlmMockPrefixMarker {
served_by: crate::llm::mock::LlmMockPrefixServedBy::Fixture,
call: 1,
live_after_calls: 1,
})
);
let handed_off = vm_call_completion_full(&opts, "prefix", Some("suffix"))
.await
.unwrap();
assert_eq!(handed_off.text, "provider-reached");
assert_eq!(requests.load(Ordering::SeqCst), 2);
assert_eq!(
handed_off.telemetry.llm_mock_prefix.as_deref(),
Some(&crate::llm::mock::LlmMockPrefixMarker {
served_by: crate::llm::mock::LlmMockPrefixServedBy::Live,
call: 2,
live_after_calls: 1,
})
);
});
}
#[test]
fn openai_completion_replays_without_io_and_records_the_live_control_once() {
assert_completion_fixture(Some("/completions"));
}
#[test]
fn ollama_completion_replays_without_io_and_records_the_live_control_once() {
assert_completion_fixture(Some("/api/generate"));
}
#[test]
fn chat_completion_fallback_replays_without_io_and_records_the_live_control_once() {
assert_completion_fixture(None);
}