sync from monorepo @ 2452e92e
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
# Dirigent Protocol Tests
|
||||
|
||||
This directory contains comprehensive tests for the Dirigent protocol and OpenCode adapter.
|
||||
|
||||
## Test Files
|
||||
|
||||
### `protocol_tests.rs`
|
||||
Core protocol translation tests that verify OpenCode events are correctly translated to Dirigent protocol events.
|
||||
|
||||
**Coverage:**
|
||||
- Session creation and updates
|
||||
- User and assistant messages
|
||||
- Message parts (text, thinking, tool)
|
||||
- Event stream parsing
|
||||
- Protocol serialization/deserialization
|
||||
|
||||
**Run:** `cargo test --test protocol_tests`
|
||||
|
||||
### `deduplication_tests.rs`
|
||||
Tests for the stateful adapter's deduplication logic, ensuring no duplicate messages or parts appear in the UI.
|
||||
|
||||
**Coverage:**
|
||||
- Duplicate `MessageStarted` filtering
|
||||
- Duplicate `MessageCompleted` filtering
|
||||
- Part completion signal (`delta: null`) filtering
|
||||
- Different part types not being filtered
|
||||
- Streaming part updates working correctly
|
||||
- Full tit-tat conversation flow
|
||||
- Adapter state independence
|
||||
|
||||
**Run:** `cargo test --test deduplication_tests`
|
||||
|
||||
### `session_list_tests.rs`
|
||||
Tests for parsing OpenCode session list responses.
|
||||
|
||||
**Coverage:**
|
||||
- Session list array parsing
|
||||
- Empty session list handling
|
||||
- Single session deserialization
|
||||
- Optional fields handling
|
||||
- Timestamp parsing validation
|
||||
|
||||
**Run:** `cargo test --test session_list_tests`
|
||||
|
||||
## Fixtures
|
||||
|
||||
### `fixtures/sample_events.jsonl`
|
||||
Sample OpenCode SSE events in JSONL format (one event per line). Used for parsing validation and event stream testing.
|
||||
|
||||
### `fixtures/opencode_session_response.json`
|
||||
Real OpenCode session list response. Used for session deserialization tests.
|
||||
|
||||
**Source:** Copied from `/docs/building/opencode_session_response.json`
|
||||
|
||||
## Running All Tests
|
||||
|
||||
```bash
|
||||
# Run all protocol tests
|
||||
cargo test --package dirigent_protocol
|
||||
|
||||
# Run with output
|
||||
cargo test --package dirigent_protocol -- --nocapture
|
||||
|
||||
# Run specific test
|
||||
cargo test --package dirigent_protocol test_tit_tat_flow
|
||||
|
||||
# Run tests matching pattern
|
||||
cargo test --package dirigent_protocol duplicate
|
||||
```
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
### For OpenCode Event Translation
|
||||
|
||||
Add to `protocol_tests.rs`:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn test_translate_new_feature() {
|
||||
let adapter = OpenCodeAdapter::new();
|
||||
|
||||
// Create OpenCode event
|
||||
let oc_event = oc::Event::YourEvent { ... };
|
||||
|
||||
// Translate
|
||||
let result = adapter.translate_event(oc_event);
|
||||
|
||||
// Assert
|
||||
assert!(result.is_ok());
|
||||
match result.unwrap() {
|
||||
Event::YourDirigentEvent(data) => {
|
||||
assert_eq!(data.field, expected_value);
|
||||
}
|
||||
_ => panic!("Expected YourDirigentEvent"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### For Deduplication Logic
|
||||
|
||||
Add to `deduplication_tests.rs`:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn test_new_deduplication_rule() {
|
||||
let adapter = OpenCodeAdapter::new();
|
||||
|
||||
// Send first event
|
||||
let result1 = adapter.translate_event(first_event);
|
||||
assert!(result1.is_ok());
|
||||
|
||||
// Send duplicate event
|
||||
let result2 = adapter.translate_event(duplicate_event);
|
||||
assert!(result2.is_err());
|
||||
assert!(matches!(result2.unwrap_err(), TranslationError::Duplicate));
|
||||
}
|
||||
```
|
||||
|
||||
### For New Fixtures
|
||||
|
||||
1. Place fixture file in `tests/fixtures/`
|
||||
2. Use `include_str!` to load it:
|
||||
```rust
|
||||
let fixture = include_str!("fixtures/your_file.json");
|
||||
```
|
||||
|
||||
## Test Principles
|
||||
|
||||
### Stateful Adapter Pattern
|
||||
|
||||
⚠️ **IMPORTANT:** The adapter maintains state, so:
|
||||
|
||||
```rust
|
||||
// ✅ CORRECT: One adapter for entire event stream
|
||||
let adapter = OpenCodeAdapter::new();
|
||||
for event in events {
|
||||
adapter.translate_event(event);
|
||||
}
|
||||
|
||||
// ❌ WRONG: New adapter each time (loses state!)
|
||||
for event in events {
|
||||
let adapter = OpenCodeAdapter::new();
|
||||
adapter.translate_event(event);
|
||||
}
|
||||
```
|
||||
|
||||
### Testing Duplicates
|
||||
|
||||
When testing deduplication:
|
||||
1. Send the first event → should succeed
|
||||
2. Send the duplicate event → should fail with `TranslationError::Duplicate`
|
||||
3. Always use the SAME adapter instance
|
||||
|
||||
### Real-World Fixtures
|
||||
|
||||
Fixtures should come from actual OpenCode API responses when possible:
|
||||
- Captures real-world edge cases
|
||||
- Ensures compatibility with API changes
|
||||
- Documents actual behavior
|
||||
|
||||
## CI Integration
|
||||
|
||||
These tests run automatically on:
|
||||
- Every commit (via `cargo test`)
|
||||
- Pull requests
|
||||
- Before releases
|
||||
|
||||
**Status:** All tests should pass before merging.
|
||||
|
||||
## Coverage Report
|
||||
|
||||
```bash
|
||||
# Install tarpaulin for coverage
|
||||
cargo install cargo-tarpaulin
|
||||
|
||||
# Generate coverage report
|
||||
cargo tarpaulin --package dirigent_protocol --out Html
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [SSE Deduplication](../../../docs/building/sse_deduplication.md) - How deduplication works
|
||||
- [SSE Event Flow Analysis](../../../docs/building/sse_event_flow_analysis.md) - OpenCode event patterns
|
||||
- [Protocol Abstraction Plan](../../../docs/building/protocol_abstraction_plan.md) - Adapter architecture
|
||||
|
||||
## Test Statistics
|
||||
|
||||
**Last Updated:** 2025-11-01
|
||||
|
||||
- **Total Tests:** 24
|
||||
- **Deduplication Tests:** 7
|
||||
- **Session Tests:** 5
|
||||
- **Protocol Tests:** 12
|
||||
- **All Passing:** ✅
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Test Fails with "argument #1 of type &OpenCodeAdapter is missing"
|
||||
|
||||
**Problem:** You're calling `OpenCodeAdapter::translate_event(event)` as a static method.
|
||||
|
||||
**Solution:** Create an adapter instance first:
|
||||
```rust
|
||||
let adapter = OpenCodeAdapter::new();
|
||||
let result = adapter.translate_event(event);
|
||||
```
|
||||
|
||||
### Test Fails with "pattern does not mention field part_id"
|
||||
|
||||
**Problem:** The `MessagePartAdded` event now includes a `part_id` field.
|
||||
|
||||
**Solution:** Update your pattern match:
|
||||
```rust
|
||||
// Before
|
||||
Event::MessagePartAdded { message_id, part, delta } => { ... }
|
||||
|
||||
// After
|
||||
Event::MessagePartAdded { message_id, part_id: _, part, delta } => { ... }
|
||||
```
|
||||
|
||||
### Deduplication Test Unexpectedly Passes
|
||||
|
||||
**Problem:** You're creating a new adapter for each event.
|
||||
|
||||
**Solution:** Create ONE adapter and reuse it:
|
||||
```rust
|
||||
let adapter = OpenCodeAdapter::new();
|
||||
adapter.translate_event(event1); // First time
|
||||
adapter.translate_event(event1); // Should be duplicate!
|
||||
```
|
||||
Reference in New Issue
Block a user