/** * Authentication Test Utilities * * Provides reusable mocking patterns, factories, and helpers for authentication tests. * These utilities standardize how we mock OAuth responses, create test users, * set up fetch mocks, and generate test tokens. * * @module @dotdo/postgres-shared/auth-test-utils * * @example * ```typescript * import { * createMockFetch, * createMockUser, * testUsers, * tokens, * createSuccessfulOAuthResponse, * } from '@dotdo/postgres-shared/auth-test-utils' * * describe('auth tests', () => { * const { mockFn, install, restore } = createMockFetch() * * beforeEach(() => install()) * afterEach(() => restore()) * * it('validates tokens', async () => { * mockFn.mockResolvedValueOnce(createSuccessfulOAuthResponse(testUsers.valid)) * // ... test code * }) * }) * ``` */ import type { AuthenticatedUser, AuthTokenValidationResult } from './auth.js'; /** * Mock function interface compatible with Vitest's Mock type. * Using a generic interface allows these utilities to work without * importing vitest directly. */ export interface MockFunction { (...args: TArgs): TReturn; mockReset(): void; mockClear(): void; mockResolvedValue(value: TReturn extends Promise ? U : TReturn): this; mockResolvedValueOnce(value: TReturn extends Promise ? U : TReturn): this; mockRejectedValue(error: unknown): this; mockRejectedValueOnce(error: unknown): this; mockImplementation(fn: (...args: TArgs) => TReturn): this; mockImplementationOnce(fn: (...args: TArgs) => TReturn): this; mockReturnValue(value: TReturn): this; mockReturnValueOnce(value: TReturn): this; mock: { calls: TArgs[]; results: Array<{ type: 'return' | 'throw' | 'incomplete'; value: unknown; }>; instances: unknown[]; lastCall?: TArgs | undefined; }; } /** * Interface for configuring mock fetch behavior */ export interface MockFetchConfig { /** The mock function instance */ mockFn: MockFunction<[string | URL, RequestInit?], Promise>; /** Store the original fetch for restoration */ originalFetch?: typeof globalThis.fetch; } /** * Standard OAuth response structure for mocking */ export interface MockOAuthResponse { ok: boolean; status?: number; statusText?: string; json: () => Promise<{ user?: AuthenticatedUser; error?: string; expires?: string; }>; } /** * Standard SQL query response structure for mocking */ export interface MockSQLResponse { ok: boolean; status?: number; statusText?: string; json: () => Promise<{ rows?: unknown[]; fields?: Array<{ name: string; dataTypeID: number; }>; rowCount?: number; command?: string; error?: { message: string; code?: string; }; }>; } /** * JWT payload structure for test token generation */ export interface JWTPayload { /** Subject (user ID) */ sub?: string; /** Role claim */ role?: string; /** Issued at timestamp (seconds) */ iat?: number; /** Expiration timestamp (seconds) */ exp?: number; /** Not before timestamp (seconds) */ nbf?: number; /** Audience */ aud?: string | string[]; /** Issuer */ iss?: string; /** JWT ID */ jti?: string; /** Additional custom claims */ [key: string]: unknown; } /** * Creates a mock fetch configuration. * Note: The actual mock function must be provided by the test framework (e.g., vi.fn()). * * @param createMockFn - Factory function to create a mock (e.g., vi.fn) * @returns Mock fetch configuration with install/restore utilities * * @example * ```typescript * import { vi } from 'vitest' * import { createMockFetchConfig } from '@dotdo/postgres-shared/auth-test-utils' * * const { mockFn, install, restore } = createMockFetchConfig(() => vi.fn()) * * beforeEach(() => install()) * afterEach(() => restore()) * ``` */ export declare function createMockFetchConfig(createMockFn: () => T): MockFetchConfig & { install: () => void; restore: () => void; reset: () => void; }; /** * Creates a mock authenticated user with default values. * * @param overrides - Partial user object to override defaults * @returns A complete AuthenticatedUser object * * @example * ```typescript * const user = createMockUser({ id: 'custom-id', email: 'custom@example.com' }) * ``` */ export declare function createMockUser(overrides?: Partial): AuthenticatedUser; /** * Creates multiple mock users. * * @param count - Number of users to create * @param overrides - Partial user object to apply to all users * @returns Array of AuthenticatedUser objects * * @example * ```typescript * const users = createMockUsers(3, { metadata: { role: 'admin' } }) * ``` */ export declare function createMockUsers(count: number, overrides?: Partial): AuthenticatedUser[]; /** * Pre-defined test users for common scenarios. * Use these for consistent test data across test files. */ export declare const testUsers: { /** Standard valid user with all fields populated */ readonly valid: AuthenticatedUser; /** Admin user for authorization tests */ readonly admin: AuthenticatedUser; /** User with minimal data (no name or metadata) */ readonly minimal: AuthenticatedUser; /** User for service account tests */ readonly service: AuthenticatedUser; /** User with special characters in ID for edge case testing */ readonly specialChars: AuthenticatedUser; }; /** * Creates a successful OAuth validation response. * * @param user - The authenticated user to return * @param expires - Optional expiration date string * @returns MockOAuthResponse object for fetch mock * * @example * ```typescript * mockFetch.mockResolvedValueOnce(createSuccessfulOAuthResponse(testUsers.valid)) * ``` */ export declare function createSuccessfulOAuthResponse(user: AuthenticatedUser, expires?: string): MockOAuthResponse; /** * Creates a failed OAuth validation response (401 Unauthorized). * * @param error - Error message to include * @returns MockOAuthResponse object for fetch mock * * @example * ```typescript * mockFetch.mockResolvedValueOnce(createUnauthorizedOAuthResponse('Token expired')) * ``` */ export declare function createUnauthorizedOAuthResponse(error?: string): MockOAuthResponse; /** * Creates a rate-limited OAuth response (429 Too Many Requests). * * @param retryAfter - Optional retry-after value in seconds * @returns MockOAuthResponse object for fetch mock * * @example * ```typescript * mockFetch.mockResolvedValueOnce(createRateLimitedOAuthResponse(60)) * ``` */ export declare function createRateLimitedOAuthResponse(retryAfter?: number): MockOAuthResponse; /** * Creates an OAuth server error response (500). * * @param error - Error message * @returns MockOAuthResponse object for fetch mock */ export declare function createOAuthServerErrorResponse(error?: string): MockOAuthResponse; /** * Creates an OAuth network error for rejected fetch promises. * * @param message - Error message * @returns Error object for mockRejectedValue */ export declare function createOAuthNetworkError(message?: string): Error; /** * Creates a successful SQL query response. * * @param rows - Array of result rows * @param options - Additional response options * @returns MockSQLResponse object for fetch mock */ export declare function createSuccessfulSQLResponse(rows?: unknown[], options?: { command?: string; fields?: Array<{ name: string; dataTypeID: number; }>; }): MockSQLResponse; /** * Creates an empty SQL query response. */ export declare function createEmptySQLResponse(): MockSQLResponse; /** * Creates an SQL error response. * * @param message - Error message * @param code - PostgreSQL error code * @param status - HTTP status code */ export declare function createSQLErrorResponse(message: string, code?: string, status?: number): MockSQLResponse; /** * Pre-defined test tokens for common scenarios. * Use these for consistent token values across tests. */ export declare const tokens: { /** A valid-looking test token */ readonly valid: "valid-token-abc123"; /** An explicitly invalid token */ readonly invalid: "invalid-token"; /** A token representing an expired session */ readonly expired: "expired-token-xyz789"; /** A malformed token */ readonly malformed: "not-a-real-token!!!"; /** An empty token */ readonly empty: ""; /** A JWT-style token (structure only, not valid signature) */ readonly jwtStyle: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature"; }; /** * Generates a unique token for testing. * * @param prefix - Optional prefix for the token * @returns A unique test token string */ export declare function generateTestToken(prefix?: string): string; /** * Base64 URL encodes a string. * Used for JWT token generation. */ export declare function base64UrlEncode(str: string): string; /** * Creates a test JWT token structure (for testing purposes). * * Note: This creates a JWT with a fake signature. Use for testing * JWT parsing and structure validation, not for cryptographic verification. * * @param payload - JWT payload claims * @param options - Token generation options * @returns A JWT-formatted string * * @example * ```typescript * const token = createTestJWTStructure({ * sub: 'user-123', * role: 'authenticated', * exp: Math.floor(Date.now() / 1000) + 3600, * }) * ``` */ export declare function createTestJWTStructure(payload: JWTPayload, options?: { algorithm?: string; fakeSignature?: string; }): string; /** * Creates a signed test JWT token using HMAC-SHA256. * Requires Web Crypto API (available in modern browsers and Node.js 16+). * * @param payload - JWT payload claims * @param secret - Signing secret (minimum 32 bytes recommended) * @param algorithm - Algorithm to use (default: HS256) * @returns Promise resolving to a signed JWT string * * @example * ```typescript * const token = await createSignedTestJWT( * { sub: 'user-123', exp: Math.floor(Date.now() / 1000) + 3600 }, * 'your-32-byte-or-longer-secret-key' * ) * ``` */ export declare function createSignedTestJWT(payload: JWTPayload, secret: string, algorithm?: 'HS256' | 'HS384' | 'HS512'): Promise; /** * Creates JWT payload with sensible defaults for testing. * Automatically sets iat to now and exp to 1 hour from now if not provided. * * @param overrides - Partial payload to override defaults * @returns Complete JWT payload */ export declare function createTestJWTPayload(overrides?: Partial): JWTPayload; /** * Creates an expired JWT payload for testing token expiration. * * @param overrides - Partial payload to override * @returns JWT payload that is already expired */ export declare function createExpiredJWTPayload(overrides?: Partial): JWTPayload; /** * Creates a "not yet valid" JWT payload for testing nbf claim. * * @param overrides - Partial payload to override * @returns JWT payload that is not yet valid */ export declare function createNotYetValidJWTPayload(overrides?: Partial): JWTPayload; /** * Creates a Request with Bearer token authentication. * * @param url - Request URL * @param token - Bearer token * @param options - Additional request options * @returns Request object with Authorization header */ export declare function createAuthenticatedRequest(url?: string, token?: string, options?: RequestInit): Request; /** * Creates a Request without authentication. * * @param url - Request URL * @param options - Additional request options */ export declare function createUnauthenticatedRequest(url?: string, options?: RequestInit): Request; /** * Creates a Request with Basic auth (not Bearer). * Useful for testing auth type validation. * * @param url - Request URL * @param credentials - Base64 encoded credentials * @param options - Additional request options */ export declare function createBasicAuthRequest(url?: string, credentials?: string, // user:pass options?: RequestInit): Request; /** * Creates a Request with API key in X-API-Key header. * * @param url - Request URL * @param apiKey - API key value * @param options - Additional request options */ export declare function createApiKeyRequest(url: string | undefined, apiKey: string, options?: RequestInit): Request; /** * Creates a successful token validation result. * * @param user - The authenticated user * @param expiresAt - Optional expiration date */ export declare function createValidTokenResult(user: AuthenticatedUser, expiresAt?: Date): AuthTokenValidationResult; /** * Creates a failed token validation result. * * @param error - Error message */ export declare function createInvalidTokenResult(error: string): AuthTokenValidationResult; /** * Builder for creating sequences of mock fetch responses. * Useful for tests that make multiple fetch calls in sequence. * * @example * ```typescript * const sequence = new MockFetchSequenceBuilder(mockFn) * sequence * .addOAuthSuccess(testUsers.valid) * .addSQLSuccess([{ id: 1, name: 'Test' }]) * .apply() * ``` */ export declare class MockFetchSequenceBuilder { private responses; private mockFn; constructor(mockFn: MockFunction); /** Add an OAuth success response to the sequence */ addOAuthSuccess(user: AuthenticatedUser, expires?: string): this; /** Add an OAuth failure response (401) */ addOAuthFailure(error?: string): this; /** Add an OAuth rate limit response (429) */ addOAuthRateLimit(retryAfter?: number): this; /** Add an OAuth network error */ addOAuthNetworkError(message?: string): this; /** Add a successful SQL response */ addSQLSuccess(rows?: unknown[]): this; /** Add an empty SQL response */ addEmptySQL(): this; /** Add an SQL error response */ addSQLError(message: string, code?: string): this; /** Apply the sequence to the mock function */ apply(): void; /** Reset and clear the sequence */ reset(): void; /** Get the number of responses in the sequence */ get length(): number; } /** * Creates a new mock fetch sequence builder. * * @param mockFn - The mock function to configure */ export declare function createMockFetchSequence(mockFn: MockFunction): MockFetchSequenceBuilder; /** * Creates a rate limit test configuration. * * @param maxQueries - Maximum queries in window * @param windowMs - Window size in milliseconds */ export interface RateLimitTestConfig { maxQueries: number; windowMs: number; keyFn?: (request: unknown) => string; } /** * Default rate limit configuration for tests. * Uses conservative limits to easily trigger rate limiting. */ export declare const defaultRateLimitTestConfig: RateLimitTestConfig; /** * Strict rate limit configuration for testing rate limit behavior. * Allows only 1 request per window. */ export declare const strictRateLimitTestConfig: RateLimitTestConfig; /** * Assertion helper: checks if mock fetch was called with expected Authorization header. * * @param mockFn - The mock function to check * @param expectedToken - The expected token value * @param callIndex - Which call to check (default: 0) * @throws Error if assertion fails */ export declare function assertAuthorizationHeader(mockFn: MockFunction, expectedToken: string, callIndex?: number): void; /** * Assertion helper: checks if mock fetch was called with expected URL. * * @param mockFn - The mock function to check * @param expectedUrl - Expected URL string or RegExp * @param callIndex - Which call to check */ export declare function assertFetchUrl(mockFn: MockFunction, expectedUrl: string | RegExp, callIndex?: number): void; /** * Assertion helper: checks response status code. * * @param response - Response to check * @param expectedStatus - Expected HTTP status code */ export declare function assertStatus(response: Response, expectedStatus: number): void; /** * Assertion helper: checks response JSON body contains expected values. * * @param response - Response to check * @param expectedBody - Expected body properties */ export declare function assertJsonBody(response: Response, expectedBody: Record): Promise; /** * Assertion helper: checks that response is unauthorized (401). * * @param response - Response to check */ export declare function assertUnauthorized(response: Response): void; /** * Assertion helper: checks that response is forbidden (403). * * @param response - Response to check */ export declare function assertForbidden(response: Response): void; /** * Assertion helper: checks that response is rate limited (429). * * @param response - Response to check */ export declare function assertRateLimited(response: Response): void; //# sourceMappingURL=auth-test-utils.d.ts.map