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