121 lines
3.8 KiB
TypeScript
121 lines
3.8 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const mockFetch = vi.fn()
|
|
vi.stubGlobal('fetch', mockFetch)
|
|
|
|
vi.mock('@/router', () => ({
|
|
default: {
|
|
currentRoute: { value: { name: 'hermes.chat' } },
|
|
replace: vi.fn(),
|
|
},
|
|
}))
|
|
|
|
import {
|
|
fetchMyAvatar,
|
|
resetMyAvatar,
|
|
updateMyAvatar,
|
|
} from '../../packages/client/src/api/auth'
|
|
import { setApiKey } from '../../packages/client/src/api/client'
|
|
|
|
function makeResponse(status: number, body: any, ok = status >= 200 && status < 300) {
|
|
return {
|
|
ok,
|
|
status,
|
|
text: () => Promise.resolve(typeof body === 'string' ? body : JSON.stringify(body)),
|
|
json: () => Promise.resolve(body),
|
|
}
|
|
}
|
|
|
|
describe('client auth API: avatar endpoints', () => {
|
|
beforeEach(() => {
|
|
localStorage.clear()
|
|
vi.clearAllMocks()
|
|
setApiKey('test-token')
|
|
})
|
|
|
|
describe('fetchMyAvatar', () => {
|
|
it('returns null when the server responds with an empty avatar string', async () => {
|
|
mockFetch.mockResolvedValueOnce(makeResponse(200, { avatar: '' }))
|
|
|
|
const avatar = await fetchMyAvatar()
|
|
|
|
expect(avatar).toBeNull()
|
|
expect(mockFetch).toHaveBeenCalledOnce()
|
|
const [url] = mockFetch.mock.calls[0]
|
|
expect(url).toBe('/api/auth/avatar')
|
|
})
|
|
|
|
it('returns null when the server returns an unparseable JSON string', async () => {
|
|
mockFetch.mockResolvedValueOnce(makeResponse(200, { avatar: '{not valid json' }))
|
|
|
|
const avatar = await fetchMyAvatar()
|
|
|
|
expect(avatar).toBeNull()
|
|
})
|
|
|
|
it('returns null when the parsed avatar has an unknown type', async () => {
|
|
mockFetch.mockResolvedValueOnce(
|
|
makeResponse(200, { avatar: JSON.stringify({ type: 'emoji', seed: 'x' }) }),
|
|
)
|
|
|
|
const avatar = await fetchMyAvatar()
|
|
|
|
expect(avatar).toBeNull()
|
|
})
|
|
|
|
it('parses a valid image avatar payload from the server', async () => {
|
|
const payload = { type: 'image' as const, dataUrl: 'data:image/png;base64,AAAA' }
|
|
mockFetch.mockResolvedValueOnce(makeResponse(200, { avatar: JSON.stringify(payload) }))
|
|
|
|
const avatar = await fetchMyAvatar()
|
|
|
|
expect(avatar).toEqual(payload)
|
|
})
|
|
|
|
it('parses a valid default avatar payload from the server', async () => {
|
|
const payload = { type: 'default' as const, seed: 'abc' }
|
|
mockFetch.mockResolvedValueOnce(makeResponse(200, { avatar: JSON.stringify(payload) }))
|
|
|
|
const avatar = await fetchMyAvatar()
|
|
|
|
expect(avatar).toEqual(payload)
|
|
})
|
|
})
|
|
|
|
describe('updateMyAvatar', () => {
|
|
it('PUTs the avatar as a JSON-string field to /api/auth/avatar', async () => {
|
|
mockFetch.mockResolvedValueOnce(makeResponse(200, { success: true, avatar: '' }))
|
|
const payload = { type: 'image' as const, dataUrl: 'data:image/png;base64,HI==' }
|
|
|
|
await updateMyAvatar(payload)
|
|
|
|
expect(mockFetch).toHaveBeenCalledOnce()
|
|
const [url, options] = mockFetch.mock.calls[0]
|
|
expect(url).toBe('/api/auth/avatar')
|
|
expect(options.method).toBe('PUT')
|
|
expect(options.headers['Content-Type']).toBe('application/json')
|
|
|
|
const body = JSON.parse(options.body)
|
|
expect(body.avatar).toBe(JSON.stringify(payload))
|
|
// The string must round-trip parse to the same object
|
|
expect(JSON.parse(body.avatar)).toEqual(payload)
|
|
})
|
|
})
|
|
|
|
describe('resetMyAvatar', () => {
|
|
it('PUTs a default avatar object to /api/auth/avatar', async () => {
|
|
mockFetch.mockResolvedValueOnce(makeResponse(200, { success: true }))
|
|
|
|
await resetMyAvatar()
|
|
|
|
expect(mockFetch).toHaveBeenCalledOnce()
|
|
const [url, options] = mockFetch.mock.calls[0]
|
|
expect(url).toBe('/api/auth/avatar')
|
|
expect(options.method).toBe('PUT')
|
|
const body = JSON.parse(options.body)
|
|
expect(body).toEqual({ avatar: { type: 'default' } })
|
|
})
|
|
})
|
|
})
|