# OAuth Plugin Testing Guide

This document provides guidance for testing the OAuth plugin, including patterns for mocking OAuth providers, JWT Auth Plugin integration, and test database setup.

## Test Structure

Tests are organized using Jasmine as the test framework. All test files should follow the pattern `*.spec.ts` and be placed in the `spec/` directory.

### Test Helpers

The `spec/helpers/` directory contains reusable test utilities:

- **mockOAuthProviders.ts** - Mock OAuth provider responses (GitHub, Google)
- **mockJwtAuthPlugin.ts** - Mock JWT Auth Plugin for testing
- **testDatabase.ts** - Database setup and cleanup utilities
- **testHelpers.ts** - General test helper functions

## Mocking OAuth Providers

### Creating Mock Profiles

Use the helper functions to create mock OAuth profiles:

```typescript
import { createMockGitHubProfile, createMockGoogleProfile } from './helpers/mockOAuthProviders';

// Create a default GitHub profile
const githubProfile = createMockGitHubProfile();

// Create a GitHub profile with custom data
const customProfile = createMockGitHubProfile({
  email: 'custom@example.com',
  name: 'Custom User'
});

// Create a Google profile
const googleProfile = createMockGoogleProfile();
```

### Mocking Token Exchange

Mock token exchange responses for testing:

```typescript
import { createMockTokenResponse, mockGitHubAPI } from './helpers/mockOAuthProviders';

// Create mock tokens
const tokens = createMockTokenResponse();

// Simulate GitHub token exchange
const tokenResponse = mockGitHubAPI.tokenExchange('valid_code');
// Returns: { access_token: 'gho_...', token_type: 'bearer', scope: 'user:email' }

// Simulate invalid code
const errorResponse = mockGitHubAPI.tokenExchange('invalid_code');
// Returns: 400 error response
```

### Mocking User Profile Fetch

Mock user profile API calls:

```typescript
import { mockGitHubAPI } from './helpers/mockOAuthProviders';

// Simulate successful profile fetch
const profileResponse = mockGitHubAPI.userProfile('valid_access_token');
// Returns: 200 with full GitHub user profile

// Simulate invalid token
const errorResponse = mockGitHubAPI.userProfile('invalid_token');
// Returns: 401 error
```

## Mocking JWT Auth Plugin

The OAuth plugin depends on the JWT Auth Plugin for token generation. Use the mock JWT Auth Plugin for testing:

### Basic Mock Setup

```typescript
import { createMockJwtAuthPlugin } from './helpers/mockJwtAuthPlugin';

// Create a mock JWT Auth Plugin
const mockJwtAuth = createMockJwtAuthPlugin();

// Use in tests
const token = await mockJwtAuth.createToken(
  { userId: '123', email: 'test@example.com' },
  ['user']
);

expect(token).toBeDefined();
```

### Testing Token Generation

```typescript
import { createMockJwtAuthPlugin, decodeTestToken } from './helpers/mockJwtAuthPlugin';

const mockJwtAuth = createMockJwtAuthPlugin('test-secret');

// Generate token
const token = await mockJwtAuth.createToken(
  { userId: '123' },
  ['user', 'admin']
);

// Decode and verify token
const decoded = decodeTestToken(token, 'test-secret');
expect(decoded.userId).toBe('123');
expect(decoded.roles).toEqual(['user', 'admin']);
```

### Testing JWT Generation Failures

Test error handling when JWT token generation fails:

```typescript
import { createFailingMockJwtAuthPlugin } from './helpers/mockJwtAuthPlugin';

// Create a mock that always fails
const failingMock = createFailingMockJwtAuthPlugin();

// Test error handling
try {
  await failingMock.createToken({ userId: '123' }, ['user']);
  fail('Should have thrown an error');
} catch (error) {
  expect(error.message).toContain('JWT token generation failed');
}
```

## Test Database Setup

### Setup and Cleanup

Use the database utilities to ensure test isolation:

```typescript
import {
  beforeEachTest,
  afterAllTests,
  getTestDatabase,
  cleanupTestDatabase
} from './helpers/testDatabase';

describe('OAuth Repository Tests', () => {
  // Clean database before each test
  beforeEach(async () => {
    await beforeEachTest();
  });

  // Close connection after all tests
  afterAll(async () => {
    await afterAllTests();
  });

  it('should create OAuth session', async () => {
    const db = await getTestDatabase();
    // Your test code here
  });
});
```

### Working with Test Data

Insert and retrieve test data:

```typescript
import { insertTestData, getTestData, clearTestCollection } from './helpers/testDatabase';

// Insert test sessions
await insertTestData('oauth_sessions', [
  { sessionId: 'session1', state: 'state1', provider: 'github' },
  { sessionId: 'session2', state: 'state2', provider: 'google' }
]);

// Retrieve test data
const sessions = await getTestData('oauth_sessions');
expect(sessions.length).toBe(2);

// Clear specific collection
await clearTestCollection('oauth_sessions');
```

