Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
# 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` |
|
||||
Reference in New Issue
Block a user