import { EventEmitter } from 'node:events'; import type { IncomingMessage, ServerResponse } from 'http'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { handleUserSyncRoutes } from '@electron/api/routes/user-sync'; const fetchMock = vi.hoisted(() => vi.fn()); const providerServiceMock = vi.hoisted(() => ({ upsertSyncedAccountMetadata: vi.fn(), })); vi.mock('@electron/utils/proxy-fetch', () => ({ proxyAwareFetch: (...args: unknown[]) => fetchMock(...args), })); vi.mock('@electron/services/providers/provider-service', () => ({ getProviderService: () => providerServiceMock, })); function createResponse() { const chunks: string[] = []; const res = { statusCode: 0, setHeader: vi.fn(), end: vi.fn((chunk?: string) => { if (chunk) chunks.push(chunk); }), } as unknown as ServerResponse; return { res, get statusCode() { return res.statusCode; }, json: () => JSON.parse(chunks.join('')) as unknown, }; } function createRequest( method: string, body?: unknown, headers: Record = {}, ): IncomingMessage { const req = new EventEmitter(); Object.assign(req, { method, headers: body === undefined ? headers : { ...headers, 'content-type': 'application/json' }, [Symbol.asyncIterator]: async function* () { if (body !== undefined) { yield Buffer.from(JSON.stringify(body)); } }, }); return req as IncomingMessage; } describe('user sync host api routes', () => { beforeEach(() => { vi.clearAllMocks(); vi.restoreAllMocks(); providerServiceMock.upsertSyncedAccountMetadata.mockResolvedValue(undefined); }); it('requires a current user access token', async () => { const response = createResponse(); const handled = await handleUserSyncRoutes( createRequest('GET'), response.res, new URL('http://127.0.0.1/api/user-sync/bootstrap'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(400); expect(response.json()).toEqual({ success: false, error: 'Missing x-niancode-access-token', }); expect(fetchMock).not.toHaveBeenCalled(); }); it('fetches bootstrap, applies provider metadata, and strips secrets from the response', async () => { fetchMock.mockResolvedValueOnce(new Response(JSON.stringify({ preferences: { theme: 'dark', sidebar_collapsed: true, }, provider_accounts: [{ id: 'openai-account-1', vendor_id: 'openai', label: 'OpenAI', auth_mode: 'api_key', model: 'gpt-4.1-mini', fallback_models: ['gpt-4o-mini'], enabled: true, is_default: true, api_key: 'sk-should-not-return', refresh_token: 'refresh-should-not-return', headers: { Authorization: 'Bearer secret' }, updated_at: '2026-07-04T00:00:00.000Z', }], partners: [], usage_aggregates: [], }), { status: 200 })); const response = createResponse(); const handled = await handleUserSyncRoutes( createRequest('GET', undefined, { 'x-niancode-access-token': 'access-token' }), response.res, new URL('http://127.0.0.1/api/user-sync/bootstrap'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, bootstrap: { preferences: { theme: 'dark', sidebarCollapsed: true, }, providerAccounts: [{ id: 'openai-account-1', vendorId: 'openai', label: 'OpenAI', authMode: 'api_key', model: 'gpt-4.1-mini', fallbackModels: ['gpt-4o-mini'], enabled: true, isDefault: true, metadata: {}, updatedAt: '2026-07-04T00:00:00.000Z', }], partners: [], usageAggregates: [], }, applied: { providerAccounts: 1 }, }); expect(JSON.stringify(response.json())).not.toContain('sk-should-not-return'); expect(JSON.stringify(response.json())).not.toContain('refresh-should-not-return'); expect(JSON.stringify(response.json())).not.toContain('Authorization'); expect(fetchMock).toHaveBeenCalledWith('https://square.nianxx.cn/api/me/bootstrap', { method: 'GET', headers: { Authorization: 'Bearer access-token' }, }); expect(providerServiceMock.upsertSyncedAccountMetadata).toHaveBeenCalledWith({ id: 'openai-account-1', vendorId: 'openai', label: 'OpenAI', authMode: 'api_key', model: 'gpt-4.1-mini', fallbackModels: ['gpt-4o-mini'], enabled: true, isDefault: true, metadata: {}, updatedAt: '2026-07-04T00:00:00.000Z', }); }); it('proxies preferences as sanitized server payloads', async () => { fetchMock.mockResolvedValueOnce(new Response(JSON.stringify({ preferences: { theme: 'dark' }, provider_accounts: [], partners: [], usage_aggregates: [], }), { status: 200 })); const response = createResponse(); const handled = await handleUserSyncRoutes( createRequest('PUT', { theme: 'dark', language: 'zh-CN', launchAtStartup: true, activeProjectId: 'prj_1', }, { 'x-niancode-access-token': 'access-token' }), response.res, new URL('http://127.0.0.1/api/user-sync/preferences'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(fetchMock).toHaveBeenCalledWith('https://square.nianxx.cn/api/me/preferences', { method: 'PUT', headers: { Authorization: 'Bearer access-token', 'Content-Type': 'application/json', }, body: JSON.stringify({ theme: 'dark', language: 'zh-CN' }), }); }); it('proxies partners as sanitized server payloads', async () => { fetchMock.mockResolvedValueOnce(new Response(JSON.stringify({ preferences: {}, provider_accounts: [], partners: [], usage_aggregates: [], }), { status: 200 })); const response = createResponse(); const handled = await handleUserSyncRoutes( createRequest('POST', { id: 'makelore-pm-test', displayName: 'PM', templateRoleId: 'custom', skillIds: ['pm-project-plan'], filePath: 'D:/repo/.opencode/agent/makelore-pm-test.md', projectRuntimeConfigsByProjectId: { prj_1: { filePath: 'D:/repo/file.md' } }, createdAt: '2026-07-04T00:00:00.000Z', updatedAt: '2026-07-04T00:00:00.000Z', }, { 'x-niancode-access-token': 'access-token' }), response.res, new URL('http://127.0.0.1/api/user-sync/partners'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(fetchMock).toHaveBeenCalledWith('https://square.nianxx.cn/api/me/partners', { method: 'POST', headers: { Authorization: 'Bearer access-token', 'Content-Type': 'application/json', }, body: JSON.stringify({ id: 'makelore-pm-test', display_name: 'PM', template_role_id: 'custom', skill_ids: ['pm-project-plan'], created_at: '2026-07-04T00:00:00.000Z', updated_at: '2026-07-04T00:00:00.000Z', }), }); }); });