# 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!
```
