From 890f55ab8e81ad522e8740f1ed7b34e094acadb8 Mon Sep 17 00:00:00 2001 From: Brummel Date: Mon, 13 Apr 2026 17:28:33 +0200 Subject: [PATCH] Add tests for Ollama client This commit adds several unit tests for the `generate_oneliner` function, which interacts with the Ollama API. These tests cover: - Successful parsing of a chat response. - Normalization of the response content to remove extra whitespace and quotes. - Handling of HTTP 500 errors from the server. - Verification of sent request parameters like `keep_alive` and `model`. - Parsing errors when the `content` field is missing in the response. --- server/tests/transcribe_test.rs | 111 +++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/server/tests/transcribe_test.rs b/server/tests/transcribe_test.rs index d782124..10e2264 100644 --- a/server/tests/transcribe_test.rs +++ b/server/tests/transcribe_test.rs @@ -3,9 +3,11 @@ use std::time::Duration; use doctate_server::transcribe; use doctate_server::transcribe::ffmpeg::remux_faststart; +use doctate_server::transcribe::ollama::{generate_oneliner, OllamaError}; use doctate_server::transcribe::recovery::scan_and_enqueue; use doctate_server::transcribe::whisper::{transcribe, WhisperError}; -use wiremock::matchers::{method, path, query_param}; +use serde_json::json; +use wiremock::matchers::{body_partial_json, method, path, query_param}; use wiremock::{Mock, MockServer, ResponseTemplate}; fn fixture(name: &str) -> PathBuf { @@ -155,3 +157,110 @@ async fn recovery_enqueues_only_pending_recordings() { assert!(received[0].ends_with("2026-04-10T10-00-00Z.m4a")); assert!(received[1].ends_with("2026-04-12T10-00-00Z.m4a")); } + +// -------------------- Ollama client -------------------- + +const MODEL: &str = "gemma3:4b"; + +fn ok_chat_body(content: &str) -> serde_json::Value { + json!({ + "model": MODEL, + "message": {"role": "assistant", "content": content}, + "done": true + }) +} + +#[tokio::test] +async fn ollama_oneliner_parses_chat_response() { + let server = MockServer::start().await; + + Mock::given(method("POST")) + .and(path("/api/chat")) + .respond_with(ResponseTemplate::new(200).set_body_json(ok_chat_body("HOCM mit Septum-Hypertrophie"))) + .expect(1) + .mount(&server) + .await; + + let client = reqwest::Client::new(); + let out = generate_oneliner(&client, &server.uri(), MODEL, 0, "irrelevant", Duration::from_secs(5)) + .await + .expect("call failed"); + + assert_eq!(out, "HOCM mit Septum-Hypertrophie"); +} + +#[tokio::test] +async fn ollama_oneliner_normalizes_response() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(path("/api/chat")) + .respond_with(ResponseTemplate::new(200).set_body_json(ok_chat_body("\"Kniegelenk re., V.a. Meniskus.\"\nSonstiges"))) + .mount(&server) + .await; + + let client = reqwest::Client::new(); + let out = generate_oneliner(&client, &server.uri(), MODEL, 0, "x", Duration::from_secs(5)) + .await + .unwrap(); + assert_eq!(out, "Kniegelenk re., V.a. Meniskus"); +} + +#[tokio::test] +async fn ollama_oneliner_returns_status_on_500() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(path("/api/chat")) + .respond_with(ResponseTemplate::new(500).set_body_string("boom")) + .mount(&server) + .await; + + let client = reqwest::Client::new(); + let err = generate_oneliner(&client, &server.uri(), MODEL, 0, "x", Duration::from_secs(5)) + .await + .expect_err("expected error"); + match err { + OllamaError::Status { status, body } => { + assert_eq!(status, 500); + assert_eq!(body, "boom"); + } + other => panic!("unexpected: {other:?}"), + } +} + +#[tokio::test] +async fn ollama_oneliner_sends_keep_alive_and_model() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(path("/api/chat")) + .and(body_partial_json(json!({ + "model": MODEL, + "stream": false, + "keep_alive": 0 + }))) + .respond_with(ResponseTemplate::new(200).set_body_json(ok_chat_body("OK"))) + .expect(1) + .mount(&server) + .await; + + let client = reqwest::Client::new(); + let _ = generate_oneliner(&client, &server.uri(), MODEL, 0, "irrelevant", Duration::from_secs(5)) + .await + .expect("call failed"); + // Mock::expect(1) + server drop verifies the matcher hit exactly once. +} + +#[tokio::test] +async fn ollama_oneliner_parse_error_on_missing_content() { + let server = MockServer::start().await; + Mock::given(method("POST")) + .and(path("/api/chat")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({"done": true}))) + .mount(&server) + .await; + + let client = reqwest::Client::new(); + let err = generate_oneliner(&client, &server.uri(), MODEL, 0, "x", Duration::from_secs(5)) + .await + .expect_err("expected parse error"); + assert!(matches!(err, OllamaError::Parse(_)), "got {err:?}"); +}