Files
toor 01d9e301fc
Build / build (push) Canceled after 0s
Playwright / browser (push) Canceled after 0s
initialize Electron desktop app and remove unused files
2026-10-25 01:09:01 +08:00

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' } })
})
})
})