mock-igd 0.1.0

A mock UPnP Internet Gateway Device (IGD) server for testing
Documentation
# mock-igd Design Document

## Overview

A mock server for UPnP IGD (Internet Gateway Device). Used for testing client implementations.

## Architecture

### UPnP IGD Components

1. **SSDP** (UDP 1900) - Device discovery protocol
2. **Device Description** (HTTP GET) - XML device information
3. **SOAP Actions** (HTTP POST) - Port mapping and other operations

### Module Structure

```
src/
├── lib.rs              # Public API
├── server/
│   ├── mod.rs
│   ├── ssdp.rs         # SSDP response (M-SEARCH)
│   └── http.rs         # HTTP/SOAP server
├── action/
│   ├── mod.rs
│   ├── types.rs        # UPnP IGD action definitions
│   └── parser.rs       # SOAP request parser
├── matcher/
│   ├── mod.rs
│   └── builder.rs      # Request matching conditions
├── responder/
│   ├── mod.rs
│   ├── builder.rs      # Response generation
│   └── templates.rs    # XML/SOAP templates
└── mock.rs             # Mock registration and management
```

## Behavior Definition (Matcher + Responder Pattern)

Design inspired by wiremock-rs. Flexibility is achieved by separating request matching conditions from responses.

### Basic Usage

```rust
use mock_igd::{MockIgdServer, Action, Responder};

#[tokio::test]
async fn test_get_external_ip() {
    let server = MockIgdServer::start().await;

    // Define behavior
    server.mock(
        Action::GetExternalIPAddress,
        Responder::success()
            .with_external_ip("203.0.113.1")
    ).await;

    // Run the client under test
    let client = IgdClient::new(server.url());
    let ip = client.get_external_ip().await.unwrap();
    assert_eq!(ip, "203.0.113.1".parse().unwrap());
}
```

### Conditional Matching

```rust
// Match specific parameters
server.mock(
    Action::AddPortMapping
        .with_external_port(8080)
        .with_protocol("TCP"),
    Responder::success()
).await;

// Match any request
server.mock(
    Action::any(),
    Responder::error(501, "ActionNotImplemented")
).await;
```

### Error Responses

```rust
// UPnP standard error codes
server.mock(
    Action::AddPortMapping.with_external_port(80),
    Responder::error(718, "ConflictInMappingEntry")
).await;
```

### Custom Responses

```rust
// Return a fully custom response
server.mock(
    Action::GetExternalIPAddress,
    Responder::custom(|_request| {
        // Arbitrary logic
        HttpResponse::Ok()
            .content_type("text/xml")
            .body(custom_soap_xml)
    })
).await;
```

## Core Type Definitions

### Action (UPnP IGD Actions)

```rust
pub enum Action {
    // WANIPConnection
    GetExternalIPAddress,
    AddPortMapping(AddPortMappingMatcher),
    DeletePortMapping(DeletePortMappingMatcher),
    GetGenericPortMappingEntry(GetGenericPortMappingEntryMatcher),
    GetSpecificPortMappingEntry(GetSpecificPortMappingEntryMatcher),

    // WANCommonInterfaceConfig
    GetCommonLinkProperties,
    GetTotalBytesReceived,
    GetTotalBytesSent,

    // Match any action
    Any,
}
```

### Responder

```rust
pub struct Responder {
    kind: ResponderKind,
}

enum ResponderKind {
    Success(SuccessResponse),
    Error { code: u16, description: String },
    Custom(Box<dyn Fn(&SoapRequest) -> HttpResponse + Send + Sync>),
}
```

### Mock

```rust
pub struct Mock {
    action: Action,
    responder: Responder,
    priority: u32,        // Higher = checked first
    times: Option<u32>,   // Max match count (None = unlimited)
}
```

## Matching Priority

1. Only Mocks with remaining `times` are considered
2. Ordered by `priority` (highest first)
3. For equal priority, registration order (last wins)
4. First matching Mock's Responder is used
5. If no match, return 404 or 501 error

## Future Extensions (Phase 2+)

### Stateful Mode

Maintains an internal port mapping table and behaves like a real IGD.

```rust
let server = MockIgdServer::start()
    .with_stateful_behavior()
    .with_external_ip("203.0.113.1")
    .await;

// Entries added via AddPortMapping can be retrieved
// via GetGenericPortMappingEntry
```

### Record & Replay Mode

```rust
// Record
let server = MockIgdServer::start()
    .record_to("fixtures/session.json")
    .await;

// Replay
let server = MockIgdServer::from_recording("fixtures/session.json").await;
```

### Configuration File Loading

```rust
let server = MockIgdServer::from_config("mock-config.yaml").await;
```

## Dependencies

- `tokio` - Async runtime
- `axum` - HTTP server
- `quick-xml` - XML parsing and generation
- `socket2` - UDP socket (for SSDP)