Ctrl K
ComunidadGuías…

tests/cli-login-page.unit.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// @vitest-environment happy-dom

import type { App } from 'vue'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createApp, h, nextTick } from 'vue'
import { createI18n } from 'vue-i18n'
import LoginCliPage from '../src/pages/login-cli.vue'

const route = vi.hoisted(() => ({ query: { ai: '1' } as Record<string, string> }))
const router = vi.hoisted(() => ({ push: vi.fn() }))
const clipboardWrite = vi.hoisted(() => vi.fn())
const cliLoginMocks = vi.hoisted(() => ({
  createCliLoginKeyDependencies: vi.fn(() => ({})),
  getCliLoginDestination: vi.fn(() => '/dashboard'),
  isCliAiQuery: vi.fn((value: unknown) => {
    const raw = Array.isArray(value) ? value[0] : value
    return typeof raw === 'string' && raw.replace(/\/+$/, '') === '1'
  }),
  isMatchingCliLoginEvent: vi.fn(() => false),
  isValidCliLoginSession: vi.fn(() => true),
  prepareCliLoginKey: vi.fn(),
}))
const organizationApps = vi.hoisted(() => new Map([
  ['org-1', [{ app_id: 'com.test.app', name: 'Test App', owner_org: 'org-1', onboarding: { setup: { todo_list_version: 2 } } }]],
]))
const organizationStore = vi.hoisted(() => ({
  awaitInitialLoad: vi.fn(async () => {}),
  organizations: [{ gid: 'org-1', name: 'Test organization', app_count: 1 }],
  getAppsByOrgId: vi.fn((orgId: string) => organizationApps.get(orgId) ?? []),
}))
const capgo = vi.hoisted(() => ({
  channel: vi.fn(),
  from: vi.fn(),
  removeChannel: vi.fn(),
}))

vi.mock('vue-router', () => ({
  useRoute: () => route,
  useRouter: () => router,
}))

vi.mock('vue-sonner', () => ({
  toast: { error: vi.fn(), success: vi.fn() },
}))

vi.mock('~/services/cliLogin', () => cliLoginMocks)

vi.mock('~/services/console', () => ({
  useConsole: () => capgo,
}))

vi.mock('~/stores/main', () => ({
  useMainStore: () => ({ user: { id: 'user-1' } }),
}))

vi.mock('~/stores/organization', () => ({
  isPendingOrganizationInvite: () => false,
  useOrganizationStore: () => organizationStore,
}))

const messages = JSON.parse(readFileSync(resolve(process.cwd(), 'messages/en.json'), 'utf8')) as Record<string, string>
const preparedKey = 'capgo_ai_setup_secret'
const mountedApps: App[] = []

function mountLoginCliPage() {
  const app = createApp({ render: () => h(LoginCliPage) })
  app.use(createI18n({
    legacy: false,
    locale: 'en',
    messages: { en: messages },
  }))
  const container = document.createElement('div')
  document.body.append(container)
  app.mount(container)
  mountedApps.push(app)
  return container
}

async function flushPromises() {
  await Promise.resolve()
  await Promise.resolve()
  await nextTick()
}

beforeEach(() => {
  vi.clearAllMocks()
  route.query = { ai: '1' }
  cliLoginMocks.prepareCliLoginKey.mockResolvedValue({
    status: 'ready',
    keyName: 'Capgo CLI',
    secret: preparedKey,
    eligibleOrgIds: ['org-1'],
    skippedOrganizations: [],
    policy: { hashed: false, expiresAt: null },
    reused: false,
  })
  Object.defineProperty(navigator, 'clipboard', {
    configurable: true,
    value: { writeText: clipboardWrite },
  })
})

afterEach(() => {
  mountedApps.splice(0).forEach(app => app.unmount())
  document.body.replaceChildren()
})

async function copyAiPrompt(container: HTMLElement) {
  const copyButton = Array.from(container.querySelectorAll<HTMLButtonElement>('button'))
    .find(button => button.textContent?.includes(messages['cli-login-ai-copy']))
  expect(copyButton).toBeDefined()
  copyButton?.click()
  await flushPromises()
  expect(clipboardWrite).toHaveBeenCalledOnce()
  return clipboardWrite.mock.calls[0]?.[0] as string
}

