/**
* Auth Module Tests
*
* Tests for authentication-related utilities and session handling.
* Note: Full integration tests for OAuth flows require E2E testing setup.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
// Mock the supabase-admin module before any imports
vi.mock('@/lib/supabase-admin', () => ({
getSupabaseAdmin: () => ({
from: () => ({
select: () => ({
eq: () => ({
maybeSingle: () => Promise.resolve({ data: null, error: null }),
single: () => Promise.resolve({ data: null, error: null }),
}),
}),
insert: () => ({
select: () => ({
single: () => Promise.resolve({ data: null, error: null }),
}),
}),
update: () => ({
eq: () => Promise.resolve({ data: null, error: null }),
}),
upsert: () => Promise.resolve({ data: null, error: null }),
}),
auth: {
signInWithPassword: () => Promise.resolve({ data: null, error: null }),
admin: {
listUsers: () => Promise.resolve({ data: { users: [] }, error: null }),
createUser: () => Promise.resolve({ data: null, error: null }),
},
},
rpc: () => Promise.resolve({ data: false, error: null }),
}),
}))
// Mock encryption module
vi.mock('@/lib/encryption', () => ({
encryptToken: (token: string) => `encrypted_${token}`,
decryptToken: (token: string) => token.replace('encrypted_', ''),
}))
describe('Auth Session Mapping', () => {
// Test the session callback mapping logic in isolation
// This simulates how token data gets mapped to session user object
interface MockToken {
id: string
username: string | null
avatarUrl: string | null
subscriptionTier: string
monthlyUsage: number
createdAt: string
usageResetDate: string | null
linkedProviders: string[]
isBetaTester: boolean
usesOwnKey: boolean
creditBalance: number
preferredMpId: string | null
postalCode: string | null
showMyMpSection: boolean
isAdmin: boolean
isModerator: boolean
partyAffiliation: string | null
partyAffiliationVisibility: string
}
interface MockSessionUser {
id?: string
username?: string | null
image?: string | null
subscriptionTier?: string
monthlyUsage?: number
createdAt?: string
usageResetDate?: string | null
linkedProviders?: string[]
isBetaTester?: boolean
usesOwnKey?: boolean
creditBalance?: number
preferredMpId?: string | null
postalCode?: string | null
showMyMpSection?: boolean
isAdmin?: boolean
isModerator?: boolean
partyAffiliation?: string | null
partyAffiliationVisibility?: string
}
// Simulate the session callback mapping logic from auth.ts
function mapTokenToSession(token: MockToken): MockSessionUser {
return {
id: token.id,
username: token.username,
image: token.avatarUrl,
subscriptionTier: token.subscriptionTier || 'FREE',
monthlyUsage: token.monthlyUsage || 0,
createdAt: token.createdAt,
usageResetDate: token.usageResetDate,
linkedProviders: token.linkedProviders,
isBetaTester: token.isBetaTester || false,
usesOwnKey: token.usesOwnKey || false,
creditBalance: token.creditBalance || 0,
preferredMpId: token.preferredMpId,
postalCode: token.postalCode,
showMyMpSection: token.showMyMpSection !== undefined ? token.showMyMpSection : true,
isAdmin: token.isAdmin || false,
isModerator: token.isModerator || false,
partyAffiliation: token.partyAffiliation,
partyAffiliationVisibility: token.partyAffiliationVisibility || 'public',
}
}
it('maps all token fields to session user', () => {
const token: MockToken = {
id: 'user-123',
username: 'testuser',
avatarUrl: 'https://example.com/avatar.jpg',
subscriptionTier: 'PRO',
monthlyUsage: 50,
createdAt: '2025-01-01T00:00:00Z',
usageResetDate: '2025-02-01T00:00:00Z',
linkedProviders: ['google', 'github'],
isBetaTester: true,
usesOwnKey: true,
creditBalance: 100,
preferredMpId: 'mp-123',
postalCode: 'K1A0A6',
showMyMpSection: true,
isAdmin: false,
isModerator: true,
partyAffiliation: 'liberal',
partyAffiliationVisibility: 'followers',
}
const session = mapTokenToSession(token)
expect(session.id).toBe('user-123')
expect(session.username).toBe('testuser')
expect(session.image).toBe('https://example.com/avatar.jpg')
expect(session.subscriptionTier).toBe('PRO')
expect(session.monthlyUsage).toBe(50)
expect(session.createdAt).toBe('2025-01-01T00:00:00Z')
expect(session.usageResetDate).toBe('2025-02-01T00:00:00Z')
expect(session.linkedProviders).toEqual(['google', 'github'])
expect(session.isBetaTester).toBe(true)
expect(session.usesOwnKey).toBe(true)
expect(session.creditBalance).toBe(100)
expect(session.preferredMpId).toBe('mp-123')
expect(session.postalCode).toBe('K1A0A6')
expect(session.showMyMpSection).toBe(true)
expect(session.isAdmin).toBe(false)
expect(session.isModerator).toBe(true)
expect(session.partyAffiliation).toBe('liberal')
expect(session.partyAffiliationVisibility).toBe('followers')
})
it('applies default values for missing/undefined fields', () => {
const token: MockToken = {
id: 'user-456',
username: null,
avatarUrl: null,
subscriptionTier: '', // Empty string
monthlyUsage: 0,
createdAt: '2025-01-01T00:00:00Z',
usageResetDate: null,
linkedProviders: [],
isBetaTester: false,
usesOwnKey: false,
creditBalance: 0,
preferredMpId: null,
postalCode: null,
showMyMpSection: true,
isAdmin: false,
isModerator: false,
partyAffiliation: null,
partyAffiliationVisibility: '',
}
const session = mapTokenToSession(token)
expect(session.subscriptionTier).toBe('FREE') // Default for empty
expect(session.monthlyUsage).toBe(0)
expect(session.isBetaTester).toBe(false)
expect(session.usesOwnKey).toBe(false)
expect(session.creditBalance).toBe(0)
expect(session.isAdmin).toBe(false)
expect(session.isModerator).toBe(false)
expect(session.showMyMpSection).toBe(true) // Default
expect(session.partyAffiliationVisibility).toBe('public') // Default for empty
})
it('handles showMyMpSection defaulting to true when undefined', () => {
const token = {
id: 'user-789',
username: null,
avatarUrl: null,
subscriptionTier: 'FREE',
monthlyUsage: 0,
createdAt: '2025-01-01T00:00:00Z',
usageResetDate: null,
linkedProviders: [],
isBetaTester: false,
usesOwnKey: false,
creditBalance: 0,
preferredMpId: null,
postalCode: null,
showMyMpSection: undefined as unknown as boolean, // Simulate undefined
isAdmin: false,
isModerator: false,
partyAffiliation: null,
partyAffiliationVisibility: 'public',
}
const session = mapTokenToSession(token as MockToken)
expect(session.showMyMpSection).toBe(true) // Should default to true
})
it('respects showMyMpSection when explicitly set to false', () => {
const token: MockToken = {
id: 'user-101',
username: null,
avatarUrl: null,
subscriptionTier: 'FREE',
monthlyUsage: 0,
createdAt: '2025-01-01T00:00:00Z',
usageResetDate: null,
linkedProviders: [],
isBetaTester: false,
usesOwnKey: false,
creditBalance: 0,
preferredMpId: null,
postalCode: null,
showMyMpSection: false, // Explicitly false
isAdmin: false,
isModerator: false,
partyAffiliation: null,
partyAffiliationVisibility: 'public',
}
const session = mapTokenToSession(token)
expect(session.showMyMpSection).toBe(false)
})
})
describe('Subscription Tier Handling', () => {
it('normalizes subscription tier to uppercase', () => {
const tiers = ['free', 'FREE', 'pro', 'PRO', 'enterprise', 'ENTERPRISE']
const expected = ['FREE', 'FREE', 'PRO', 'PRO', 'ENTERPRISE', 'ENTERPRISE']
tiers.forEach((tier, i) => {
const normalized = tier.toUpperCase() || 'FREE'
expect(normalized).toBe(expected[i])
})
})
it('defaults empty tier to FREE', () => {
const tier = ''
const normalized = tier.toUpperCase() || 'FREE'
expect(normalized).toBe('FREE')
})
})
describe('Linked Providers', () => {
it('handles empty providers array', () => {
const providers: string[] = []
expect(providers).toEqual([])
expect(providers.length).toBe(0)
})
it('handles multiple providers', () => {
const providers = ['google', 'github', 'facebook', 'linkedin']
expect(providers).toContain('google')
expect(providers).toContain('github')
expect(providers.length).toBe(4)
})
it('provider names are lowercase', () => {
const providers = ['google', 'github', 'facebook', 'linkedin']
providers.forEach((p) => {
expect(p).toBe(p.toLowerCase())
})
})
})
describe('Postal Code Validation', () => {
it('accepts valid Canadian postal codes', () => {
const validCodes = ['K1A0A6', 'V6B 1A1', 'M5V3L9', 'H3B 2Y5']
const postalCodeRegex = /^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$/
validCodes.forEach((code) => {
expect(postalCodeRegex.test(code)).toBe(true)
})
})
it('rejects invalid postal codes', () => {
const invalidCodes = ['12345', 'ABCDEF', '123 456', '']
const postalCodeRegex = /^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$/
invalidCodes.forEach((code) => {
expect(postalCodeRegex.test(code)).toBe(false)
})
})
})
describe('Party Affiliation Visibility', () => {
it('supports valid visibility options', () => {
const validOptions = ['public', 'followers', 'private']
validOptions.forEach((option) => {
expect(['public', 'followers', 'private']).toContain(option)
})
})
it('defaults to public when empty', () => {
const emptyValue = '' as string | undefined
const visibility = emptyValue || 'public'
expect(visibility).toBe('public')
})
})