mirror of
https://github.com/langgenius/dify.git
synced 2026-04-14 07:56:31 +08:00
300 lines
8.1 KiB
TypeScript
300 lines
8.1 KiB
TypeScript
import { act, renderHook } from '@testing-library/react'
|
|
import { PARTNER_STACK_CONFIG } from '@/config'
|
|
import usePSInfo from '../use-ps-info'
|
|
|
|
let searchParamsValues: Record<string, string | null> = {}
|
|
const setSearchParams = (values: Record<string, string | null>) => {
|
|
searchParamsValues = values
|
|
}
|
|
|
|
type PartnerStackGlobal = typeof globalThis & {
|
|
__partnerStackCookieMocks?: {
|
|
get: ReturnType<typeof vi.fn>
|
|
set: ReturnType<typeof vi.fn>
|
|
remove: ReturnType<typeof vi.fn>
|
|
}
|
|
__partnerStackMutateAsync?: ReturnType<typeof vi.fn>
|
|
}
|
|
|
|
function getPartnerStackGlobal(): PartnerStackGlobal {
|
|
return globalThis as PartnerStackGlobal
|
|
}
|
|
|
|
const ensureCookieMocks = () => {
|
|
const globals = getPartnerStackGlobal()
|
|
if (!globals.__partnerStackCookieMocks)
|
|
throw new Error('Cookie mocks not initialized')
|
|
return globals.__partnerStackCookieMocks
|
|
}
|
|
|
|
const ensureMutateAsync = () => {
|
|
const globals = getPartnerStackGlobal()
|
|
if (!globals.__partnerStackMutateAsync)
|
|
throw new Error('Mutate mock not initialized')
|
|
return globals.__partnerStackMutateAsync
|
|
}
|
|
|
|
vi.mock('js-cookie', () => {
|
|
const get = vi.fn()
|
|
const set = vi.fn()
|
|
const remove = vi.fn()
|
|
const globals = getPartnerStackGlobal()
|
|
globals.__partnerStackCookieMocks = { get, set, remove }
|
|
const cookieApi = { get, set, remove }
|
|
return {
|
|
default: cookieApi,
|
|
get,
|
|
set,
|
|
remove,
|
|
}
|
|
})
|
|
vi.mock('@/next/navigation', () => ({
|
|
useSearchParams: () => ({
|
|
get: (key: string) => searchParamsValues[key] ?? null,
|
|
}),
|
|
}))
|
|
vi.mock('@/service/use-billing', () => {
|
|
const mutateAsync = vi.fn()
|
|
const globals = getPartnerStackGlobal()
|
|
globals.__partnerStackMutateAsync = mutateAsync
|
|
return {
|
|
useBindPartnerStackInfo: () => ({
|
|
mutateAsync,
|
|
}),
|
|
}
|
|
})
|
|
|
|
describe('usePSInfo', () => {
|
|
const originalLocationDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'location')
|
|
|
|
beforeAll(() => {
|
|
Object.defineProperty(globalThis, 'location', {
|
|
value: { hostname: 'cloud.dify.ai' },
|
|
configurable: true,
|
|
})
|
|
})
|
|
|
|
beforeEach(() => {
|
|
setSearchParams({})
|
|
const { get, set, remove } = ensureCookieMocks()
|
|
get.mockReset()
|
|
set.mockReset()
|
|
remove.mockReset()
|
|
const mutate = ensureMutateAsync()
|
|
mutate.mockReset()
|
|
mutate.mockResolvedValue(undefined)
|
|
get.mockReturnValue('{}')
|
|
})
|
|
|
|
afterAll(() => {
|
|
if (originalLocationDescriptor)
|
|
Object.defineProperty(globalThis, 'location', originalLocationDescriptor)
|
|
})
|
|
|
|
it('saves partner info when query params change', () => {
|
|
const { get, set } = ensureCookieMocks()
|
|
get.mockReturnValue(JSON.stringify({ partnerKey: 'old', clickId: 'old-click' }))
|
|
setSearchParams({
|
|
ps_partner_key: 'new-partner',
|
|
ps_xid: 'new-click',
|
|
})
|
|
|
|
const { result } = renderHook(() => usePSInfo())
|
|
|
|
expect(result.current.psPartnerKey).toBe('new-partner')
|
|
expect(result.current.psClickId).toBe('new-click')
|
|
|
|
act(() => {
|
|
result.current.saveOrUpdate()
|
|
})
|
|
|
|
expect(set).toHaveBeenCalledWith(
|
|
PARTNER_STACK_CONFIG.cookieName,
|
|
JSON.stringify({
|
|
partnerKey: 'new-partner',
|
|
clickId: 'new-click',
|
|
}),
|
|
{
|
|
expires: PARTNER_STACK_CONFIG.saveCookieDays,
|
|
path: '/',
|
|
domain: '.dify.ai',
|
|
},
|
|
)
|
|
})
|
|
|
|
it('does not overwrite cookie when params do not change', () => {
|
|
setSearchParams({
|
|
ps_partner_key: 'existing',
|
|
ps_xid: 'existing-click',
|
|
})
|
|
const { get } = ensureCookieMocks()
|
|
get.mockReturnValue(JSON.stringify({
|
|
partnerKey: 'existing',
|
|
clickId: 'existing-click',
|
|
}))
|
|
|
|
const { result } = renderHook(() => usePSInfo())
|
|
|
|
act(() => {
|
|
result.current.saveOrUpdate()
|
|
})
|
|
|
|
const { set } = ensureCookieMocks()
|
|
expect(set).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('binds partner info and clears cookie once', async () => {
|
|
setSearchParams({
|
|
ps_partner_key: 'bind-partner',
|
|
ps_xid: 'bind-click',
|
|
})
|
|
|
|
const { result } = renderHook(() => usePSInfo())
|
|
|
|
const mutate = ensureMutateAsync()
|
|
const { remove } = ensureCookieMocks()
|
|
await act(async () => {
|
|
await result.current.bind()
|
|
})
|
|
|
|
expect(mutate).toHaveBeenCalledWith({
|
|
partnerKey: 'bind-partner',
|
|
clickId: 'bind-click',
|
|
})
|
|
expect(remove).toHaveBeenCalledWith(PARTNER_STACK_CONFIG.cookieName, {
|
|
path: '/',
|
|
domain: '.dify.ai',
|
|
})
|
|
|
|
await act(async () => {
|
|
await result.current.bind()
|
|
})
|
|
|
|
expect(mutate).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('still removes cookie when bind fails with status 400', async () => {
|
|
const mutate = ensureMutateAsync()
|
|
mutate.mockRejectedValueOnce({ status: 400 })
|
|
setSearchParams({
|
|
ps_partner_key: 'bind-partner',
|
|
ps_xid: 'bind-click',
|
|
})
|
|
|
|
const { result } = renderHook(() => usePSInfo())
|
|
|
|
await act(async () => {
|
|
await result.current.bind()
|
|
})
|
|
|
|
const { remove } = ensureCookieMocks()
|
|
expect(remove).toHaveBeenCalledWith(PARTNER_STACK_CONFIG.cookieName, {
|
|
path: '/',
|
|
domain: '.dify.ai',
|
|
})
|
|
})
|
|
|
|
// Cookie parse failure: covers catch block (L14-16)
|
|
it('should fall back to empty object when cookie contains invalid JSON', () => {
|
|
const { get } = ensureCookieMocks()
|
|
get.mockReturnValue('not-valid-json{{{')
|
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
setSearchParams({
|
|
ps_partner_key: 'from-url',
|
|
ps_xid: 'click-url',
|
|
})
|
|
|
|
const { result } = renderHook(() => usePSInfo())
|
|
|
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
'Failed to parse partner stack info from cookie:',
|
|
expect.any(SyntaxError),
|
|
)
|
|
// Should still pick up values from search params
|
|
expect(result.current.psPartnerKey).toBe('from-url')
|
|
expect(result.current.psClickId).toBe('click-url')
|
|
consoleSpy.mockRestore()
|
|
})
|
|
|
|
// No keys at all: covers saveOrUpdate early return (L30) and bind no-op (L45 false branch)
|
|
it('should not save or bind when neither search params nor cookie have keys', () => {
|
|
const { get, set } = ensureCookieMocks()
|
|
get.mockReturnValue('{}')
|
|
setSearchParams({})
|
|
|
|
const { result } = renderHook(() => usePSInfo())
|
|
|
|
expect(result.current.psPartnerKey).toBeUndefined()
|
|
expect(result.current.psClickId).toBeUndefined()
|
|
|
|
act(() => {
|
|
result.current.saveOrUpdate()
|
|
})
|
|
expect(set).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should not call mutateAsync when keys are missing during bind', async () => {
|
|
const { get } = ensureCookieMocks()
|
|
get.mockReturnValue('{}')
|
|
setSearchParams({})
|
|
|
|
const { result } = renderHook(() => usePSInfo())
|
|
|
|
const mutate = ensureMutateAsync()
|
|
await act(async () => {
|
|
await result.current.bind()
|
|
})
|
|
|
|
expect(mutate).not.toHaveBeenCalled()
|
|
})
|
|
|
|
// Non-400 error: covers L55 false branch (shouldRemoveCookie stays false)
|
|
it('should not remove cookie when bind fails with non-400 error', async () => {
|
|
const mutate = ensureMutateAsync()
|
|
mutate.mockRejectedValueOnce({ status: 500 })
|
|
setSearchParams({
|
|
ps_partner_key: 'bind-partner',
|
|
ps_xid: 'bind-click',
|
|
})
|
|
|
|
const { result } = renderHook(() => usePSInfo())
|
|
|
|
await act(async () => {
|
|
await result.current.bind()
|
|
})
|
|
|
|
const { remove } = ensureCookieMocks()
|
|
expect(remove).not.toHaveBeenCalled()
|
|
})
|
|
|
|
// Fallback to cookie values: covers L19-20 right side of || operator
|
|
it('should use cookie values when search params are absent', () => {
|
|
const { get } = ensureCookieMocks()
|
|
get.mockReturnValue(JSON.stringify({
|
|
partnerKey: 'cookie-partner',
|
|
clickId: 'cookie-click',
|
|
}))
|
|
setSearchParams({})
|
|
|
|
const { result } = renderHook(() => usePSInfo())
|
|
|
|
expect(result.current.psPartnerKey).toBe('cookie-partner')
|
|
expect(result.current.psClickId).toBe('cookie-click')
|
|
})
|
|
|
|
// Partial key missing: only partnerKey present, no clickId
|
|
it('should not save when only one key is available', () => {
|
|
const { get, set } = ensureCookieMocks()
|
|
get.mockReturnValue('{}')
|
|
setSearchParams({ ps_partner_key: 'partial-key' })
|
|
|
|
const { result } = renderHook(() => usePSInfo())
|
|
|
|
act(() => {
|
|
result.current.saveOrUpdate()
|
|
})
|
|
|
|
expect(set).not.toHaveBeenCalled()
|
|
})
|
|
})
|