import { EventEmitter } from 'node:events'; import type { IncomingMessage, ServerResponse } from 'http'; import { shell } from 'electron'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { handleAuthRoutes } from '@electron/api/routes/auth'; import { clearWorksSquareSession, getWorksSquareSessionSnapshot, } from '@electron/services/works-square-session'; const providerServiceMock = vi.hoisted(() => ({ deleteAccountApiKey: vi.fn(), })); 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): IncomingMessage { const req = new EventEmitter(); Object.assign(req, { method, headers: body === undefined ? {} : { 'content-type': 'application/json' }, [Symbol.asyncIterator]: async function* () { if (body !== undefined) { yield Buffer.from(JSON.stringify(body)); } }, }); return req as IncomingMessage; } describe('auth host api routes', () => { beforeEach(() => { vi.restoreAllMocks(); clearWorksSquareSession(); providerServiceMock.deleteAccountApiKey.mockReset(); providerServiceMock.deleteAccountApiKey.mockResolvedValue(true); }); it('exchanges username and AES-encrypted password through the app SSO token endpoint', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ access_token: 'access-token', refresh_token: 'refresh-token', token_type: 'Bearer', expires_in: 43200, username: 'zhangsan', user_id: '1', client_id: 'app', }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleAuthRoutes( createRequest('POST', { username: 'zhangsan', password: 'passw0rd', code: 'a7k9', randomStr: '333e6825-760c-4c1a-8b56-eb9539b43dbd', scope: 'server', }), response.res, new URL('http://127.0.0.1:13210/api/auth/login'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, token: { access_token: 'access-token', refresh_token: 'refresh-token', token_type: 'Bearer', expires_in: 43200, username: 'zhangsan', user_id: '1', client_id: 'app', }, }); expect(fetchMock).toHaveBeenCalledOnce(); const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]; expect(url).toBe('https://biz.nianxx.cn/auth/oauth2/token'); expect(init.method).toBe('POST'); expect(init.headers).toEqual({ Authorization: `Basic ${Buffer.from('app:app').toString('base64')}`, 'Content-Type': 'application/x-www-form-urlencoded', }); expect(String(init.body)).toBe( 'grant_type=password&username=zhangsan&password=ILsdQ7Wp2P8%3D&scope=server&code=a7k9&randomStr=333e6825-760c-4c1a-8b56-eb9539b43dbd', ); expect(String(init.body)).not.toContain('passw0rd'); }); it('passes through SSO credential errors without exposing secrets', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ code: 1, msg: 'Bad credentials', data: null }), { status: 401 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleAuthRoutes( createRequest('POST', { username: 'zhangsan', password: 'wrong', }), response.res, new URL('http://127.0.0.1:13210/api/auth/login'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(401); expect(response.json()).toEqual({ success: false, error: 'Bad credentials', }); expect(JSON.stringify(response.json())).not.toContain('app:app'); }); it('opens Works Square browser authorization and returns the approved desktop token', async () => { const fetchMock = vi.fn() .mockResolvedValueOnce( new Response(JSON.stringify({ request_id: 'desktop-request-id', device_secret: 'desktop-device-secret', authorize_url: 'https://square.nianxx.cn/#desktop-auth?request_id=desktop-request-id', poll_interval_seconds: 0, }), { status: 200 }), ) .mockResolvedValueOnce( new Response(JSON.stringify({ status: 'pending' }), { status: 200 }), ) .mockResolvedValueOnce( new Response(JSON.stringify({ status: 'approved', token: { access_token: 'desktop-access-token', refresh_token: 'desktop-refresh-token', token_type: 'Bearer', expires_in: 43200, username: 'student', user_id: '42', }, }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleAuthRoutes( createRequest('POST'), response.res, new URL('http://127.0.0.1:13210/api/auth/browser/start'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, token: { access_token: 'desktop-access-token', refresh_token: 'desktop-refresh-token', token_type: 'Bearer', expires_in: 43200, username: 'student', user_id: '42', }, }); expect(shell.openExternal).toHaveBeenCalledWith( 'https://square.nianxx.cn/#desktop-auth?request_id=desktop-request-id', ); expect(fetchMock.mock.calls.map(([url]) => String(url))).toEqual([ 'https://square.nianxx.cn/api/auth/desktop/start', 'https://square.nianxx.cn/api/auth/desktop/token?request_id=desktop-request-id&device_secret=desktop-device-secret', 'https://square.nianxx.cn/api/auth/desktop/token?request_id=desktop-request-id&device_secret=desktop-device-secret', ]); }); it('stores the approved browser token bundle in the Main Works Square session cache', async () => { vi.setSystemTime(new Date('2026-07-06T08:00:00.000Z')); const fetchMock = vi.fn() .mockResolvedValueOnce( new Response(JSON.stringify({ request_id: 'desktop-request-id', device_secret: 'desktop-device-secret', authorize_url: 'https://square.nianxx.cn/#desktop-auth?request_id=desktop-request-id', poll_interval_seconds: 0, }), { status: 200 }), ) .mockResolvedValueOnce( new Response(JSON.stringify({ status: 'approved', token: { access_token: 'desktop-access-token', refresh_token: 'desktop-refresh-token', token_type: 'Bearer', expires_in: 43200, }, }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); await handleAuthRoutes( createRequest('POST'), response.res, new URL('http://127.0.0.1:13210/api/auth/browser/start'), {} as never, ); expect(response.statusCode).toBe(200); expect(getWorksSquareSessionSnapshot()).toMatchObject({ accessToken: 'desktop-access-token', refreshToken: 'desktop-refresh-token', tokenType: 'Bearer', expiresAt: Date.now() + 43_200_000, }); }); it('accepts renderer session sync after app restart', async () => { const response = createResponse(); const handled = await handleAuthRoutes( createRequest('POST', { accessToken: 'persisted-access-token', refreshToken: 'persisted-refresh-token', tokenType: 'Bearer', expiresAt: 1_783_000_000_000, }), response.res, new URL('http://127.0.0.1:13210/api/auth/session/sync'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true }); expect(getWorksSquareSessionSnapshot()).toEqual({ accessToken: 'persisted-access-token', refreshToken: 'persisted-refresh-token', tokenType: 'Bearer', expiresAt: 1_783_000_000_000, }); }); it('stops the runtime and clears the managed Works Square key on logout', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ success: true }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const stop = vi.fn(async () => undefined); const closeEventSessions = vi.fn(async () => undefined); const response = createResponse(); const handled = await handleAuthRoutes( createRequest('POST', { accessToken: 'access-token' }), response.res, new URL('http://127.0.0.1:13210/api/auth/logout'), { opencodeManager: { stop }, imageWorkspace: { closeEventSessions }, } as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true }); expect(stop).toHaveBeenCalledOnce(); expect(closeEventSessions).toHaveBeenCalledOnce(); expect(providerServiceMock.deleteAccountApiKey).toHaveBeenCalledWith('niancode-user-models'); }); it('does not report logout success when managed runtime cleanup fails', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ success: true }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const stop = vi.fn().mockRejectedValue(new Error('runtime stop failed')); const response = createResponse(); await handleAuthRoutes( createRequest('POST', { accessToken: 'access-token' }), response.res, new URL('http://127.0.0.1:13210/api/auth/logout'), { opencodeManager: { stop }, } as never, ); expect(response.statusCode).toBe(500); expect(response.json()).toEqual({ success: false, error: 'Failed to clear local AI runtime state', }); expect(providerServiceMock.deleteAccountApiKey).toHaveBeenCalledWith('niancode-user-models'); }); });