test(web): add comprehensive unit and integration tests for plugins and tools modules (#32220)

Co-authored-by: CodingOnStar <hanxujiang@dify.com>
This commit is contained in:
Coding On Star
2026-02-12 10:04:56 +08:00
committed by GitHub
parent 10f85074e8
commit d6b025e91e
195 changed files with 12219 additions and 7840 deletions

View File

@ -0,0 +1,45 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import AuthorizedInDataSourceNode from '../authorized-in-data-source-node'
vi.mock('@/app/components/header/indicator', () => ({
default: ({ color }: { color: string }) => <span data-testid="indicator" data-color={color} />,
}))
describe('AuthorizedInDataSourceNode', () => {
const mockOnJump = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
cleanup()
})
it('renders with green indicator', () => {
render(<AuthorizedInDataSourceNode authorizationsNum={1} onJumpToDataSourcePage={mockOnJump} />)
expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'green')
})
it('renders singular text for 1 authorization', () => {
render(<AuthorizedInDataSourceNode authorizationsNum={1} onJumpToDataSourcePage={mockOnJump} />)
expect(screen.getByText('plugin.auth.authorization')).toBeInTheDocument()
})
it('renders plural text for multiple authorizations', () => {
render(<AuthorizedInDataSourceNode authorizationsNum={3} onJumpToDataSourcePage={mockOnJump} />)
expect(screen.getByText('plugin.auth.authorizations')).toBeInTheDocument()
})
it('calls onJumpToDataSourcePage when button is clicked', () => {
render(<AuthorizedInDataSourceNode authorizationsNum={1} onJumpToDataSourcePage={mockOnJump} />)
fireEvent.click(screen.getByRole('button'))
expect(mockOnJump).toHaveBeenCalledTimes(1)
})
it('renders settings button', () => {
render(<AuthorizedInDataSourceNode authorizationsNum={1} onJumpToDataSourcePage={mockOnJump} />)
expect(screen.getByRole('button')).toBeInTheDocument()
})
})

View File

