Skip to content

Commit 569f9d1

Browse files
committed
test(coverage): batch 5.5 — local_ai/{core,gif_decision}
- local_ai/core.rs: 33.33% (pins model_artifact_path structure: models/local-ai dir, `.ollama` suffix, colon→dash normalisation for Windows-safe filenames; Arc sharing across global() calls) - local_ai/gif_decision.rs: 44.04% (trim whitespace in parse, length/word-count boundary cases, tenor_search empty-query guard, local_ai_should_send_gif empty-message early return) 10 new deterministic tests. Refs tinyhumansai#530.
1 parent ce5f38f commit 569f9d1

2 files changed

Lines changed: 106 additions & 0 deletions

File tree

‎src/openhuman/local_ai/core.rs‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,54 @@ pub fn model_artifact_path(config: &Config) -> PathBuf {
2626
.join("local-ai")
2727
.join(effective_chat_model_id(config).replace(':', "-") + ".ollama")
2828
}
29+
30+
#[cfg(test)]
31+
mod tests {
32+
use super::*;
33+
34+
#[test]
35+
fn model_artifact_path_includes_models_local_ai_subdirs() {
36+
let config = Config::default();
37+
let path = model_artifact_path(&config);
38+
let path_str = path.to_string_lossy();
39+
assert!(
40+
path_str.contains("models"),
41+
"expected `models` in path: {path_str}"
42+
);
43+
assert!(
44+
path_str.contains("local-ai"),
45+
"expected `local-ai` subdir in path: {path_str}"
46+
);
47+
}
48+
49+
#[test]
50+
fn model_artifact_path_ends_with_ollama_suffix() {
51+
let config = Config::default();
52+
let path = model_artifact_path(&config);
53+
assert_eq!(
54+
path.extension().and_then(|s| s.to_str()),
55+
Some("ollama"),
56+
"model artifact must have `.ollama` extension: {}",
57+
path.display()
58+
);
59+
}
60+
61+
#[test]
62+
fn model_artifact_path_replaces_colon_in_model_id_with_dash() {
63+
// Model IDs commonly look like `qwen2:1.5b`; colons are illegal on
64+
// Windows path components, so we normalise to `-`. This test pins
65+
// that mapping.
66+
let config = Config::default();
67+
let path = model_artifact_path(&config);
68+
let file = path.file_name().unwrap().to_string_lossy().to_string();
69+
assert!(!file.contains(':'), "filename must not contain `:`: {file}");
70+
}
71+
72+
#[test]
73+
fn global_returns_same_arc_across_calls() {
74+
let config = Config::default();
75+
let a = global(&config);
76+
let b = global(&config);
77+
assert!(Arc::ptr_eq(&a, &b), "global() must return a shared Arc");
78+
}
79+
}

‎src/openhuman/local_ai/gif_decision.rs‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,59 @@ mod tests {
259259
let d = parse_gif_response("no gif");
260260
assert!(!d.should_send_gif);
261261
}
262+
263+
#[test]
264+
fn parse_trims_surrounding_whitespace() {
265+
let d = parse_gif_response(" NONE ");
266+
assert!(!d.should_send_gif);
267+
268+
let d = parse_gif_response(" hello wave ");
269+
assert!(d.should_send_gif);
270+
assert_eq!(d.search_query.as_deref(), Some("hello wave"));
271+
}
272+
273+
#[test]
274+
fn parse_reject_over_eighty_chars_even_if_word_count_small() {
275+
// 8 words but ≥ 80 chars is still rejected — protects against
276+
// words that are URL-like or extremely long.
277+
let long_word = "x".repeat(90);
278+
let d = parse_gif_response(&long_word);
279+
assert!(!d.should_send_gif);
280+
}
281+
282+
#[test]
283+
fn parse_reject_more_than_eight_words() {
284+
let nine_words = "one two three four five six seven eight nine";
285+
let d = parse_gif_response(nine_words);
286+
assert!(!d.should_send_gif);
287+
}
288+
289+
#[test]
290+
fn parse_accepts_boundary_eight_words() {
291+
// Exactly 8 words: accepted.
292+
let eight = "one two three four five six seven eight";
293+
let d = parse_gif_response(eight);
294+
assert!(d.should_send_gif);
295+
}
296+
297+
// ── tenor_search guard paths ─────────────────────────────────
298+
299+
#[tokio::test]
300+
async fn tenor_search_rejects_empty_query() {
301+
let config = crate::openhuman::config::Config::default();
302+
let err = tenor_search(&config, " ", Some(5)).await.unwrap_err();
303+
assert!(err.contains("query is required"));
304+
}
305+
306+
// ── local_ai_should_send_gif early-returns ──────────────────
307+
308+
#[tokio::test]
309+
async fn should_send_gif_returns_false_for_empty_message() {
310+
let config = crate::openhuman::config::Config::default();
311+
let outcome = local_ai_should_send_gif(&config, " ", "slack")
312+
.await
313+
.unwrap();
314+
assert!(!outcome.value.should_send_gif);
315+
assert!(outcome.logs.iter().any(|l| l.contains("empty message")));
316+
}
262317
}

0 commit comments

Comments
 (0)