package shared import ( "encoding/json" "io" "net/http" "net/http/httptest" "strings" "testing" ) func TestNeedsPasswordAndTokenMethod(t *testing.T) { for _, m := range []string{"ldap", "userpass", "okta", "radius"} { if !NeedsPassword(m) { t.Errorf("NeedsPassword(%q) = false, want true", m) } } if NeedsPassword("token") { t.Error("NeedsPassword(token) should be false") } if !IsTokenMethod("token") { t.Error("IsTokenMethod(token) should be true") } if IsTokenMethod("ldap") { t.Error("IsTokenMethod(ldap) should be false") } } func TestLoginPasswordMethod(t *testing.T) { var gotPath, gotNS string var gotBody map[string]string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.Path gotNS = r.Header.Get("X-Vault-Namespace") body, _ := io.ReadAll(r.Body) _ = json.Unmarshal(body, &gotBody) _, _ = io.WriteString(w, `{"auth":{"client_token":"s.tok","accessor":"acc","token_policies":["default","kv"],"lease_duration":3600,"renewable":true}}`) })) defer srv.Close() rc := ResolvedContext{Name: "sydney", Address: srv.URL, Method: "ldap", Path: "ldap", User: "ben", Namespace: "team-a"} tok, err := Login(rc, "hunter2") if err != nil { t.Fatalf("Login: %v", err) } if gotPath != "/v1/auth/ldap/login/ben" { t.Errorf("login path = %q", gotPath) } if gotNS != "team-a" { t.Errorf("namespace header = %q", gotNS) } if gotBody["password"] != "hunter2" { t.Errorf("password body = %v", gotBody) } if tok.Token != "s.tok" || tok.Accessor != "acc" || !tok.Renewable { t.Errorf("token = %+v", tok) } if len(tok.Policies) != 2 || tok.Policies[0] != "default" { t.Errorf("policies = %v", tok.Policies) } if tok.ExpiresAt.IsZero() { t.Error("ExpiresAt should be set from lease_duration") } } func TestLoginCustomAuthPath(t *testing.T) { var gotPath string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.Path _, _ = io.WriteString(w, `{"auth":{"client_token":"s.x","lease_duration":60}}`) })) defer srv.Close() rc := ResolvedContext{Name: "legacy", Address: srv.URL, Method: "userpass", Path: "userpass2", User: "svc"} if _, err := Login(rc, "pw"); err != nil { t.Fatal(err) } if gotPath != "/v1/auth/userpass2/login/svc" { t.Errorf("login path = %q, want custom auth path", gotPath) } } func TestLoginTokenMethodUsesLookupSelf(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/auth/token/lookup-self" { t.Errorf("unexpected path %q", r.URL.Path) } if r.Header.Get("X-Vault-Token") != "s.raw" { t.Errorf("token header = %q", r.Header.Get("X-Vault-Token")) } _, _ = io.WriteString(w, `{"data":{"accessor":"acc2","policies":["root"],"ttl":0,"renewable":false}}`) })) defer srv.Close() rc := ResolvedContext{Name: "root-ctx", Address: srv.URL, Method: "token"} tok, err := Login(rc, "s.raw") if err != nil { t.Fatalf("Login(token): %v", err) } if tok.Token != "s.raw" || tok.Accessor != "acc2" { t.Errorf("token = %+v", tok) } if !tok.ExpiresAt.IsZero() { t.Error("ttl=0 should leave ExpiresAt zero") } } func TestRenewPreservesAccessor(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/auth/token/renew-self" { t.Errorf("path = %q", r.URL.Path) } // renew response omits accessor; Renew should carry it from prev. _, _ = io.WriteString(w, `{"auth":{"client_token":"s.tok","lease_duration":7200,"renewable":true}}`) })) defer srv.Close() rc := ResolvedContext{Name: "sydney", Address: srv.URL} prev := &Token{Context: "sydney", Address: srv.URL, Token: "s.tok", Accessor: "acc-prev"} tok, err := Renew(rc, prev) if err != nil { t.Fatalf("Renew: %v", err) } if tok.Accessor != "acc-prev" { t.Errorf("accessor = %q, want carried-over acc-prev", tok.Accessor) } if tok.LeaseDurationSeconds != 7200 { t.Errorf("lease = %d, want 7200", tok.LeaseDurationSeconds) } } func TestLoginSurfacesVaultError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) _, _ = io.WriteString(w, `{"errors":["ldap operation failed"]}`) })) defer srv.Close() rc := ResolvedContext{Name: "sydney", Address: srv.URL, Method: "ldap", Path: "ldap", User: "ben"} _, err := Login(rc, "bad") if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), "ldap operation failed") { t.Errorf("error should surface vault message, got %v", err) } }