@ -0,0 +1,210 @@
import type { ReactNode } from 'react'
import type { Credential, PluginPayload } from '../types'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory, CredentialTypeEnum } from '../types'
// ==================== Mock Setup ====================
const mockGetPluginCredentialInfo = vi.fn()
const mockGetPluginOAuthClientSchema = vi.fn()
vi.mock('@/service/use-plugins-auth', () => ({
useGetPluginCredentialInfo: (url: string) => ({
data: url ? mockGetPluginCredentialInfo() : undefined,
isLoading: false,
}),
useDeletePluginCredential: () => ({ mutateAsync: vi.fn() }),
useSetPluginDefaultCredential: () => ({ mutateAsync: vi.fn() }),
useUpdatePluginCredential: () => ({ mutateAsync: vi.fn() }),
useInvalidPluginCredentialInfo: () => vi.fn(),
useGetPluginOAuthUrl: () => ({ mutateAsync: vi.fn() }),
useGetPluginOAuthClientSchema: () => ({
data: mockGetPluginOAuthClientSchema(),
isLoading: false,
}),
useSetPluginOAuthCustomClient: () => ({ mutateAsync: vi.fn() }),
useDeletePluginOAuthCustomClient: () => ({ mutateAsync: vi.fn() }),
useInvalidPluginOAuthClientSchema: () => vi.fn(),
useAddPluginCredential: () => ({ mutateAsync: vi.fn() }),
useGetPluginCredentialSchema: () => ({ data: undefined, isLoading: false }),
}))
vi.mock('@/service/use-tools', () => ({
useInvalidToolsByType: () => vi.fn(),
}))
const mockIsCurrentWorkspaceManager = vi.fn()
vi.mock('@/context/app-context', () => ({
useAppContext: () => ({
isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(),
}),
}))
vi.mock('@/app/components/base/toast', () => ({
useToastContext: () => ({ notify: vi.fn() }),
}))
vi.mock('@/hooks/use-oauth', () => ({
openOAuthPopup: vi.fn(),
}))
vi.mock('@/service/use-triggers', () => ({
useTriggerPluginDynamicOptions: () => ({ data: { options: [] }, isLoading: false }),
useTriggerPluginDynamicOptionsInfo: () => ({ data: null, isLoading: false }),
useInvalidTriggerDynamicOptions: () => vi.fn(),
}))
// ==================== Test Utilities ====================
const createTestQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: { retry: false, gcTime: 0 },
},
})
const createWrapper = () => {
const testQueryClient = createTestQueryClient()
return ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={testQueryClient}>
{children}
</QueryClientProvider>
)
}
const createPluginPayload = (overrides: Partial<PluginPayload> = {}): PluginPayload => ({
category: AuthCategory.tool,
provider: 'test-provider',
...overrides,
})
const createCredential = (overrides: Partial<Credential> = {}): Credential => ({
id: 'test-credential-id',
name: 'Test Credential',
provider: 'test-provider',
credential_type: CredentialTypeEnum.API_KEY,
is_default: false,
credentials: { api_key: 'test-key' },
...overrides,
})
// ==================== Tests ====================
describe('AuthorizedInNode Component', () => {
beforeEach(() => {
vi.clearAllMocks()
mockIsCurrentWorkspaceManager.mockReturnValue(true)
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [createCredential({ is_default: true })],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
mockGetPluginOAuthClientSchema.mockReturnValue({
schema: [],
is_oauth_custom_client_enabled: false,
is_system_oauth_params_exists: false,
})
})
it('should render with workspace default when no credentialId', async () => {
const AuthorizedInNode = (await import('../authorized-in-node')).default
const pluginPayload = createPluginPayload()
render(
<AuthorizedInNode pluginPayload={pluginPayload} onAuthorizationItemClick={vi.fn()} />,
{ wrapper: createWrapper() },
)
expect(screen.getByText('plugin.auth.workspaceDefault')).toBeInTheDocument()
})
it('should render credential name when credentialId matches', async () => {
const AuthorizedInNode = (await import('../authorized-in-node')).default
const credential = createCredential({ id: 'selected-id', name: 'My Credential' })
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [credential],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
const pluginPayload = createPluginPayload()
render(
<AuthorizedInNode pluginPayload={pluginPayload} onAuthorizationItemClick={vi.fn()} credentialId="selected-id" />,
{ wrapper: createWrapper() },
)
expect(screen.getByText('My Credential')).toBeInTheDocument()
})
it('should show auth removed when credentialId not found', async () => {
const AuthorizedInNode = (await import('../authorized-in-node')).default
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [createCredential()],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
const pluginPayload = createPluginPayload()
render(
<AuthorizedInNode pluginPayload={pluginPayload} onAuthorizationItemClick={vi.fn()} credentialId="non-existent" />,
{ wrapper: createWrapper() },
)
expect(screen.getByText('plugin.auth.authRemoved')).toBeInTheDocument()
})
it('should show unavailable when credential is not allowed', async () => {
const AuthorizedInNode = (await import('../authorized-in-node')).default
const credential = createCredential({
id: 'unavailable-id',
not_allowed_to_use: true,
from_enterprise: false,
})
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [credential],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
const pluginPayload = createPluginPayload()
render(
<AuthorizedInNode pluginPayload={pluginPayload} onAuthorizationItemClick={vi.fn()} credentialId="unavailable-id" />,
{ wrapper: createWrapper() },
)
const button = screen.getByRole('button')
expect(button.textContent).toContain('plugin.auth.unavailable')
})
it('should show unavailable when default credential is not allowed', async () => {
const AuthorizedInNode = (await import('../authorized-in-node')).default
const credential = createCredential({
is_default: true,
not_allowed_to_use: true,
})
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [credential],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
const pluginPayload = createPluginPayload()
render(
<AuthorizedInNode pluginPayload={pluginPayload} onAuthorizationItemClick={vi.fn()} />,
{ wrapper: createWrapper() },
)
const button = screen.getByRole('button')
expect(button.textContent).toContain('plugin.auth.unavailable')
})
it('should call onAuthorizationItemClick when clicking', async () => {
const AuthorizedInNode = (await import('../authorized-in-node')).default
const onAuthorizationItemClick = vi.fn()
const pluginPayload = createPluginPayload()
render(
<AuthorizedInNode pluginPayload={pluginPayload} onAuthorizationItemClick={onAuthorizationItemClick} />,
{ wrapper: createWrapper() },
)
const buttons = screen.getAllByRole('button')
fireEvent.click(buttons[0])
expect(screen.getAllByRole('button').length).toBeGreaterThan(0)
})
it('should be memoized', async () => {
const AuthorizedInNodeModule = await import('../authorized-in-node')
expect(typeof AuthorizedInNodeModule.default).toBe('object')
})
})

