package main import ( "encoding/json" "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" "strings" "testing" ) // binaryPath is set once by TestMain and used by all CLI smoke tests. var binaryPath string // TestMain builds the real binary once, runs all tests, then cleans up. func TestMain(m *testing.M) { tmp, err := os.MkdirTemp("", "latprobe-cli-*") if err != nil { panic("TestMain: MkdirTemp: " + err.Error()) } binaryPath = filepath.Join(tmp, "latprobe") out, err := exec.Command("go", "build", "-o", binaryPath, ".").CombinedOutput() if err != nil { panic("TestMain: go build failed:\n" + string(out)) } code := m.Run() os.RemoveAll(tmp) os.Exit(code) } // cli runs the compiled binary and returns stdout, stderr, and the exit code. func cli(args ...string) (stdout, stderr string, code int) { cmd := exec.Command(binaryPath, args...) var outBuf, errBuf strings.Builder cmd.Stdout = &outBuf cmd.Stderr = &errBuf err := cmd.Run() if err != nil { if ex, ok := err.(*exec.ExitError); ok { return outBuf.String(), errBuf.String(), ex.ExitCode() } } return outBuf.String(), errBuf.String(), 0 } // ── smoke tests ─────────────────────────────────────────────────────────────── // TestCLISuccess verifies the happy path against a real local server: // all phases shown, status 200, exit 0. func TestCLISuccess(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(200) })) defer srv.Close() stdout, stderr, code := cli(srv.URL) if code != 0 { t.Fatalf("exit code = %d, want 0\nstdout: %s\nstderr: %s", code, stdout, stderr) } // httptest uses 127.0.0.1 — Go skips DNS for loopback, so no DNS row. for _, want := range []string{"(200)", "TCP connect", "Total"} { if !strings.Contains(stdout, want) { t.Errorf("stdout missing %q\nstdout: %s", want, stdout) } } } // TestCLIDNSFailure verifies that an NXDOMAIN resolves to exit 2 in the // real binary (exercises the actual os.Exit path). func TestCLIDNSFailure(t *testing.T) { _, _, code := cli("https://no.such.host.for.cli.smoke.invalid") if code != exitDNS { t.Errorf("exit code = %d, want %d (dns)", code, exitDNS) } } // TestCLIJSONDNSFailure checks that --json + a DNS failure produces valid JSON // with a correctly classified error entry — exercising the full output pipeline. func TestCLIJSONDNSFailure(t *testing.T) { stdout, _, code := cli("--json", "https://no.such.host.json.cli.invalid") if code != exitDNS { t.Fatalf("exit code = %d, want %d\nstdout: %s", code, exitDNS, stdout) } var results []struct { Succeeded int `json:"succeeded"` Failed int `json:"failed"` Errors []struct { Phase string `json:"phase"` } `json:"errors"` } if err := json.Unmarshal([]byte(stdout), &results); err != nil { t.Fatalf("invalid JSON: %v\nstdout: %s", err, stdout) } if len(results) == 0 || len(results[0].Errors) == 0 { t.Fatalf("expected non-empty errors in JSON\nstdout: %s", stdout) } if results[0].Errors[0].Phase != "dns" { t.Errorf("error phase = %q, want \"dns\"", results[0].Errors[0].Phase) } }