# Quickstart: Smart DNS Server Resolution Modes

**Feature Branch**: `002-server-resolution-modes`  
**Date**: 2026-03-04  

---

## Prerequisites

- Go 1.20 toolchain installed and on `PATH`
- Repository cloned and on branch `002-server-resolution-modes`
- `go mod tidy` run successfully

## Build

```powershell
# From repository root
go build -o dns-helper.exe .
```

Cross-compile (existing build.ps1 pattern):

```powershell
.\build.ps1
```

## Run Tests

```powershell
go test ./... -v
```

## TDD Workflow

This feature follows the Red-Green-Refactor cycle mandated by Constitution Principle VI.

### Order of Implementation

The recommended implementation order (each step follows TDD):

1. **`resolver/parse.go`** — `ParseServerFlag()` and `ExtractLabelLevels()` (pure functions, no I/O)
2. **`platform/network.go`** — `NetworkInfo` type, `NetworkDiscoverer` interface, `FakeNetworkDiscoverer`
3. **`platform/network_windows.go`** — Windows `Discover()` implementation
4. **`platform/network_linux.go`** — Linux `Discover()` implementation
5. **`platform/network_darwin.go`** — macOS `Discover()` implementation
6. **`resolver/pool.go`** — `BuildResolverPool()` and `BootstrapResolvers`
7. **`resolver/transport.go`** — Generalized `udpQuery()` shared transport (extract from existing `resolver.go`)
8. **`resolver/authority.go`** — `ParallelNSFanOut()`, `SelectAuthoritativeNS()`, `QueryAuthoritative()`
9. **`resolver/splithorizon.go`** — `CheckSplitHorizon()` (Stage 2.5 cross-check)
10. **`resolver/fallback.go`** — `ParallelAFallback()`
11. **`resolver/modes.go`** — `Resolve()` mode dispatcher integrating all components
12. **`main.go`** — Update CLI flag parsing, wire new resolver pipeline

### Per-Step TDD Cycle

For each item above:

1. **Red**: Write the test in `*_test.go` — it should fail (function doesn't exist yet).
2. **Green**: Implement the minimum code to make the test pass.
3. **Refactor**: Clean up while keeping tests green.

Example for `ParseServerFlag`:

```go
// resolver/parse_test.go
func TestParseServerFlag_Default(t *testing.T) {
    mode, err := resolver.ParseServerFlag("")
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if mode.Mode != "default" {
        t.Errorf("expected mode 'default', got %q", mode.Mode)
    }
}

func TestParseServerFlag_Local(t *testing.T) {
    mode, err := resolver.ParseServerFlag("local")
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if mode.Mode != "local" {
        t.Errorf("expected mode 'local', got %q", mode.Mode)
    }
}

func TestParseServerFlag_InvalidPort(t *testing.T) {
    _, err := resolver.ParseServerFlag("10.0.0.53:0")
    if err == nil {
        t.Fatal("expected error for port 0")
    }
}
```

### Testing Parallel Fan-out

Use the existing fake DNS server helpers from `resolver/resolver_test.go`:

```go
// Start multiple fake DNS servers, each responding to NS queries differently.
// Pass their addresses as the resolver pool to ParallelNSFanOut.
ns1 := startFakeDNS(t, handleNSQuery("example.com", []string{"ns1.example.com."}))
ns2 := startFakeDNS(t, handleNSQueryNXDOMAIN)

results := resolver.ParallelNSFanOut(ctx, []string{ns1, ns2}, []string{"example.com"}, 3*time.Second)
// Assert: 2 results, one with NS records, one with nil
```

### Testing Platform Discovery

Use `FakeNetworkDiscoverer` for unit tests — never call real OS commands in unit tests:

```go
fake := &platform.FakeNetworkDiscoverer{
    Info: platform.NetworkInfo{
        DNSServers: []string{"10.0.0.1", "10.0.0.2"},
        Gateway:    "10.0.0.1",
        Interface:  "eth0",
    },
}
pool := resolver.BuildResolverPool(resolver.ServerMode{Mode: "default"}, fake.Info)
// Assert: pool contains local resolvers + bootstrap set
```

Platform-specific integration tests (build-tagged) can test real `os/exec` parsing:

```go
//go:build windows

func TestWindowsDiscover(t *testing.T) {
    d := &platform.WindowsNetworkDiscoverer{}
    info, err := d.Discover()
    // Assert: no error, DNSServers non-empty on a typical system
}
```

## Manual Verification

### Smart Default Resolution (no -server)

```powershell
# Should discover authoritative NS for example.com and query it directly
.\dns-helper.exe add -host www.example.com -verbose
```

### Local Resolver Mode

```powershell
.\dns-helper.exe add -host internal.corp.local -server local -verbose
```

### Gateway Mode

```powershell
.\dns-helper.exe add -host www.example.com -server gateway -verbose
```

### Explicit IP (existing, unchanged)

```powershell
.\dns-helper.exe add -host www.example.com -server 8.8.8.8
```

### Timeout Override

```powershell
.\dns-helper.exe add -host www.example.com -timeout 1 -verbose
```

## Key Files Changed (Summary)

| File | Change |
|------|--------|
| `main.go` | Updated `runAdd()`: `-server` optional, new `-timeout`/`-verbose` flags, mode dispatch |
| `resolver/resolver.go` | Existing code unchanged; generalized UDP transport extracted |
| `resolver/parse.go` | **New**: `ParseServerFlag()`, `ExtractLabelLevels()` |
| `resolver/pool.go` | **New**: `BuildResolverPool()`, `BootstrapResolvers` |
| `resolver/transport.go` | **New**: shared `udpQuery()` transport layer |
| `resolver/authority.go` | **New**: `ParallelNSFanOut()`, `SelectAuthoritativeNS()`, `QueryAuthoritative()` |
| `resolver/splithorizon.go` | **New**: `CheckSplitHorizon()` — Stage 2.5 cross-check |
| `resolver/fallback.go` | **New**: `ParallelAFallback()` |
| `resolver/modes.go` | **New**: `Resolve()` mode dispatcher |
| `platform/platform.go` | Extended with `NetworkInfo`, `NetworkDiscoverer` interface |
| `platform/platform_windows.go` | **Extended**: `Discover()` via `netsh` |
| `platform/platform_linux.go` | **Extended**: `Discover()` via `ip route` + `/etc/resolv.conf` |
| `platform/platform_darwin.go` | **Extended**: `Discover()` via `route` + `scutil` |