View File

@ -0,0 +1,247 @@
import type { ReactNode } from 'react'
import type { Credential, PluginPayload } from '../types'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { describe, expect, it, vi } from 'vitest'
import { AuthCategory, CredentialTypeEnum } from '../types'
const mockGetPluginCredentialInfo = vi.fn()
const mockDeletePluginCredential = vi.fn()
const mockSetPluginDefaultCredential = vi.fn()
const mockUpdatePluginCredential = vi.fn()
const mockInvalidPluginCredentialInfo = vi.fn()
const mockGetPluginOAuthUrl = vi.fn()
const mockGetPluginOAuthClientSchema = vi.fn()
const mockSetPluginOAuthCustomClient = vi.fn()
const mockDeletePluginOAuthCustomClient = vi.fn()
const mockInvalidPluginOAuthClientSchema = vi.fn()
const mockAddPluginCredential = vi.fn()
const mockGetPluginCredentialSchema = vi.fn()
const mockInvalidToolsByType = vi.fn()
vi.mock('@/service/use-plugins-auth', () => ({
useGetPluginCredentialInfo: (url: string) => ({
data: url ? mockGetPluginCredentialInfo() : undefined,
isLoading: false,
}),
useDeletePluginCredential: () => ({
mutateAsync: mockDeletePluginCredential,
}),
useSetPluginDefaultCredential: () => ({
mutateAsync: mockSetPluginDefaultCredential,
}),
useUpdatePluginCredential: () => ({
mutateAsync: mockUpdatePluginCredential,
}),
useInvalidPluginCredentialInfo: () => mockInvalidPluginCredentialInfo,
useGetPluginOAuthUrl: () => ({
mutateAsync: mockGetPluginOAuthUrl,
}),
useGetPluginOAuthClientSchema: () => ({
data: mockGetPluginOAuthClientSchema(),
isLoading: false,
}),
useSetPluginOAuthCustomClient: () => ({
mutateAsync: mockSetPluginOAuthCustomClient,
}),
useDeletePluginOAuthCustomClient: () => ({
mutateAsync: mockDeletePluginOAuthCustomClient,
}),
useInvalidPluginOAuthClientSchema: () => mockInvalidPluginOAuthClientSchema,
useAddPluginCredential: () => ({
mutateAsync: mockAddPluginCredential,
}),
useGetPluginCredentialSchema: () => ({
data: mockGetPluginCredentialSchema(),
isLoading: false,
}),
}))
vi.mock('@/service/use-tools', () => ({
useInvalidToolsByType: () => mockInvalidToolsByType,
}))
const mockIsCurrentWorkspaceManager = vi.fn()
vi.mock('@/context/app-context', () => ({
useAppContext: () => ({
isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(),
}),
}))
const mockNotify = vi.fn()
vi.mock('@/app/components/base/toast', () => ({
useToastContext: () => ({
notify: mockNotify,
}),
}))
vi.mock('@/hooks/use-oauth', () => ({
openOAuthPopup: vi.fn(),
}))
vi.mock('@/service/use-triggers', () => ({
useTriggerPluginDynamicOptions: () => ({
data: { options: [] },
isLoading: false,
}),
useTriggerPluginDynamicOptionsInfo: () => ({
data: null,
isLoading: false,
}),
useInvalidTriggerDynamicOptions: () => vi.fn(),
}))
const createTestQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: {
retry: false,
gcTime: 0,
},
},
})
const _createWrapper = () => {
const testQueryClient = createTestQueryClient()
return ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={testQueryClient}>
{children}
</QueryClientProvider>
)
}
const _createPluginPayload = (overrides: Partial<PluginPayload> = {}): PluginPayload => ({
category: AuthCategory.tool,
provider: 'test-provider',
...overrides,
})
const createCredential = (overrides: Partial<Credential> = {}): Credential => ({
id: 'test-credential-id',
name: 'Test Credential',
provider: 'test-provider',
credential_type: CredentialTypeEnum.API_KEY,
is_default: false,
credentials: { api_key: 'test-key' },
...overrides,
})
const _createCredentialList = (count: number, overrides: Partial<Credential>[] = []): Credential[] => {
return Array.from({ length: count }, (_, i) => createCredential({
id: `credential-${i}`,
name: `Credential ${i}`,
is_default: i === 0,
...overrides[i],
}))
}
describe('Index Exports', () => {
it('should export all required components and hooks', async () => {
const exports = await import('../index')
expect(exports.AddApiKeyButton).toBeDefined()
expect(exports.AddOAuthButton).toBeDefined()
expect(exports.ApiKeyModal).toBeDefined()
expect(exports.Authorized).toBeDefined()
expect(exports.AuthorizedInDataSourceNode).toBeDefined()
expect(exports.AuthorizedInNode).toBeDefined()
expect(exports.usePluginAuth).toBeDefined()
expect(exports.PluginAuth).toBeDefined()
expect(exports.PluginAuthInAgent).toBeDefined()
expect(exports.PluginAuthInDataSourceNode).toBeDefined()
}, 15000)
it('should export AuthCategory enum', async () => {
const exports = await import('../index')
expect(exports.AuthCategory).toBeDefined()
expect(exports.AuthCategory.tool).toBe('tool')
expect(exports.AuthCategory.datasource).toBe('datasource')
expect(exports.AuthCategory.model).toBe('model')
expect(exports.AuthCategory.trigger).toBe('trigger')
}, 15000)
it('should export CredentialTypeEnum', async () => {
const exports = await import('../index')
expect(exports.CredentialTypeEnum).toBeDefined()
expect(exports.CredentialTypeEnum.OAUTH2).toBe('oauth2')
expect(exports.CredentialTypeEnum.API_KEY).toBe('api-key')
}, 15000)
})
describe('Types', () => {
describe('AuthCategory enum', () => {
it('should have correct values', () => {
expect(AuthCategory.tool).toBe('tool')
expect(AuthCategory.datasource).toBe('datasource')
expect(AuthCategory.model).toBe('model')
expect(AuthCategory.trigger).toBe('trigger')
})
it('should have exactly 4 categories', () => {
const values = Object.values(AuthCategory)
expect(values).toHaveLength(4)
})
})
describe('CredentialTypeEnum', () => {
it('should have correct values', () => {
expect(CredentialTypeEnum.OAUTH2).toBe('oauth2')
expect(CredentialTypeEnum.API_KEY).toBe('api-key')
})
it('should have exactly 2 types', () => {
const values = Object.values(CredentialTypeEnum)
expect(values).toHaveLength(2)
})
})
describe('Credential type', () => {
it('should allow creating valid credentials', () => {
const credential: Credential = {
id: 'test-id',
name: 'Test',
provider: 'test-provider',
is_default: true,
}
expect(credential.id).toBe('test-id')
expect(credential.is_default).toBe(true)
})
it('should allow optional fields', () => {
const credential: Credential = {
id: 'test-id',
name: 'Test',
provider: 'test-provider',
is_default: false,
credential_type: CredentialTypeEnum.API_KEY,
credentials: { key: 'value' },
isWorkspaceDefault: true,
from_enterprise: false,
not_allowed_to_use: false,
}
expect(credential.credential_type).toBe(CredentialTypeEnum.API_KEY)
expect(credential.isWorkspaceDefault).toBe(true)
})
})
describe('PluginPayload type', () => {
it('should allow creating valid plugin payload', () => {
const payload: PluginPayload = {
category: AuthCategory.tool,
provider: 'test-provider',
}
expect(payload.category).toBe(AuthCategory.tool)
})
it('should allow optional fields', () => {
const payload: PluginPayload = {
category: AuthCategory.datasource,
provider: 'test-provider',
providerType: 'builtin',
detail: undefined,
}
expect(payload.providerType).toBe('builtin')
})
})
})

