import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' import { afterEach, describe, expect, it, vi } from 'vitest' import { saveCustomEndpoint, validateCustomEndpoint } from '@/hermes' import { I18nProvider } from '@/i18n/context' import { AiturkProvider } from './aiturk-provider' vi.mock('@/hermes', () => ({ saveCustomEndpoint: vi.fn(), validateCustomEndpoint: vi.fn() })) vi.mock('./flow', () => ({ DocsLink: ({ children, href }: { children: React.ReactNode; href: string }) => {children} })) afterEach(() => { cleanup() vi.resetAllMocks() vi.useRealTimers() delete (window as { hermesDesktop?: unknown }).hermesDesktop }) function setup() { const onConnected = vi.fn().mockResolvedValue(undefined) render() const input = screen.getByLabelText('TurkServis API anahtarı') as HTMLInputElement fireEvent.change(input, { target: { value: 'test-key-never-real' } }) fireEvent.click(screen.getByRole('button', { name: 'Bağlan' })) return { input, onConnected } } describe('TurkServis provider setup', () => { it('discovers an authorized model before saving it as the default', async () => { vi.mocked(validateCustomEndpoint).mockResolvedValue({ ok: true, reachable: true, models: ['account/model-a', 'account/model-b'], message: '' }) vi.mocked(saveCustomEndpoint).mockResolvedValue({ id: 'turkservis', endpoints: [], current: { base_url: 'https://ai.turkservis.online/v1', model: 'account/model-a', provider: 'turkservis' }, ok: true }) const { input, onConnected } = setup() expect(input.type).toBe('password') await waitFor(() => expect(onConnected).toHaveBeenCalledOnce()) expect(saveCustomEndpoint).toHaveBeenCalledWith(expect.objectContaining({ base_url: 'https://ai.turkservis.online/v1', api_key: 'test-key-never-real', model: 'account/model-a', models: ['account/model-a', 'account/model-b'], make_default: true })) expect(input.value).toBe('') }) it('does not persist rejected credentials or claim a successful connection', async () => { vi.mocked(validateCustomEndpoint).mockResolvedValue({ ok: false, reachable: true, models: [], message: 'Anahtar geçersiz (401)' }) const { onConnected } = setup() await waitFor(() => expect(screen.getByRole('alert').textContent).toContain('401')) expect(saveCustomEndpoint).not.toHaveBeenCalled() expect(onConnected).not.toHaveBeenCalled() }) it('does not invent a model when the authenticated catalog is empty', async () => { vi.mocked(validateCustomEndpoint).mockResolvedValue({ ok: true, reachable: true, models: [], message: '' }) setup() await waitFor(() => expect(screen.getByRole('alert').textContent).toContain('model bulunamadı')) expect(saveCustomEndpoint).not.toHaveBeenCalled() }) }) const URI = 'https://turkservis.online/account/aiturk/device-login/0f8fad5b-d9cb-469f-a165-70867728950e' function setupLogin(polls: unknown[]) { vi.useFakeTimers() const bridge = { start: vi.fn().mockResolvedValue({ userCode: 'ABCD2345', verificationUri: URI, expiresAt: Date.now() + 600_000 }), poll: vi.fn(async () => polls.shift() ?? { state: 'pending' }), cancel: vi.fn().mockResolvedValue({ ok: true }), overwriteCli: vi.fn().mockResolvedValue({ cli: 'ok' }) } const openExternal = vi.fn().mockResolvedValue(undefined) ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = { aiturkLogin: bridge, openExternal } const onConnected = vi.fn().mockResolvedValue(undefined) render() return { bridge, onConnected, openExternal } } const click = async (name: string) => { await act(async () => { fireEvent.click(screen.getByRole('button', { name })) await Promise.resolve() }) } const tick = () => act(async () => { await vi.advanceTimersByTimeAsync(3000) }) describe('TurkServis one-click sign-in', () => { it('shows the code, lets the user reopen the page and polls every 3 seconds', async () => { const { bridge, onConnected, openExternal } = setupLogin([{ state: 'pending' }, { state: 'pending' }]) await click('Giriş yap') expect(bridge.start).toHaveBeenCalledOnce() expect(screen.getByLabelText('Giriş kodu').textContent).toBe('ABCD2345') expect(screen.getByText('Tarayıcıda açılan sayfadaki kod bununla aynıysa onaylayın.')).toBeTruthy() expect(bridge.poll).not.toHaveBeenCalled() await tick() expect(bridge.poll).toHaveBeenCalledTimes(1) await tick() expect(bridge.poll).toHaveBeenCalledTimes(2) await click('Sayfayı yeniden aç') expect(openExternal).toHaveBeenCalledWith(URI) expect(onConnected).not.toHaveBeenCalled() // No key ever reaches the renderer through this flow. expect(saveCustomEndpoint).not.toHaveBeenCalled() expect(validateCustomEndpoint).not.toHaveBeenCalled() }) it('calls onConnected once the main process reports the provider connected', async () => { const { bridge, onConnected } = setupLogin([{ state: 'pending' }, { state: 'connected', cli: 'ok' }]) await click('Giriş yap') await tick() expect(onConnected).not.toHaveBeenCalled() await tick() expect(onConnected).toHaveBeenCalledOnce() expect(screen.getByRole('status').textContent).toBe('AITURK CLI de bu hesapla hazır.') expect(screen.queryByLabelText('Giriş kodu')).toBeNull() await tick() expect(bridge.poll).toHaveBeenCalledTimes(2) }) it.each([ ['missing', ''], ['outdated', 'AITURK CLI güncellenince kendiliğinden bağlanması için yeniden giriş yapın.'], ['failed', 'AITURK CLI bağlanamadı; terminalde `aiturk login` çalıştırın.'], ['unknown-account', 'AITURK CLI bağlanamadı; terminalde `aiturk login` çalıştırın.'] ])('connects and explains CLI result %s', async (cli, text) => { const { onConnected } = setupLogin([{ state: 'connected', cli }]) await click('Giriş yap') await tick() expect(onConnected).toHaveBeenCalledOnce() expect(screen.queryByRole('status')?.textContent ?? '').toBe(text) }) it('asks before replacing another CLI account, then replaces on confirm', async () => { const { bridge, onConnected } = setupLogin([{ state: 'connected', cli: 'other-account' }]) await click('Giriş yap') await tick() expect(screen.getByText('AITURK CLI’de başka bir hesap kayıtlı. Bu hesapla değiştirilsin mi?')).toBeTruthy() expect(onConnected).not.toHaveBeenCalled() expect(bridge.overwriteCli).not.toHaveBeenCalled() await click('Değiştir') expect(bridge.overwriteCli).toHaveBeenCalledOnce() expect(onConnected).toHaveBeenCalledOnce() expect(screen.getByRole('status').textContent).toBe('AITURK CLI de bu hesapla hazır.') }) it('leaves the other CLI account alone when the user says so', async () => { const { bridge, onConnected } = setupLogin([{ state: 'connected', cli: 'other-account' }]) await click('Giriş yap') await tick() await click('Olduğu gibi bırak') expect(bridge.overwriteCli).not.toHaveBeenCalled() expect(bridge.cancel).toHaveBeenCalledOnce() expect(onConnected).toHaveBeenCalledOnce() }) it('cancels on Vazgeç and returns to the sign-in button', async () => { const { bridge, onConnected } = setupLogin([]) await click('Giriş yap') await click('Vazgeç') expect(bridge.cancel).toHaveBeenCalledOnce() expect(screen.getByRole('button', { name: 'Giriş yap' })).toBeTruthy() await tick() expect(bridge.poll).not.toHaveBeenCalled() expect(onConnected).not.toHaveBeenCalled() }) it('reports an expired code and a failed start without connecting', async () => { const { bridge, onConnected } = setupLogin([{ state: 'expired' }]) await click('Giriş yap') await tick() expect(screen.getByRole('alert').textContent).toBe('Giriş kodunun süresi doldu. Yeniden deneyin.') bridge.start.mockRejectedValueOnce( new Error("Error invoking remote method 'aiturk:login:start': AiturkLoginError: aiturk-login:rate-limited") ) await click('Giriş yap') expect(screen.getByRole('alert').textContent).toBe('Çok fazla deneme yapıldı. Biraz sonra yeniden deneyin.') expect(onConnected).not.toHaveBeenCalled() }) it('revokes a pending login when the panel goes away', async () => { const { bridge } = setupLogin([]) await click('Giriş yap') cleanup() expect(bridge.cancel).toHaveBeenCalledOnce() }) it('still connects with a pasted API key', async () => { vi.mocked(validateCustomEndpoint).mockResolvedValue({ ok: true, reachable: true, models: ['account/model-a'], message: '' }) vi.mocked(saveCustomEndpoint).mockResolvedValue({ id: 'turkservis', endpoints: [], current: { base_url: 'https://ai.turkservis.online/v1', model: 'account/model-a', provider: 'turkservis' }, ok: true }) const { bridge, onConnected } = setupLogin([]) vi.useRealTimers() expect(screen.getByText('ya da API anahtarı yapıştırın')).toBeTruthy() fireEvent.change(screen.getByLabelText('TurkServis API anahtarı'), { target: { value: 'test-key-never-real' } }) fireEvent.click(screen.getByRole('button', { name: 'Bağlan' })) await waitFor(() => expect(onConnected).toHaveBeenCalledOnce()) expect(saveCustomEndpoint).toHaveBeenCalledWith(expect.objectContaining({ api_key: 'test-key-never-real', model: 'account/model-a' })) expect(bridge.start).not.toHaveBeenCalled() }) })