package main import ( "context" "strings" "testing" "github.com/alicebob/miniredis/v2" "github.com/redis/go-redis/v9" "github.com/wangjia/pangolin/server/internal/mtls" ) // newTestBootstrapManager spins up an in-process miniredis instance and returns // a BootstrapTokenManager backed by it, plus a cleanup function. func newTestBootstrapManager(t *testing.T) (*mtls.BootstrapTokenManager, *miniredis.Miniredis) { t.Helper() mr := miniredis.RunT(t) rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) t.Cleanup(func() { rdb.Close() }) return mtls.NewBootstrapTokenManager(rdb), mr } // TestBootstrapTokenCmd_IssuesHexToken verifies that the token manager (the // same one wired into cmdBootstrapToken) produces a well-formed 64-char hex token. func TestBootstrapTokenCmd_IssuesHexToken(t *testing.T) { mgr, _ := newTestBootstrapManager(t) ctx := context.Background() const nodeUUID = "11111111-2222-3333-4444-555555555555" token, err := mgr.IssueToken(ctx, nodeUUID) if err != nil { t.Fatalf("IssueToken: %v", err) } if len(token) != 64 { t.Errorf("token length = %d; want 64 hex chars", len(token)) } // Verify it's pure lowercase hex. const hexChars = "0123456789abcdef" for _, c := range token { if !strings.ContainsRune(hexChars, c) { t.Errorf("token contains non-hex character %q in %q", c, token) } } } // TestBootstrapTokenCmd_ConsumeOnce verifies the one-time guarantee: the token // can be consumed exactly once, and a second call returns an error. func TestBootstrapTokenCmd_ConsumeOnce(t *testing.T) { mgr, _ := newTestBootstrapManager(t) ctx := context.Background() const nodeUUID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" token, err := mgr.IssueToken(ctx, nodeUUID) if err != nil { t.Fatalf("IssueToken: %v", err) } // First consume must return the correct UUID. got, err := mgr.ConsumeToken(ctx, token) if err != nil { t.Fatalf("ConsumeToken (1st): %v", err) } if got != nodeUUID { t.Errorf("ConsumeToken returned %q; want %q", got, nodeUUID) } // Second consume must fail (one-time guarantee). _, err = mgr.ConsumeToken(ctx, token) if err == nil { t.Fatal("ConsumeToken (2nd): expected error, got nil") } } // TestBootstrapTokenCmd_MissingNode verifies that cmdBootstrapToken returns an // error when -node is not supplied, without touching Redis. func TestBootstrapTokenCmd_MissingNode(t *testing.T) { ctx := context.Background() err := run(ctx, "bootstrap-token", []string{}) if err == nil { t.Fatal("expected error when -node is empty, got nil") } if !strings.Contains(err.Error(), "-node") { t.Errorf("error message %q should mention -node flag", err.Error()) } } // TestBootstrapTokenCmd_UnknownRedis verifies that cmdBootstrapToken returns an // error when REDIS_ADDR points to an unreachable address. func TestBootstrapTokenCmd_UnknownRedis(t *testing.T) { t.Setenv("REDIS_ADDR", "127.0.0.1:1") // port 1 is always unreachable ctx := context.Background() err := run(ctx, "bootstrap-token", []string{"-node", "some-uuid"}) if err == nil { t.Fatal("expected error for unreachable Redis, got nil") } if !strings.Contains(err.Error(), "redis") && !strings.Contains(err.Error(), "connect") { t.Errorf("error %q should mention redis/connect", err.Error()) } } // TestRun_UnknownSubcommand verifies that run returns an error for unrecognised // subcommands (regression guard on the switch statement). func TestRun_UnknownSubcommand(t *testing.T) { err := run(context.Background(), "nonexistent", nil) if err == nil { t.Fatal("expected error for unknown subcommand, got nil") } if !strings.Contains(err.Error(), "unknown subcommand") { t.Errorf("error %q should mention 'unknown subcommand'", err.Error()) } }