ref:55b779991adfc48844106650be0fdb5514cf7526

fix(runner): repair heartbeat.rs after two green branches merged red

`main` has not compiled its tests since #60 landed on 2026-08-06. My fault, and a shape worth naming: **both branches were green and the merge was broken.** #58 added a wiremock-backed `mod tests` to `heartbeat.rs` with eleven callers of `local_models(client, url)`. #60 gave `local_models` a third parameter — the per-model detail cache that keeps `/api/show` off every heartbeat — and added a *second* `mod tests` for the `/api/show` parsing. Neither branch could see the other's, so CI passed on both and `main` got `E0428` (duplicate module) plus eleven `E0061`s (wrong arity). Git had no textual conflict to report because the two modules were appended at different points in the file. The repair is what the merge should have done: one `mod tests`, with #60's parsing tests folded into the module #58 established, and every pre-existing caller given its own empty cache. They are testing what `/api/tags` reports, not the cache, so a fresh `HashMap` per call is the honest argument rather than a shared fixture that would couple them. Nothing about the behaviour changes; this is the merge resolution that was never performed. `cargo check --all-targets` (the job that failed), `cargo test` (456 lib + 267 integration), `cargo clippy --all-targets -- -D warnings` and `cargo fmt --check` all clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SHA: 55b779991adfc48844106650be0fdb5514cf7526
Author: Cole Christensen <cole.christensen@gmail.com>
Date: 2026-08-08 17:53
Parents: 4719e13
1 files changed +35 -29
Type
src/runner/heartbeat.rs +35 −29
@@ -300,6 +300,8 @@
mod tests {
use super::*;
use serde_json::json;
use wiremock::matchers::{method, path as req_path};
use wiremock::{Mock, MockServer, ResponseTemplate};
#[test]
fn reads_the_architecture_prefixed_context_length() {
@@ -368,15 +370,7 @@
Some("llama3.2:latest".to_string())
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use wiremock::matchers::{method, path as req_path};
use wiremock::{Mock, MockServer, ResponseTemplate};
/// An Ollama that answers `/api/tags` with `body`.
async fn ollama_serving(body: serde_json::Value) -> MockServer {
let server = MockServer::start().await;
@@ -454,6 +448,6 @@
]}))
.await;
let models = local_models(&reqwest::Client::new(), &ollama.uri())
let models = local_models(&reqwest::Client::new(), &ollama.uri(), &mut HashMap::new())
.await
.expect("a served model must be reported");
@@ -470,6 +464,6 @@
// empty list would read as "supports nothing" and hide it forever.
let ollama = ollama_serving(json!({"models": [{"name": "mystery:latest"}]})).await;
let models = local_models(&reqwest::Client::new(), &ollama.uri())
let models = local_models(&reqwest::Client::new(), &ollama.uri(), &mut HashMap::new())
.await
.expect("the model is still served");
@@ -486,6 +480,6 @@
async fn an_empty_capabilities_array_is_also_reported_as_unknown() {
let ollama = ollama_serving(json!({"models": [{"name": "m", "capabilities": []}]})).await;
let models = local_models(&reqwest::Client::new(), &ollama.uri())
let models = local_models(&reqwest::Client::new(), &ollama.uri(), &mut HashMap::new())
.await
.unwrap();
@@ -504,6 +498,6 @@
]}))
.await;
let models = local_models(&reqwest::Client::new(), &ollama.uri())
let models = local_models(&reqwest::Client::new(), &ollama.uri(), &mut HashMap::new())
.await
.unwrap();
@@ -519,6 +513,6 @@
]}))
.await;
let models = local_models(&reqwest::Client::new(), &ollama.uri())
let models = local_models(&reqwest::Client::new(), &ollama.uri(), &mut HashMap::new())
.await
.unwrap();
@@ -532,18 +526,22 @@
// advertised a moment ago; omitting the key leaves them in place.
let ollama = ollama_serving(json!({"models": []})).await;
assert!(local_models(&reqwest::Client::new(), &ollama.uri())
.await
.is_none());
assert!(
local_models(&reqwest::Client::new(), &ollama.uri(), &mut HashMap::new())
.await
.is_none()
);
}
#[tokio::test]
async fn a_response_without_a_models_key_reports_none() {
let ollama = ollama_serving(json!({"something_else": true})).await;
assert!(local_models(&reqwest::Client::new(), &ollama.uri())
.await
.is_none());
assert!(
local_models(&reqwest::Client::new(), &ollama.uri(), &mut HashMap::new())
.await
.is_none()
);
}
#[tokio::test]
@@ -555,9 +553,11 @@
.mount(&server)
.await;
assert!(local_models(&reqwest::Client::new(), &server.uri())
.await
.is_none());
assert!(
local_models(&reqwest::Client::new(), &server.uri(), &mut HashMap::new())
.await
.is_none()
);
}
#[tokio::test]
@@ -569,18 +569,24 @@
.mount(&server)
.await;
assert!(local_models(&reqwest::Client::new(), &server.uri())
.await
.is_none());
assert!(
local_models(&reqwest::Client::new(), &server.uri(), &mut HashMap::new())
.await
.is_none()
);
}
#[tokio::test]
async fn an_unreachable_ollama_reports_none_rather_than_costing_a_heartbeat() {
// Best-effort is the whole contract here: a dead Ollama must not
// stop the runner reporting that it is alive.
assert!(local_models(&reqwest::Client::new(), &closed_port_url())
.await
.is_none());
assert!(local_models(
&reqwest::Client::new(),
&closed_port_url(),
&mut HashMap::new()
)
.await
.is_none());
}
#[tokio::test]
@@ -590,7 +596,7 @@
let with_slash = format!("{}/", ollama.uri());
assert!(
local_models(&reqwest::Client::new(), &with_slash)
local_models(&reqwest::Client::new(), &with_slash, &mut HashMap::new())
.await
.is_some(),
"the mock only answers /api/tags, so a doubled slash means None"