//! Shared test helpers for the watchdog module. use std::path::Path; mod limits_tests; mod orphan_tests; /// Write a fake session log file with `n` tool-using assistant turn entries. /// /// Each turn includes a single `tool_use` content block so it counts under /// the watchdog's tool-only turn filter (story 923). The file is named /// `{agent_name}-{session_id}.log` to match the real naming convention /// used by `AgentLogWriter`. pub(super) fn write_fake_session_log( project_root: &Path, story_id: &str, agent_name: &str, session_id: &str, n_turns: u64, ) { write_fake_mixed_session_log(project_root, story_id, agent_name, session_id, n_turns, 0); } /// Write a fake session log with `n_tool` tool-using assistant turns and /// `n_narration` text-only narration turns (story 923 regression). /// /// Tool turns contain a `tool_use` content block; narration turns contain /// only a `text` block. The watchdog's `count_turns_in_log` must report /// `n_tool` only — narration must not count. pub(super) fn write_fake_mixed_session_log( project_root: &Path, story_id: &str, agent_name: &str, session_id: &str, n_tool: u64, n_narration: u64, ) { let log_dir = project_root.join(".huskies").join("logs").join(story_id); std::fs::create_dir_all(&log_dir).unwrap(); let log_path = log_dir.join(format!("{agent_name}-{session_id}.log")); let mut content = String::new(); for _ in 0..n_tool { content.push_str( &serde_json::to_string(&serde_json::json!({ "timestamp": "2026-04-25T00:00:00Z", "type": "agent_json", "story_id": story_id, "agent_name": agent_name, "data": { "type": "assistant", "message": { "content": [ { "type": "tool_use", "name": "Read", "input": {} } ] } } })) .unwrap(), ); content.push('\n'); } for _ in 0..n_narration { content.push_str( &serde_json::to_string(&serde_json::json!({ "timestamp": "2026-04-25T00:00:00Z", "type": "agent_json", "story_id": story_id, "agent_name": agent_name, "data": { "type": "assistant", "message": { "content": [ { "type": "text", "text": "Now let me read the next file." } ] } } })) .unwrap(), ); content.push('\n'); } std::fs::write(log_path, content).unwrap(); } /// Write a fake session log containing a `result` event with the given cost. /// /// Used to test budget enforcement via the watchdog's per-session log /// reading (not `token_usage.jsonl`). pub(super) fn write_fake_budget_session_log( project_root: &Path, story_id: &str, agent_name: &str, session_id: &str, cost_usd: f64, ) { let log_dir = project_root.join(".huskies").join("logs").join(story_id); std::fs::create_dir_all(&log_dir).unwrap(); let log_path = log_dir.join(format!("{agent_name}-{session_id}.log")); let content = serde_json::to_string(&serde_json::json!({ "timestamp": "2026-04-25T00:00:00Z", "type": "agent_json", "story_id": story_id, "agent_name": agent_name, "data": { "type": "result", "total_cost_usd": cost_usd } })) .unwrap() + "\n"; std::fs::write(log_path, content).unwrap(); } /// Write a minimal project.toml with the given agent config. pub(super) fn write_project_config(project_root: &Path, config_toml: &str) { let huskies_dir = project_root.join(".huskies"); std::fs::create_dir_all(&huskies_dir).unwrap(); std::fs::write(huskies_dir.join("project.toml"), config_toml).unwrap(); }