# Testing & Verification — Phase 0

How to verify Phase 0 is complete, both via automated tests and by hand.

## Automated tests

```bash
go test ./...
```

Coverage map (one or more meaningful tests per subsystem, per CLAUDE.md):

| Package | Tests |
|---|---|
| `internal/logging` | `TestRedactCoversAllSensitiveFields`, `TestRedactInsideZerologOutput`, `TestRedactHandlesNilAndPrimitives`, `TestRedactDoesNotLeakUnexportedFields`, `TestSetupEmitsServiceField` |
| `internal/secrets` | `TestRoundTrip`, `TestUpsertOverwrites`, `TestDelete`, `TestEncryptionIsOpaque`, `TestMissingIdentityWithoutGenerateFails`, `TestReuseExistingIdentity` |
| `internal/config` | `TestLoadFromBytesValid`, `TestLoadFromBytesAppliesDefaults`, `TestValidationRejectsBadYAML`, `TestValidationRejectsMalformedYAML`, `TestManagerHotReload`, `TestManagerRejectsBadReload` |
| `internal/db` | `TestLoadMigrationsParses`, `TestLoadMigrationsDetectsDuplicateVersion`, `TestApplyAndIdempotent`, `TestApplyRollsBackOnFailure`, `TestApplyEmpty` |
| `internal/observability` | `TestRecordAndQuery`, `TestRecordWithoutDB`, `TestHealthChecker` |
| `internal/server` | `TestMatchesAny`, `TestFindTailscaleAddrEmptyPattern`, `TestFindTailscaleAddrNoMatchReturnsError` |

The redaction test asserts the exact set of sensitive field names from CLAUDE.md (`password`, `cookie`, `mam_id`, `token`, `secret`, `authorization`, `api_key`, `set-cookie`, `raw_cookie`) and verifies the placeholder `[REDACTED]` appears for each.

### Building without DuckDB

If your dev host has no C compiler, run tests with the no-DuckDB tag:

```bash
go test -tags no_duckdb ./...
```

All tests pass; the DuckDB health check reports `degraded` in this build mode.

### Building the binary on a clean checkout

```bash
git clone <repo> command-center && cd command-center
go build ./cmd/command-center          # Go-only build (frontend served as 404)
./scripts/build.sh                     # Full build with embedded frontend
```

`scripts/build.sh` runs `npm ci`, `npm run build`, copies `web/dist/` into `internal/webui/dist/`, then `go build`.

## Manual verification

These are the steps a human operator runs to confirm Phase 0 actually works in a realistic deployment.

### V1. Dev-mode boot

```bash
./scripts/dev.sh
```

Expected logs:

```
{"level":"info","component":"server","addr":"127.0.0.1:8443","message":"listening"}
```

A warning line saying `dev_mode: no Tailscale interface; falling back to 127.0.0.1` is expected and intended on hosts without Tailscale.

### V2. Health endpoint

```bash
curl -s http://127.0.0.1:8443/api/system/health | jq
```

`status` should be `ok` (or `degraded` if you built with `-tags no_duckdb`). All four checks should be present.

### V3. Events endpoint

```bash
curl -s 'http://127.0.0.1:8443/api/system/events?limit=5' | jq
```

At minimum the "command-center booting" event written by `main.go` should appear.

Filter by component:

```bash
curl -s 'http://127.0.0.1:8443/api/system/events?component=boot' | jq
```

Filter by level:

```bash
curl -s 'http://127.0.0.1:8443/api/system/events?level=info' | jq
```

### V4. Frontend

Open http://127.0.0.1:8443/ in a browser. You should see "Hello Command Center" and a list of health checks. The PWA manifest is available at http://127.0.0.1:8443/manifest.webmanifest and validates with browser devtools' "Application → Manifest" panel.

### V5. Hot reload

While the server runs, edit `config/system.yaml`, change `logging.level` from `info` to `debug`, save. The server logs:

```
{"level":"info","component":"config","message":"config reloaded"}
```

Now break the config: set `listen.port: 0` and save. The server logs:

```
{"level":"error","component":"config","error":"config: invalid system.yaml: listen.port out of range: 0","message":"config reload rejected; previous config remains in effect"}
```

The previous valid config remains active; the server keeps serving.

### V6. Tailscale-only refusal (production)

On a Linux host with Tailscale installed but `dev_mode: false` in `/etc/command-center/config/system.yaml`:

```bash
sudo systemctl start command-center
journalctl -u command-center -n 5
```

Expected: bound to the Tailscale interface, not 0.0.0.0 or any other address. Verify with:

```bash
sudo ss -tlnp | grep command-center
```

The bind address should match `ip -4 addr show tailscale0` output. Stop Tailscale (`sudo tailscale down`), restart the service, observe the failure-to-start with a clear error:

```
{"level":"error","error":"server: tailscale interface (pattern \"tailscale*\") not present and dev_mode is off: server: no Tailscale interface found","message":"command-center exited with error"}
```

Bring Tailscale back, restart, confirm recovery.

### V7. Redaction in real logs

In a Go REPL or scratch test:

```go
package main

import (
    "github.com/rs/zerolog"
    "os"
    "github.com/operator/command-center/internal/logging"
)

type cookieJar struct {
    Password, Cookie, MamID string
}

func main() {
    log := logging.Setup("debug", os.Stdout)
    log.Info().Interface("creds", logging.Redact(cookieJar{
        Password: "p4ssw0rd", Cookie: "sid=abc", MamID: "xyz",
    })).Msg("test")
}
```

Output:

```
{"level":"info","creds":{"Password":"[REDACTED]","Cookie":"[REDACTED]","MamID":"[REDACTED]"},...}
```

## Deliverables checklist mapping

Cross-reference with the `CLAUDE.md` Phase 0 deliverables checklist:

- [x] `go build ./cmd/command-center` — `scripts/build.sh`
- [x] Boots, opens SQLite + DuckDB, binds to Tailscale (or 127.0.0.1 in dev) — V1, V6
- [x] `GET /api/system/health` returns 200 + JSON — V2
- [x] `GET /api/system/events` returns recent events with filtering — V3
- [x] Fails gracefully when Tailscale absent / age key missing / config missing — V6 and unit tests
- [x] `systemd/command-center.service` present and documented — `DEPLOYMENT.md`
- [x] SQLite Phase 0 schema matches `PROJECT.md` Appendix A — `migrations/001_phase0_foundation.sql`, `TestApplyAndIdempotent`
- [x] DuckDB analytical snapshot tables — `internal/db/duckdb.go`
- [x] Structured logger with redaction verified — `internal/logging` tests
- [x] Age secrets round-trip test — `internal/secrets/secrets_test.go`
- [x] Config loader with fsnotify hot reload and rollback — V5 and tests
- [x] Frontend builds, embeds via `go:embed`, serves landing page — V4
- [x] PWA manifest present and valid — `web/public/manifest.webmanifest`, V4
- [x] `PROGRESS.md`, `DECISIONS.md`, `DEPLOYMENT.md`, `TESTING.md` written — this repo
- [x] Clean checkout can `go build`, `npm run build`, pass tests — `scripts/build.sh`, `go test ./...`
