c3d08b4821
gates / gates (push) Successful in 19s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
68 lines
2.8 KiB
Go
68 lines
2.8 KiB
Go
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 <vmid> -- docker inspect -f <tmpl> 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)
|
|
}
|
|
}
|