package idp import ( "baron-sso-backend/internal/domain" "errors" "net/http" "reflect" "strings" "testing" ) type stubProvider struct { name string metadata []string createErr error initiateErr error verifyErr error updateErr error signInErr error initiateCalls int verifyCalls int updateCalls int signInCalls int createCalls int verifyResponse *domain.AuthInfo } func (s *stubProvider) Name() string { return s.name } func (s *stubProvider) GetMetadata() (*domain.IDPMetadata, error) { return &domain.IDPMetadata{SupportedFields: s.metadata}, nil } func (s *stubProvider) CreateUser(user *domain.BrokerUser, password string) (string, error) { s.createCalls++ if s.createErr != nil { return "", s.createErr } return "created-id", nil } func (s *stubProvider) SignIn(loginID, password string) (*domain.AuthInfo, error) { s.signInCalls++ if s.signInErr != nil { return nil, s.signInErr } return &domain.AuthInfo{Subject: "subject-123"}, nil } func (s *stubProvider) InitiatePasswordReset(loginID, redirectUrl string) error { s.initiateCalls++ return s.initiateErr } func (s *stubProvider) VerifyPasswordResetToken(token string) (*domain.AuthInfo, error) { s.verifyCalls++ if s.verifyErr != nil { return nil, s.verifyErr } if s.verifyResponse != nil { return s.verifyResponse, nil } return &domain.AuthInfo{}, nil } func (s *stubProvider) UpdateUserPassword(loginID, newPassword string, r *http.Request) error { s.updateCalls++ return s.updateErr } func TestChainedProviderMetadataUnion(t *testing.T) { p1 := &stubProvider{name: "primary", metadata: []string{"id", "email"}} p2 := &stubProvider{name: "backup", metadata: []string{"email", "phone_number", "grade"}} chain := newChainedProvider([]domain.IdentityProvider{p1, p2}) meta, err := chain.GetMetadata() if err != nil { t.Fatalf("GetMetadata returned error: %v", err) } expected := []string{"id", "email", "phone_number", "grade"} if !reflect.DeepEqual(meta.SupportedFields, expected) { t.Fatalf("metadata mismatch: got %v, want %v", meta.SupportedFields, expected) } } func TestChainedProviderUpdateUserPasswordFallback(t *testing.T) { p1 := &stubProvider{name: "primary", metadata: []string{"id"}, updateErr: errors.New("boom")} p2 := &stubProvider{name: "backup", metadata: []string{"id"}} chain := newChainedProvider([]domain.IdentityProvider{p1, p2}) if err := chain.UpdateUserPassword("user@example.com", "Sup3r!Pass123", nil); err != nil { t.Fatalf("expected fallback to succeed, got error: %v", err) } if p1.updateCalls != 1 || p2.updateCalls != 1 { t.Fatalf("unexpected call counts: p1=%d p2=%d", p1.updateCalls, p2.updateCalls) } } func TestChainedProviderUpdateUserPasswordAllFail(t *testing.T) { p1 := &stubProvider{name: "primary", metadata: []string{"id"}, updateErr: errors.New("fail1")} p2 := &stubProvider{name: "backup", metadata: []string{"id"}, updateErr: errors.New("fail2")} chain := newChainedProvider([]domain.IdentityProvider{p1, p2}) err := chain.UpdateUserPassword("user@example.com", "Sup3r!Pass123", nil) if err == nil { t.Fatalf("expected error when all providers fail") } if !strings.Contains(err.Error(), "all IDP providers failed") { t.Fatalf("unexpected error: %v", err) } if p1.updateCalls != 1 || p2.updateCalls != 1 { t.Fatalf("unexpected call counts: p1=%d p2=%d", p1.updateCalls, p2.updateCalls) } }