import { screen, waitFor } from '@testing-library/react' import * as React from 'react' import { renderToString } from 'react-dom/server' import WebAppStoreProvider, { useWebAppStore } from '@/context/web-app-context' import { AccessMode } from '@/models/access-control' import { createConsoleQueryWrapper, renderWithConsoleQuery } from '@/test/console/query-data' const navigationMocks = vi.hoisted(() => ({ usePathname: vi.fn(() => '/chatbot/sample-app'), useSearchParams: vi.fn(() => new URLSearchParams()), })) const useGetWebAppAccessModeByCodeMock = vi.hoisted(() => vi.fn()) vi.mock('@/next/navigation', () => ({ usePathname: navigationMocks.usePathname, useSearchParams: navigationMocks.useSearchParams, })) vi.mock('@/service/use-share', () => ({ useGetWebAppAccessModeByCode: (...args: unknown[]) => useGetWebAppAccessModeByCodeMock(...args), })) const mockGetProcessedSystemVariablesFromUrlParams = vi.fn() vi.mock('@/app/components/base/chat/utils', () => ({ getProcessedSystemVariablesFromUrlParams: (...args: any[]) => mockGetProcessedSystemVariablesFromUrlParams(...args), })) const TestConsumer = () => { const embeddedUserId = useWebAppStore((state) => state.embeddedUserId) const embeddedConversationId = useWebAppStore((state) => state.embeddedConversationId) return ( <>
{embeddedUserId ?? 'null'}
{embeddedConversationId ?? 'null'}
) } const initialWebAppStore = (() => { const snapshot = useWebAppStore.getState() return { shareCode: null as string | null, appInfo: null, appParams: null, webAppAccessMode: snapshot.webAppAccessMode, appMeta: null, userCanAccessApp: false, embeddedUserId: null, embeddedConversationId: null, updateShareCode: snapshot.updateShareCode, updateAppInfo: snapshot.updateAppInfo, updateAppParams: snapshot.updateAppParams, updateWebAppAccessMode: snapshot.updateWebAppAccessMode, updateWebAppMeta: snapshot.updateWebAppMeta, updateUserCanAccessApp: snapshot.updateUserCanAccessApp, updateEmbeddedUserId: snapshot.updateEmbeddedUserId, updateEmbeddedConversationId: snapshot.updateEmbeddedConversationId, } })() beforeEach(() => { mockGetProcessedSystemVariablesFromUrlParams.mockReset() navigationMocks.usePathname.mockReset() navigationMocks.usePathname.mockReturnValue('/chatbot/sample-app') navigationMocks.useSearchParams.mockReset() navigationMocks.useSearchParams.mockReturnValue(new URLSearchParams()) useGetWebAppAccessModeByCodeMock.mockReset() useGetWebAppAccessModeByCodeMock.mockReturnValue({ isLoading: false, data: { accessMode: AccessMode.PUBLIC }, }) useWebAppStore.setState(initialWebAppStore, true) }) describe('WebAppStoreProvider embedded user id handling', () => { it('parses share code from redirect_url during server render without window', () => { const params = new URLSearchParams() params.set('redirect_url', encodeURIComponent('/chatbot/redirected-app')) navigationMocks.usePathname.mockReturnValue('/webapp-signin') navigationMocks.useSearchParams.mockReturnValue(params) const originalWindow = globalThis.window Object.defineProperty(globalThis, 'window', { configurable: true, value: undefined, }) const { wrapper: Wrapper } = createConsoleQueryWrapper() try { expect(() => renderToString(
, ), ).not.toThrow() } finally { Object.defineProperty(globalThis, 'window', { configurable: true, value: originalWindow, }) } expect(useGetWebAppAccessModeByCodeMock).toHaveBeenCalledWith('redirected-app') }) it('does not derive a share code from an external redirect target', () => { const params = new URLSearchParams() params.set('redirect_url', 'https://evil.example/chatbot/evil-app') navigationMocks.usePathname.mockReturnValue('/webapp-signin') navigationMocks.useSearchParams.mockReturnValue(params) mockGetProcessedSystemVariablesFromUrlParams.mockResolvedValue({}) renderWithConsoleQuery( , ) expect(useGetWebAppAccessModeByCodeMock).toHaveBeenCalledWith(null) }) it.each(['/webapp-signin/check-code', '/console/webapp-signin/check-code'])( 'does not derive a share code from the sign-in route %s', (pathname) => { navigationMocks.usePathname.mockReturnValue(pathname) mockGetProcessedSystemVariablesFromUrlParams.mockResolvedValue({}) renderWithConsoleQuery( , ) expect(useGetWebAppAccessModeByCodeMock).toHaveBeenCalledWith(null) }, ) it('hydrates embedded user and conversation ids from system variables', async () => { mockGetProcessedSystemVariablesFromUrlParams.mockResolvedValue({ user_id: 'iframe-user-123', conversation_id: 'conversation-456', }) renderWithConsoleQuery( , ) await waitFor(() => { expect(screen.getByTestId('embedded-user-id')).toHaveTextContent('iframe-user-123') expect(screen.getByTestId('embedded-conversation-id')).toHaveTextContent('conversation-456') }) expect(useWebAppStore.getState().embeddedUserId).toBe('iframe-user-123') expect(useWebAppStore.getState().embeddedConversationId).toBe('conversation-456') }) it('clears embedded user id when system variable is absent', async () => { useWebAppStore.setState((state) => ({ ...state, embeddedUserId: 'previous-user', embeddedConversationId: 'existing-conversation', })) mockGetProcessedSystemVariablesFromUrlParams.mockResolvedValue({}) renderWithConsoleQuery( , ) await waitFor(() => { expect(screen.getByTestId('embedded-user-id')).toHaveTextContent('null') expect(screen.getByTestId('embedded-conversation-id')).toHaveTextContent('null') }) expect(useWebAppStore.getState().embeddedUserId).toBeNull() expect(useWebAppStore.getState().embeddedConversationId).toBeNull() }) })