You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
126 lines
4.9 KiB
126 lines
4.9 KiB
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')
|
|
})
|
|
})
|
|
})
|