From 14a671555cd89179f9e34a477eaf78cd66596519 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 03:25:35 +0000 Subject: [PATCH 1/4] Introduce shared mock server manager to stabilize integration tests Co-authored-by: KaveeshaPiumini <62582918+KaveeshaPiumini@users.noreply.github.com> --- tests/integration/authn/oauth_auth_test.go | 16 +- tests/integration/authn/oidc_auth_test.go | 16 +- .../flowauthn/conditional_exec_auth_test.go | 22 +- .../flowauthn/decision_auth_test.go | 23 +- .../integration/flowauthn/github_auth_test.go | 24 +- .../integration/flowauthn/google_auth_test.go | 26 +- .../http_request_executor_auth_test.go | 23 +- tests/integration/flowauthn/sms_auth_test.go | 23 +- .../github_registration_test.go | 24 +- .../google_registration_test.go | 26 +- ...http_request_executor_registration_test.go | 23 +- .../flowregistration/ou_registration_test.go | 19 +- .../flowregistration/sms_registration_test.go | 23 +- .../testutils/shared_mock_servers.go | 335 ++++++++++++++++++ 14 files changed, 435 insertions(+), 188 deletions(-) create mode 100644 tests/integration/testutils/shared_mock_servers.go diff --git a/tests/integration/authn/oauth_auth_test.go b/tests/integration/authn/oauth_auth_test.go index b89c3c16a..b39682d09 100644 --- a/tests/integration/authn/oauth_auth_test.go +++ b/tests/integration/authn/oauth_auth_test.go @@ -34,7 +34,6 @@ import ( const ( oauthAuthStart = "/auth/oauth/standard/start" oauthAuthFinish = "/auth/oauth/standard/finish" - mockOAuthPort = 8092 ) var oauthAuthTestOU = testutils.OrganizationUnit{ @@ -82,8 +81,13 @@ func TestOAuthAuthTestSuite(t *testing.T) { } func (suite *OAuthAuthTestSuite) SetupSuite() { - suite.mockOAuthServer = testutils.NewMockOAuthServer(mockOAuthPort, + // Get shared OAuth server (started once for all test suites) + var err error + suite.mockOAuthServer, err = testutils.GetSharedMockServers().GetOAuthServer( "test-oauth-client", "test-oauth-secret") + if err != nil { + suite.T().Fatalf("Failed to get shared OAuth server: %v", err) + } suite.mockOAuthServer.AddUser(&testutils.OAuthUserInfo{ Sub: "user123", @@ -96,9 +100,6 @@ func (suite *OAuthAuthTestSuite) SetupSuite() { }, }) - err := suite.mockOAuthServer.Start() - suite.Require().NoError(err, "Failed to start mock OAuth server") - ouID, err := testutils.CreateOrganizationUnit(oauthAuthTestOU) suite.Require().NoError(err, "Failed to create test organization unit") suite.ouID = ouID @@ -191,9 +192,8 @@ func (suite *OAuthAuthTestSuite) TearDownSuite() { _ = testutils.DeleteIDP(suite.idpID) } - if suite.mockOAuthServer != nil { - _ = suite.mockOAuthServer.Stop() - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. if suite.ouID != "" { if err := testutils.DeleteOrganizationUnit(suite.ouID); err != nil { diff --git a/tests/integration/authn/oidc_auth_test.go b/tests/integration/authn/oidc_auth_test.go index a48ef6a6a..003cc4150 100644 --- a/tests/integration/authn/oidc_auth_test.go +++ b/tests/integration/authn/oidc_auth_test.go @@ -33,7 +33,6 @@ import ( const ( oidcAuthStart = "/auth/oauth/standard/start" oidcAuthFinish = "/auth/oauth/standard/finish" - mockOIDCPort = 8093 ) var oidcAuthTestOU = testutils.OrganizationUnit{ @@ -81,10 +80,13 @@ func TestOIDCAuthTestSuite(t *testing.T) { } func (suite *OIDCAuthTestSuite) SetupSuite() { + // Get shared OIDC server (started once for all test suites) var err error - suite.mockOIDCServer, err = testutils.NewMockOIDCServer(mockOIDCPort, + suite.mockOIDCServer, err = testutils.GetSharedMockServers().GetOIDCServer( "test-oidc-client", "test-oidc-secret") - suite.Require().NoError(err, "Failed to create mock OIDC server") + if err != nil { + suite.T().Fatalf("Failed to get shared OIDC server: %v", err) + } suite.mockOIDCServer.AddUser(&testutils.OIDCUserInfo{ Sub: "user456", @@ -100,9 +102,6 @@ func (suite *OIDCAuthTestSuite) SetupSuite() { }, }) - err = suite.mockOIDCServer.Start() - suite.Require().NoError(err, "Failed to start mock OIDC server") - ouID, err := testutils.CreateOrganizationUnit(oidcAuthTestOU) suite.Require().NoError(err, "Failed to create test organization unit") suite.ouID = ouID @@ -200,9 +199,8 @@ func (suite *OIDCAuthTestSuite) TearDownSuite() { _ = testutils.DeleteIDP(suite.idpID) } - if suite.mockOIDCServer != nil { - _ = suite.mockOIDCServer.Stop() - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. if suite.ouID != "" { if err := testutils.DeleteOrganizationUnit(suite.ouID); err != nil { diff --git a/tests/integration/flowauthn/conditional_exec_auth_test.go b/tests/integration/flowauthn/conditional_exec_auth_test.go index 8f5647e36..34074cc0c 100644 --- a/tests/integration/flowauthn/conditional_exec_auth_test.go +++ b/tests/integration/flowauthn/conditional_exec_auth_test.go @@ -21,14 +21,12 @@ package flowauthn import ( "encoding/json" "testing" - "time" "github.com/asgardeo/thunder/tests/integration/testutils" "github.com/stretchr/testify/suite" ) const ( - conditionalExecMockGooglePort = 8093 conditionalExecNewUserSub = "conditional-exec-new-user-sub" conditionalExecNewUserEmail = "newuser@conditional-exec-test.com" conditionalExecExistingUserSub = "conditional-exec-existing-user-sub" @@ -91,11 +89,13 @@ func TestConditionalExecAuthFlowTestSuite(t *testing.T) { } func (ts *ConditionalExecAuthFlowTestSuite) SetupSuite() { - // Start mock Google server - mockGoogleServer, err := testutils.NewMockGoogleOIDCServer(conditionalExecMockGooglePort, + // Get shared Google OIDC server (started once for all test suites) + var err error + ts.mockGoogleServer, err = testutils.GetSharedMockServers().GetGoogleServer( "test_google_client", "test_google_secret") - ts.Require().NoError(err, "Failed to create mock Google server") - ts.mockGoogleServer = mockGoogleServer + if err != nil { + ts.T().Fatalf("Failed to get shared Google server: %v", err) + } // Add test users ts.mockGoogleServer.AddUser(&testutils.GoogleUserInfo{ @@ -119,9 +119,6 @@ func (ts *ConditionalExecAuthFlowTestSuite) SetupSuite() { Locale: "en", }) - err = ts.mockGoogleServer.Start() - ts.Require().NoError(err, "Failed to start mock Google server") - // Create test organization unit ouID, err := testutils.CreateOrganizationUnit(conditionalExecTestOU) ts.Require().NoError(err, "Failed to create test organization unit") @@ -186,11 +183,8 @@ func (ts *ConditionalExecAuthFlowTestSuite) TearDownSuite() { _ = testutils.DeleteOrganizationUnit(conditionalExecPreCreatedOUID) } - // Stop mock server - if ts.mockGoogleServer != nil { - _ = ts.mockGoogleServer.Stop() - time.Sleep(200 * time.Millisecond) - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. } func (ts *ConditionalExecAuthFlowTestSuite) TestSkipConditionalNodes() { diff --git a/tests/integration/flowauthn/decision_auth_test.go b/tests/integration/flowauthn/decision_auth_test.go index f895338c8..985f5cbeb 100644 --- a/tests/integration/flowauthn/decision_auth_test.go +++ b/tests/integration/flowauthn/decision_auth_test.go @@ -27,9 +27,7 @@ import ( "github.com/stretchr/testify/suite" ) -const ( - mockDecisionNotificationServerPort = 8098 -) + var ( decisionTestApp = testutils.Application{ @@ -139,14 +137,12 @@ func (ts *DecisionAndMFAFlowTestSuite) SetupSuite() { } decisionUserSchemaID = schemaID - // Start mock notification server - ts.mockServer = testutils.NewMockNotificationServer(mockDecisionNotificationServerPort) - err = ts.mockServer.Start() + // Get shared notification server (started once for all test suites) + ts.mockServer, err = testutils.GetSharedMockServers().GetNotificationServer() if err != nil { - ts.T().Fatalf("Failed to start mock notification server: %v", err) + ts.T().Fatalf("Failed to get shared notification server: %v", err) } - time.Sleep(100 * time.Millisecond) - ts.T().Log("Mock notification server started successfully") + ts.T().Log("Using shared notification server") // Create test users with the created OU userWithMobile := testUserWithMobileDecision @@ -180,13 +176,8 @@ func (ts *DecisionAndMFAFlowTestSuite) TearDownSuite() { ts.T().Logf("Failed to cleanup users during teardown: %v", err) } - // Stop mock server - if ts.mockServer != nil { - err := ts.mockServer.Stop() - if err != nil { - ts.T().Logf("Failed to stop mock notification server during teardown: %v", err) - } - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. // Delete test application if decisionTestAppID != "" { diff --git a/tests/integration/flowauthn/github_auth_test.go b/tests/integration/flowauthn/github_auth_test.go index b83e82a61..8e42fff0a 100644 --- a/tests/integration/flowauthn/github_auth_test.go +++ b/tests/integration/flowauthn/github_auth_test.go @@ -23,7 +23,6 @@ import ( "net/url" "strings" "testing" - "time" "github.com/asgardeo/thunder/tests/integration/testutils" "github.com/stretchr/testify/suite" @@ -51,9 +50,7 @@ var ( } ) -const ( - mockGithubFlowPort = 8092 -) + var githubUserSchema = testutils.UserSchema{ Name: "github_flow_user", @@ -92,9 +89,13 @@ func TestGithubAuthFlowTestSuite(t *testing.T) { } func (ts *GithubAuthFlowTestSuite) SetupSuite() { - // Start mock GitHub server - ts.mockGithubServer = testutils.NewMockGithubOAuthServer(mockGithubFlowPort, + // Get shared GitHub OAuth server (started once for all test suites) + var err error + ts.mockGithubServer, err = testutils.GetSharedMockServers().GetGithubServer( "test_github_client", "test_github_secret") + if err != nil { + ts.T().Fatalf("Failed to get shared GitHub server: %v", err) + } email := "testuser@github.com" ts.mockGithubServer.AddUser(&testutils.GithubUserInfo{ @@ -115,9 +116,6 @@ func (ts *GithubAuthFlowTestSuite) SetupSuite() { }, }) - err := ts.mockGithubServer.Start() - ts.Require().NoError(err, "Failed to start mock GitHub server") - // Use the IDP created by database scripts ts.idpID = "test-github-idp-id" @@ -183,12 +181,8 @@ func (ts *GithubAuthFlowTestSuite) TearDownSuite() { _ = testutils.DeleteUserType(ts.userSchemaID) } - // Stop mock server - if ts.mockGithubServer != nil { - _ = ts.mockGithubServer.Stop() - // Wait for port to be released - time.Sleep(200 * time.Millisecond) - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. } func (ts *GithubAuthFlowTestSuite) TestGithubAuthFlowInitiation() { diff --git a/tests/integration/flowauthn/google_auth_test.go b/tests/integration/flowauthn/google_auth_test.go index 247864d50..3da920ee9 100644 --- a/tests/integration/flowauthn/google_auth_test.go +++ b/tests/integration/flowauthn/google_auth_test.go @@ -23,7 +23,6 @@ import ( "net/url" "strings" "testing" - "time" "github.com/asgardeo/thunder/tests/integration/testutils" "github.com/stretchr/testify/suite" @@ -51,9 +50,7 @@ var ( } ) -const ( - mockGoogleFlowPort = 8093 -) + var googleUserSchema = testutils.UserSchema{ Name: "google_flow_user", @@ -92,11 +89,13 @@ func TestGoogleAuthFlowTestSuite(t *testing.T) { } func (ts *GoogleAuthFlowTestSuite) SetupSuite() { - // Start mock Google server - mockServer, err := testutils.NewMockGoogleOIDCServer(mockGoogleFlowPort, + // Get shared Google OIDC server (started once for all test suites) + var err error + ts.mockGoogleServer, err = testutils.GetSharedMockServers().GetGoogleServer( "test_google_client", "test_google_secret") - ts.Require().NoError(err, "Failed to create mock Google server") - ts.mockGoogleServer = mockServer + if err != nil { + ts.T().Fatalf("Failed to get shared Google server: %v", err) + } ts.mockGoogleServer.AddUser(&testutils.GoogleUserInfo{ Sub: "google-test-user-123", @@ -109,9 +108,6 @@ func (ts *GoogleAuthFlowTestSuite) SetupSuite() { Locale: "en", }) - err = ts.mockGoogleServer.Start() - ts.Require().NoError(err, "Failed to start mock Google server") - // Use the IDP created by database scripts ts.idpID = "test-google-idp-id" @@ -177,12 +173,8 @@ func (ts *GoogleAuthFlowTestSuite) TearDownSuite() { _ = testutils.DeleteUserType(ts.userSchemaID) } - // Stop mock server - if ts.mockGoogleServer != nil { - _ = ts.mockGoogleServer.Stop() - // Wait for port to be released - time.Sleep(200 * time.Millisecond) - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. } func (ts *GoogleAuthFlowTestSuite) TestGoogleAuthFlowInitiation() { diff --git a/tests/integration/flowauthn/http_request_executor_auth_test.go b/tests/integration/flowauthn/http_request_executor_auth_test.go index f77b0d47f..5eb19b0e4 100644 --- a/tests/integration/flowauthn/http_request_executor_auth_test.go +++ b/tests/integration/flowauthn/http_request_executor_auth_test.go @@ -27,9 +27,7 @@ import ( "github.com/stretchr/testify/suite" ) -const ( - mockHTTPServerPort = 9091 -) + var ( httpRequestTestApp = testutils.Application{ @@ -121,14 +119,12 @@ func (ts *HTTPRequestAuthFlowTestSuite) SetupSuite() { } httpRequestTestAppID = appID - // Start mock HTTP server - ts.mockServer = testutils.NewMockHTTPServer(mockHTTPServerPort) - err = ts.mockServer.Start() + // Get shared HTTP mock server (started once for all test suites) + ts.mockServer, err = testutils.GetSharedMockServers().GetHTTPServer() if err != nil { - ts.T().Fatalf("Failed to start mock HTTP server: %v", err) + ts.T().Fatalf("Failed to get shared HTTP server: %v", err) } - time.Sleep(100 * time.Millisecond) - ts.T().Log("Mock HTTP server started successfully") + ts.T().Log("Using shared HTTP mock server") // Create test user with the created OU testUser := httpRequestTestUser @@ -142,13 +138,8 @@ func (ts *HTTPRequestAuthFlowTestSuite) SetupSuite() { } func (ts *HTTPRequestAuthFlowTestSuite) TearDownSuite() { - // Stop the mock HTTP server - if ts.mockServer != nil { - err := ts.mockServer.Stop() - if err != nil { - ts.T().Logf("Failed to stop mock HTTP server during teardown: %v", err) - } - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. // Delete all created users if err := testutils.CleanupUsers(ts.config.CreatedUserIDs); err != nil { diff --git a/tests/integration/flowauthn/sms_auth_test.go b/tests/integration/flowauthn/sms_auth_test.go index 2d63a05b7..8b072c21a 100644 --- a/tests/integration/flowauthn/sms_auth_test.go +++ b/tests/integration/flowauthn/sms_auth_test.go @@ -27,9 +27,7 @@ import ( "github.com/stretchr/testify/suite" ) -const ( - mockNotificationServerPort = 8098 -) + var ( smsAuthTestApp = testutils.Application{ @@ -149,14 +147,12 @@ func (ts *SMSAuthFlowTestSuite) SetupSuite() { } smsAuthUserSchemaID = schemaID - // Start mock notification server - ts.mockServer = testutils.NewMockNotificationServer(mockNotificationServerPort) - err = ts.mockServer.Start() + // Get shared notification server (started once for all test suites) + ts.mockServer, err = testutils.GetSharedMockServers().GetNotificationServer() if err != nil { - ts.T().Fatalf("Failed to start mock notification server: %v", err) + ts.T().Fatalf("Failed to get shared notification server: %v", err) } - time.Sleep(100 * time.Millisecond) - ts.T().Log("Mock notification server started successfully") + ts.T().Log("Using shared notification server") // Create test user with mobile number using the created OU testUserWithMobile := testUserWithMobile @@ -181,13 +177,8 @@ func (ts *SMSAuthFlowTestSuite) TearDownSuite() { ts.T().Logf("Failed to cleanup users during teardown: %v", err) } - // Stop mock server - if ts.mockServer != nil { - err := ts.mockServer.Stop() - if err != nil { - ts.T().Logf("Failed to stop mock notification server during teardown: %v", err) - } - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. // Delete test application if smsAuthTestAppID != "" { diff --git a/tests/integration/flowregistration/github_registration_test.go b/tests/integration/flowregistration/github_registration_test.go index 48b51f140..9b09b985b 100644 --- a/tests/integration/flowregistration/github_registration_test.go +++ b/tests/integration/flowregistration/github_registration_test.go @@ -23,7 +23,6 @@ import ( "net/url" "strings" "testing" - "time" "github.com/asgardeo/thunder/tests/integration/testutils" "github.com/stretchr/testify/suite" @@ -79,9 +78,7 @@ var ( githubRegTestOUID string ) -const ( - mockGithubRegFlowPort = 8092 -) + type GithubRegistrationFlowTestSuite struct { suite.Suite @@ -98,9 +95,13 @@ func TestGithubRegistrationFlowTestSuite(t *testing.T) { func (ts *GithubRegistrationFlowTestSuite) SetupSuite() { ts.config = &TestSuiteConfig{} - // Start mock GitHub server - ts.mockGithubServer = testutils.NewMockGithubOAuthServer(mockGithubRegFlowPort, + // Get shared GitHub OAuth server (started once for all test suites) + var err error + ts.mockGithubServer, err = testutils.GetSharedMockServers().GetGithubServer( "test_github_client", "test_github_secret") + if err != nil { + ts.T().Fatalf("Failed to get shared GitHub server: %v", err) + } email := "reguser@github.com" ts.mockGithubServer.AddUser(&testutils.GithubUserInfo{ @@ -121,9 +122,6 @@ func (ts *GithubRegistrationFlowTestSuite) SetupSuite() { }, }) - err := ts.mockGithubServer.Start() - ts.Require().NoError(err, "Failed to start mock GitHub server") - // Use the IDP created by database scripts ts.idpID = "test-github-idp-id" @@ -186,12 +184,8 @@ func (ts *GithubRegistrationFlowTestSuite) TearDownSuite() { _ = testutils.DeleteUserType(ts.userSchemaID) } - // Stop mock server - if ts.mockGithubServer != nil { - _ = ts.mockGithubServer.Stop() - // Wait for port to be released - time.Sleep(200 * time.Millisecond) - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. } func (ts *GithubRegistrationFlowTestSuite) TestGithubRegistrationFlowInitiation() { diff --git a/tests/integration/flowregistration/google_registration_test.go b/tests/integration/flowregistration/google_registration_test.go index 814826692..4fffecf89 100644 --- a/tests/integration/flowregistration/google_registration_test.go +++ b/tests/integration/flowregistration/google_registration_test.go @@ -23,7 +23,6 @@ import ( "net/url" "strings" "testing" - "time" "github.com/asgardeo/thunder/tests/integration/testutils" "github.com/stretchr/testify/suite" @@ -79,9 +78,7 @@ var ( googleRegTestOUID string ) -const ( - mockGoogleRegFlowPort = 8093 -) + type GoogleRegistrationFlowTestSuite struct { suite.Suite @@ -98,11 +95,13 @@ func TestGoogleRegistrationFlowTestSuite(t *testing.T) { func (ts *GoogleRegistrationFlowTestSuite) SetupSuite() { ts.config = &TestSuiteConfig{} - // Start mock Google server - mockServer, err := testutils.NewMockGoogleOIDCServer(mockGoogleRegFlowPort, + // Get shared Google OIDC server (started once for all test suites) + var err error + ts.mockGoogleServer, err = testutils.GetSharedMockServers().GetGoogleServer( "test_google_client", "test_google_secret") - ts.Require().NoError(err, "Failed to create mock Google server") - ts.mockGoogleServer = mockServer + if err != nil { + ts.T().Fatalf("Failed to get shared Google server: %v", err) + } ts.mockGoogleServer.AddUser(&testutils.GoogleUserInfo{ Sub: "google-reg-user-456", @@ -115,9 +114,6 @@ func (ts *GoogleRegistrationFlowTestSuite) SetupSuite() { Locale: "en", }) - err = ts.mockGoogleServer.Start() - ts.Require().NoError(err, "Failed to start mock Google server") - // Use the IDP created by database scripts ts.idpID = "test-google-idp-id" @@ -180,12 +176,8 @@ func (ts *GoogleRegistrationFlowTestSuite) TearDownSuite() { _ = testutils.DeleteUserType(ts.userSchemaID) } - // Stop mock server - if ts.mockGoogleServer != nil { - _ = ts.mockGoogleServer.Stop() - // Wait for port to be released - time.Sleep(200 * time.Millisecond) - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. } func (ts *GoogleRegistrationFlowTestSuite) TestGoogleRegistrationFlowInitiation() { diff --git a/tests/integration/flowregistration/http_request_executor_registration_test.go b/tests/integration/flowregistration/http_request_executor_registration_test.go index d1055eb4c..3a438289e 100644 --- a/tests/integration/flowregistration/http_request_executor_registration_test.go +++ b/tests/integration/flowregistration/http_request_executor_registration_test.go @@ -26,9 +26,7 @@ import ( "github.com/stretchr/testify/suite" ) -const ( - mockHTTPServerPortReg = 9091 -) + var ( httpRequestRegTestOU = testutils.OrganizationUnit{ @@ -111,24 +109,17 @@ func (ts *HTTPRequestRegistrationFlowTestSuite) SetupSuite() { } httpRequestRegTestAppID = appID - // Start mock HTTP server - ts.mockServer = testutils.NewMockHTTPServer(mockHTTPServerPortReg) - err = ts.mockServer.Start() + // Get shared HTTP mock server (started once for all test suites) + ts.mockServer, err = testutils.GetSharedMockServers().GetHTTPServer() if err != nil { - ts.T().Fatalf("Failed to start mock HTTP server: %v", err) + ts.T().Fatalf("Failed to get shared HTTP server: %v", err) } - time.Sleep(100 * time.Millisecond) - ts.T().Log("Mock HTTP server started successfully") + ts.T().Log("Using shared HTTP mock server") } func (ts *HTTPRequestRegistrationFlowTestSuite) TearDownSuite() { - // Stop the mock HTTP server - if ts.mockServer != nil { - err := ts.mockServer.Stop() - if err != nil { - ts.T().Logf("Failed to stop mock HTTP server during teardown: %v", err) - } - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. // Delete all created users if err := testutils.CleanupUsers(ts.config.CreatedUserIDs); err != nil { diff --git a/tests/integration/flowregistration/ou_registration_test.go b/tests/integration/flowregistration/ou_registration_test.go index 4071bb7e9..9c3f2805a 100644 --- a/tests/integration/flowregistration/ou_registration_test.go +++ b/tests/integration/flowregistration/ou_registration_test.go @@ -27,9 +27,7 @@ import ( "github.com/stretchr/testify/suite" ) -const ( - mockNotificationServerPortOU = 8098 -) + var ( ouRegTestOU = testutils.OrganizationUnit{ @@ -149,12 +147,11 @@ func (ts *OURegistrationFlowTestSuite) SetupSuite() { } ts.smsFlowTestOUID = smsOUID - ts.mockServer = testutils.NewMockNotificationServer(mockNotificationServerPortOU) - err = ts.mockServer.Start() + // Get shared notification server (started once for all test suites) + ts.mockServer, err = testutils.GetSharedMockServers().GetNotificationServer() if err != nil { - ts.T().Fatalf("Failed to start mock notification server: %v", err) + ts.T().Fatalf("Failed to get shared notification server: %v", err) } - time.Sleep(100 * time.Millisecond) } func (ts *OURegistrationFlowTestSuite) TearDownSuite() { @@ -166,12 +163,8 @@ func (ts *OURegistrationFlowTestSuite) TearDownSuite() { ts.T().Logf("Failed to delete created OU %s during teardown: %v", ouID, err) } } - if ts.mockServer != nil { - err := ts.mockServer.Stop() - if err != nil { - ts.T().Logf("Failed to stop mock notification server during teardown: %v", err) - } - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. if ts.basicFlowTestAppID != "" { if err := testutils.DeleteApplication(ts.basicFlowTestAppID); err != nil { ts.T().Logf("Failed to delete test application during teardown: %v", err) diff --git a/tests/integration/flowregistration/sms_registration_test.go b/tests/integration/flowregistration/sms_registration_test.go index 46f2a4784..009f1d782 100644 --- a/tests/integration/flowregistration/sms_registration_test.go +++ b/tests/integration/flowregistration/sms_registration_test.go @@ -27,9 +27,7 @@ import ( "github.com/stretchr/testify/suite" ) -const ( - mockNotificationServerPort = 8098 -) + var ( smsRegTestOU = testutils.OrganizationUnit{ @@ -116,14 +114,12 @@ func (ts *SMSRegistrationFlowTestSuite) SetupSuite() { } ts.testAppID = appID - // Start mock notification server - ts.mockServer = testutils.NewMockNotificationServer(mockNotificationServerPort) - err = ts.mockServer.Start() + // Get shared notification server (started once for all test suites) + ts.mockServer, err = testutils.GetSharedMockServers().GetNotificationServer() if err != nil { - ts.T().Fatalf("Failed to start mock notification server: %v", err) + ts.T().Fatalf("Failed to get shared notification server: %v", err) } - time.Sleep(100 * time.Millisecond) - ts.T().Log("Mock notification server started successfully") + ts.T().Log("Using shared notification server") // Store original app config (this will be the created app config) ts.config.OriginalAppConfig, err = getAppConfig(ts.testAppID) @@ -138,13 +134,8 @@ func (ts *SMSRegistrationFlowTestSuite) TearDownSuite() { ts.T().Logf("Failed to cleanup users during teardown: %v", err) } - // Stop mock server - if ts.mockServer != nil { - err := ts.mockServer.Stop() - if err != nil { - ts.T().Logf("Failed to stop mock notification server during teardown: %v", err) - } - } + // Note: We don't stop the mock server here because it's shared across test suites. + // The shared server will be cleaned up when the test process exits. // Delete test application if ts.testAppID != "" { diff --git a/tests/integration/testutils/shared_mock_servers.go b/tests/integration/testutils/shared_mock_servers.go new file mode 100644 index 000000000..02b674671 --- /dev/null +++ b/tests/integration/testutils/shared_mock_servers.go @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package testutils + +import ( + "fmt" + "log" + "sync" + "time" +) + +// SharedMockServers provides a centralized manager for all mock servers used in integration tests. +// It ensures that each mock server is started only once and shared across all test suites, +// preventing port conflicts and race conditions. +type SharedMockServers struct { + notificationServer *MockNotificationServer + httpServer *MockHTTPServer + githubServer *MockGithubOAuthServer + googleServer *MockGoogleOIDCServer + oauthServer *MockOAuthServer + oidcServer *MockOIDCServer + + // Track initialization state + notificationStarted bool + httpStarted bool + githubStarted bool + googleStarted bool + oauthStarted bool + oidcStarted bool + + mutex sync.Mutex +} + +// Default ports for mock servers +const ( + DefaultNotificationPort = 8098 + DefaultHTTPPort = 9091 + DefaultGithubPort = 8092 + DefaultGooglePort = 8093 + DefaultOAuthPort = 8092 + DefaultOIDCPort = 8093 +) + +// Singleton instance +var ( + sharedServers *SharedMockServers + sharedServersOnce sync.Once +) + +// GetSharedMockServers returns the singleton instance of SharedMockServers. +// This ensures all test suites use the same mock server instances. +func GetSharedMockServers() *SharedMockServers { + sharedServersOnce.Do(func() { + sharedServers = &SharedMockServers{} + }) + return sharedServers +} + +// GetNotificationServer returns the shared notification server, starting it if necessary. +func (s *SharedMockServers) GetNotificationServer() (*MockNotificationServer, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.notificationStarted { + return s.notificationServer, nil + } + + s.notificationServer = NewMockNotificationServer(DefaultNotificationPort) + if err := s.notificationServer.Start(); err != nil { + return nil, fmt.Errorf("failed to start notification server: %w", err) + } + + // Wait for server to be ready + time.Sleep(100 * time.Millisecond) + s.notificationStarted = true + log.Printf("Shared notification server started on port %d", DefaultNotificationPort) + + return s.notificationServer, nil +} + +// GetHTTPServer returns the shared HTTP mock server, starting it if necessary. +func (s *SharedMockServers) GetHTTPServer() (*MockHTTPServer, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.httpStarted { + return s.httpServer, nil + } + + s.httpServer = NewMockHTTPServer(DefaultHTTPPort) + if err := s.httpServer.Start(); err != nil { + return nil, fmt.Errorf("failed to start HTTP server: %w", err) + } + + // Wait for server to be ready + time.Sleep(100 * time.Millisecond) + s.httpStarted = true + log.Printf("Shared HTTP mock server started on port %d", DefaultHTTPPort) + + return s.httpServer, nil +} + +// GetGithubServer returns the shared GitHub OAuth server, starting it if necessary. +// The clientID and clientSecret are used for the first initialization only. +// Note: This server and GetOAuthServer use the same port (8092). Only one can be active at a time. +func (s *SharedMockServers) GetGithubServer(clientID, clientSecret string) (*MockGithubOAuthServer, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.githubStarted { + return s.githubServer, nil + } + + // Check if OAuth server is running on the same port + if s.oauthStarted { + return nil, fmt.Errorf("cannot start GitHub server: OAuth server already running on port %d", DefaultOAuthPort) + } + + s.githubServer = NewMockGithubOAuthServer(DefaultGithubPort, clientID, clientSecret) + if err := s.githubServer.Start(); err != nil { + return nil, fmt.Errorf("failed to start GitHub server: %w", err) + } + + // Wait for server to be ready + time.Sleep(100 * time.Millisecond) + s.githubStarted = true + log.Printf("Shared GitHub OAuth server started on port %d", DefaultGithubPort) + + return s.githubServer, nil +} + +// GetGoogleServer returns the shared Google OIDC server, starting it if necessary. +// The clientID and clientSecret are used for the first initialization only. +// Note: This server and GetOIDCServer use the same port (8093). Only one can be active at a time. +func (s *SharedMockServers) GetGoogleServer(clientID, clientSecret string) (*MockGoogleOIDCServer, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.googleStarted { + return s.googleServer, nil + } + + // Check if OIDC server is running on the same port + if s.oidcStarted { + return nil, fmt.Errorf("cannot start Google server: OIDC server already running on port %d", DefaultOIDCPort) + } + + var err error + s.googleServer, err = NewMockGoogleOIDCServer(DefaultGooglePort, clientID, clientSecret) + if err != nil { + return nil, fmt.Errorf("failed to create Google server: %w", err) + } + + if err := s.googleServer.Start(); err != nil { + return nil, fmt.Errorf("failed to start Google server: %w", err) + } + + // Wait for server to be ready + time.Sleep(100 * time.Millisecond) + s.googleStarted = true + log.Printf("Shared Google OIDC server started on port %d", DefaultGooglePort) + + return s.googleServer, nil +} + +// GetOAuthServer returns the shared generic OAuth server, starting it if necessary. +// Note: This server and GetGithubServer use the same port (8092). Only one can be active at a time. +func (s *SharedMockServers) GetOAuthServer(clientID, clientSecret string) (*MockOAuthServer, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.oauthStarted { + return s.oauthServer, nil + } + + // Check if GitHub server is running on the same port + if s.githubStarted { + return nil, fmt.Errorf("cannot start OAuth server: GitHub server already running on port %d", DefaultGithubPort) + } + + s.oauthServer = NewMockOAuthServer(DefaultOAuthPort, clientID, clientSecret) + if err := s.oauthServer.Start(); err != nil { + return nil, fmt.Errorf("failed to start OAuth server: %w", err) + } + + // Wait for server to be ready + time.Sleep(100 * time.Millisecond) + s.oauthStarted = true + log.Printf("Shared OAuth server started on port %d", DefaultOAuthPort) + + return s.oauthServer, nil +} + +// GetOIDCServer returns the shared generic OIDC server, starting it if necessary. +// Note: This server and GetGoogleServer use the same port (8093). Only one can be active at a time. +func (s *SharedMockServers) GetOIDCServer(clientID, clientSecret string) (*MockOIDCServer, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.oidcStarted { + return s.oidcServer, nil + } + + // Check if Google server is running on the same port + if s.googleStarted { + return nil, fmt.Errorf("cannot start OIDC server: Google server already running on port %d", DefaultGooglePort) + } + + var err error + s.oidcServer, err = NewMockOIDCServer(DefaultOIDCPort, clientID, clientSecret) + if err != nil { + return nil, fmt.Errorf("failed to create OIDC server: %w", err) + } + + if err := s.oidcServer.Start(); err != nil { + return nil, fmt.Errorf("failed to start OIDC server: %w", err) + } + + // Wait for server to be ready + time.Sleep(100 * time.Millisecond) + s.oidcStarted = true + log.Printf("Shared OIDC server started on port %d", DefaultOIDCPort) + + return s.oidcServer, nil +} + +// StopAll stops all mock servers. This should be called once when the entire test suite finishes. +// Note: In practice, the servers will be stopped when the test process exits. +// This method is provided for explicit cleanup if needed. +func (s *SharedMockServers) StopAll() { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.notificationStarted && s.notificationServer != nil { + if err := s.notificationServer.Stop(); err != nil { + log.Printf("Error stopping notification server: %v", err) + } + s.notificationStarted = false + } + + if s.httpStarted && s.httpServer != nil { + if err := s.httpServer.Stop(); err != nil { + log.Printf("Error stopping HTTP server: %v", err) + } + s.httpStarted = false + } + + if s.githubStarted && s.githubServer != nil { + if err := s.githubServer.Stop(); err != nil { + log.Printf("Error stopping GitHub server: %v", err) + } + s.githubStarted = false + } + + if s.googleStarted && s.googleServer != nil { + if err := s.googleServer.Stop(); err != nil { + log.Printf("Error stopping Google server: %v", err) + } + s.googleStarted = false + } + + if s.oauthStarted && s.oauthServer != nil { + if err := s.oauthServer.Stop(); err != nil { + log.Printf("Error stopping OAuth server: %v", err) + } + s.oauthStarted = false + } + + if s.oidcStarted && s.oidcServer != nil { + if err := s.oidcServer.Stop(); err != nil { + log.Printf("Error stopping OIDC server: %v", err) + } + s.oidcStarted = false + } + + log.Println("All shared mock servers stopped") +} + +// IsNotificationServerRunning returns true if the notification server is running. +func (s *SharedMockServers) IsNotificationServerRunning() bool { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.notificationStarted +} + +// IsHTTPServerRunning returns true if the HTTP server is running. +func (s *SharedMockServers) IsHTTPServerRunning() bool { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.httpStarted +} + +// IsGithubServerRunning returns true if the GitHub server is running. +func (s *SharedMockServers) IsGithubServerRunning() bool { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.githubStarted +} + +// IsGoogleServerRunning returns true if the Google server is running. +func (s *SharedMockServers) IsGoogleServerRunning() bool { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.googleStarted +} + +// IsOAuthServerRunning returns true if the OAuth server is running. +func (s *SharedMockServers) IsOAuthServerRunning() bool { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.oauthStarted +} + +// IsOIDCServerRunning returns true if the OIDC server is running. +func (s *SharedMockServers) IsOIDCServerRunning() bool { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.oidcStarted +} From 5ccb016f29dadf81bbebe21cf4641a89850524bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 03:29:54 +0000 Subject: [PATCH 2/4] Address code review feedback: extract magic number to constant and improve comments Co-authored-by: KaveeshaPiumini <62582918+KaveeshaPiumini@users.noreply.github.com> --- .../testutils/shared_mock_servers.go | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/tests/integration/testutils/shared_mock_servers.go b/tests/integration/testutils/shared_mock_servers.go index 02b674671..d2d80d0fd 100644 --- a/tests/integration/testutils/shared_mock_servers.go +++ b/tests/integration/testutils/shared_mock_servers.go @@ -53,8 +53,12 @@ const ( DefaultHTTPPort = 9091 DefaultGithubPort = 8092 DefaultGooglePort = 8093 - DefaultOAuthPort = 8092 - DefaultOIDCPort = 8093 + // Note: OAuth and OIDC servers share the same ports as GitHub and Google servers respectively. + // Only one of each pair can be active at a time. Tests are run sequentially, so this is acceptable. + DefaultOAuthPort = 8092 + DefaultOIDCPort = 8093 + // ServerStartupWaitTime is the duration to wait for a mock server to start + ServerStartupWaitTime = 100 * time.Millisecond ) // Singleton instance @@ -87,7 +91,7 @@ func (s *SharedMockServers) GetNotificationServer() (*MockNotificationServer, er } // Wait for server to be ready - time.Sleep(100 * time.Millisecond) + time.Sleep(ServerStartupWaitTime) s.notificationStarted = true log.Printf("Shared notification server started on port %d", DefaultNotificationPort) @@ -109,7 +113,7 @@ func (s *SharedMockServers) GetHTTPServer() (*MockHTTPServer, error) { } // Wait for server to be ready - time.Sleep(100 * time.Millisecond) + time.Sleep(ServerStartupWaitTime) s.httpStarted = true log.Printf("Shared HTTP mock server started on port %d", DefaultHTTPPort) @@ -118,7 +122,7 @@ func (s *SharedMockServers) GetHTTPServer() (*MockHTTPServer, error) { // GetGithubServer returns the shared GitHub OAuth server, starting it if necessary. // The clientID and clientSecret are used for the first initialization only. -// Note: This server and GetOAuthServer use the same port (8092). Only one can be active at a time. +// Note: This server and GetOAuthServer use the same port (DefaultGithubPort). Only one can be active at a time. func (s *SharedMockServers) GetGithubServer(clientID, clientSecret string) (*MockGithubOAuthServer, error) { s.mutex.Lock() defer s.mutex.Unlock() @@ -138,7 +142,7 @@ func (s *SharedMockServers) GetGithubServer(clientID, clientSecret string) (*Moc } // Wait for server to be ready - time.Sleep(100 * time.Millisecond) + time.Sleep(ServerStartupWaitTime) s.githubStarted = true log.Printf("Shared GitHub OAuth server started on port %d", DefaultGithubPort) @@ -147,7 +151,7 @@ func (s *SharedMockServers) GetGithubServer(clientID, clientSecret string) (*Moc // GetGoogleServer returns the shared Google OIDC server, starting it if necessary. // The clientID and clientSecret are used for the first initialization only. -// Note: This server and GetOIDCServer use the same port (8093). Only one can be active at a time. +// Note: This server and GetOIDCServer use the same port (DefaultGooglePort). Only one can be active at a time. func (s *SharedMockServers) GetGoogleServer(clientID, clientSecret string) (*MockGoogleOIDCServer, error) { s.mutex.Lock() defer s.mutex.Unlock() @@ -172,7 +176,7 @@ func (s *SharedMockServers) GetGoogleServer(clientID, clientSecret string) (*Moc } // Wait for server to be ready - time.Sleep(100 * time.Millisecond) + time.Sleep(ServerStartupWaitTime) s.googleStarted = true log.Printf("Shared Google OIDC server started on port %d", DefaultGooglePort) @@ -180,7 +184,7 @@ func (s *SharedMockServers) GetGoogleServer(clientID, clientSecret string) (*Moc } // GetOAuthServer returns the shared generic OAuth server, starting it if necessary. -// Note: This server and GetGithubServer use the same port (8092). Only one can be active at a time. +// Note: This server and GetGithubServer use the same port (DefaultOAuthPort). Only one can be active at a time. func (s *SharedMockServers) GetOAuthServer(clientID, clientSecret string) (*MockOAuthServer, error) { s.mutex.Lock() defer s.mutex.Unlock() @@ -200,7 +204,7 @@ func (s *SharedMockServers) GetOAuthServer(clientID, clientSecret string) (*Mock } // Wait for server to be ready - time.Sleep(100 * time.Millisecond) + time.Sleep(ServerStartupWaitTime) s.oauthStarted = true log.Printf("Shared OAuth server started on port %d", DefaultOAuthPort) @@ -208,7 +212,7 @@ func (s *SharedMockServers) GetOAuthServer(clientID, clientSecret string) (*Mock } // GetOIDCServer returns the shared generic OIDC server, starting it if necessary. -// Note: This server and GetGoogleServer use the same port (8093). Only one can be active at a time. +// Note: This server and GetGoogleServer use the same port (DefaultOIDCPort). Only one can be active at a time. func (s *SharedMockServers) GetOIDCServer(clientID, clientSecret string) (*MockOIDCServer, error) { s.mutex.Lock() defer s.mutex.Unlock() @@ -233,7 +237,7 @@ func (s *SharedMockServers) GetOIDCServer(clientID, clientSecret string) (*MockO } // Wait for server to be ready - time.Sleep(100 * time.Millisecond) + time.Sleep(ServerStartupWaitTime) s.oidcStarted = true log.Printf("Shared OIDC server started on port %d", DefaultOIDCPort) From 89528a8279d733278d18e53360197261ffc049dc Mon Sep 17 00:00:00 2001 From: Piumini Kaveesha Ranasinghe <62582918+KaveeshaPiumini@users.noreply.github.com> Date: Mon, 8 Dec 2025 09:14:45 +0000 Subject: [PATCH 3/4] Fix intermittent integration test failures by resetting shared mock server state The shared mock server implementation was causing race conditions where users, auth codes, and tokens from one test suite would persist when another test suite ran. This led to non-deterministic behavior since Go maps iterate in random order, causing the mock server to randomly select different users. Changes: - Added Reset() method to all mock servers (Google, GitHub, OAuth, OIDC) to clear users, auth codes, tokens, and custom authorize functions - Updated all test suites to call Reset() in SetupSuite() before adding test users, ensuring clean state for each test suite - This eliminates race conditions and makes tests deterministic Affected test suites: - GoogleAuthFlowTestSuite - ConditionalExecAuthFlowTestSuite - GoogleRegistrationFlowTestSuite - GithubAuthFlowTestSuite - GithubRegistrationFlowTestSuite - OAuthAuthTestSuite - OIDCAuthTestSuite Fixes intermittent failures in TestGoogleAuthFlowCompleteSuccess and TestGoogleAuthFlowMultipleUsersSuccess among others. --- tests/integration/authn/oauth_auth_test.go | 3 +++ tests/integration/authn/oidc_auth_test.go | 3 +++ .../flowauthn/conditional_exec_auth_test.go | 3 +++ tests/integration/flowauthn/github_auth_test.go | 3 +++ tests/integration/flowauthn/google_auth_test.go | 3 +++ .../flowregistration/github_registration_test.go | 3 +++ .../flowregistration/google_registration_test.go | 3 +++ .../testutils/mock_github_oauth_server.go | 12 ++++++++++++ .../integration/testutils/mock_google_oidc_server.go | 11 +++++++++++ tests/integration/testutils/mock_oauth_server.go | 11 +++++++++++ tests/integration/testutils/mock_oidc_server.go | 11 +++++++++++ 11 files changed, 66 insertions(+) diff --git a/tests/integration/authn/oauth_auth_test.go b/tests/integration/authn/oauth_auth_test.go index b39682d09..e96c9c7cd 100644 --- a/tests/integration/authn/oauth_auth_test.go +++ b/tests/integration/authn/oauth_auth_test.go @@ -89,6 +89,9 @@ func (suite *OAuthAuthTestSuite) SetupSuite() { suite.T().Fatalf("Failed to get shared OAuth server: %v", err) } + // Reset the server to clear any state from previous test suites + suite.mockOAuthServer.Reset() + suite.mockOAuthServer.AddUser(&testutils.OAuthUserInfo{ Sub: "user123", Email: "testuser@example.com", diff --git a/tests/integration/authn/oidc_auth_test.go b/tests/integration/authn/oidc_auth_test.go index 003cc4150..5d2bf1c54 100644 --- a/tests/integration/authn/oidc_auth_test.go +++ b/tests/integration/authn/oidc_auth_test.go @@ -88,6 +88,9 @@ func (suite *OIDCAuthTestSuite) SetupSuite() { suite.T().Fatalf("Failed to get shared OIDC server: %v", err) } + // Reset the server to clear any state from previous test suites + suite.mockOIDCServer.Reset() + suite.mockOIDCServer.AddUser(&testutils.OIDCUserInfo{ Sub: "user456", Email: "testuser@oidc.com", diff --git a/tests/integration/flowauthn/conditional_exec_auth_test.go b/tests/integration/flowauthn/conditional_exec_auth_test.go index 34074cc0c..64237da7c 100644 --- a/tests/integration/flowauthn/conditional_exec_auth_test.go +++ b/tests/integration/flowauthn/conditional_exec_auth_test.go @@ -97,6 +97,9 @@ func (ts *ConditionalExecAuthFlowTestSuite) SetupSuite() { ts.T().Fatalf("Failed to get shared Google server: %v", err) } + // Reset the server to clear any state from previous test suites + ts.mockGoogleServer.Reset() + // Add test users ts.mockGoogleServer.AddUser(&testutils.GoogleUserInfo{ Sub: conditionalExecNewUserSub, diff --git a/tests/integration/flowauthn/github_auth_test.go b/tests/integration/flowauthn/github_auth_test.go index 8e42fff0a..7df5bbf0c 100644 --- a/tests/integration/flowauthn/github_auth_test.go +++ b/tests/integration/flowauthn/github_auth_test.go @@ -97,6 +97,9 @@ func (ts *GithubAuthFlowTestSuite) SetupSuite() { ts.T().Fatalf("Failed to get shared GitHub server: %v", err) } + // Reset the server to clear any state from previous test suites + ts.mockGithubServer.Reset() + email := "testuser@github.com" ts.mockGithubServer.AddUser(&testutils.GithubUserInfo{ Login: "testuser", diff --git a/tests/integration/flowauthn/google_auth_test.go b/tests/integration/flowauthn/google_auth_test.go index 3da920ee9..110313dcf 100644 --- a/tests/integration/flowauthn/google_auth_test.go +++ b/tests/integration/flowauthn/google_auth_test.go @@ -97,6 +97,9 @@ func (ts *GoogleAuthFlowTestSuite) SetupSuite() { ts.T().Fatalf("Failed to get shared Google server: %v", err) } + // Reset the server to clear any state from previous test suites + ts.mockGoogleServer.Reset() + ts.mockGoogleServer.AddUser(&testutils.GoogleUserInfo{ Sub: "google-test-user-123", Email: "testuser@gmail.com", diff --git a/tests/integration/flowregistration/github_registration_test.go b/tests/integration/flowregistration/github_registration_test.go index 9b09b985b..c699ce8af 100644 --- a/tests/integration/flowregistration/github_registration_test.go +++ b/tests/integration/flowregistration/github_registration_test.go @@ -103,6 +103,9 @@ func (ts *GithubRegistrationFlowTestSuite) SetupSuite() { ts.T().Fatalf("Failed to get shared GitHub server: %v", err) } + // Reset the server to clear any state from previous test suites + ts.mockGithubServer.Reset() + email := "reguser@github.com" ts.mockGithubServer.AddUser(&testutils.GithubUserInfo{ Login: "reguser", diff --git a/tests/integration/flowregistration/google_registration_test.go b/tests/integration/flowregistration/google_registration_test.go index 4fffecf89..0866394b3 100644 --- a/tests/integration/flowregistration/google_registration_test.go +++ b/tests/integration/flowregistration/google_registration_test.go @@ -103,6 +103,9 @@ func (ts *GoogleRegistrationFlowTestSuite) SetupSuite() { ts.T().Fatalf("Failed to get shared Google server: %v", err) } + // Reset the server to clear any state from previous test suites + ts.mockGoogleServer.Reset() + ts.mockGoogleServer.AddUser(&testutils.GoogleUserInfo{ Sub: "google-reg-user-456", Email: "reguser@gmail.com", diff --git a/tests/integration/testutils/mock_github_oauth_server.go b/tests/integration/testutils/mock_github_oauth_server.go index 906a1e391..2518f53cc 100644 --- a/tests/integration/testutils/mock_github_oauth_server.go +++ b/tests/integration/testutils/mock_github_oauth_server.go @@ -163,6 +163,18 @@ func (m *MockGithubOAuthServer) Stop() error { return nil } +// Reset clears all users, auth codes, and tokens from the mock server. +// This should be called at the start of each test suite to ensure clean state. +func (m *MockGithubOAuthServer) Reset() { + m.mutex.Lock() + defer m.mutex.Unlock() + m.authCodes = make(map[string]*GithubAuthCodeData) + m.accessTokens = make(map[string]*GithubTokenData) + m.users = make(map[string]*GithubUserInfo) + m.emails = make(map[string][]*GithubEmail) + m.authorizeFunc = nil +} + // GetURL returns the base URL of the mock server func (m *MockGithubOAuthServer) GetURL() string { return fmt.Sprintf("http://localhost:%d", m.port) diff --git a/tests/integration/testutils/mock_google_oidc_server.go b/tests/integration/testutils/mock_google_oidc_server.go index fca5d805b..28c74d8bf 100644 --- a/tests/integration/testutils/mock_google_oidc_server.go +++ b/tests/integration/testutils/mock_google_oidc_server.go @@ -182,6 +182,17 @@ func (m *MockGoogleOIDCServer) Stop() error { return nil } +// Reset clears all users, auth codes, and tokens from the mock server. +// This should be called at the start of each test suite to ensure clean state. +func (m *MockGoogleOIDCServer) Reset() { + m.mutex.Lock() + defer m.mutex.Unlock() + m.authCodes = make(map[string]*AuthCodeData) + m.accessTokens = make(map[string]*TokenData) + m.users = make(map[string]*GoogleUserInfo) + m.authorizeFunc = nil +} + // GetURL returns the base URL of the mock server func (m *MockGoogleOIDCServer) GetURL() string { return fmt.Sprintf("http://localhost:%d", m.port) diff --git a/tests/integration/testutils/mock_oauth_server.go b/tests/integration/testutils/mock_oauth_server.go index ad1e9b1a5..48d6e883c 100644 --- a/tests/integration/testutils/mock_oauth_server.go +++ b/tests/integration/testutils/mock_oauth_server.go @@ -127,6 +127,17 @@ func (m *MockOAuthServer) Stop() error { return nil } +// Reset clears all users, auth codes, and tokens from the mock server. +// This should be called at the start of each test suite to ensure clean state. +func (m *MockOAuthServer) Reset() { + m.mutex.Lock() + defer m.mutex.Unlock() + m.authCodes = make(map[string]*OAuthAuthCodeData) + m.accessTokens = make(map[string]*OAuthTokenData) + m.users = make(map[string]*OAuthUserInfo) + m.authorizeFunc = nil +} + // GetURL returns the base URL func (m *MockOAuthServer) GetURL() string { return m.baseURL diff --git a/tests/integration/testutils/mock_oidc_server.go b/tests/integration/testutils/mock_oidc_server.go index 29569ff78..cf5ad3968 100644 --- a/tests/integration/testutils/mock_oidc_server.go +++ b/tests/integration/testutils/mock_oidc_server.go @@ -179,6 +179,17 @@ func (m *MockOIDCServer) Stop() error { return nil } +// Reset clears all users, auth codes, and tokens from the mock server. +// This should be called at the start of each test suite to ensure clean state. +func (m *MockOIDCServer) Reset() { + m.mutex.Lock() + defer m.mutex.Unlock() + m.authCodes = make(map[string]*OIDCAuthCodeData) + m.accessTokens = make(map[string]*OIDCTokenData) + m.users = make(map[string]*OIDCUserInfo) + m.authorizeFunc = nil +} + // GetURL returns the base URL func (m *MockOIDCServer) GetURL() string { return m.issuer From d5964f259661f2eb0ab8cdcbbad55988e73fa7f5 Mon Sep 17 00:00:00 2001 From: KaveeshaPiumini Date: Tue, 9 Dec 2025 16:38:50 +0530 Subject: [PATCH 4/4] fix: improve SMS test reliability with WaitForMessage - Add WaitForMessage method to MockNotificationServer that polls for messages with a configurable timeout instead of using fixed sleep - Replace time.Sleep + GetLastMessage calls with WaitForMessage in SMS registration tests for more reliable test execution - This addresses intermittent test failures caused by timing issues when waiting for SMS OTP messages --- .../flowregistration/ou_registration_test.go | 16 ++++----- .../flowregistration/sms_registration_test.go | 33 +++++++------------ .../testutils/mock_notification_server.go | 24 ++++++++++++++ 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/tests/integration/flowregistration/ou_registration_test.go b/tests/integration/flowregistration/ou_registration_test.go index 9c3f2805a..cccadd3b4 100644 --- a/tests/integration/flowregistration/ou_registration_test.go +++ b/tests/integration/flowregistration/ou_registration_test.go @@ -27,8 +27,6 @@ import ( "github.com/stretchr/testify/suite" ) - - var ( ouRegTestOU = testutils.OrganizationUnit{ Handle: "ou-reg-flow-test-ou", @@ -364,10 +362,9 @@ func (ts *OURegistrationFlowTestSuite) TestSMSRegistrationFlowWithOUCreation() { ts.Require().NoError(err) ts.Require().Equal("INCOMPLETE", flowStep.FlowStatus) - time.Sleep(500 * time.Millisecond) - - lastMessage := ts.mockServer.GetLastMessage() - ts.Require().NotNil(lastMessage) + // Wait for SMS message with timeout (more reliable than fixed sleep) + lastMessage := ts.mockServer.WaitForMessage(2000) + ts.Require().NotNil(lastMessage, "Expected SMS message to be received within timeout") ts.Require().NotEmpty(lastMessage.OTP) inputs = map[string]string{ @@ -466,10 +463,9 @@ func (ts *OURegistrationFlowTestSuite) TestSMSRegistrationFlowWithOUCreationDupl flowStep, err := initiateRegistrationFlow(ts.smsFlowTestAppID, inputs) ts.Require().NoError(err) - time.Sleep(500 * time.Millisecond) - - lastMessage := ts.mockServer.GetLastMessage() - ts.Require().NotNil(lastMessage) + // Wait for SMS message with timeout (more reliable than fixed sleep) + lastMessage := ts.mockServer.WaitForMessage(2000) + ts.Require().NotNil(lastMessage, "Expected SMS message to be received within timeout") newHandle := tc.newOUHandle if newHandle == "" { diff --git a/tests/integration/flowregistration/sms_registration_test.go b/tests/integration/flowregistration/sms_registration_test.go index 009f1d782..c722b5417 100644 --- a/tests/integration/flowregistration/sms_registration_test.go +++ b/tests/integration/flowregistration/sms_registration_test.go @@ -27,8 +27,6 @@ import ( "github.com/stretchr/testify/suite" ) - - var ( smsRegTestOU = testutils.OrganizationUnit{ Handle: "sms-reg-flow-test-ou", @@ -205,12 +203,9 @@ func (ts *SMSRegistrationFlowTestSuite) TestSMSRegistrationFlowWithMobileNumber( ts.Require().NotEmpty(otpFlowStep.Data.Inputs, "Flow should require inputs") ts.Require().True(HasInput(otpFlowStep.Data.Inputs, "otp"), "OTP input should be required") - // Wait for SMS to be sent - time.Sleep(1000 * time.Millisecond) - - // Verify SMS was sent - lastMessage := ts.mockServer.GetLastMessage() - ts.Require().NotNil(lastMessage, "Last message should not be nil") + // Wait for SMS message with timeout (more reliable than fixed sleep) + lastMessage := ts.mockServer.WaitForMessage(2000) + ts.Require().NotNil(lastMessage, "Expected SMS message to be received within timeout") ts.Require().NotEmpty(lastMessage.OTP, "OTP should be extracted from message") // Step 3: Complete registration with OTP @@ -345,12 +340,9 @@ func (ts *SMSRegistrationFlowTestSuite) TestSMSRegistrationFlowWithUsername() { ts.Require().NotEmpty(otpFlowStep.Data.Inputs, "Flow should require inputs after username input") ts.Require().True(HasInput(otpFlowStep.Data.Inputs, "otp"), "OTP input should be required after username input") - // Wait for SMS to be sent - time.Sleep(500 * time.Millisecond) - - // Verify SMS was sent - lastMessage := ts.mockServer.GetLastMessage() - ts.Require().NotNil(lastMessage, "SMS should have been sent") + // Wait for SMS message with timeout (more reliable than fixed sleep) + lastMessage := ts.mockServer.WaitForMessage(2000) + ts.Require().NotNil(lastMessage, "Expected SMS message to be received within timeout") ts.Require().NotEmpty(lastMessage.OTP, "OTP should be available") // Step 4: Complete registration with OTP @@ -424,8 +416,8 @@ func (ts *SMSRegistrationFlowTestSuite) TestSMSRegistrationFlowInvalidOTP() { ts.Require().Equal("INCOMPLETE", otpFlowStep.FlowStatus, "Expected flow status to be INCOMPLETE") - // Wait for SMS to be sent - time.Sleep(500 * time.Millisecond) + // Wait for SMS to be sent (we don't need the actual message for this test, just ensure it was sent) + _ = ts.mockServer.WaitForMessage(2000) // Step 2: Try with invalid OTP invalidOTPInputs := map[string]string{ @@ -475,12 +467,9 @@ func (ts *SMSRegistrationFlowTestSuite) TestSMSRegistrationFlowSingleRequestWith ts.Require().Equal("INCOMPLETE", flowStep.FlowStatus, "Expected flow status to be INCOMPLETE") ts.Require().Equal("VIEW", flowStep.Type, "Expected flow type to be VIEW") - // Wait for SMS to be sent - time.Sleep(500 * time.Millisecond) - - // Get the OTP from mock server - lastMessage := ts.mockServer.GetLastMessage() - ts.Require().NotNil(lastMessage, "SMS should have been sent") + // Wait for SMS message with timeout (more reliable than fixed sleep) + lastMessage := ts.mockServer.WaitForMessage(2000) + ts.Require().NotNil(lastMessage, "Expected SMS message to be received within timeout") ts.Require().NotEmpty(lastMessage.OTP, "OTP should be available") // Step 2: Complete with OTP diff --git a/tests/integration/testutils/mock_notification_server.go b/tests/integration/testutils/mock_notification_server.go index 5bb76aa8c..07e98a980 100644 --- a/tests/integration/testutils/mock_notification_server.go +++ b/tests/integration/testutils/mock_notification_server.go @@ -25,6 +25,7 @@ import ( "log" "net/http" "sync" + "time" ) // MockNotificationServer provides a mock HTTP server for testing SMS notifications @@ -182,6 +183,29 @@ func (m *MockNotificationServer) GetLastMessage() *SMSMessage { return &lastMessage } +// WaitForMessage waits for a message to arrive with a timeout. +// It polls every 50ms until a message is available or the timeout is reached. +// This is more reliable than a fixed sleep for integration tests. +func (m *MockNotificationServer) WaitForMessage(timeoutMs int) *SMSMessage { + pollInterval := 50 // milliseconds + elapsed := 0 + + for elapsed < timeoutMs { + m.mutex.RLock() + hasMessages := len(m.messages) > 0 + m.mutex.RUnlock() + + if hasMessages { + return m.GetLastMessage() + } + + time.Sleep(time.Duration(pollInterval) * time.Millisecond) + elapsed += pollInterval + } + + return nil +} + // ClearMessages clears all stored messages func (m *MockNotificationServer) ClearMessages() { m.mutex.Lock()