Testing Authentication Flows
Learn how to test Agent Auth authentication flows in development, staging, and production environments with comprehensive testing strategies.
Thorough testing of authentication flows ensures your Agent Auth integration works reliably before production deployment. This guide covers testing strategies, tools, and best practices.
Testing environments
Section titled “Testing environments”Development environment
Section titled “Development environment”Purpose: Rapid iteration and debugging
Characteristics:
- Local development server
- Test accounts and credentials
- Verbose logging enabled
- Quick feedback loops
Setup:
SCALEKIT_ENV_URL=https://your-env.scalekit.devSCALEKIT_CLIENT_ID=dev_client_idSCALEKIT_CLIENT_SECRET=dev_client_secretDEBUG=trueLOG_LEVEL=debugStaging environment
Section titled “Staging environment”Purpose: Pre-production validation
Characteristics:
- Production-like configuration
- Realistic data volumes
- Integration with staging third-party accounts
- Performance testing
Setup:
SCALEKIT_ENV_URL=https://your-env.scalekit.cloudSCALEKIT_CLIENT_ID=staging_client_idSCALEKIT_CLIENT_SECRET=staging_client_secretDEBUG=falseLOG_LEVEL=infoProduction environment
Section titled “Production environment”Purpose: Live user traffic
Characteristics:
- Real user data
- Verified OAuth applications
- Monitoring and alerts
- Minimal logging
Setup:
SCALEKIT_ENV_URL=https://your-env.scalekit.cloudSCALEKIT_CLIENT_ID=prod_client_idSCALEKIT_CLIENT_SECRET=prod_client_secretDEBUG=falseLOG_LEVEL=warnTest account setup
Section titled “Test account setup”Creating test providers
Section titled “Creating test providers”Set up test accounts for each provider:
Google Workspace:
- Create test Google account
- Enable 2FA if testing MFA scenarios
- Use for Gmail, Calendar, Drive testing
Slack:
- Create free Slack workspace
- Install your Slack app
- Use for messaging and notification testing
Microsoft 365:
- Get Microsoft 365 developer account (free)
- Create test users
- Use for Outlook, Teams, OneDrive testing
Jira/Atlassian:
- Create free Atlassian Cloud account
- Set up test projects
- Generate API tokens for testing
Test user patterns
Section titled “Test user patterns”Create different test users for scenarios:
# Test user configurationsTEST_USERS = { "basic_user": { "identifier": "test_user_001", "providers": ["gmail"], "scenario": "Single provider, basic authentication" }, "power_user": { "identifier": "test_user_002", "providers": ["gmail", "slack", "jira", "calendar"], "scenario": "Multiple providers, full feature access" }, "expired_user": { "identifier": "test_user_003", "providers": ["gmail"], "scenario": "Expired tokens, test refresh logic", "setup": "Manually expire tokens" }, "revoked_user": { "identifier": "test_user_004", "providers": ["slack"], "scenario": "User revoked access, test re-auth flow" }}Unit testing authentication
Section titled “Unit testing authentication”Test connected account creation
Section titled “Test connected account creation”import unittestfrom unittest.mock import Mock, patch
class TestConnectedAccountCreation(unittest.TestCase): def setUp(self): self.actions = Mock() self.user_id = "test_user_123" self.provider = "gmail"
def test_create_connected_account_success(self): """Test successful connected account creation""" # Mock response mock_response = Mock() mock_response.connected_account = Mock( id="account_123", status="PENDING", connection_name="gmail" ) self.actions.get_or_create_connected_account.return_value = mock_response
# Execute response = self.actions.get_or_create_connected_account( connection_name=self.provider, identifier=self.user_id )
# Assert self.assertEqual(response.connected_account.status, "PENDING") self.assertEqual(response.connected_account.connection_name, "gmail")
def test_generate_authorization_link(self): """Test authorization link generation""" mock_response = Mock() mock_response.link = "https://accounts.google.com/oauth/authorize?..."
self.actions.get_authorization_link.return_value = mock_response
response = self.actions.get_authorization_link( connection_name=self.provider, identifier=self.user_id )
self.assertIn("https://", response.link) self.actions.get_authorization_link.assert_called_once()
if __name__ == '__main__': unittest.main()const { describe, it, expect, jest, beforeEach } = require('@jest/globals');
describe('Connected Account Creation', () => { let mockActions; const userId = 'test_user_123'; const provider = 'gmail';
beforeEach(() => { mockActions = { getOrCreateConnectedAccount: jest.fn(), getAuthorizationLink: jest.fn() }; });
it('should create connected account successfully', async () => { // Mock response const mockResponse = { connectedAccount: { id: 'account_123', status: 'PENDING', connectionName: 'gmail' } };
mockActions.getOrCreateConnectedAccount.mockResolvedValue(mockResponse);
// Execute const response = await mockActions.getOrCreateConnectedAccount({ connectionName: provider, identifier: userId });
// Assert expect(response.connectedAccount.status).toBe('PENDING'); expect(response.connectedAccount.connectionName).toBe('gmail'); });
it('should generate authorization link', async () => { const mockResponse = { link: 'https://accounts.google.com/oauth/authorize?...' };
mockActions.getAuthorizationLink.mockResolvedValue(mockResponse);
const response = await mockActions.getAuthorizationLink({ connectionName: provider, identifier: userId });
expect(response.link).toContain('https://'); expect(mockActions.getAuthorizationLink).toHaveBeenCalledTimes(1); });});package auth_test
import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock")
type MockActions struct { mock.Mock}
func (m *MockActions) GetOrCreateConnectedAccount(connectionName, identifier string) (*ConnectedAccountResponse, error) { args := m.Called(connectionName, identifier) return args.Get(0).(*ConnectedAccountResponse), args.Error(1)}
func TestCreateConnectedAccount(t *testing.T) { // Arrange mockActions := new(MockActions) userId := "test_user_123" provider := "gmail"
expectedResponse := &ConnectedAccountResponse{ ConnectedAccount: ConnectedAccount{ ID: "account_123", Status: "PENDING", ConnectionName: "gmail", }, }
mockActions.On("GetOrCreateConnectedAccount", provider, userId). Return(expectedResponse, nil)
// Act response, err := mockActions.GetOrCreateConnectedAccount(provider, userId)
// Assert assert.NoError(t, err) assert.Equal(t, "PENDING", response.ConnectedAccount.Status) assert.Equal(t, "gmail", response.ConnectedAccount.ConnectionName) mockActions.AssertExpectations(t)}import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.Test;import org.mockito.Mock;import org.mockito.MockitoAnnotations;import static org.junit.jupiter.api.Assertions.*;import static org.mockito.Mockito.*;
class ConnectedAccountCreationTest { @Mock private Actions mockActions;
private String userId; private String provider;
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this); userId = "test_user_123"; provider = "gmail"; }
@Test void testCreateConnectedAccountSuccess() { // Arrange ConnectedAccount account = new ConnectedAccount(); account.setId("account_123"); account.setStatus("PENDING"); account.setConnectionName("gmail");
ConnectedAccountResponse mockResponse = new ConnectedAccountResponse(); mockResponse.setConnectedAccount(account);
when(mockActions.getOrCreateConnectedAccount(provider, userId)) .thenReturn(mockResponse);
// Act ConnectedAccountResponse response = mockActions .getOrCreateConnectedAccount(provider, userId);
// Assert assertEquals("PENDING", response.getConnectedAccount().getStatus()); assertEquals("gmail", response.getConnectedAccount().getConnectionName()); verify(mockActions, times(1)).getOrCreateConnectedAccount(provider, userId); }}Test token refresh logic
Section titled “Test token refresh logic”def test_token_refresh_scenarios(self): """Test various token refresh scenarios""" test_cases = [ { "name": "successful_refresh", "initial_status": "EXPIRED", "expected_status": "ACTIVE", "should_succeed": True }, { "name": "refresh_token_invalid", "initial_status": "EXPIRED", "expected_status": "EXPIRED", "should_succeed": False }, { "name": "already_active", "initial_status": "ACTIVE", "expected_status": "ACTIVE", "should_succeed": True } ]
for case in test_cases: with self.subTest(case=case["name"]): # Setup mock mock_account = Mock() mock_account.status = case["expected_status"]
if case["should_succeed"]: self.actions.refresh_connected_account.return_value = mock_account else: self.actions.refresh_connected_account.side_effect = Exception("Refresh failed")
# Execute try: result = self.actions.refresh_connected_account( identifier="test_user", connection_name="gmail" ) success = True except Exception: success = False
# Assert self.assertEqual(success, case["should_succeed"])Integration testing
Section titled “Integration testing”Test complete authentication flow
Section titled “Test complete authentication flow”import time
def test_complete_oauth_flow_integration(): """ Integration test for complete OAuth authentication flow. Requires manual intervention for OAuth consent. """ user_id = "integration_test_user" provider = "gmail"
# Step 1: Create connected account print("Step 1: Creating connected account...") response = actions.get_or_create_connected_account( connection_name=provider, identifier=user_id )
account = response.connected_account assert account.status == "PENDING", f"Expected PENDING, got {account.status}" print(f"✓ Connected account created: {account.id}")
# Step 2: Generate authorization link print("\nStep 2: Generating authorization link...") link_response = actions.get_authorization_link( connection_name=provider, identifier=user_id )
print(f"✓ Authorization link: {link_response.link}") print("\n⚠ MANUAL STEP: Open this link in a browser and complete OAuth") print(" Press Enter after completing OAuth flow...") input()
# Step 3: Verify account is now active print("\nStep 3: Verifying account status...") time.sleep(2) # Brief delay for processing
account = actions.get_connected_account( identifier=user_id, connection_name=provider )
assert account.status == "ACTIVE", f"Expected ACTIVE, got {account.status}" print(f"✓ Account is ACTIVE") print(f" Granted scopes: {account.scopes}")
# Step 4: Test tool execution print("\nStep 4: Testing tool execution...") result = actions.execute_tool( identifier=user_id, tool_name="gmail_get_profile", tool_input={} )
assert result is not None, "Tool execution failed" print(f"✓ Tool executed successfully")
print("\n✓✓✓ Integration test completed successfully")
# Run with: pytest test_auth_integration.py -s (to see output)Test error scenarios
Section titled “Test error scenarios”def test_error_scenarios(): """Test various error scenarios""" user_id = "error_test_user"
# Test 1: Invalid provider print("Test 1: Invalid provider...") try: actions.get_or_create_connected_account( connection_name="invalid_provider", identifier=user_id ) assert False, "Should have raised error" except Exception as e: print(f"✓ Caught expected error: {type(e).__name__}")
# Test 2: Execute tool without authentication print("\nTest 2: Tool execution without auth...") try: actions.execute_tool( identifier="nonexistent_user", tool_name="gmail_send_email", tool_input={"to": "test@example.com"} ) assert False, "Should have raised error" except Exception as e: print(f"✓ Caught expected error: {type(e).__name__}")
# Test 3: Missing required scopes print("\nTest 3: Missing required scopes...") # This test requires setup with insufficient scopes print("⚠ Skipped: Requires special setup")
print("\n✓✓✓ Error scenario tests completed")Automated testing
Section titled “Automated testing”Test authentication in CI/CD
Section titled “Test authentication in CI/CD”name: Test Authentication Flows
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest
steps: - uses: actions/checkout@v2
- name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.9'
- name: Install dependencies run: | pip install -r requirements.txt pip install pytest pytest-cov
- name: Run unit tests env: SCALEKIT_CLIENT_ID: ${{ secrets.TEST_CLIENT_ID }} SCALEKIT_CLIENT_SECRET: ${{ secrets.TEST_CLIENT_SECRET }} SCALEKIT_ENV_URL: ${{ secrets.TEST_ENV_URL }} run: | pytest tests/test_auth.py -v --cov=src/auth
- name: Run integration tests (non-OAuth) env: SCALEKIT_CLIENT_ID: ${{ secrets.TEST_CLIENT_ID }} SCALEKIT_CLIENT_SECRET: ${{ secrets.TEST_CLIENT_SECRET }} SCALEKIT_ENV_URL: ${{ secrets.TEST_ENV_URL }} run: | pytest tests/test_auth_integration.py -v -k "not oauth"Mock OAuth flows
Section titled “Mock OAuth flows”from unittest.mock import patch, Mock
def test_oauth_flow_with_mocks(): """Test OAuth flow with mocked responses (no actual OAuth)"""
with patch('scalekit.actions.get_or_create_connected_account') as mock_create, \ patch('scalekit.actions.get_authorization_link') as mock_link, \ patch('scalekit.actions.get_connected_account') as mock_get:
# Mock connected account creation mock_account = Mock() mock_account.id = "account_123" mock_account.status = "PENDING"
mock_response = Mock() mock_response.connected_account = mock_account mock_create.return_value = mock_response
# Mock authorization link mock_link_response = Mock() mock_link_response.link = "https://mock-oauth-url.com" mock_link.return_value = mock_link_response
# Mock successful authentication (simulate user completing OAuth) mock_account.status = "ACTIVE" mock_account.scopes = ["gmail.readonly", "gmail.send"] mock_get.return_value = mock_account
# Test the flow # 1. Create account response = mock_create(connection_name="gmail", identifier="user_123") assert response.connected_account.status == "PENDING"
# 2. Get auth link link = mock_link(connection_name="gmail", identifier="user_123") assert "https://" in link.link
# 3. Simulate user completing OAuth (status changes to ACTIVE) account = mock_get(identifier="user_123", connection_name="gmail") assert account.status == "ACTIVE" assert len(account.scopes) > 0
print("✓ OAuth flow test with mocks completed")Performance testing
Section titled “Performance testing”Test token refresh performance
Section titled “Test token refresh performance”import time
def test_token_refresh_performance(): """Measure token refresh latency""" user_id = "perf_test_user" provider = "gmail"
# Setup: Create account with expired token # (This requires manually setting up an expired account)
iterations = 10 refresh_times = []
for i in range(iterations): start_time = time.time()
try: actions.refresh_connected_account( identifier=user_id, connection_name=provider ) elapsed = time.time() - start_time refresh_times.append(elapsed) print(f"Iteration {i+1}: {elapsed:.3f}s") except Exception as e: print(f"Iteration {i+1} failed: {e}")
if refresh_times: avg_time = sum(refresh_times) / len(refresh_times) min_time = min(refresh_times) max_time = max(refresh_times)
print(f"\nToken Refresh Performance:") print(f" Average: {avg_time:.3f}s") print(f" Min: {min_time:.3f}s") print(f" Max: {max_time:.3f}s")
# Assert reasonable performance (adjust threshold as needed) assert avg_time < 2.0, f"Average refresh time too slow: {avg_time:.3f}s"Best practices
Section titled “Best practices”Test checklist
Section titled “Test checklist”- Unit tests - Test individual authentication functions
- Integration tests - Test complete OAuth flows
- Error handling - Test all error scenarios
- Token refresh - Test automatic and manual refresh
- Multi-provider - Test multiple simultaneous connections
- Performance - Measure and optimize latency
- Security - Verify token encryption and secure storage
Testing dos and don’ts
Section titled “Testing dos and don’ts”✅ Do:
- Use separate test accounts for each provider
- Test both success and failure scenarios
- Mock external OAuth calls in unit tests
- Test token refresh before expiration
- Verify error messages are helpful
- Test with realistic data volumes
❌ Don’t:
- Use production accounts for testing
- Hardcode test credentials in source code
- Skip error scenario testing
- Assume OAuth always succeeds
- Neglect performance testing
- Test only happy path scenarios
Security testing
Section titled “Security testing”def test_security_scenarios(): """Test security-related authentication scenarios"""
# Test 1: Verify tokens are not exposed in logs print("Test 1: Token exposure check...") with patch('logging.Logger.debug') as mock_log: account = actions.get_connected_account( identifier="test_user", connection_name="gmail" )
# Verify no access tokens in log calls for call in mock_log.call_args_list: log_message = str(call) assert "access_token" not in log_message.lower() assert "refresh_token" not in log_message.lower()
print("✓ No tokens in logs")
# Test 2: Verify HTTPS for OAuth redirects print("\nTest 2: HTTPS verification...") link_response = actions.get_authorization_link( connection_name="gmail", identifier="test_user" )
assert link_response.link.startswith("https://") print("✓ OAuth uses HTTPS")
# Test 3: State parameter validation print("\nTest 3: State parameter present...") assert "state=" in link_response.link print("✓ State parameter included")
print("\n✓✓✓ Security tests completed")Next steps
Section titled “Next steps”- Authentication Troubleshooting - Debug authentication issues
- Token Management - Manage tokens effectively
- Multi-Provider Authentication - Test multiple providers