diff --git a/.gitignore b/.gitignore index 4868765..aafef0f 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,8 @@ client/pubspec.lock .DS_Store *.log *.bak + +# 编译产物(不应跟踪) +/server/server +/server/nodectl +/server/agent diff --git a/server/cmd/nodectl/bootstrap_token_test.go b/server/cmd/nodectl/bootstrap_token_test.go new file mode 100644 index 0000000..45c5c2f --- /dev/null +++ b/server/cmd/nodectl/bootstrap_token_test.go @@ -0,0 +1,113 @@ +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()) + } +} diff --git a/server/server b/server/server deleted file mode 100755 index 0cd6d0f..0000000 Binary files a/server/server and /dev/null differ