package secrets import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" ) func TestFetchNodeReturnsData(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v1/kv/data/atlas/nodes/n1": w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{ "data": map[string]any{ "data": map[string]any{ "ssh_password": "p1", "atlas_password_hash": "$atlas$hash", "root_password": "root-pw", "k3s_token": "t1", "cloud_init": "ci", }, }, }) default: http.NotFound(w, r) } })) defer srv.Close() c := &Client{Addr: srv.URL, Token: "tok"} sec, err := c.FetchNode(context.Background(), "n1") if err != nil { t.Fatalf("fetch: %v", err) } if sec.SSHPassword != "p1" || sec.AtlasPasswordHash != "$atlas$hash" || sec.RootPassword != "root-pw" || sec.K3sToken != "t1" || sec.CloudInit != "ci" { t.Fatalf("unexpected secrets: %+v", sec) } } func TestApproRoleLogin(t *testing.T) { loginCalled := false srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v1/auth/approle/login": loginCalled = true w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{ "auth": map[string]any{ "client_token": "newtoken", }, }) case "/v1/kv/data/atlas/nodes/n1": if r.Header.Get("X-Vault-Token") != "newtoken" { t.Fatalf("missing token after approle login") } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{ "data": map[string]any{ "data": map[string]any{}, }, }) default: http.NotFound(w, r) } })) defer srv.Close() c := &Client{Addr: srv.URL, RoleID: "r", SecretID: "s", Client: srv.Client()} if _, err := c.FetchNode(context.Background(), "n1"); err != nil { t.Fatalf("fetch with approle: %v", err) } if !loginCalled { t.Fatalf("approle login not called") } } func TestLoginIfNeededNoopWithToken(t *testing.T) { c := &Client{Addr: "http://example.invalid", Token: "existing"} if err := c.LoginIfNeeded(context.Background()); err != nil { t.Fatalf("LoginIfNeeded: %v", err) } if c.Token != "existing" { t.Fatalf("token unexpectedly changed") } } func TestNewFromEnvPopulatesCredentials(t *testing.T) { t.Setenv("VAULT_ADDR", "http://vault.example") t.Setenv("VAULT_TOKEN", "tok") t.Setenv("VAULT_ROLE_ID", "role") t.Setenv("VAULT_SECRET_ID", "secret") c := NewFromEnv() if c.Addr != "http://vault.example" || c.Token != "tok" || c.RoleID != "role" || c.SecretID != "secret" { t.Fatalf("unexpected env client: %+v", c) } } func TestFetchNodeAndLoginErrorBranches(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v1/auth/approle/login": http.Error(w, "denied", http.StatusForbidden) case "/v1/kv/data/atlas/nodes/missing": http.Error(w, "down", http.StatusInternalServerError) default: http.NotFound(w, r) } })) defer srv.Close() c := &Client{Addr: srv.URL, RoleID: "r", SecretID: "s", Client: srv.Client()} if _, err := c.FetchNode(context.Background(), "missing"); err == nil { t.Fatal("expected approle login failure") } c = &Client{Addr: srv.URL, Token: "tok", Client: srv.Client()} if _, err := c.FetchNode(context.Background(), "missing"); err == nil { t.Fatal("expected fetch error for 500 response") } }