View File

@ -0,0 +1,255 @@
import type { ReactNode } from 'react'
import type { Credential, PluginPayload } from '../types'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AuthCategory, CredentialTypeEnum } from '../types'
// ==================== Mock Setup ====================
const mockGetPluginCredentialInfo = vi.fn()
const mockGetPluginOAuthClientSchema = vi.fn()
vi.mock('@/service/use-plugins-auth', () => ({
useGetPluginCredentialInfo: (url: string) => ({
data: url ? mockGetPluginCredentialInfo() : undefined,
isLoading: false,
}),
useDeletePluginCredential: () => ({ mutateAsync: vi.fn() }),
useSetPluginDefaultCredential: () => ({ mutateAsync: vi.fn() }),
useUpdatePluginCredential: () => ({ mutateAsync: vi.fn() }),
useInvalidPluginCredentialInfo: () => vi.fn(),
useGetPluginOAuthUrl: () => ({ mutateAsync: vi.fn() }),
useGetPluginOAuthClientSchema: () => ({
data: mockGetPluginOAuthClientSchema(),
isLoading: false,
}),
useSetPluginOAuthCustomClient: () => ({ mutateAsync: vi.fn() }),
useDeletePluginOAuthCustomClient: () => ({ mutateAsync: vi.fn() }),
useInvalidPluginOAuthClientSchema: () => vi.fn(),
useAddPluginCredential: () => ({ mutateAsync: vi.fn() }),
useGetPluginCredentialSchema: () => ({ data: undefined, isLoading: false }),
}))
vi.mock('@/service/use-tools', () => ({
useInvalidToolsByType: () => vi.fn(),
}))
const mockIsCurrentWorkspaceManager = vi.fn()
vi.mock('@/context/app-context', () => ({
useAppContext: () => ({
isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(),
}),
}))
vi.mock('@/app/components/base/toast', () => ({
useToastContext: () => ({ notify: vi.fn() }),
}))
vi.mock('@/hooks/use-oauth', () => ({
openOAuthPopup: vi.fn(),
}))
vi.mock('@/service/use-triggers', () => ({
useTriggerPluginDynamicOptions: () => ({ data: { options: [] }, isLoading: false }),
useTriggerPluginDynamicOptionsInfo: () => ({ data: null, isLoading: false }),
useInvalidTriggerDynamicOptions: () => vi.fn(),
}))
// ==================== Test Utilities ====================
const createTestQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: { retry: false, gcTime: 0 },
},
})
const createWrapper = () => {
const testQueryClient = createTestQueryClient()
return ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={testQueryClient}>
{children}
</QueryClientProvider>
)
}
const createPluginPayload = (overrides: Partial<PluginPayload> = {}): PluginPayload => ({
category: AuthCategory.tool,
provider: 'test-provider',
...overrides,
})
const createCredential = (overrides: Partial<Credential> = {}): Credential => ({
id: 'test-credential-id',
name: 'Test Credential',
provider: 'test-provider',
credential_type: CredentialTypeEnum.API_KEY,
is_default: false,
credentials: { api_key: 'test-key' },
...overrides,
})
// ==================== Tests ====================
describe('PluginAuthInAgent Component', () => {
beforeEach(() => {
vi.clearAllMocks()
mockIsCurrentWorkspaceManager.mockReturnValue(true)
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [createCredential()],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
mockGetPluginOAuthClientSchema.mockReturnValue({
schema: [],
is_oauth_custom_client_enabled: false,
is_system_oauth_params_exists: false,
})
})
it('should render Authorize when not authorized', async () => {
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
const pluginPayload = createPluginPayload()
render(
<PluginAuthInAgent pluginPayload={pluginPayload} />,
{ wrapper: createWrapper() },
)
expect(screen.getByRole('button')).toBeInTheDocument()
})
it('should render Authorized with workspace default when authorized', async () => {
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const pluginPayload = createPluginPayload()
render(
<PluginAuthInAgent pluginPayload={pluginPayload} />,
{ wrapper: createWrapper() },
)
expect(screen.getByRole('button')).toBeInTheDocument()
expect(screen.getByText('plugin.auth.workspaceDefault')).toBeInTheDocument()
})
it('should show credential name when credentialId is provided', async () => {
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const credential = createCredential({ id: 'selected-id', name: 'Selected Credential' })
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [credential],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
const pluginPayload = createPluginPayload()
render(
<PluginAuthInAgent pluginPayload={pluginPayload} credentialId="selected-id" />,
{ wrapper: createWrapper() },
)
expect(screen.getByText('Selected Credential')).toBeInTheDocument()
})
it('should show auth removed when credential not found', async () => {
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [createCredential()],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
const pluginPayload = createPluginPayload()
render(
<PluginAuthInAgent pluginPayload={pluginPayload} credentialId="non-existent-id" />,
{ wrapper: createWrapper() },
)
expect(screen.getByText('plugin.auth.authRemoved')).toBeInTheDocument()
})
it('should show unavailable when credential is not allowed to use', async () => {
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const credential = createCredential({
id: 'unavailable-id',
name: 'Unavailable Credential',
not_allowed_to_use: true,
from_enterprise: false,
})
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [credential],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
const pluginPayload = createPluginPayload()
render(
<PluginAuthInAgent pluginPayload={pluginPayload} credentialId="unavailable-id" />,
{ wrapper: createWrapper() },
)
const button = screen.getByRole('button')
expect(button.textContent).toContain('plugin.auth.unavailable')
})
it('should call onAuthorizationItemClick when item is clicked', async () => {
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const onAuthorizationItemClick = vi.fn()
const pluginPayload = createPluginPayload()
render(
<PluginAuthInAgent pluginPayload={pluginPayload} onAuthorizationItemClick={onAuthorizationItemClick} />,
{ wrapper: createWrapper() },
)
const buttons = screen.getAllByRole('button')
fireEvent.click(buttons[0])
expect(screen.getAllByRole('button').length).toBeGreaterThan(0)
})
it('should trigger handleAuthorizationItemClick and close popup when item is clicked', async () => {
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const onAuthorizationItemClick = vi.fn()
const credential = createCredential({ id: 'test-cred-id', name: 'Test Credential' })
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [credential],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
const pluginPayload = createPluginPayload()
render(
<PluginAuthInAgent pluginPayload={pluginPayload} onAuthorizationItemClick={onAuthorizationItemClick} />,
{ wrapper: createWrapper() },
)
const triggerButton = screen.getByRole('button')
fireEvent.click(triggerButton)
const workspaceDefaultItems = screen.getAllByText('plugin.auth.workspaceDefault')
const popupItem = workspaceDefaultItems.length > 1 ? workspaceDefaultItems[1] : workspaceDefaultItems[0]
fireEvent.click(popupItem)
expect(onAuthorizationItemClick).toHaveBeenCalledWith('')
})
it('should call onAuthorizationItemClick with credential id when specific credential is clicked', async () => {
const PluginAuthInAgent = (await import('../plugin-auth-in-agent')).default
const onAuthorizationItemClick = vi.fn()
const credential = createCredential({
id: 'specific-cred-id',
name: 'Specific Credential',
credential_type: CredentialTypeEnum.API_KEY,
})
mockGetPluginCredentialInfo.mockReturnValue({
credentials: [credential],
supported_credential_types: [CredentialTypeEnum.API_KEY],
allow_custom_token: true,
})
const pluginPayload = createPluginPayload()
render(
<PluginAuthInAgent pluginPayload={pluginPayload} onAuthorizationItemClick={onAuthorizationItemClick} />,
{ wrapper: createWrapper() },
)
const triggerButton = screen.getByRole('button')
fireEvent.click(triggerButton)
const credentialItems = screen.getAllByText('Specific Credential')
const popupItem = credentialItems[credentialItems.length - 1]
fireEvent.click(popupItem)
expect(onAuthorizationItemClick).toHaveBeenCalledWith('specific-cred-id')
})
it('should be memoized', async () => {
const PluginAuthInAgentModule = await import('../plugin-auth-in-agent')
expect(typeof PluginAuthInAgentModule.default).toBe('object')
})
})

