package hub import ( "context" "errors" "io" "strings" "testing" ) // R-841: the three states from one `docker inspect` answer. Red-proof: map "unhealthy" to running (the container // state alone — what a plain "is it running" probe would say) and the "running but not connected" case fails. func TestClassifyTunnel(t *testing.T) { cases := []struct { name, out, errOut string err error want string detail string }{ {"connected", "running|0|healthy\n", "", nil, TunnelRunning, "connected"}, {"running but not connected", "running|0|unhealthy\n", "", nil, TunnelNotRunning, "NOT connected"}, {"stopped", "exited|137|unhealthy\n", "", nil, TunnelNotRunning, "exit code 137"}, {"absent", "", "Error: No such object: cloudflared", errors.New("exit status 1"), TunnelNotRunning, "no cloudflared container"}, {"still starting", "running|0|starting\n", "", nil, TunnelUnknown, "starting"}, {"no health check (older controller)", "running|0|none\n", "", nil, TunnelRunning, "connection not checked"}, {"guest not running", "", "CT 9201 not running", errors.New("exit status 255"), TunnelUnknown, "could not ask"}, {"sudo refused", "", "sudo: a password is required", errors.New("exit status 1"), TunnelUnknown, "could not ask"}, } for _, c := range cases { st, d := ClassifyTunnel(c.out, c.errOut, c.err) if st != c.want || !strings.Contains(d, c.detail) { t.Errorf("%s: got %q (%s), want %q (…%s…)", c.name, st, d, c.want, c.detail) } } } type tunnelRunner struct { calls []string out map[string]string } func (r *tunnelRunner) Run(_ context.Context, name string, args ...string) ([]byte, []byte, error) { line := name + " " + strings.Join(args, " ") r.calls = append(r.calls, line) return []byte(r.out[args[1]]), nil, nil } func (r *tunnelRunner) RunStdin(ctx context.Context, _ io.Reader, name string, args ...string) ([]byte, []byte, error) { return r.Run(ctx, name, args...) } // The probe uses EXACTLY the existing sudoers shape `pct exec -- docker inspect -f cloudflared`, and // with no customer guest it is unknown, never down. func TestGuestTunnelProber(t *testing.T) { r := &tunnelRunner{out: map[string]string{"9201": "running|0|healthy"}} p := GuestTunnelProber{Runner: r, Guests: func(context.Context) ([]int, error) { return []int{9201}, nil }} if st, d := p.Status(context.Background()); st != TunnelRunning || d != "connected" { t.Fatalf("got %q %q", st, d) } want := "/usr/sbin/pct exec 9201 -- docker inspect -f " + tunnelInspect + " cloudflared" if len(r.calls) != 1 || r.calls[0] != want { t.Fatalf("command = %q, want %q", r.calls, want) } none := GuestTunnelProber{Runner: r, Guests: func(context.Context) ([]int, error) { return nil, nil }} if st, _ := none.Status(context.Background()); st != TunnelUnknown { t.Fatalf("no guest → %q, want unknown", st) } }