package auth import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "net/url" "testing" ) // newTestOIDCVerifier spins up a fake OIDC discovery endpoint (just enough // for oidc.NewProvider's discovery GET to succeed) and returns a real // oidcVerifier backed by it, for tests that exercise EndSessionURL without // a live Keycloak. func newTestOIDCVerifier(t *testing.T) Verifier { t.Helper() var issuerURL string mux := http.NewServeMux() mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{ "issuer": %[1]q, "authorization_endpoint": "%[1]s/auth", "token_endpoint": "%[1]s/token", "end_session_endpoint": "%[1]s/logout", "jwks_uri": "%[1]s/certs" }`, issuerURL) }) mux.HandleFunc("/certs", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, `{"keys":[]}`) }) srv := httptest.NewServer(mux) t.Cleanup(srv.Close) issuerURL = srv.URL verifier, err := NewOIDCVerifier(context.Background(), OIDCConfig{ IssuerURL: issuerURL, ClientID: "geniusrun", ClientSecret: "secret", RedirectURL: issuerURL + "/callback", }) if err != nil { t.Fatalf("NewOIDCVerifier: %v", err) } return verifier } func TestEndSessionURL_IncludesIDTokenHintWhenProvided(t *testing.T) { verifier := newTestOIDCVerifier(t) got := verifier.EndSessionURL("https://app.example.com/", "raw-id-token-jwt") u, err := url.Parse(got) if err != nil { t.Fatalf("parse EndSessionURL result %q: %v", got, err) } q := u.Query() if q.Get("id_token_hint") != "raw-id-token-jwt" { t.Errorf("id_token_hint = %q, want %q", q.Get("id_token_hint"), "raw-id-token-jwt") } if q.Get("client_id") != "geniusrun" { t.Errorf("client_id = %q, want geniusrun", q.Get("client_id")) } if q.Get("post_logout_redirect_uri") != "https://app.example.com/" { t.Errorf("post_logout_redirect_uri = %q, want https://app.example.com/", q.Get("post_logout_redirect_uri")) } } func TestEndSessionURL_OmitsIDTokenHintWhenEmpty(t *testing.T) { verifier := newTestOIDCVerifier(t) got := verifier.EndSessionURL("https://app.example.com/", "") u, err := url.Parse(got) if err != nil { t.Fatalf("parse EndSessionURL result %q: %v", got, err) } if u.Query().Has("id_token_hint") { t.Errorf("expected no id_token_hint param when idTokenHint is empty, got %q", got) } } func TestIDTokenClaims_HasRole(t *testing.T) { cases := []struct { name string json string role string want bool }{ {"role present among others", `{"realm_access":{"roles":["geniusrun-user","other"]}}`, "geniusrun-user", true}, {"role absent", `{"realm_access":{"roles":["other"]}}`, "geniusrun-user", false}, {"realm_access missing entirely", `{}`, "geniusrun-user", false}, {"roles array empty", `{"realm_access":{"roles":[]}}`, "geniusrun-user", false}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { var c idTokenClaims if err := json.Unmarshal([]byte(tc.json), &c); err != nil { t.Fatalf("unmarshal: %v", err) } if got := c.hasRole(tc.role); got != tc.want { t.Errorf("hasRole(%q) = %v, want %v", tc.role, got, tc.want) } }) } }