Files
makelore/tests/unit/agent-profile.test.ts
inman 22add3f01f
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled
完善客户端模块与工作区能力
2026-08-13 19:51:02 +08:00

186 lines
5.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
AgentProfileApiError,
deleteAgentAvatar,
getAgentProfile,
getAgentProfileApiErrorMessage,
putAgentProfile,
uploadAgentAvatar,
} from '@/lib/agent-profile';
const hostApiFetchMock = vi.hoisted(() => vi.fn());
vi.mock('@/lib/host-api', () => ({
hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
}));
const profile = {
display_name: '小泥',
age: 18,
gender: 'female' as const,
share_age_with_agents: true,
share_gender_with_agents: true,
analysis_enabled: true,
completed: true,
version: 2,
updated_at: '2026-07-12T00:00:00Z',
};
describe('Agent Profile API client', () => {
beforeEach(() => {
hostApiFetchMock.mockReset();
});
it('loads the current profile through the authenticated Host API proxy', async () => {
hostApiFetchMock.mockResolvedValue({ success: true, profile });
await expect(getAgentProfile('access-token')).resolves.toEqual(profile);
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/user/agent-profile', {
headers: { 'X-NianCode-Access-Token': 'access-token' },
});
});
it('sends a complete versioned PUT payload', async () => {
hostApiFetchMock.mockResolvedValue({ success: true, profile });
const update = {
display_name: '小泥',
age: 18,
gender: 'female' as const,
share_age_with_agents: true,
share_gender_with_agents: true,
analysis_enabled: true,
version: 1,
};
await expect(putAgentProfile('access-token', update)).resolves.toEqual(profile);
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/user/agent-profile', {
method: 'PUT',
body: JSON.stringify(update),
headers: { 'X-NianCode-Access-Token': 'access-token' },
});
});
it('sends a completed local session snapshot alongside the profile', async () => {
hostApiFetchMock.mockResolvedValue({ success: true, profile });
const sessionData = {
project_id: 'prj_1',
session_id: 'ses_1',
updated_at: '2026-08-13T10:00:00.000Z',
messages: [{
id: 'msg_1',
role: 'user' as const,
created_at: '2026-08-13T09:59:00.000Z',
text: '请帮我解释这个问题。',
}],
};
await expect(putAgentProfile('access-token', {
display_name: '小泥',
age: 18,
gender: 'female',
share_age_with_agents: true,
share_gender_with_agents: true,
analysis_enabled: true,
version: 2,
session_data: sessionData,
})).resolves.toEqual(profile);
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/user/agent-profile', {
method: 'PUT',
body: JSON.stringify({
display_name: '小泥',
age: 18,
gender: 'female',
share_age_with_agents: true,
share_gender_with_agents: true,
analysis_enabled: true,
version: 2,
session_data: sessionData,
}),
headers: { 'X-NianCode-Access-Token': 'access-token' },
});
});
it('uploads a prepared avatar through the authenticated Host API proxy', async () => {
hostApiFetchMock.mockResolvedValue({ success: true, avatar_url: 'https://cdn.example/avatar.webp' });
await expect(uploadAgentAvatar('access-token', {
fileName: 'avatar.webp',
mimeType: 'image/webp',
dataBase64: 'QUJD',
})).resolves.toBe('https://cdn.example/avatar.webp');
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/user/avatar', {
method: 'POST',
body: JSON.stringify({
fileName: 'avatar.webp',
mimeType: 'image/webp',
dataBase64: 'QUJD',
}),
headers: { 'X-NianCode-Access-Token': 'access-token' },
});
});
it('deletes the current avatar through the authenticated Host API proxy', async () => {
hostApiFetchMock.mockResolvedValue({ success: true, avatar_url: null });
await expect(deleteAgentAvatar('access-token')).resolves.toBeNull();
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/user/avatar', {
method: 'DELETE',
headers: { 'X-NianCode-Access-Token': 'access-token' },
});
});
it('retains upstream conflict detail for the store to handle', async () => {
hostApiFetchMock.mockResolvedValue({
success: false,
status: 409,
error: 'Profile version conflict',
detail: { code: 'agent_profile_version_conflict', current_version: 4 },
});
await expect(getAgentProfile('access-token')).rejects.toMatchObject<Partial<AgentProfileApiError>>({
name: 'AgentProfileApiError',
status: 409,
detail: { code: 'agent_profile_version_conflict', current_version: 4 },
code: 'agent_profile_version_conflict',
});
});
it('retains avatar validation error codes from the Host API proxy', async () => {
hostApiFetchMock.mockResolvedValue({
success: false,
status: 415,
code: 'avatar_unsupported_type',
error: 'Unsupported avatar image type',
});
await expect(deleteAgentAvatar('access-token')).rejects.toMatchObject<Partial<AgentProfileApiError>>({
status: 415,
code: 'avatar_unsupported_type',
});
});
it('keeps a structured service error visible instead of replacing it with generic 503 text', async () => {
hostApiFetchMock.mockResolvedValue({
success: false,
status: 503,
error: 'Agent Profile database migration is pending',
detail: {
code: 'agent_profile_storage_unavailable',
message: 'Agent Profile database migration is pending',
},
code: 'agent_profile_storage_unavailable',
});
const error = await getAgentProfile('access-token').catch((value) => value as AgentProfileApiError);
expect(error).toMatchObject({
status: 503,
code: 'agent_profile_storage_unavailable',
message: 'Agent Profile database migration is pending',
});
expect(getAgentProfileApiErrorMessage(error)).toBe('Agent Profile database migration is pending');
});
});