describe('/login-cli page', () => {
  it('does not prepare a key in direct mode without a valid CLI session', async () => {
    route.query = {}
    cliLoginMocks.isValidCliLoginSession.mockReturnValueOnce(false)
    mountLoginCliPage()
    await flushPromises()

    expect(cliLoginMocks.prepareCliLoginKey).not.toHaveBeenCalled()
  })

  it('keeps the prepared key out of the DOM until explicit reveal in direct mode', async () => {
    route.query = { session: 'AbCdEfGhIjKlMnOpQrStUv' }
    const channel = { on: vi.fn(() => channel), subscribe: vi.fn((callback: (status: string) => void) => {
      callback('SUBSCRIBED')
      return channel
    }) }
    capgo.channel.mockReturnValue(channel)
    const container = mountLoginCliPage()
    await flushPromises()
    await vi.waitFor(() => expect(container.textContent).toContain('capgo_xxxxxxxxxxxxxxxxxxxx'))

    expect(cliLoginMocks.prepareCliLoginKey).toHaveBeenCalledOnce()
    expect(container.innerHTML).not.toContain(preparedKey)
  })

  it('stops waiting for a silent event stream after the fallback delay', async () => {
    vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
    try {
      route.query = { session: 'AbCdEfGhIjKlMnOpQrStUv' }
      // The channel never reports a status, as when the events request hangs.
      const channel = { on: vi.fn(() => channel), subscribe: vi.fn(() => channel) }
      capgo.channel.mockReturnValue(channel)
      const container = mountLoginCliPage()
      await flushPromises()
      expect(container.textContent).not.toContain(messages['cli-login-realtime-unavailable'])

      await vi.advanceTimersByTimeAsync(15_000)
      await flushPromises()
      expect(container.textContent).toContain(messages['cli-login-realtime-unavailable'])
      expect(container.textContent).toContain('capgo_xxxxxxxxxxxxxxxxxxxx')
    }
    finally {
      vi.useRealTimers()
    }
  })

  it('copies a prompt containing the prepared key in AI mode', async () => {
    const container = mountLoginCliPage()
    await flushPromises()

    const copiedPrompt = await copyAiPrompt(container)
    expect(copiedPrompt).toContain(`login ${preparedKey}`)
    expect(copiedPrompt).toContain('Organization: "Test organization" (organization ID: `org-1`)')
    expect(copiedPrompt).toContain('App: "Test App" (Capgo app ID: `com.test.app`)')
    expect(copiedPrompt).toContain('## 8. Test the first live update')
    expect(copiedPrompt.match(new RegExp(preparedKey, 'g'))).toHaveLength(1)
  })

  it('copies the Builder prompt when requested by route intent', async () => {
    route.query = { ai: '1', intent: 'builder' }
    const container = mountLoginCliPage()
    await flushPromises()

    const copiedPrompt = await copyAiPrompt(container)
    expect(copiedPrompt).toContain(`login ${preparedKey}`)
    expect(copiedPrompt).toContain('start_capgo_builder_onboarding')
    expect(copiedPrompt).not.toContain('## 8. Test the first live update')
  })

  it.each([
    [3, true],
    [2, false],
  ])('copies OTA checklist checkpoints for explicit OTA intent and a v%s app: %s', async (todoListVersion, expectChecklist) => {
    route.query = { ai: '1', intent: 'ota' }
    organizationStore.getAppsByOrgId.mockReturnValueOnce([{
      app_id: 'com.test.app',
      name: 'Test App',
      owner_org: 'org-1',
      onboarding: { setup: { todo_list_version: todoListVersion } },
    }])
    const container = mountLoginCliPage()
    await flushPromises()

    const copiedPrompt = await copyAiPrompt(container)
    expect(copiedPrompt.includes('app todo {SELECTED_CAPGO_APP_ID}')).toBe(expectChecklist)
    expect(copiedPrompt.includes('OTA todo list progress checks')).toBe(expectChecklist)
  })
})