View File

@ -0,0 +1,51 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import PluginAuthInDataSourceNode from '../plugin-auth-in-datasource-node'
describe('PluginAuthInDataSourceNode', () => {
const mockOnJump = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
cleanup()
})
it('renders connect button when not authorized', () => {
render(<PluginAuthInDataSourceNode onJumpToDataSourcePage={mockOnJump} />)
expect(screen.getByText('common.integrations.connect')).toBeInTheDocument()
})
it('renders connect button', () => {
render(<PluginAuthInDataSourceNode onJumpToDataSourcePage={mockOnJump} />)
expect(screen.getByRole('button', { name: /common\.integrations\.connect/ })).toBeInTheDocument()
})
it('calls onJumpToDataSourcePage when connect button is clicked', () => {
render(<PluginAuthInDataSourceNode onJumpToDataSourcePage={mockOnJump} />)
fireEvent.click(screen.getByRole('button', { name: /common\.integrations\.connect/ }))
expect(mockOnJump).toHaveBeenCalledTimes(1)
})
it('hides connect button and shows children when authorized', () => {
render(
<PluginAuthInDataSourceNode isAuthorized onJumpToDataSourcePage={mockOnJump}>
<div data-testid="child-content">Data Source Connected</div>
</PluginAuthInDataSourceNode>,
)
expect(screen.queryByText('common.integrations.connect')).not.toBeInTheDocument()
expect(screen.getByTestId('child-content')).toBeInTheDocument()
})
it('shows connect button when isAuthorized is false', () => {
render(
<PluginAuthInDataSourceNode isAuthorized={false} onJumpToDataSourcePage={mockOnJump}>
<div data-testid="child-content">Data Source Connected</div>
</PluginAuthInDataSourceNode>,
)
expect(screen.getByText('common.integrations.connect')).toBeInTheDocument()
expect(screen.queryByTestId('child-content')).not.toBeInTheDocument()
})
})

