mirror of
https://github.com/Dicklesworthstone/coding_agent_session_search.git
synced 2026-09-22 11:59:25 +08:00
357cd1776d
The merge series9fe26cc5->63533a35->47332ca5integrated a June-26-era stale working line (56020f59..71938040, the v0.6.12 line) into main and resolved conflicts by keeping the stale blobs wholesale. That silently reverted ~2 months of released work on 86 files, including: - Cargo.toml/package identity regressed 0.6.23 -> 0.6.12, asupersync pin regressed =0.3.9 -> =0.3.4, and the dependency graph no longer resolved (cargo could not select a version for asupersync) — main was unbuildable. - src/lib.rs lost ~38 module declarations and the entire post-v0.6.23 triage wave, including the GH #353 forever-defer fix (aa03fd06), the GH #374 doctor/authority fixes (7327191f,56bf1a01,43ab9235), the GH #364 false-OOM quarantine fix (ef7e2595), and the GH #368 series. - src/indexer/mod.rs lost the quarantine_retry module wiring. - Golden robot fixtures, doctor/health schemas, and install scripts regressed to their June shapes. This commit restores all 86 clobbered paths to their state at8e308ea3(the last good main commit, v0.6.23 lineage), with three exceptions kept from the merged side: .beads/cli-robot-enhancements.md (new note), tests/e2e_resilience_scenario_runner.rs (new suite), and .beads/issues.jsonl (merged as a union by bead id, newer updated_at wins, preserving the local kupq4 bead alongside the remote gh-wave beads). Additional repairs required to make --all-targets compile again: - tests/connector_{aider,amp,antigravity,claude,cline,codex,gemini, opencode,pi_agent}.rs, tests/{fs_errors,logging,parse_errors}.rs: add `progress_tick: None` to every franken_agent_detection ScanContext struct literal. The restored FAD pin (dd2c694, 0.1.10) added the cass#373 progress_tick field, and these suites still used the three-field literal — `cargo check --tests` was red on main even before the bad merges (independently reported in PR #376). - tests/e2e_resilience_scenario_runner.rs: temporarily disabled via `#![cfg(any())]` with an explanatory header. It was authored against the e2e_runner artifact APIs fromf6cd6376, which are not on the restored line yet. NOT restored (needs deliberate re-landing, tracked in beads): the genuine feature work fromf6cd6376(CLI wall-clock budgets --timeout/budget_ms/ per_host_budget_ms, robot budget envelopes, search refresh-order rework, encrypt metadata taxonomy tests). Its diff remains intact in history atf6cd6376and must be rebased onto this repaired line rather than merged blob-wise. Verification: cargo check --all-targets exits 0 (previously failed at dependency resolution); version string back at 0.6.23 matching the latest release tag. Co-Authored-By: Claude <noreply@anthropic.com>
556 lines
19 KiB
Rust
556 lines
19 KiB
Rust
//! Comprehensive parsing error tests (tst.err.parse)
|
|
//!
|
|
//! Tests handling of malformed input files across all connectors.
|
|
//! Cases: invalid JSON, missing required fields, wrong field types,
|
|
//! truncated files, binary in text fields, invalid UTF-8.
|
|
//!
|
|
//! Expected behavior: parse error logged with context, session skipped,
|
|
//! remaining valid sessions still processed.
|
|
|
|
use coding_agent_search::connectors::claude_code::ClaudeCodeConnector;
|
|
use coding_agent_search::connectors::cline::ClineConnector;
|
|
use coding_agent_search::connectors::codex::CodexConnector;
|
|
use coding_agent_search::connectors::gemini::GeminiConnector;
|
|
use coding_agent_search::connectors::{Connector, ScanContext};
|
|
use std::fs;
|
|
use tempfile::TempDir;
|
|
|
|
// =============================================================================
|
|
// Claude Code Connector - Parsing Error Tests
|
|
// =============================================================================
|
|
|
|
fn create_claude_temp() -> TempDir {
|
|
TempDir::new().unwrap()
|
|
}
|
|
|
|
/// Invalid JSON syntax (missing braces, unquoted strings)
|
|
#[test]
|
|
fn claude_skips_invalid_json_syntax() {
|
|
let dir = create_claude_temp();
|
|
let projects = dir.path().join("fixture-claude/projects/test-proj");
|
|
fs::create_dir_all(&projects).unwrap();
|
|
let file = projects.join("session.jsonl");
|
|
|
|
let sample = r#"{"type":"user","message":{"role":"user","content":"Valid"},"timestamp":"2025-11-12T18:31:18.000Z"}
|
|
{ this is not valid json }
|
|
{missing colon here}
|
|
"just a string"
|
|
{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"After errors"}]},"timestamp":"2025-11-12T18:31:20.000Z"}
|
|
"#;
|
|
fs::write(&file, sample).unwrap();
|
|
|
|
let conn = ClaudeCodeConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: dir.path().join("fixture-claude"),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
let convs = conn.scan(&ctx).unwrap();
|
|
assert_eq!(convs.len(), 1);
|
|
// Only valid lines are processed
|
|
assert_eq!(convs[0].messages.len(), 2);
|
|
}
|
|
|
|
/// Missing required "type" field
|
|
#[test]
|
|
fn claude_skips_missing_type_field() {
|
|
let dir = create_claude_temp();
|
|
let projects = dir.path().join("fixture-claude/projects/test-proj");
|
|
fs::create_dir_all(&projects).unwrap();
|
|
let file = projects.join("session.jsonl");
|
|
|
|
let sample = r#"{"type":"user","message":{"role":"user","content":"Valid"},"timestamp":"2025-11-12T18:31:18.000Z"}
|
|
{"message":{"role":"user","content":"Missing type"},"timestamp":"2025-11-12T18:31:19.000Z"}
|
|
{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"End"}]},"timestamp":"2025-11-12T18:31:20.000Z"}
|
|
"#;
|
|
fs::write(&file, sample).unwrap();
|
|
|
|
let conn = ClaudeCodeConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: dir.path().join("fixture-claude"),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
let convs = conn.scan(&ctx).unwrap();
|
|
assert_eq!(convs.len(), 1);
|
|
// Line missing "type" field should be skipped
|
|
assert_eq!(convs[0].messages.len(), 2);
|
|
}
|
|
|
|
/// Wrong field type (string where object expected)
|
|
#[test]
|
|
fn claude_handles_wrong_field_types() {
|
|
let dir = create_claude_temp();
|
|
let projects = dir.path().join("fixture-claude/projects/test-proj");
|
|
fs::create_dir_all(&projects).unwrap();
|
|
let file = projects.join("session.jsonl");
|
|
|
|
let sample = r#"{"type":"user","message":{"role":"user","content":"Valid"},"timestamp":"2025-11-12T18:31:18.000Z"}
|
|
{"type":"user","message":"not an object","timestamp":"2025-11-12T18:31:19.000Z"}
|
|
{"type":"user","message":{"role":"user","content":12345},"timestamp":"2025-11-12T18:31:19.500Z"}
|
|
{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"End"}]},"timestamp":"2025-11-12T18:31:20.000Z"}
|
|
"#;
|
|
fs::write(&file, sample).unwrap();
|
|
|
|
let conn = ClaudeCodeConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: dir.path().join("fixture-claude"),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
let convs = conn.scan(&ctx).unwrap();
|
|
assert_eq!(convs.len(), 1);
|
|
// Invalid type lines should be handled gracefully
|
|
// At minimum the valid lines should be present
|
|
assert!(convs[0].messages.len() >= 2);
|
|
}
|
|
|
|
/// Truncated JSON (ends mid-object)
|
|
#[test]
|
|
fn claude_handles_truncated_json() {
|
|
let dir = create_claude_temp();
|
|
let projects = dir.path().join("fixture-claude/projects/test-proj");
|
|
fs::create_dir_all(&projects).unwrap();
|
|
let file = projects.join("session.jsonl");
|
|
|
|
let sample = r#"{"type":"user","message":{"role":"user","content":"Valid"},"timestamp":"2025-11-12T18:31:18.000Z"}
|
|
{"type":"user","message":{"role":"user","content":"Trunc"#;
|
|
fs::write(&file, sample).unwrap();
|
|
|
|
let conn = ClaudeCodeConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: dir.path().join("fixture-claude"),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
let convs = conn.scan(&ctx).unwrap();
|
|
assert_eq!(convs.len(), 1);
|
|
// Only the complete first line should be processed
|
|
assert_eq!(convs[0].messages.len(), 1);
|
|
}
|
|
|
|
/// Binary data in content field (null bytes)
|
|
#[test]
|
|
fn claude_handles_binary_in_content() {
|
|
let dir = create_claude_temp();
|
|
let projects = dir.path().join("fixture-claude/projects/test-proj");
|
|
fs::create_dir_all(&projects).unwrap();
|
|
let file = projects.join("session.jsonl");
|
|
|
|
// Write valid JSONL first, then binary
|
|
let valid_line = r#"{"type":"user","message":{"role":"user","content":"Valid"},"timestamp":"2025-11-12T18:31:18.000Z"}"#;
|
|
let mut content = valid_line.as_bytes().to_vec();
|
|
content.push(b'\n');
|
|
// Add a line with embedded null bytes (invalid UTF-8 in JSON context)
|
|
content.extend_from_slice(b"{\"type\":\"user\",\"message\":{\"role\":\"user\",\"content\":\"has\x00null\"},\"timestamp\":\"2025-11-12T18:31:19.000Z\"}\n");
|
|
content.extend_from_slice(
|
|
r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"End"}]},"timestamp":"2025-11-12T18:31:20.000Z"}"#.as_bytes()
|
|
);
|
|
fs::write(&file, content).unwrap();
|
|
|
|
let conn = ClaudeCodeConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: dir.path().join("fixture-claude"),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
// Should not panic - gracefully handle the file
|
|
let result = conn.scan(&ctx);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
/// Invalid UTF-8 sequence - connector returns error (expected behavior)
|
|
/// Note: The connector uses `fs::read_to_string` which fails on invalid UTF-8.
|
|
/// This is acceptable behavior - corrupted files are rare in practice.
|
|
#[test]
|
|
fn claude_returns_error_on_invalid_utf8() {
|
|
let dir = create_claude_temp();
|
|
let projects = dir.path().join("fixture-claude/projects/test-proj");
|
|
fs::create_dir_all(&projects).unwrap();
|
|
let file = projects.join("session.jsonl");
|
|
|
|
// Write bytes directly to create invalid UTF-8
|
|
// Invalid UTF-8 sequence (0xFF is never valid in UTF-8)
|
|
let mut content = Vec::new();
|
|
content.extend_from_slice(b"{\"type\":\"user\",\"message\":{\"role\":\"user\",\"content\":\"bad\xFF\xFEutf8\"},\"timestamp\":\"2025-11-12T18:31:19.000Z\"}\n");
|
|
fs::write(&file, content).unwrap();
|
|
|
|
let conn = ClaudeCodeConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: dir.path().join("fixture-claude"),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
// The scanner uses BufRead::lines() which returns Err on invalid UTF-8,
|
|
// but the implementation silently skips such lines for resilience.
|
|
// This is the correct behavior for real-world data that may be corrupted.
|
|
let result = conn.scan(&ctx);
|
|
assert!(
|
|
result.is_ok(),
|
|
"Scanner should be resilient to invalid UTF-8 lines"
|
|
);
|
|
// The invalid line is skipped, so we get 0 conversations
|
|
let convs = result.unwrap();
|
|
assert!(
|
|
convs.is_empty() || convs.iter().all(|c| c.messages.is_empty()),
|
|
"Invalid UTF-8 lines should be skipped"
|
|
);
|
|
}
|
|
|
|
/// Completely empty file
|
|
#[test]
|
|
fn claude_handles_empty_file() {
|
|
let dir = create_claude_temp();
|
|
let projects = dir.path().join("fixture-claude/projects/test-proj");
|
|
fs::create_dir_all(&projects).unwrap();
|
|
let file = projects.join("session.jsonl");
|
|
|
|
fs::write(&file, "").unwrap();
|
|
|
|
let conn = ClaudeCodeConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: dir.path().join("fixture-claude"),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
let convs = conn.scan(&ctx).unwrap();
|
|
// Empty file produces no conversations
|
|
assert!(convs.is_empty());
|
|
}
|
|
|
|
/// File with only whitespace
|
|
#[test]
|
|
fn claude_handles_whitespace_only_file() {
|
|
let dir = create_claude_temp();
|
|
let projects = dir.path().join("fixture-claude/projects/test-proj");
|
|
fs::create_dir_all(&projects).unwrap();
|
|
let file = projects.join("session.jsonl");
|
|
|
|
fs::write(&file, " \n\n \n ").unwrap();
|
|
|
|
let conn = ClaudeCodeConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: dir.path().join("fixture-claude"),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
let convs = conn.scan(&ctx).unwrap();
|
|
// Whitespace-only file produces no conversations
|
|
assert!(convs.is_empty());
|
|
}
|
|
|
|
// =============================================================================
|
|
// Gemini Connector - Parsing Error Tests
|
|
// =============================================================================
|
|
|
|
/// Invalid JSON in Gemini session file
|
|
#[test]
|
|
fn gemini_skips_invalid_json() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let chats_dir = tmp.path().join("hashtest").join("chats");
|
|
fs::create_dir_all(&chats_dir).unwrap();
|
|
|
|
// Write invalid JSON
|
|
fs::write(chats_dir.join("session-bad.json"), "{ not valid json }").unwrap();
|
|
|
|
let conn = GeminiConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: tmp.path().to_path_buf(),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
let convs = conn.scan(&ctx).unwrap();
|
|
// Invalid file should be skipped, no conversations
|
|
assert!(convs.is_empty());
|
|
}
|
|
|
|
/// Missing messages array in Gemini
|
|
#[test]
|
|
fn gemini_handles_missing_messages() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let chats_dir = tmp.path().join("hashtest").join("chats");
|
|
fs::create_dir_all(&chats_dir).unwrap();
|
|
|
|
let session = serde_json::json!({
|
|
"sessionId": "test-session",
|
|
"projectHash": "hashtest"
|
|
// No "messages" field
|
|
});
|
|
fs::write(
|
|
chats_dir.join("session-nomsg.json"),
|
|
serde_json::to_string(&session).unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
let conn = GeminiConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: tmp.path().to_path_buf(),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
let convs = conn.scan(&ctx).unwrap();
|
|
// File without messages should produce empty or skipped conversation
|
|
assert!(convs.is_empty() || convs[0].messages.is_empty());
|
|
}
|
|
|
|
/// Wrong type for messages field in Gemini
|
|
#[test]
|
|
fn gemini_handles_wrong_messages_type() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let chats_dir = tmp.path().join("hashtest").join("chats");
|
|
fs::create_dir_all(&chats_dir).unwrap();
|
|
|
|
let session = serde_json::json!({
|
|
"sessionId": "test-session",
|
|
"projectHash": "hashtest",
|
|
"messages": "not an array" // Wrong type
|
|
});
|
|
fs::write(
|
|
chats_dir.join("session-badtype.json"),
|
|
serde_json::to_string(&session).unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
let conn = GeminiConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: tmp.path().to_path_buf(),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
// Should not panic
|
|
let result = conn.scan(&ctx);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
// =============================================================================
|
|
// Codex Connector - Parsing Error Tests
|
|
// =============================================================================
|
|
|
|
/// Invalid JSON in Codex session file
|
|
#[test]
|
|
fn codex_skips_invalid_json() {
|
|
let tmp = TempDir::new().unwrap();
|
|
// Codex connector looks for files in sessions/ directory
|
|
// and data_root must end with "codex" (not "codex-home") to be used
|
|
let codex_home = tmp.path().join("codex");
|
|
let sessions = codex_home.join("sessions");
|
|
fs::create_dir_all(&sessions).unwrap();
|
|
|
|
// Write invalid JSON - connector uses read_to_string then serde_json::from_str
|
|
// which will fail on invalid JSON
|
|
fs::write(sessions.join("session-bad.json"), "{ invalid }").unwrap();
|
|
|
|
let conn = CodexConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: codex_home,
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
// Invalid JSON causes read error, which propagates
|
|
let result = conn.scan(&ctx);
|
|
// Codex connector currently propagates JSON errors rather than skipping
|
|
// Either behavior is acceptable - document the actual behavior
|
|
assert!(
|
|
result.is_err() || result.unwrap().is_empty(),
|
|
"Invalid JSON should either error or be skipped"
|
|
);
|
|
}
|
|
|
|
/// Codex file with missing `events/response_items`
|
|
#[test]
|
|
fn codex_handles_missing_events() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let codex_home = tmp.path().join("codex");
|
|
let sessions = codex_home.join("sessions");
|
|
fs::create_dir_all(&sessions).unwrap();
|
|
|
|
// Write valid JSON but missing the expected structure
|
|
let session = serde_json::json!({
|
|
"id": "test-session"
|
|
// No "events" or message entries
|
|
});
|
|
fs::write(
|
|
sessions.join("session-noevents.json"),
|
|
serde_json::to_string(&session).unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
let conn = CodexConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: codex_home,
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
// Should not panic - gracefully handle missing fields
|
|
let result = conn.scan(&ctx);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
// =============================================================================
|
|
// Cline Connector - Parsing Error Tests
|
|
// =============================================================================
|
|
|
|
/// Invalid JSON in Cline state file
|
|
#[test]
|
|
fn cline_skips_invalid_json() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let cline_dir = tmp.path().join("cline");
|
|
fs::create_dir_all(&cline_dir).unwrap();
|
|
|
|
// Write invalid JSON
|
|
fs::write(cline_dir.join("state.json"), "{ not valid }").unwrap();
|
|
|
|
let conn = ClineConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: tmp.path().to_path_buf(),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
let convs = conn.scan(&ctx).unwrap();
|
|
assert!(convs.is_empty());
|
|
}
|
|
|
|
/// Cline file with missing taskHistory array
|
|
#[test]
|
|
fn cline_handles_missing_task_history() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let cline_dir = tmp.path().join("cline");
|
|
fs::create_dir_all(&cline_dir).unwrap();
|
|
|
|
let state = serde_json::json!({
|
|
"version": "1.0"
|
|
// No "taskHistory" field
|
|
});
|
|
fs::write(
|
|
cline_dir.join("state.json"),
|
|
serde_json::to_string(&state).unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
let conn = ClineConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: tmp.path().to_path_buf(),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
// Should not panic
|
|
let result = conn.scan(&ctx);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
// =============================================================================
|
|
// Cross-Connector Tests - Multiple Error Types
|
|
// =============================================================================
|
|
|
|
/// Test that one bad file doesn't prevent processing other good files
|
|
#[test]
|
|
fn claude_processes_valid_files_despite_bad_ones() {
|
|
let dir = create_claude_temp();
|
|
let projects = dir.path().join("fixture-claude/projects");
|
|
|
|
// Create good project
|
|
let good_proj = projects.join("good-proj");
|
|
fs::create_dir_all(&good_proj).unwrap();
|
|
fs::write(
|
|
good_proj.join("session.jsonl"),
|
|
r#"{"type":"user","message":{"role":"user","content":"Good project"},"timestamp":"2025-11-12T18:31:18.000Z"}"#,
|
|
)
|
|
.unwrap();
|
|
|
|
// Create bad project with invalid JSON
|
|
let bad_proj = projects.join("bad-proj");
|
|
fs::create_dir_all(&bad_proj).unwrap();
|
|
fs::write(bad_proj.join("session.jsonl"), "{ invalid json }").unwrap();
|
|
|
|
let conn = ClaudeCodeConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: dir.path().join("fixture-claude"),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
let convs = conn.scan(&ctx).unwrap();
|
|
|
|
// Should have at least the good project
|
|
assert!(!convs.is_empty());
|
|
let good_conv = convs
|
|
.iter()
|
|
.find(|c| c.messages[0].content.contains("Good"));
|
|
assert!(
|
|
good_conv.is_some(),
|
|
"Good project should be processed despite bad project"
|
|
);
|
|
}
|
|
|
|
/// Test extremely long content field
|
|
#[test]
|
|
fn claude_handles_extremely_long_content() {
|
|
let dir = create_claude_temp();
|
|
let projects = dir.path().join("fixture-claude/projects/test-proj");
|
|
fs::create_dir_all(&projects).unwrap();
|
|
let file = projects.join("session.jsonl");
|
|
|
|
// Create a very long content string (1MB)
|
|
let long_content = "x".repeat(1_000_000);
|
|
let sample = format!(
|
|
r#"{{"type":"user","message":{{"role":"user","content":"{long_content}"}},"timestamp":"2025-11-12T18:31:18.000Z"}}"#
|
|
);
|
|
fs::write(&file, sample).unwrap();
|
|
|
|
let conn = ClaudeCodeConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: dir.path().join("fixture-claude"),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
// Should not panic or hang
|
|
let result = conn.scan(&ctx);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
/// Test deeply nested JSON structure
|
|
#[test]
|
|
fn claude_handles_deeply_nested_json() {
|
|
let dir = create_claude_temp();
|
|
let projects = dir.path().join("fixture-claude/projects/test-proj");
|
|
fs::create_dir_all(&projects).unwrap();
|
|
let file = projects.join("session.jsonl");
|
|
|
|
// Create nested structure (100 levels deep)
|
|
let mut nested = String::from("\"innermost\"");
|
|
for _ in 0..100 {
|
|
nested = format!("{{\"nested\":{nested}}}");
|
|
}
|
|
let sample = format!(
|
|
r#"{{"type":"user","message":{{"role":"user","content":{nested}}},"timestamp":"2025-11-12T18:31:18.000Z"}}"#
|
|
);
|
|
fs::write(&file, sample).unwrap();
|
|
|
|
let conn = ClaudeCodeConnector::new();
|
|
let ctx = ScanContext {
|
|
data_dir: dir.path().join("fixture-claude"),
|
|
scan_roots: Vec::new(),
|
|
since_ts: None,
|
|
progress_tick: None,
|
|
};
|
|
// Should not panic
|
|
let result = conn.scan(&ctx);
|
|
assert!(result.is_ok());
|
|
}
|