import { describe, it, expect, afterEach } from 'vitest'; import { parseOAuthState, isValidAppId, requireHttpsInProduction, validateGatewayUrl, validateCallbackUrl, isValidDid, isValidHandle, } from './validation'; describe('parseOAuthState', () => { it('parses valid state object', () => { const state = JSON.stringify({ returnTo: '/dashboard', nonce: 'abc123' }); const parsed = parseOAuthState(state); expect(parsed).not.toBeNull(); expect(parsed?.returnTo).toBe('/dashboard'); expect(parsed?.nonce).toBe('abc123'); }); it('returns null for non-string input', () => { expect(parseOAuthState(null)).toBeNull(); expect(parseOAuthState(undefined)).toBeNull(); expect(parseOAuthState(123)).toBeNull(); expect(parseOAuthState({})).toBeNull(); }); it('returns null for invalid JSON', () => { expect(parseOAuthState('not json')).toBeNull(); expect(parseOAuthState('{invalid')).toBeNull(); }); it('returns null for array', () => { expect(parseOAuthState('[]')).toBeNull(); expect(parseOAuthState('[1,2,3]')).toBeNull(); }); it('returns null for oversized state', () => { const huge = JSON.stringify({ data: 'x'.repeat(5000) }); expect(parseOAuthState(huge)).toBeNull(); }); it('returns null for deeply nested state', () => { const deep = JSON.stringify({ a: { b: { c: { d: { e: 'too deep' } } } } }); expect(parseOAuthState(deep)).toBeNull(); }); it('returns null for non-string returnTo', () => { expect(parseOAuthState(JSON.stringify({ returnTo: 123 }))).toBeNull(); expect(parseOAuthState(JSON.stringify({ returnTo: {} }))).toBeNull(); }); it('returns null for non-string nonce', () => { expect(parseOAuthState(JSON.stringify({ nonce: 123 }))).toBeNull(); }); it('returns null for dangerous returnTo schemes', () => { expect(parseOAuthState(JSON.stringify({ returnTo: 'javascript:alert(1)' }))).toBeNull(); expect(parseOAuthState(JSON.stringify({ returnTo: 'data:text/html,