import { describe, it, expect, vi, beforeEach } from 'vitest' import { supabase } from '../supabase.js' import * as profileApi from './profile.js' const nextResults = [] function createChain() { const getResult = () => nextResults.shift() ?? { data: null, error: null } const chain = { from: vi.fn(function () { return this }), select: vi.fn(function () { return this }), eq: vi.fn(function () { return this }), ilike: vi.fn(function () { return this }), limit: vi.fn(function () { return this }), single: vi.fn(() => Promise.resolve(getResult())), update: vi.fn(function () { return this }), then(resolve) { return Promise.resolve(getResult()).then(resolve) }, catch(fn) { return Promise.resolve(getResult()).catch(fn) }, } return chain } const mockStorageFrom = { getPublicUrl: vi.fn(() => ({ data: { publicUrl: 'https://example.com/avatars/path' } })), upload: vi.fn(() => Promise.resolve({ error: null })), } vi.mock('../supabase.js', () => ({ supabase: { from: vi.fn(() => createChain()), storage: { from: vi.fn(() => mockStorageFrom) }, }, })) describe('profile API', () => { beforeEach(() => { vi.clearAllMocks() nextResults.length = 0 }) describe('getEmailForUsername', () => { it('returns null for empty or whitespace username', async () => { expect(await profileApi.getEmailForUsername(supabase, '')).toBe(null) expect(await profileApi.getEmailForUsername(supabase, ' ')).toBe(null) expect(await profileApi.getEmailForUsername(supabase, null)).toBe(null) }) it('returns email when profile found', async () => { nextResults.push({ data: [{ email: 'user@example.com' }], error: null }) const email = await profileApi.getEmailForUsername(supabase, 'johndoe') expect(email).toBe('user@example.com') }) it('returns null when error or no data', async () => { nextResults.push({ data: [], error: null }) expect(await profileApi.getEmailForUsername(supabase, 'x')).toBe(null) nextResults.push({ data: null, error: { message: 'err' } }) expect(await profileApi.getEmailForUsername(supabase, 'y')).toBe(null) nextResults.push({ data: [{}], error: null }) expect(await profileApi.getEmailForUsername(supabase, 'z')).toBe(null) }) }) describe('getProfile', () => { it('returns profile when found', async () => { const p = { id: 'u1', display_name: 'Alice', email: 'a@b.com', avatar_url: null } nextResults.push({ data: p, error: null }) const result = await profileApi.getProfile(supabase, 'u1') expect(result).toEqual(p) }) it('returns null when not found (PGRST116)', async () => { nextResults.push({ data: null, error: { code: 'PGRST116' } }) const result = await profileApi.getProfile(supabase, 'u1') expect(result).toBe(null) }) it('throws when other error', async () => { nextResults.push({ data: null, error: new Error('Network error') }) await expect(profileApi.getProfile(supabase, 'u1')).rejects.toThrow('Network error') }) }) describe('updateProfile', () => { it('updates display_name and returns data', async () => { const updated = { id: 'u1', display_name: 'New Name', email: 'a@b.com', avatar_url: null } nextResults.push({ data: updated, error: null }) const result = await profileApi.updateProfile(supabase, 'u1', { display_name: 'New Name' }) expect(result).toEqual(updated) }) it('trims display_name and allows avatar_url', async () => { nextResults.push({ data: { id: 'u1' }, error: null }) await profileApi.updateProfile(supabase, 'u1', { display_name: ' Trim ', avatar_url: 'path/to/av.jpg' }) expect(nextResults.length).toBe(0) }) it('throws on error', async () => { nextResults.push({ data: null, error: new Error('DB error') }) await expect(profileApi.updateProfile(supabase, 'u1', { display_name: 'X' })).rejects.toThrow('DB error') }) }) describe('getAvatarPublicUrl', () => { it('returns publicUrl from storage', () => { const url = profileApi.getAvatarPublicUrl('user1/avatar.jpg') expect(url).toBe('https://example.com/avatars/path') expect(mockStorageFrom.getPublicUrl).toHaveBeenCalledWith('user1/avatar.jpg') }) }) describe('uploadAvatar', () => { it('uploads file and returns public URL', async () => { const file = new File(['x'], 'photo.jpg', { type: 'image/jpeg' }) const url = await profileApi.uploadAvatar('u1', file) expect(mockStorageFrom.upload).toHaveBeenCalledWith('u1/avatar.jpg', file, { upsert: true, contentType: 'image/jpeg' }) expect(url).toBe('https://example.com/avatars/path') }) it('throws on upload error', async () => { mockStorageFrom.upload.mockResolvedValueOnce({ error: new Error('Storage full') }) const file = new File(['x'], 'a.png', { type: 'image/png' }) await expect(profileApi.uploadAvatar('u1', file)).rejects.toThrow('Storage full') }) }) })