View File

@ -0,0 +1,139 @@
import { cleanup, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import PluginAuth from '../plugin-auth'
import { AuthCategory } from '../types'
const mockUsePluginAuth = vi.fn()
vi.mock('../hooks/use-plugin-auth', () => ({
usePluginAuth: (...args: unknown[]) => mockUsePluginAuth(...args),
}))
vi.mock('../authorize', () => ({
default: ({ pluginPayload }: { pluginPayload: { provider: string } }) => (
<div data-testid="authorize">
Authorize:
{pluginPayload.provider}
</div>
),
}))
vi.mock('../authorized', () => ({
default: ({ pluginPayload }: { pluginPayload: { provider: string } }) => (
<div data-testid="authorized">
Authorized:
{pluginPayload.provider}
</div>
),
}))
const defaultPayload = {
category: AuthCategory.tool,
provider: 'test-provider',
}
describe('PluginAuth', () => {
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
cleanup()
})
it('renders Authorize component when not authorized', () => {
mockUsePluginAuth.mockReturnValue({
isAuthorized: false,
canOAuth: false,
canApiKey: true,
credentials: [],
disabled: false,
invalidPluginCredentialInfo: vi.fn(),
notAllowCustomCredential: false,
})
render(<PluginAuth pluginPayload={defaultPayload} />)
expect(screen.getByTestId('authorize')).toBeInTheDocument()
expect(screen.queryByTestId('authorized')).not.toBeInTheDocument()
})
it('renders Authorized component when authorized and no children', () => {
mockUsePluginAuth.mockReturnValue({
isAuthorized: true,
canOAuth: true,
canApiKey: true,
credentials: [{ id: '1', name: 'key', is_default: true, provider: 'test' }],
disabled: false,
invalidPluginCredentialInfo: vi.fn(),
notAllowCustomCredential: false,
})
render(<PluginAuth pluginPayload={defaultPayload} />)
expect(screen.getByTestId('authorized')).toBeInTheDocument()
expect(screen.queryByTestId('authorize')).not.toBeInTheDocument()
})
it('renders children when authorized and children provided', () => {
mockUsePluginAuth.mockReturnValue({
isAuthorized: true,
canOAuth: false,
canApiKey: true,
credentials: [{ id: '1', name: 'key', is_default: true, provider: 'test' }],
disabled: false,
invalidPluginCredentialInfo: vi.fn(),
notAllowCustomCredential: false,
})
render(
<PluginAuth pluginPayload={defaultPayload}>
<div data-testid="custom-children">Custom Content</div>
</PluginAuth>,
)
expect(screen.getByTestId('custom-children')).toBeInTheDocument()
expect(screen.queryByTestId('authorized')).not.toBeInTheDocument()
})
it('applies className when not authorized', () => {
mockUsePluginAuth.mockReturnValue({
isAuthorized: false,
canOAuth: false,
canApiKey: true,
credentials: [],
disabled: false,
invalidPluginCredentialInfo: vi.fn(),
notAllowCustomCredential: false,
})
const { container } = render(<PluginAuth pluginPayload={defaultPayload} className="custom-class" />)
expect((container.firstChild as HTMLElement).className).toContain('custom-class')
})
it('does not apply className when authorized', () => {
mockUsePluginAuth.mockReturnValue({
isAuthorized: true,
canOAuth: false,
canApiKey: true,
credentials: [],
disabled: false,
invalidPluginCredentialInfo: vi.fn(),
notAllowCustomCredential: false,
})
const { container } = render(<PluginAuth pluginPayload={defaultPayload} className="custom-class" />)
expect((container.firstChild as HTMLElement).className).not.toContain('custom-class')
})
it('passes pluginPayload.provider to usePluginAuth', () => {
mockUsePluginAuth.mockReturnValue({
isAuthorized: false,
canOAuth: false,
canApiKey: false,
credentials: [],
disabled: false,
invalidPluginCredentialInfo: vi.fn(),
notAllowCustomCredential: false,
})
render(<PluginAuth pluginPayload={defaultPayload} />)
expect(mockUsePluginAuth).toHaveBeenCalledWith(defaultPayload, true)
})
})

