import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import * as api from '@/api/index.js'
describe('API request helper', () => {
beforeEach(() => {
vi.stubGlobal('fetch', vi.fn())
})
afterEach(() => {
vi.unstubAllGlobals()
})
it('getProfiles calls GET /agents/profiles', async () => {
global.fetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => [{ id: 'secretary' }],
})
const result = await api.getProfiles()
expect(global.fetch).toHaveBeenCalledOnce()
const [url, opts] = global.fetch.mock.calls[0]
expect(url).toContain('/agents/profiles')
expect(opts.method).toBe('GET')
expect(result).toEqual([{ id: 'secretary' }])
})
it('createSession calls POST /sessions with body', async () => {
global.fetch.mockResolvedValue({
ok: true,
status: 201,
json: async () => ({ session_id: 's1' }),
})
const result = await api.createSession('developer')
const [url, opts] = global.fetch.mock.calls[0]
expect(url).toContain('/sessions')
expect(opts.method).toBe('POST')
expect(opts.headers['Content-Type']).toBe('application/json')
expect(JSON.parse(opts.body)).toEqual({ profile_id: 'developer' })
expect(result).toEqual({ session_id: 's1' })
})
it('deleteSession calls DELETE', async () => {
global.fetch.mockResolvedValue({ ok: true, status: 204 })
await api.deleteSession('s1')
const [url, opts] = global.fetch.mock.calls[0]
expect(opts.method).toBe('DELETE')
expect(url).toContain('/sessions/s1')
})
it('204 response returns null', async () => {
global.fetch.mockResolvedValue({ ok: true, status: 204 })
const result = await api.deleteSession('s1')
expect(result).toBeNull()
})
it('4xx throws with status text', async () => {
global.fetch.mockResolvedValue({
ok: false,
status: 404,
text: async () => 'Not found',
})
await expect(api.getSession('bad')).rejects.toThrow('404')
})
it('getSessions calls GET /sessions', async () => {
global.fetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ items: [] }),
})
await api.getSessions()
const [url] = global.fetch.mock.calls[0]
expect(url).toContain('/sessions?limit=30&offset=0')
})
it('getSessions sends pagination params', async () => {
global.fetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ items: [] }),
})
await api.getSessions({ limit: 30, offset: 60 })
const [url] = global.fetch.mock.calls[0]
expect(url).toContain('/sessions?limit=30&offset=60')
})
it('getSessions sends profile filter', async () => {
global.fetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ items: [] }),
})
await api.getSessions({ limit: 10, offset: 0, profileId: 'developer' })
const [url] = global.fetch.mock.calls[0]
expect(url).toContain('/sessions?limit=10&offset=0&profile_id=developer')
})
it('getSessions omits the special param by default', async () => {
global.fetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ items: [] }),
})
await api.getSessions()
const [url] = global.fetch.mock.calls[0]
expect(url).not.toContain('special')
})
it('getSessions sends the special filter', async () => {
global.fetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ items: [] }),
})
await api.getSessions({ special: true })
const [url] = global.fetch.mock.calls[0]
expect(url).toContain('special=true')
})
it('pinSession calls PATCH with pinned', async () => {
global.fetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ pinned: true }),
})
await api.pinSession('s1', true)
const [url, opts] = global.fetch.mock.calls[0]
expect(url).toContain('/sessions/s1/pin')
expect(opts.method).toBe('PATCH')
expect(JSON.parse(opts.body)).toEqual({ pinned: true })
})
it('uploadFile sends FormData', async () => {
global.fetch.mockResolvedValue({
ok: true,
status: 201,
json: async () => ({ name: 'file.txt' }),
})
const file = new File(['content'], 'file.txt', { type: 'text/plain' })
await api.uploadFile('s1', file)
const [url, opts] = global.fetch.mock.calls[0]
expect(url).toContain('/sessions/s1/files')
expect(opts.method).toBe('POST')
expect(opts.body instanceof FormData).toBe(true)
})
})
describe('Synapse delivery targets API', () => {
beforeEach(() => {
vi.stubGlobal('fetch', vi.fn())
})
afterEach(() => {
vi.unstubAllGlobals()
})
it('getSynapseTargets calls GET /synapse-targets', async () => {
global.fetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ items: [] }),
})
await api.getSynapseTargets()
const [url, opts] = global.fetch.mock.calls[0]
expect(url).toContain('/synapse-targets')
expect(opts.method).toBe('GET')
})
it('createSynapseTarget sends snake_case body', async () => {
global.fetch.mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ id: 1 }),
})
await api.createSynapseTarget('navi-main', 'very-secret-0123')
const [url, opts] = global.fetch.mock.calls[0]
expect(url).toContain('/synapse-targets')
expect(opts.method).toBe('POST')
expect(JSON.parse(opts.body)).toEqual({
token_ref: 'navi-main',
secret: 'very-secret-0123',
})
})
it('revokeSynapseTarget calls DELETE /synapse-targets/:id', async () => {
global.fetch.mockResolvedValue({ ok: true, status: 204, json: async () => ({}) })
await api.revokeSynapseTarget(7)
const [url, opts] = global.fetch.mock.calls[0]
expect(url).toContain('/synapse-targets/7')
expect(opts.method).toBe('DELETE')
})
})