| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566 |
- import { afterEach, describe, expect, it, vi } from 'vitest'
- import {
- addKanbanComment,
- createKanbanBoard,
- createKanbanTask,
- deleteKanbanTask,
- dispatchKanban,
- getKanbanBoard,
- getKanbanStatus,
- getKanbanTask,
- listKanbanBoards,
- listKanbanProfiles,
- updateKanbanTask,
- } from '@/api/kanban'
- import { setApiCsrfToken } from '@/api/client'
- afterEach(() => {
- vi.unstubAllGlobals()
- setApiCsrfToken('')
- })
- describe('Kanban Web client protocol', () => {
- it('keeps every browser request under the CSRF-protected app alias and scopes board reads', async () => {
- const calls: Array<{ path: string; init?: RequestInit }> = []
- vi.stubGlobal('fetch', vi.fn(async (path: string, init?: RequestInit) => {
- calls.push({ path, init })
- if (path.endsWith('/status')) return Response.json({ available: true, version: '1.0.0' })
- if (path.endsWith('/boards')) return Response.json({ boards: [], current: 'default', board: { slug: 'mobile-release' } })
- if (path.includes('/board?')) return Response.json({ columns: [], tenants: [], assignees: [], latest_event_id: 0, now: 1 })
- if (path.endsWith('/profiles')) return Response.json({ profiles: [] })
- if (path.includes('/tasks/task%20%2F%20one?')) return Response.json({ task: { id: 'task / one', title: 'Task', status: 'todo' }, comments: [], events: [], links: { parents: [], children: [] }, runs: [] })
- return Response.json({ ok: true, task: { id: 'task / one', title: 'Task', status: 'ready' }, spawned: [] })
- }))
- setApiCsrfToken('csrf-kanban')
- await getKanbanStatus()
- await listKanbanBoards()
- await getKanbanBoard('mobile / 中文', true)
- await listKanbanProfiles()
- await getKanbanTask('mobile / 中文', 'task / one')
- await createKanbanBoard({ slug: 'mobile-release', name: '移动发布' })
- await createKanbanTask('mobile / 中文', { title: '实现看板', assignee: 'yaoyao' })
- await updateKanbanTask('mobile / 中文', 'task / one', { status: 'ready' })
- await addKanbanComment('mobile / 中文', 'task / one', { body: '请继续' })
- await dispatchKanban('mobile / 中文')
- await deleteKanbanTask('mobile / 中文', 'task / one')
- expect(calls.map(call => call.path)).toEqual([
- '/api/app/kanban/status',
- '/api/app/kanban/boards',
- '/api/app/kanban/board?include_archived=true&board=mobile+%2F+%E4%B8%AD%E6%96%87',
- '/api/app/kanban/profiles',
- '/api/app/kanban/tasks/task%20%2F%20one?board=mobile+%2F+%E4%B8%AD%E6%96%87',
- '/api/app/kanban/boards',
- '/api/app/kanban/tasks?board=mobile+%2F+%E4%B8%AD%E6%96%87',
- '/api/app/kanban/tasks/task%20%2F%20one?board=mobile+%2F+%E4%B8%AD%E6%96%87',
- '/api/app/kanban/tasks/task%20%2F%20one/comments?board=mobile+%2F+%E4%B8%AD%E6%96%87',
- '/api/app/kanban/dispatch?board=mobile+%2F+%E4%B8%AD%E6%96%87',
- '/api/app/kanban/tasks/task%20%2F%20one?board=mobile+%2F+%E4%B8%AD%E6%96%87',
- ])
- const mutations = calls.filter(call => !['GET', 'HEAD'].includes((call.init?.method ?? 'GET').toUpperCase()))
- expect(mutations.every(call => new Headers(call.init?.headers).get('X-CSRF-Token') === 'csrf-kanban')).toBe(true)
- expect(JSON.parse(String(calls[5]!.init?.body))).toMatchObject({ slug: 'mobile-release', switch: false })
- expect(calls.some(call => call.path.includes('/switch'))).toBe(false)
- })
- })
|