## Integration Testing Patterns

### End-to-End OAuth Flow

Test the complete OAuth flow from initiation to JWT token return:

```typescript
import { createMockJwtAuthPlugin } from './helpers/mockJwtAuthPlugin';
import { createMockGitHubProfile } from './helpers/mockOAuthProviders';
import { beforeEachTest, afterAllTests } from './helpers/testDatabase';

describe('End-to-End OAuth Flow', () => {
  let mockJwtAuth: any;

  beforeEach(async () => {
    await beforeEachTest();
    mockJwtAuth = createMockJwtAuthPlugin();
  });

  afterAll(async () => {
    await afterAllTests();
  });

  it('should complete GitHub OAuth flow with JWT token', async () => {
    // 1. Initiate OAuth flow
    // - Creates session with state
    // - Redirects to GitHub

    // 2. Handle OAuth callback
    // - Validates state
    // - Exchanges code for tokens
    // - Fetches user profile
    // - Calls onAuthSuccess callback
    // - Generates JWT token via mockJwtAuth
    // - Returns token to client

    // Your test assertions here
  });
});
```

### Testing Multiple Providers

Test linking multiple OAuth providers to a single user:

```typescript
it('should link GitHub and Google to same user', async () => {
  const userId = 'user123';

  // Link GitHub account
  // ... OAuth flow for GitHub

  // Link Google account
  // ... OAuth flow for Google

  // Verify both connections exist
  const connections = await ctx.plugins.oauth.getConnections(userId);
  expect(connections.length).toBe(2);
  expect(connections.map(c => c.provider)).toContain('github');
  expect(connections.map(c => c.provider)).toContain('google');
});
```

### Testing Token Return Formats

Test different token return formats:

```typescript
describe('Token Return Formats', () => {
  it('should return JSON with response_type=json', async () => {
    // Simulate callback with response_type=json query parameter
    // Expect JSON response: { user: {...}, token: 'jwt_token' }
  });

  it('should return token in URL fragment', async () => {
    // Simulate callback without response_type
    // Expect redirect to: https://myapp.com/callback#token=jwt_token
  });

  it('should return token in query parameter', async () => {
    // Simulate callback with alternate format
    // Expect redirect to: https://myapp.com/callback?token=jwt_token
  });
});
```

## Running Tests

### Run All Tests

```bash
npm test
```

### Run Tests in Watch Mode

```bash
npm run test:watch
```

### Run Specific Test File

```bash
npx jasmine spec/integration.spec.ts
```

## Environment Variables

Configure test environment variables:

- `TEST_MONGO_URL` - MongoDB connection URL for tests (default: `mongodb://localhost:27017`)
- `TEST_DB_NAME` - Database name for tests (default: `flink_oauth_test`)

Example:

```bash
TEST_MONGO_URL=mongodb://localhost:27017 TEST_DB_NAME=oauth_test npm test
```

## Best Practices

1. **Isolate Tests**: Each test should be independent and not rely on other tests
2. **Clean Up**: Always clean up test data after tests complete
3. **Mock External Services**: Never make real HTTP requests to OAuth providers in tests
4. **Test Critical Paths**: Focus on end-to-end workflows and integration points
5. **Clear Test Names**: Use descriptive test names that explain what is being tested
6. **Fast Execution**: Keep tests fast by using mocks and avoiding unnecessary delays

## Adding New Provider Tests

When adding tests for a new OAuth provider:

1. Create mock profile function in `mockOAuthProviders.ts`:
   ```typescript
   export function createMockNewProviderProfile(overrides?: Partial<MockOAuthProfile>): MockOAuthProfile {
     return {
       id: 'provider_user_id',
       email: 'user@provider.com',
       name: 'User Name',
       raw: { /* provider-specific fields */ }
     };
   }
   ```

2. Create mock API responses:
   ```typescript
   export const mockNewProviderAPI = {
     tokenExchange: (code: string) => { /* mock response */ },
     userProfile: (accessToken: string) => { /* mock response */ }
   };
   ```

3. Write integration tests following the patterns above

## Troubleshooting

### MongoDB Connection Errors

If you encounter MongoDB connection errors:

1. Ensure MongoDB is running locally
2. Check connection URL in environment variables
3. Verify database permissions

### Test Timeouts

If tests are timing out:

1. Check for unclosed database connections
2. Verify all async operations are properly awaited
3. Consider increasing Jasmine timeout in `spec/support/jasmine.json`

### Mock Data Issues

If mocks aren't working as expected:

1. Verify mock functions are imported correctly
2. Check that mock data matches expected schema
3. Ensure mocks are reset between tests
