102 lines
3.2 KiB
TypeScript
102 lines
3.2 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
import {
|
|
canApproveAgentProposal,
|
|
canRejectAgentProposal,
|
|
canRetryAgentProposal,
|
|
getAgentProposalStatusMeta,
|
|
type AgentProposalDto,
|
|
} from '../src/api/agentProposals'
|
|
import { apiFetch } from '../src/services/api'
|
|
import { useAuthStore } from '../src/stores/auth'
|
|
|
|
const baseProposal: AgentProposalDto = {
|
|
id: '0d2d33d7-bafa-4510-b135-c6a96021957a',
|
|
source: 'manual',
|
|
requestedName: 'Release Coordinator',
|
|
requestedAgentId: 'release-coordinator',
|
|
role: 'Delivery Operations',
|
|
description: 'Coordinates releases.',
|
|
model: null,
|
|
emoji: null,
|
|
avatar: null,
|
|
workspace: '/managed/workspace-release-coordinator',
|
|
files: [],
|
|
status: 'awaiting_approval',
|
|
requestedBy: 'bao',
|
|
approvedBy: null,
|
|
rejectedBy: null,
|
|
rejectionReason: null,
|
|
openClawAgentId: null,
|
|
openClawWorkspace: null,
|
|
error: null,
|
|
revision: 1,
|
|
createdAt: '2026-07-31T08:00:00.000Z',
|
|
updatedAt: '2026-07-31T08:00:00.000Z',
|
|
approvedAt: null,
|
|
rejectedAt: null,
|
|
completedAt: null,
|
|
}
|
|
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia())
|
|
const auth = useAuthStore()
|
|
auth.initialized = true
|
|
auth.applySession({
|
|
accessToken: 'proposal-access-token',
|
|
expiresAt: '2026-07-31T12:00:00.000Z',
|
|
user: {
|
|
id: 'bao',
|
|
email: 'bao@nexus.local',
|
|
displayName: 'Bao',
|
|
role: 'owner',
|
|
},
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
describe('agent proposal frontend contract', () => {
|
|
it('maps lifecycle states to safe owner actions', () => {
|
|
expect(getAgentProposalStatusMeta('awaiting_approval').label).toBe('Freigabe offen')
|
|
expect(canApproveAgentProposal(baseProposal)).toBe(true)
|
|
expect(canRejectAgentProposal(baseProposal)).toBe(true)
|
|
expect(canRetryAgentProposal(baseProposal)).toBe(false)
|
|
|
|
const failed = { ...baseProposal, status: 'failed' }
|
|
expect(canApproveAgentProposal(failed)).toBe(false)
|
|
expect(canRejectAgentProposal(failed)).toBe(false)
|
|
expect(canRetryAgentProposal(failed)).toBe(true)
|
|
expect(canRetryAgentProposal({ ...baseProposal, status: 'in_doubt' })).toBe(true)
|
|
})
|
|
|
|
it('preserves generated Request headers through the authenticated fetch boundary', async () => {
|
|
const fetchMock = vi.fn(async () => new Response(JSON.stringify({ ok: true }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}))
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
|
|
const request = new Request('http://localhost/api/v1/openclaw/agent-proposals', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Idempotency-Key': 'agent-proposal-create:test',
|
|
'X-Correlation-ID': 'd4f72d7d-6331-4bcc-87e8-e74480315073',
|
|
},
|
|
body: JSON.stringify({ name: 'Release Coordinator' }),
|
|
})
|
|
|
|
await apiFetch(request)
|
|
|
|
const [, init] = fetchMock.mock.calls[0]
|
|
const headers = new Headers(init?.headers)
|
|
expect(headers.get('Authorization')).toBe('Bearer proposal-access-token')
|
|
expect(headers.get('Content-Type')).toBe('application/json')
|
|
expect(headers.get('Idempotency-Key')).toBe('agent-proposal-create:test')
|
|
expect(headers.get('X-Correlation-ID')).toBe('d4f72d7d-6331-4bcc-87e8-e74480315073')
|
|
})
|
|
})
|