From eec2c5815767e2081f987f1fbfedfe8a80f710b9 Mon Sep 17 00:00:00 2001 From: Vedant Madane <6527493+VedantMadane@users.noreply.github.com> Date: Mon, 12 Jan 2026 01:54:20 +0530 Subject: [PATCH] test(oidc): add comprehensive tests for dummy OIDC service - Add tests for AuthRequest getters (ID, ClientID, Nonce, etc.) - Add tests for AuthStorage methods (Health, GetKeySet, CreateAccessToken, etc.) - Add tests for ConfClient methods (GetID, RedirectURIs, LoginURL, etc.) - Improve test coverage for OIDC mock storage and client Fixes #5381 --- docker/dummy/oidc/app/mock/client_test.go | 168 +++++++++++++++++++ docker/dummy/oidc/app/mock/storage_test.go | 180 +++++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 docker/dummy/oidc/app/mock/client_test.go diff --git a/docker/dummy/oidc/app/mock/client_test.go b/docker/dummy/oidc/app/mock/client_test.go new file mode 100644 index 000000000..860499667 --- /dev/null +++ b/docker/dummy/oidc/app/mock/client_test.go @@ -0,0 +1,168 @@ +package mock + +import ( + "testing" + "time" + + "github.com/zitadel/oidc/pkg/oidc" + "github.com/zitadel/oidc/pkg/op" +) + +func TestConfClientGetID(t *testing.T) { + c := &ConfClient{ID: "test-client"} + if got := c.GetID(); got != "test-client" { + t.Fatalf("expected ID 'test-client', got %q", got) + } +} + +func TestConfClientRedirectURIs(t *testing.T) { + c := &ConfClient{} + uris := c.RedirectURIs() + if len(uris) == 0 { + t.Fatal("expected non-empty redirect URIs") + } + // Check that localhost:2342 PhotoPrism callback is included + found := false + for _, uri := range uris { + if uri == "http://localhost:2342/api/v1/oidc/redirect" { + found = true + break + } + } + if !found { + t.Fatal("expected PhotoPrism OIDC redirect URI in list") + } +} + +func TestConfClientPostLogoutRedirectURIs(t *testing.T) { + c := &ConfClient{} + uris := c.PostLogoutRedirectURIs() + if len(uris) != 0 { + t.Fatalf("expected empty post-logout redirect URIs, got %v", uris) + } +} + +func TestConfClientLoginURL(t *testing.T) { + c := &ConfClient{} + url := c.LoginURL("abc123") + expected := "login?id=abc123" + if url != expected { + t.Fatalf("expected login URL %q, got %q", expected, url) + } +} + +func TestConfClientApplicationType(t *testing.T) { + tests := []struct { + appType op.ApplicationType + expected op.ApplicationType + }{ + {op.ApplicationTypeWeb, op.ApplicationTypeWeb}, + {op.ApplicationTypeNative, op.ApplicationTypeNative}, + {op.ApplicationTypeUserAgent, op.ApplicationTypeUserAgent}, + } + for _, tt := range tests { + c := &ConfClient{applicationType: tt.appType} + if got := c.ApplicationType(); got != tt.expected { + t.Fatalf("expected ApplicationType %v, got %v", tt.expected, got) + } + } +} + +func TestConfClientAuthMethod(t *testing.T) { + c := &ConfClient{authMethod: oidc.AuthMethodBasic} + if got := c.AuthMethod(); got != oidc.AuthMethodBasic { + t.Fatalf("expected AuthMethod %v, got %v", oidc.AuthMethodBasic, got) + } +} + +func TestConfClientIDTokenLifetime(t *testing.T) { + c := &ConfClient{} + expected := 60 * time.Minute + if got := c.IDTokenLifetime(); got != expected { + t.Fatalf("expected IDTokenLifetime %v, got %v", expected, got) + } +} + +func TestConfClientAccessTokenType(t *testing.T) { + c := &ConfClient{accessTokenType: op.AccessTokenTypeJWT} + if got := c.AccessTokenType(); got != op.AccessTokenTypeJWT { + t.Fatalf("expected AccessTokenType %v, got %v", op.AccessTokenTypeJWT, got) + } +} + +func TestConfClientResponseTypes(t *testing.T) { + expected := []oidc.ResponseType{oidc.ResponseTypeCode} + c := &ConfClient{responseTypes: expected} + got := c.ResponseTypes() + if len(got) != len(expected) { + t.Fatalf("expected %d response types, got %d", len(expected), len(got)) + } +} + +func TestConfClientGrantTypes(t *testing.T) { + expected := []oidc.GrantType{oidc.GrantTypeCode} + c := &ConfClient{grantTypes: expected} + got := c.GrantTypes() + if len(got) != len(expected) { + t.Fatalf("expected %d grant types, got %d", len(expected), len(got)) + } +} + +func TestConfClientDevMode(t *testing.T) { + c := &ConfClient{devMode: true} + if !c.DevMode() { + t.Fatal("expected DevMode to be true") + } + c.devMode = false + if c.DevMode() { + t.Fatal("expected DevMode to be false") + } +} + +func TestConfClientAllowedScopes(t *testing.T) { + c := &ConfClient{} + if got := c.AllowedScopes(); got != nil { + t.Fatalf("expected nil AllowedScopes, got %v", got) + } +} + +func TestConfClientRestrictAdditionalIdTokenScopes(t *testing.T) { + c := &ConfClient{} + fn := c.RestrictAdditionalIdTokenScopes() + scopes := []string{"openid", "profile"} + got := fn(scopes) + if len(got) != len(scopes) { + t.Fatalf("expected same scopes returned, got %v", got) + } +} + +func TestConfClientRestrictAdditionalAccessTokenScopes(t *testing.T) { + c := &ConfClient{} + fn := c.RestrictAdditionalAccessTokenScopes() + scopes := []string{"openid", "profile"} + got := fn(scopes) + if len(got) != len(scopes) { + t.Fatalf("expected same scopes returned, got %v", got) + } +} + +func TestConfClientIsScopeAllowed(t *testing.T) { + c := &ConfClient{} + if c.IsScopeAllowed("openid") { + t.Fatal("expected IsScopeAllowed to return false") + } +} + +func TestConfClientIDTokenUserinfoClaimsAssertion(t *testing.T) { + c := &ConfClient{} + if c.IDTokenUserinfoClaimsAssertion() { + t.Fatal("expected IDTokenUserinfoClaimsAssertion to return false") + } +} + +func TestConfClientClockSkew(t *testing.T) { + c := &ConfClient{} + if got := c.ClockSkew(); got != 0 { + t.Fatalf("expected ClockSkew 0, got %v", got) + } +} diff --git a/docker/dummy/oidc/app/mock/storage_test.go b/docker/dummy/oidc/app/mock/storage_test.go index 006a4d3bc..45f5db990 100644 --- a/docker/dummy/oidc/app/mock/storage_test.go +++ b/docker/dummy/oidc/app/mock/storage_test.go @@ -14,6 +14,93 @@ func TestAuthRequestResponseModeDefault(t *testing.T) { } } +func TestAuthRequestResponseModeCustom(t *testing.T) { + req := &AuthRequest{ResponseMode: oidc.ResponseModeFragment} + if got := req.GetResponseMode(); got != oidc.ResponseModeFragment { + t.Fatalf("expected response mode %q, got %q", oidc.ResponseModeFragment, got) + } +} + +func TestAuthRequestGetters(t *testing.T) { + req := &AuthRequest{ + ID: "test-id", + ClientID: "test-client", + Nonce: "test-nonce", + RedirectURI: "https://example.com/callback", + } + + if got := req.GetID(); got != "test-id" { + t.Fatalf("expected ID %q, got %q", "test-id", got) + } + if got := req.GetClientID(); got != "test-client" { + t.Fatalf("expected ClientID %q, got %q", "test-client", got) + } + if got := req.GetNonce(); got != "test-nonce" { + t.Fatalf("expected Nonce %q, got %q", "test-nonce", got) + } + if got := req.GetRedirectURI(); got != "https://example.com/callback" { + t.Fatalf("expected RedirectURI %q, got %q", "https://example.com/callback", got) + } + if got := req.GetSubject(); got != "sub00000001" { + t.Fatalf("expected Subject %q, got %q", "sub00000001", got) + } + if got := req.GetACR(); got != "" { + t.Fatalf("expected empty ACR, got %q", got) + } + if len(req.GetAMR()) != 0 { + t.Fatalf("expected empty AMR, got %v", req.GetAMR()) + } + if !req.Done() { + t.Fatal("expected Done() to return true") + } +} + +func TestAuthRequestAudience(t *testing.T) { + req := &AuthRequest{ClientID: "my-client"} + aud := req.GetAudience() + if len(aud) != 1 || aud[0] != "my-client" { + t.Fatalf("expected audience [my-client], got %v", aud) + } +} + +func TestAuthRequestScopes(t *testing.T) { + req := &AuthRequest{} + scopes := req.GetScopes() + expected := []string{"openid", "profile", "email"} + if len(scopes) != len(expected) { + t.Fatalf("expected %d scopes, got %d", len(expected), len(scopes)) + } + for i, s := range expected { + if scopes[i] != s { + t.Fatalf("expected scope %q at index %d, got %q", s, i, scopes[i]) + } + } +} + +func TestNewAuthStorage(t *testing.T) { + storage := NewAuthStorage() + if storage == nil { + t.Fatal("expected non-nil storage") + } + as, ok := storage.(*AuthStorage) + if !ok { + t.Fatal("expected *AuthStorage type") + } + if as.key == nil { + t.Fatal("expected non-nil RSA key") + } + if as.kid == "" { + t.Fatal("expected non-empty kid") + } +} + +func TestAuthStorageHealth(t *testing.T) { + storage := NewAuthStorage().(*AuthStorage) + if err := storage.Health(context.Background()); err != nil { + t.Fatalf("expected nil from Health(), got %v", err) + } +} + func TestRevokeTokenNoError(t *testing.T) { s := &AuthStorage{} if err := s.RevokeToken( @@ -25,3 +112,96 @@ func TestRevokeTokenNoError(t *testing.T) { t.Fatalf("expected nil error from RevokeToken, got %v", err) } } + +func TestAuthStorageGetKeySet(t *testing.T) { + storage := NewAuthStorage().(*AuthStorage) + keySet, err := storage.GetKeySet(context.Background()) + if err != nil { + t.Fatalf("unexpected error from GetKeySet: %v", err) + } + if keySet == nil { + t.Fatal("expected non-nil key set") + } + if len(keySet.Keys) != 2 { + t.Fatalf("expected 2 keys, got %d", len(keySet.Keys)) + } +} + +func TestAuthStorageGetKey(t *testing.T) { + storage := NewAuthStorage().(*AuthStorage) + key, err := storage.GetKey(context.Background()) + if err != nil { + t.Fatalf("unexpected error from GetKey: %v", err) + } + if key == nil { + t.Fatal("expected non-nil key") + } +} + +func TestAuthStorageCreateAccessToken(t *testing.T) { + storage := NewAuthStorage().(*AuthStorage) + tokenID, expiration, err := storage.CreateAccessToken(context.Background(), nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tokenID != "loginId" { + t.Fatalf("expected tokenID 'loginId', got %q", tokenID) + } + if expiration.IsZero() { + t.Fatal("expected non-zero expiration") + } +} + +func TestAuthStorageCreateAccessAndRefreshTokens(t *testing.T) { + storage := NewAuthStorage().(*AuthStorage) + accessID, refresh, expiration, err := storage.CreateAccessAndRefreshTokens(context.Background(), nil, "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if accessID != "loginId" { + t.Fatalf("expected accessID 'loginId', got %q", accessID) + } + if refresh != "refreshToken" { + t.Fatalf("expected refresh 'refreshToken', got %q", refresh) + } + if expiration.IsZero() { + t.Fatal("expected non-zero expiration") + } +} + +func TestAuthStorageTerminateSession(t *testing.T) { + storage := NewAuthStorage().(*AuthStorage) + if err := storage.TerminateSession(context.Background(), "user", "client"); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestAuthStorageAuthorizeClientIDSecret(t *testing.T) { + storage := NewAuthStorage().(*AuthStorage) + if err := storage.AuthorizeClientIDSecret(context.Background(), "client", "secret"); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestAuthStorageValidateJWTProfileScopes(t *testing.T) { + storage := NewAuthStorage().(*AuthStorage) + scopes := []string{"openid", "profile"} + result, err := storage.ValidateJWTProfileScopes(context.Background(), "user", scopes) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(result) != len(scopes) { + t.Fatalf("expected %d scopes, got %d", len(scopes), len(result)) + } +} + +func TestAuthStorageGetPrivateClaimsFromScopes(t *testing.T) { + storage := NewAuthStorage().(*AuthStorage) + claims, err := storage.GetPrivateClaimsFromScopes(context.Background(), "", "", nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if claims["private_claim"] != "test" { + t.Fatalf("expected private_claim 'test', got %v", claims["private_claim"]) + } +}