agtrace_testing/
assertions.rs

1//! Custom assertions for agtrace-specific validation.
2//!
3//! Provides high-level assertions that make tests more readable:
4//! - Session count validation
5//! - Project hash verification
6//! - JSON structure checks
7
8use anyhow::{Context, Result};
9use serde_json::Value;
10
11/// Assert that JSON output contains expected number of sessions.
12pub fn assert_session_count(json: &Value, expected: usize) -> Result<()> {
13    let sessions = json["content"]["sessions"]
14        .as_array()
15        .context("Expected 'content.sessions' array in JSON")?;
16
17    if sessions.len() != expected {
18        anyhow::bail!("Expected {} sessions, got {}", expected, sessions.len());
19    }
20
21    Ok(())
22}
23
24/// Assert that JSON output contains expected number of projects.
25pub fn assert_project_count(json: &Value, expected: usize) -> Result<()> {
26    let projects = json["content"]["projects"]
27        .as_array()
28        .context("Expected 'content.projects' array in JSON")?;
29
30    if projects.len() != expected {
31        anyhow::bail!("Expected {} projects, got {}", expected, projects.len());
32    }
33
34    Ok(())
35}
36
37/// Assert that all sessions belong to the specified project hash.
38pub fn assert_sessions_belong_to_project(json: &Value, project_hash: &str) -> Result<()> {
39    let sessions = json["content"]["sessions"]
40        .as_array()
41        .context("Expected 'content.sessions' array in JSON")?;
42
43    for (i, session) in sessions.iter().enumerate() {
44        let session_project = session["project_hash"]
45            .as_str()
46            .with_context(|| format!("Session {} missing project_hash", i))?;
47
48        if session_project != project_hash {
49            anyhow::bail!(
50                "Session {} belongs to project {} but expected {}",
51                i,
52                session_project,
53                project_hash
54            );
55        }
56    }
57
58    Ok(())
59}
60
61/// Assert that project list contains specific project hashes.
62pub fn assert_projects_contain(json: &Value, expected_hashes: &[&str]) -> Result<()> {
63    let projects = json["content"]["projects"]
64        .as_array()
65        .context("Expected 'content.projects' array in JSON")?;
66
67    let project_hashes: Vec<String> = projects
68        .iter()
69        .filter_map(|p| p["hash"].as_str().map(String::from))
70        .collect();
71
72    for expected in expected_hashes {
73        if !project_hashes.contains(&expected.to_string()) {
74            anyhow::bail!(
75                "Expected project hash {} not found in {:?}",
76                expected,
77                project_hashes
78            );
79        }
80    }
81
82    Ok(())
83}
84
85/// Assert that a specific session comes from the expected provider.
86///
87/// # Example
88/// ```no_run
89/// # use agtrace_testing::assertions;
90/// # use serde_json::json;
91/// let json = json!({
92///     "content": {
93///         "sessions": [
94///             {"source": "claude_code"},
95///             {"source": "gemini"}
96///         ]
97///     }
98/// });
99///
100/// assertions::assert_session_provider(&json, 0, "claude_code").unwrap();
101/// assertions::assert_session_provider(&json, 1, "gemini").unwrap();
102/// ```
103pub fn assert_session_provider(
104    json: &Value,
105    session_index: usize,
106    expected_provider: &str,
107) -> Result<()> {
108    let sessions = json["content"]["sessions"]
109        .as_array()
110        .context("Expected 'content.sessions' array in JSON")?;
111
112    let session = sessions
113        .get(session_index)
114        .with_context(|| format!("Session index {} out of bounds", session_index))?;
115
116    // Check both "source" and "provider" fields (different views may use different names)
117    let actual = session["source"]
118        .as_str()
119        .or_else(|| session["provider"].as_str())
120        .with_context(|| {
121            format!(
122                "Session {} missing 'source' or 'provider' field",
123                session_index
124            )
125        })?;
126
127    if actual != expected_provider {
128        anyhow::bail!(
129            "Session {} expected provider '{}', got '{}'",
130            session_index,
131            expected_provider,
132            actual
133        );
134    }
135
136    Ok(())
137}
138
139/// Assert that all sessions in the list come from the expected provider.
140///
141/// # Example
142/// ```no_run
143/// # use agtrace_testing::assertions;
144/// # use serde_json::json;
145/// let json = json!({
146///     "content": {
147///         "sessions": [
148///             {"source": "claude_code"},
149///             {"source": "claude_code"}
150///         ]
151///     }
152/// });
153///
154/// assertions::assert_all_sessions_from_provider(&json, "claude_code").unwrap();
155/// ```
156pub fn assert_all_sessions_from_provider(json: &Value, expected_provider: &str) -> Result<()> {
157    let sessions = json["content"]["sessions"]
158        .as_array()
159        .context("Expected 'content.sessions' array in JSON")?;
160
161    for (i, session) in sessions.iter().enumerate() {
162        let actual = session["source"]
163            .as_str()
164            .or_else(|| session["provider"].as_str())
165            .with_context(|| format!("Session {} missing 'source' or 'provider' field", i))?;
166
167        if actual != expected_provider {
168            anyhow::bail!(
169                "Session {} expected provider '{}', got '{}'",
170                i,
171                expected_provider,
172                actual
173            );
174        }
175    }
176
177    Ok(())
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use serde_json::json;
184
185    #[test]
186    fn test_assert_session_count() {
187        let json = json!({
188            "content": {
189                "sessions": [
190                    {"id": "1"},
191                    {"id": "2"}
192                ]
193            }
194        });
195
196        assert!(assert_session_count(&json, 2).is_ok());
197        assert!(assert_session_count(&json, 1).is_err());
198    }
199
200    #[test]
201    fn test_assert_sessions_belong_to_project() {
202        let json = json!({
203            "content": {
204                "sessions": [
205                    {"project_hash": "abc123"},
206                    {"project_hash": "abc123"}
207                ]
208            }
209        });
210
211        assert!(assert_sessions_belong_to_project(&json, "abc123").is_ok());
212        assert!(assert_sessions_belong_to_project(&json, "xyz789").is_err());
213    }
214}