View File

@ -0,0 +1,55 @@
import { describe, expect, it } from 'vitest'
import { transformFormSchemasSecretInput } from '../utils'
describe('plugin-auth/utils', () => {
describe('transformFormSchemasSecretInput', () => {
it('replaces secret input values with [__HIDDEN__]', () => {
const values = { api_key: 'sk-12345', username: 'admin' }
const result = transformFormSchemasSecretInput(['api_key'], values)
expect(result.api_key).toBe('[__HIDDEN__]')
expect(result.username).toBe('admin')
})
it('does not replace falsy values (empty string)', () => {
const values = { api_key: '', username: 'admin' }
const result = transformFormSchemasSecretInput(['api_key'], values)
expect(result.api_key).toBe('')
})
it('does not replace undefined values', () => {
const values = { username: 'admin' }
const result = transformFormSchemasSecretInput(['api_key'], values)
expect(result.api_key).toBeUndefined()
})
it('handles multiple secret fields', () => {
const values = { key1: 'secret1', key2: 'secret2', normal: 'value' }
const result = transformFormSchemasSecretInput(['key1', 'key2'], values)
expect(result.key1).toBe('[__HIDDEN__]')
expect(result.key2).toBe('[__HIDDEN__]')
expect(result.normal).toBe('value')
})
it('does not mutate the original values', () => {
const values = { api_key: 'sk-12345' }
const result = transformFormSchemasSecretInput(['api_key'], values)
expect(result).not.toBe(values)
expect(values.api_key).toBe('sk-12345')
})
it('returns same values when no secret names provided', () => {
const values = { api_key: 'sk-12345', username: 'admin' }
const result = transformFormSchemasSecretInput([], values)
expect(result).toEqual(values)
})
it('handles null-like values correctly', () => {
const values = { key: null, key2: 0, key3: false }
const result = transformFormSchemasSecretInput(['key', 'key2', 'key3'], values)
// null, 0, false are falsy — should not be replaced
expect(result.key).toBeNull()
expect(result.key2).toBe(0)
expect(result.key3).toBe(false)
})
})
})