> **Building with AI coding agents?** If you're using an AI coding agent, install the official Scalekit plugin. It gives your agent full awareness of the Scalekit API — reducing hallucinations and enabling faster, more accurate code generation.
>
> - **Claude Code**: `/plugin marketplace add scalekit-inc/claude-code-authstack` then `/plugin install <auth-type>@scalekit-auth-stack`
> - **GitHub Copilot CLI**: `copilot plugin marketplace add scalekit-inc/github-copilot-authstack` then `copilot plugin install <auth-type>@scalekit-auth-stack`
> - **Codex**: run the bash installer, restart, then open Plugin Directory and enable `<auth-type>`
> - **Skills CLI** (Windsurf, Cline, 40+ agents): `npx skills add scalekit-inc/skills --list` then `--skill <skill-name>`
>
> `<auth-type>` / `<skill-name>`: `agentkit`, `full-stack-auth`, `mcp-auth`, `modular-sso`, `modular-scim` — [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Testing Authentication Flows

Learn how to test AgentKit authentication flows in development, staging, and production environments with comprehensive testing strategies.
Thorough testing of authentication flows ensures your AgentKit integration works reliably before production deployment. This guide covers testing strategies, tools, and best practices.

## Testing environments

### Development environment

**Purpose:** Rapid iteration and debugging

**Characteristics:**
- Local development server
- Test accounts and credentials
- Verbose logging enabled
- Quick feedback loops

**Setup:**
```python
# development.env
SCALEKIT_ENV_URL=https://your-env.scalekit.dev
SCALEKIT_CLIENT_ID=dev_client_id
SCALEKIT_CLIENT_SECRET=dev_client_secret
DEBUG=true
LOG_LEVEL=debug
```

### Staging environment

**Purpose:** Pre-production validation

**Characteristics:**
- Production-like configuration
- Realistic data volumes
- Integration with staging third-party accounts
- Performance testing

**Setup:**
```python
# staging.env
SCALEKIT_ENV_URL=https://your-env.scalekit.cloud
SCALEKIT_CLIENT_ID=staging_client_id
SCALEKIT_CLIENT_SECRET=staging_client_secret
DEBUG=false
LOG_LEVEL=info
```

### Production environment

**Purpose:** Live user traffic

**Characteristics:**
- Real user data
- Verified OAuth applications
- Monitoring and alerts
- Minimal logging

**Setup:**
```python
# production.env
SCALEKIT_ENV_URL=https://your-env.scalekit.cloud
SCALEKIT_CLIENT_ID=prod_client_id
SCALEKIT_CLIENT_SECRET=prod_client_secret
DEBUG=false
LOG_LEVEL=warn
```

## Test account setup

### Creating test providers

Set up test accounts for each provider:

**Google Workspace:**
1. Create test Google account
2. Enable 2FA if testing MFA scenarios
3. Use for Gmail, Calendar, Drive testing

**Slack:**
1. Create free Slack workspace
2. Install your Slack app
3. Use for messaging and notification testing

**Microsoft 365:**
1. Get Microsoft 365 developer account (free)
2. Create test users
3. Use for Outlook, Teams, OneDrive testing

**Jira/Atlassian:**
1. Create free Atlassian Cloud account
2. Set up test projects
3. Generate API tokens for testing

### Test user patterns

Create different test users for scenarios:

```python
# Test user configurations
TEST_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

### Test connected account creation

### Python

```python

from 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()
```

### Node.js

```javascript
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);
  });
});
```

### Go

```go
package auth_test

    "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)
}
```

### Java

```java

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

```python
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

### Test complete authentication flow

```python

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

```python
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

### Test authentication in CI/CD

```yaml
# .github/workflows/test-auth.yml
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

```python
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

### Test token refresh performance

```python

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

### Test checklist

1. **Unit tests** - Test individual authentication functions
2. **Integration tests** - Test complete OAuth flows
3. **Error handling** - Test all error scenarios
4. **Token refresh** - Test automatic and manual refresh
5. **Multi-provider** - Test multiple simultaneous connections
6. **Performance** - Measure and optimize latency
7. **Security** - Verify token encryption and secure storage

### 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

```python
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

- [Authentication Troubleshooting](/agentkit/authentication/troubleshooting) - Debug authentication issues
- [Multi-Provider Authentication](/agentkit/authentication/multi-provider) - Test multiple providers


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
