dify/web/__tests__/embedded-user-id-store.test.tsx
Wu Tianwei 33edf97f81
feat: RBAC (#37107)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: fatelei <fatelei@gmail.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: 盐粒 Yanli <yanli@dify.ai>
Co-authored-by: Charles Yao <chongbinyao33@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: yunlu.wen <yunlu.wen@dify.ai>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
Co-authored-by: Jingyi <jingyi.qi@dify.ai>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: hjlarry <hjlarry@163.com>
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com>
Co-authored-by: gigglewang <gigglewang@dify.ai>
Co-authored-by: chariri <w@chariri.moe>
Co-authored-by: Evan <2869018789@qq.com>
Co-authored-by: zyssyz123 <916125788@qq.com>
2026-06-18 16:35:29 +00:00

152 lines
5.3 KiB
TypeScript

import { screen, waitFor } from '@testing-library/react'
import * as React from 'react'
import { renderToString } from 'react-dom/server'
import { createSystemFeaturesWrapper, renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
import WebAppStoreProvider, { useWebAppStore } from '@/context/web-app-context'
import { AccessMode } from '@/models/access-control'
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 (
<>
<div data-testid="embedded-user-id">{embeddedUserId ?? 'null'}</div>
<div data-testid="embedded-conversation-id">{embeddedConversationId ?? 'null'}</div>
</>
)
}
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 } = createSystemFeaturesWrapper()
try {
expect(() => renderToString(
<Wrapper>
<WebAppStoreProvider>
<div />
</WebAppStoreProvider>
</Wrapper>,
)).not.toThrow()
}
finally {
Object.defineProperty(globalThis, 'window', {
configurable: true,
value: originalWindow,
})
}
expect(useGetWebAppAccessModeByCodeMock).toHaveBeenCalledWith('redirected-app')
})
it('hydrates embedded user and conversation ids from system variables', async () => {
mockGetProcessedSystemVariablesFromUrlParams.mockResolvedValue({
user_id: 'iframe-user-123',
conversation_id: 'conversation-456',
})
renderWithSystemFeatures(
<WebAppStoreProvider>
<TestConsumer />
</WebAppStoreProvider>,
)
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({})
renderWithSystemFeatures(
<WebAppStoreProvider>
<TestConsumer />
</WebAppStoreProvider>,
)
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()
})
})