//! Anthropic フィクスチャベースの統合テスト //! //! 記録されたAPIレスポンスを使ってイベントパースをテストする use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; use worker_types::{BlockType, DeltaContent, Event, ResponseStatus}; /// フィクスチャファイルからEventを読み込む fn load_events_from_fixture(path: impl AsRef) -> Vec { let file = File::open(path).expect("Failed to open fixture file"); let reader = BufReader::new(file); let mut lines = reader.lines(); // 最初の行はメタデータ、スキップ let _metadata = lines.next().expect("Empty fixture file").unwrap(); // 残りはイベント let mut events = Vec::new(); for line in lines { let line = line.unwrap(); if line.is_empty() { continue; } // RecordedEvent構造体をパース let recorded: serde_json::Value = serde_json::from_str(&line).unwrap(); let data = recorded["data"].as_str().unwrap(); // data フィールドからEventをデシリアライズ let event: Event = serde_json::from_str(data).unwrap(); events.push(event); } events } /// フィクスチャディレクトリからanthropic_*ファイルを検索 fn find_anthropic_fixtures() -> Vec { let fixtures_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures"); if !fixtures_dir.exists() { return Vec::new(); } std::fs::read_dir(&fixtures_dir) .unwrap() .filter_map(|e| e.ok()) .map(|e| e.path()) .filter(|p| { p.file_name() .and_then(|n| n.to_str()) .is_some_and(|n| n.starts_with("anthropic_") && n.ends_with(".jsonl")) }) .collect() } #[test] fn test_fixture_events_deserialize() { let fixtures = find_anthropic_fixtures(); assert!(!fixtures.is_empty(), "No anthropic fixtures found"); for fixture_path in fixtures { println!("Testing fixture: {:?}", fixture_path); let events = load_events_from_fixture(&fixture_path); assert!(!events.is_empty(), "Fixture should contain events"); // 各イベントが正しくデシリアライズされているか確認 for event in &events { // Debugトレイトで出力可能か確認 let _ = format!("{:?}", event); } println!(" Loaded {} events", events.len()); } } #[test] fn test_fixture_event_sequence() { let fixtures = find_anthropic_fixtures(); if fixtures.is_empty() { println!("No fixtures found, skipping test"); return; } // 最初のフィクスチャをテスト let events = load_events_from_fixture(&fixtures[0]); // 期待されるイベントシーケンスを検証 // Usage -> BlockStart -> BlockDelta -> BlockStop -> Usage -> Status // 最初のUsageイベント assert!( matches!(&events[0], Event::Usage(_)), "First event should be Usage" ); // BlockStartイベント if let Event::BlockStart(start) = &events[1] { assert_eq!(start.block_type, BlockType::Text); assert_eq!(start.index, 0); } else { panic!("Second event should be BlockStart"); } // BlockDeltaイベント if let Event::BlockDelta(delta) = &events[2] { assert_eq!(delta.index, 0); if let DeltaContent::Text(text) = &delta.delta { assert!(!text.is_empty(), "Delta text should not be empty"); println!(" Text content: {}", text); } else { panic!("Delta should be Text"); } } else { panic!("Third event should be BlockDelta"); } // BlockStopイベント if let Event::BlockStop(stop) = &events[3] { assert_eq!(stop.block_type, BlockType::Text); assert_eq!(stop.index, 0); } else { panic!("Fourth event should be BlockStop"); } // 最後のStatusイベント if let Event::Status(status) = events.last().unwrap() { assert_eq!(status.status, ResponseStatus::Completed); } else { panic!("Last event should be Status(Completed)"); } } #[test] fn test_fixture_usage_tokens() { let fixtures = find_anthropic_fixtures(); if fixtures.is_empty() { println!("No fixtures found, skipping test"); return; } let events = load_events_from_fixture(&fixtures[0]); // Usageイベントを収集 let usage_events: Vec<_> = events .iter() .filter_map(|e| { if let Event::Usage(u) = e { Some(u) } else { None } }) .collect(); assert!( !usage_events.is_empty(), "Should have at least one Usage event" ); // 最後のUsageイベントはトークン数を持つはず let last_usage = usage_events.last().unwrap(); assert!(last_usage.input_tokens.is_some()); assert!(last_usage.output_tokens.is_some()); assert!(last_usage.total_tokens.is_some()); println!( " Token usage: {} input, {} output, {} total", last_usage.input_tokens.unwrap(), last_usage.output_tokens.unwrap(), last_usage.total_tokens.unwrap() ); } #[test] fn test_fixture_with_timeline() { use std::sync::{Arc, Mutex}; use worker::{Handler, TextBlockEvent, TextBlockKind, Timeline}; let fixtures = find_anthropic_fixtures(); if fixtures.is_empty() { println!("No fixtures found, skipping test"); return; } let events = load_events_from_fixture(&fixtures[0]); // テスト用ハンドラー struct TestCollector { texts: Arc>>, } impl Handler for TestCollector { type Scope = String; fn on_event(&mut self, buffer: &mut String, event: &TextBlockEvent) { match event { TextBlockEvent::Start(_) => {} TextBlockEvent::Delta(text) => buffer.push_str(text), TextBlockEvent::Stop(_) => { let text = std::mem::take(buffer); self.texts.lock().unwrap().push(text); } } } } let collected = Arc::new(Mutex::new(Vec::new())); let mut timeline = Timeline::new(); timeline.on_text_block(TestCollector { texts: collected.clone(), }); // フィクスチャからのイベントをTimelineにディスパッチ for event in &events { timeline.dispatch(event); } // テキストが収集されたことを確認 let texts = collected.lock().unwrap(); assert_eq!(texts.len(), 1, "Should have collected one text block"); assert!(!texts[0].is_empty(), "Collected text should not be empty"); println!(" Collected text: {}", texts[0]); }