<% Call ASPUnit.AddModule( _ ASPUnit.CreateModule( _ "Keycloak Callback Behavior Tests", _ Array( _ ASPUnit.CreateTest("EnsurePendingLoginValueCreatesNewValueWhenMissing"), _ ASPUnit.CreateTest("EnsurePendingLoginValueReusesExistingValue"), _ ASPUnit.CreateTest("HandleCallbackReturnsFalseWhenCodeIsMissing"), _ ASPUnit.CreateTest("HandleCallbackSetsErrorMessageOnMissingCode"), _ ASPUnit.CreateTest("StateValidationErrorExplainsMissingStoredState"), _ ASPUnit.CreateTest("StateValidationErrorExplainsMismatchedStoredState"), _ ASPUnit.CreateTest("IsLoggedInReturnsFalseWithEmptySession"), _ ASPUnit.CreateTest("IsLoggedInReturnsTrueWhenTokenStoredInSession"), _ ASPUnit.CreateTest("ClearSessionRemovesStoredTokens") _ ), _ ASPUnit.CreateLifeCycle("SetupKeycloakCallbackBehavior", "TeardownKeycloakCallbackBehavior") _ ) _ ) Call ASPUnit.Run() Sub SetupKeycloakCallbackBehavior() Call ResetTestRuntime() KeycloakAuth_Class__Singleton = Empty Session.Contents.Remove "Keycloak_AccessToken" Session.Contents.Remove "Keycloak_RefreshToken" Session.Contents.Remove "Keycloak_IdToken" Session.Contents.Remove "Keycloak_State" Session.Contents.Remove "Keycloak_Nonce" Session.Contents.Remove "Keycloak_UserInfoJson" End Sub Sub TeardownKeycloakCallbackBehavior() Session.Contents.Remove "Keycloak_AccessToken" Session.Contents.Remove "Keycloak_RefreshToken" Session.Contents.Remove "Keycloak_IdToken" Session.Contents.Remove "Keycloak_State" Session.Contents.Remove "Keycloak_Nonce" Session.Contents.Remove "Keycloak_UserInfoJson" KeycloakAuth_Class__Singleton = Empty Call ResetTestRuntime() End Sub ' Builds a configured KeycloakAuth_Class instance for callback behavior tests Function NewCallbackTestAuth() Dim auth Set auth = New KeycloakAuth_Class Call auth.Configure("https://login.example.test/", "survey", "classic-app", "secret", "https://app.example.test/auth/callback") auth.LogoutRedirectUri = "https://app.example.test/" Set NewCallbackTestAuth = auth End Function ' A fresh login should create a new pending value in Session so the redirect can ' include state/nonce even when the session starts empty. Function EnsurePendingLoginValueCreatesNewValueWhenMissing() Dim auth, value Set auth = NewCallbackTestAuth() Session.Contents.Remove "Keycloak_State" value = auth.EnsurePendingLoginValue("Keycloak_State") Call ASPUnit.Ok(Len(value) > 0, "EnsurePendingLoginValue should create a value when the session does not have one") Call ASPUnit.Equal(Session("Keycloak_State"), value, "EnsurePendingLoginValue should store the created value in Session") End Function ' Repeated hits to /auth/login in the same session should keep using the same ' pending state instead of overwriting it and breaking the first callback. Function EnsurePendingLoginValueReusesExistingValue() Dim auth, value Set auth = NewCallbackTestAuth() Session("Keycloak_State") = "existing-state-123" value = auth.EnsurePendingLoginValue("Keycloak_State") Call ASPUnit.Equal(value, "existing-state-123", "EnsurePendingLoginValue should reuse an existing pending value") Call ASPUnit.Equal(Session("Keycloak_State"), "existing-state-123", "EnsurePendingLoginValue should not overwrite an existing session value") End Function ' HandleCallback returns False when the request has no 'code' query parameter ' (the typical outcome of a direct navigation or an incomplete redirect) Function HandleCallbackReturnsFalseWhenCodeIsMissing() Dim auth, result Set auth = NewCallbackTestAuth() result = auth.HandleCallback() Call ASPUnit.Ok(Not CBool(result), "HandleCallback should return False when no authorization code is present in the request") End Function ' HandleCallback must describe the failure in ErrorMessage so the view ' can surface a meaningful message to the user Function HandleCallbackSetsErrorMessageOnMissingCode() Dim auth Set auth = NewCallbackTestAuth() Call auth.HandleCallback() Call ASPUnit.Ok( _ InStr(LCase(auth.ErrorMessage), "authorization code") > 0, _ "HandleCallback should set an error message mentioning 'authorization code' when the code parameter is absent" _ ) End Function ' IsLoggedIn reflects the presence of an access token in the Session — ' no token means not logged in Function IsLoggedInReturnsFalseWithEmptySession() Dim auth Set auth = NewCallbackTestAuth() Call ASPUnit.Ok(Not auth.IsLoggedIn(), "IsLoggedIn should return False when no access token is stored in session") End Function ' Placing a token in the Session should cause IsLoggedIn to return True ' without any HTTP call — the session IS the auth state Function IsLoggedInReturnsTrueWhenTokenStoredInSession() Dim auth Set auth = NewCallbackTestAuth() Session("Keycloak_AccessToken") = "eyJ.test.token" Call ASPUnit.Ok(auth.IsLoggedIn(), "IsLoggedIn should return True when an access token is present in session") End Function ' ClearSession must remove all Keycloak_ prefixed keys so that a subsequent ' IsLoggedIn check correctly reports the user as signed out Function ClearSessionRemovesStoredTokens() Dim auth Set auth = NewCallbackTestAuth() Session("Keycloak_AccessToken") = "eyJ.test.token" Session("Keycloak_RefreshToken") = "eyJ.refresh.token" Call auth.ClearSession() Call ASPUnit.Ok(Not auth.IsLoggedIn(), "ClearSession should remove stored tokens so IsLoggedIn returns False") End Function ' When the stored login state is absent, the callback failure should point to ' session loss, callback reload, or direct navigation instead of a generic ' mismatch so troubleshooting is faster. Function StateValidationErrorExplainsMissingStoredState() Dim auth, message Set auth = NewCallbackTestAuth() message = auth.StateValidationError("callback-state-123") Call ASPUnit.Ok( _ InStr(LCase(message), "stored session state is missing") > 0, _ "StateValidationError should explain when the stored session state is missing" _ ) End Function ' When a session state exists but differs from the callback state, the helper ' should describe a stale callback or overlapping login rather than session loss. Function StateValidationErrorExplainsMismatchedStoredState() Dim auth, message Set auth = NewCallbackTestAuth() Session("Keycloak_State") = "expected-state-123" message = auth.StateValidationError("callback-state-456") Call ASPUnit.Ok( _ InStr(LCase(message), "did not match the active login session") > 0 And InStr(LCase(message), "another login attempt") > 0, _ "StateValidationError should explain when the callback state differs from the stored login state" _ ) End Function %>