Ctrl K
ComunidadGuías…

src/features/account/queries.test.tsx

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

Archivo original ↗Preguntar a KiBot
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { renderHook, waitFor } from '@testing-library/react'
import type { ReactNode } from 'react'
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
import { base, createAccountServer, createAccountStore, http, json } from './account.test-utils'
import {
  useAccountAgents,
  useAccountApplicationAuthorizations,
  useAccountConfig,
  useAccountMutation,
  useAccountOrganizationTeamMembers,
  useAccountOrganizationTeams,
  useAccountPasskeys,
  useAccountProfile,
  useAccountSecurity,
  useAccountSessions,
  useLinkedAccounts,
} from './queries'

const success = vi.fn()
const error = vi.fn()
vi.mock('sonner', () => ({
  toast: {
    success: (...args: unknown[]) => success(...args),
    error: (...args: unknown[]) => error(...args),
  },
}))

const store = createAccountStore()
const server = createAccountServer(store)

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => {
  server.resetHandlers()
  success.mockClear()
  error.mockClear()
})
afterAll(() => server.close())

function wrapper(client: QueryClient) {
  return function Wrapper({ children }: { children: ReactNode }) {
    return <QueryClientProvider client={client}>{children}</QueryClientProvider>
  }
}

function newClient() {
  return new QueryClient({ defaultOptions: { queries: { retry: false } } })
}

describe('account query hooks', () => {
  it('loads config and profile data', async () => {
    const client = newClient()
    const config = renderHook(() => useAccountConfig(), { wrapper: wrapper(client) })
    const profile = renderHook(() => useAccountProfile(), { wrapper: wrapper(client) })
    await waitFor(() => expect(config.result.current.isSuccess).toBe(true))
    await waitFor(() => expect(profile.result.current.isSuccess).toBe(true))
    expect(config.result.current.data?.copy.productName).toBe('Realmroot')
    expect(profile.result.current.data?.user.email).toBe('jane@example.com')
  })

  it('loads security and passkeys', async () => {
    const client = newClient()
    const security = renderHook(() => useAccountSecurity(), { wrapper: wrapper(client) })
    const passkeys = renderHook(() => useAccountPasskeys(), { wrapper: wrapper(client) })
    await waitFor(() => expect(security.result.current.isSuccess).toBe(true))
    await waitFor(() => expect(passkeys.result.current.isSuccess).toBe(true))
    expect(security.result.current.data?.security.passkeys.enabled).toBe(true)
    expect(passkeys.result.current.data?.passkeys).toEqual([])
  })

  it('respects the enabled flag for gated queries', async () => {
    const client = newClient()
    const disabled = renderHook(() => useAccountSessions(false), { wrapper: wrapper(client) })
    expect(disabled.result.current.fetchStatus).toBe('idle')

    const enabled = renderHook(() => useAccountSessions(true), { wrapper: wrapper(client) })
    await waitFor(() => expect(enabled.result.current.isSuccess).toBe(true))

    const linked = renderHook(() => useLinkedAccounts(true), { wrapper: wrapper(client) })
    const apps = renderHook(() => useAccountApplicationAuthorizations(true), { wrapper: wrapper(client) })
    const agents = renderHook(() => useAccountAgents(), { wrapper: wrapper(client) })
    await waitFor(() => expect(linked.result.current.isSuccess).toBe(true))
    await waitFor(() => expect(apps.result.current.isSuccess).toBe(true))
    await waitFor(() => expect(agents.result.current.isSuccess).toBe(true))
  })

  it('loads one requested Team member page and gates the query until a Team is selected', async () => {
    const requestedPages: number[] = []
    server.use(
      http.get(`${base}/api/auth/organization/list-teams`, () =>
        json([{ id: 'team-1', name: 'platform-admins', organizationId: 'org-1' }]),
      ),
      http.get(`${base}/api/account/organizations/org-1/teams/team-1/members`, ({ request }) => {
        const page = Number(new URL(request.url).searchParams.get('page'))
        requestedPages.push(page)
        const offset = (page - 1) * 20
        const items = Array.from({ length: page === 1 ? 20 : 1 }, (_, index) => ({
          id: `membership-${offset + index + 1}`,
          teamId: 'team-1',
          userId: `user-${offset + index + 1}`,
          createdAt: '2026-08-01T00:00:00Z',
        }))
        return json({
          items,
          pagination: {
            page,
            pageSize: 20,
            totalItems: 21,
            totalPages: 2,
          },
        })
      }),
    )
    const client = newClient()
    const teams = renderHook(() => useAccountOrganizationTeams('org-1'), { wrapper: wrapper(client) })
    const disabledMembers = renderHook(() => useAccountOrganizationTeamMembers('org-1', null), {
      wrapper: wrapper(client),
    })
    expect(disabledMembers.result.current.fetchStatus).toBe('idle')
    await waitFor(() => expect(teams.result.current.data?.[0]?.name).toBe('platform-admins'))

    const members = renderHook(
      ({ page }) => useAccountOrganizationTeamMembers('org-1', 'team-1', { page, pageSize: 20 }),
      {
        initialProps: { page: 1 },
        wrapper: wrapper(client),
      },
    )
    await waitFor(() => expect(members.result.current.data?.items).toHaveLength(20))
    expect(requestedPages).toEqual([1])

    members.rerender({ page: 2 })
    await waitFor(() => expect(members.result.current.data?.pagination.page).toBe(2))
    expect(members.result.current.data?.items).toHaveLength(1)
    expect(members.result.current.data?.items[0]?.userId).toBe('user-21')
    expect(requestedPages).toEqual([1, 2])
  })

  it('rejects an invalid Team member page at the client boundary', async () => {
    server.use(http.get(`${base}/api/account/organizations/org-1/teams/team-1/members`, () => json({ items: [] })))
    const members = renderHook(() => useAccountOrganizationTeamMembers('org-1', 'team-1', { page: 1, pageSize: 20 }), {
      wrapper: wrapper(newClient()),
    })

    await waitFor(() => expect(members.result.current.isError).toBe(true))
  })
})

