package diagnostics import ( "bytes" "context" "encoding/json" "io" "log/slog" "net" "os" "path/filepath" "strings" "syscall" "testing" ) func TestExportPersistsOnlyAllowlistedFields(t *testing.T) { t.Parallel() var output bytes.Buffer path := filepath.Join(t.TempDir(), "diagnostics.jsonl") r := &Recorder{path: path, logger: slog.New(slog.NewJSONHandler(&output, nil))} const secret = "password-token-cookie-SID-secret" r.Record(Event{Code: "ip_fetch_failed", Target: "https://user:" + secret + "@private.test/?token=" + secret, Failure: secret, Address: secret, Family: secret, Network: &Network{Nameservers: []string{"127.0.0.11", secret}, DockerUpstreams: []string{secret}, Routes: []Route{{secret, secret, secret, secret}}}}) r.Record(Event{Code: secret, Target: secret}) r.Record(Event{Code: "ip_fetch_failed", Address: "fe80::1%" + secret, DNSResolver: "fe80::2%" + secret, Network: &Network{Nameservers: []string{"fe80::3%" + secret}}}) reloaded := &Recorder{path: path, logger: slog.New(slog.NewTextHandler(io.Discard, nil))} encoded, err := json.Marshal(reloaded.Snapshot()) if err != nil { t.Fatal(err) } file, err := os.ReadFile(path) if err != nil { t.Fatal(err) } for _, data := range []string{string(encoded), string(file), output.String()} { if strings.Contains(data, secret) || strings.Contains(data, "private.test") { t.Fatalf("secret escaped allowlist: %s", data) } } if !strings.Contains(string(encoded), "127.0.0.11") || !strings.Contains(string(encoded), "ip_fetch_failed") { t.Fatal("technical evidence lost") } info, err := os.Stat(path) if err != nil || info.Mode().Perm() != 0600 { t.Fatalf("unsafe diagnostic permissions: %v %v", info, err) } } func TestDiagnosticRotationIsBoundedAndReloadable(t *testing.T) { t.Parallel() path := filepath.Join(t.TempDir(), "diagnostics.jsonl") file, err := os.Create(path) if err != nil { t.Fatal(err) } if err = file.Truncate(maxFileSize); err != nil { t.Fatal(err) } file.Close() r := &Recorder{path: path, logger: slog.New(slog.NewTextHandler(io.Discard, nil))} r.Record(Event{Code: "update_recovered", Attempt: 20}) info, err := os.Stat(path + ".1") if err != nil || info.Size() != maxFileSize { t.Fatalf("rotation: %v %v", info, err) } events := r.Snapshot() if len(events) != 1 || events[0].Attempt != 20 { t.Fatalf("new event lost after rotation: %#v", events) } } func TestNetworkFailuresAndRouteDecoding(t *testing.T) { t.Parallel() for _, test := range []struct { err error want string }{ {&net.DNSError{IsTemporary: true}, "dns_server_failure"}, {&net.DNSError{IsTimeout: true}, "dns_timeout"}, {syscall.EHOSTUNREACH, "no_route"}, {syscall.ECONNREFUSED, "connection_refused"}, {context.DeadlineExceeded, "timeout"}, } { if got := Failure(test.err); got != test.want { t.Errorf("failure=%s want %s", got, test.want) } } if got := routeIP("010012AC"); got != "172.18.0.1" { t.Fatalf("default gateway decoded incorrectly: %s", got) } }