describe('useAccountMutation', () => {
  it('reports success, invalidates queries, and returns the result', async () => {
    const client = newClient()
    const invalidateSpy = vi.spyOn(client, 'invalidateQueries')
    const { result } = renderHook(() => useAccountMutation(), { wrapper: wrapper(client) })

    const value = await result.current('Saved.', async () => ({ ok: true }), {
      invalidate: [['account', 'profile']],
    })

    expect(value).toEqual({ ok: true })
    expect(success).toHaveBeenCalledWith('Saved.')
    expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['account', 'profile'] })
  })

  it('can invalidate a collection without refetching active detail queries', async () => {
    const client = newClient()
    const invalidateSpy = vi.spyOn(client, 'invalidateQueries')
    const { result } = renderHook(() => useAccountMutation(), { wrapper: wrapper(client) })

    await result.current('Organization deleted.', async () => undefined, {
      invalidateExact: [['account', 'organizations']],
    })

    expect(invalidateSpy).toHaveBeenCalledWith({ exact: true, queryKey: ['account', 'organizations'] })
  })

  it('reports an error message and calls the onError callback', async () => {
    const client = newClient()
    const onError = vi.fn()
    const { result } = renderHook(() => useAccountMutation(), { wrapper: wrapper(client) })

    const value = await result.current(
      'Saved.',
      async () => {
        throw new Error('Specific failure.')
      },
      { onError },
    )

    expect(value).toBeUndefined()
    expect(onError).toHaveBeenCalledWith('Specific failure.')
    expect(error).toHaveBeenCalledWith('Specific failure.')
    expect(success).not.toHaveBeenCalled()
  })

  it('falls back to a generic message for non-Error throws', async () => {
    const client = newClient()
    const { result } = renderHook(() => useAccountMutation(), { wrapper: wrapper(client) })

    const value = await result.current('Saved.', async () => {
      throw 'string failure'
    })

    expect(value).toBeUndefined()
    expect(error).toHaveBeenCalledWith('Account update failed.')
  })
})