diff --git a/web/__tests__/goto-anything/search-error-handling.test.ts b/web/__tests__/goto-anything/search-error-handling.test.ts index 2651d98f36..fe786689f7 100644 --- a/web/__tests__/goto-anything/search-error-handling.test.ts +++ b/web/__tests__/goto-anything/search-error-handling.test.ts @@ -10,9 +10,7 @@ import type { MockedFunction } from 'vitest' */ import { appScope, knowledgeScope, pluginScope, searchAnything } from '@/app/components/goto-anything/actions' -import { fetchAppList } from '@/service/apps' -import { postMarketplace } from '@/service/base' -import { fetchDatasets } from '@/service/datasets' +import { searchApps, searchDatasets, searchPlugins } from '@/service/use-goto-anything' // Mock react-i18next before importing modules that use it vi.mock('react-i18next', () => ({ @@ -22,30 +20,21 @@ vi.mock('react-i18next', () => ({ }), })) -// Mock API functions -vi.mock('@/service/base', () => ({ - postMarketplace: vi.fn(), +// Mock the actual service functions used by the scopes +vi.mock('@/service/use-goto-anything', () => ({ + searchPlugins: vi.fn(), + searchApps: vi.fn(), + searchDatasets: vi.fn(), })) -vi.mock('@/service/apps', () => ({ - fetchAppList: vi.fn(), -})) - -vi.mock('@/service/datasets', () => ({ - fetchDatasets: vi.fn(), -})) - -const mockPostMarketplace = postMarketplace as MockedFunction -const mockFetchAppList = fetchAppList as MockedFunction -const mockFetchDatasets = fetchDatasets as MockedFunction +const mockSearchPlugins = searchPlugins as MockedFunction +const mockSearchApps = searchApps as MockedFunction +const mockSearchDatasets = searchDatasets as MockedFunction describe('GotoAnything Search Error Handling', () => { beforeEach(() => { vi.clearAllMocks() - // Suppress console.warn for clean test output - vi.spyOn(console, 'warn').mockImplementation(() => { - // Suppress console.warn for clean test output - }) + vi.spyOn(console, 'warn').mockImplementation(() => {}) }) afterEach(() => { @@ -54,29 +43,17 @@ describe('GotoAnything Search Error Handling', () => { describe('@plugin search error handling', () => { it('should return empty array when API fails instead of throwing error', async () => { - // Mock marketplace API failure (403 permission denied) - mockPostMarketplace.mockRejectedValue(new Error('HTTP 403: Forbidden')) + mockSearchPlugins.mockRejectedValue(new Error('HTTP 403: Forbidden')) - // Directly call plugin action's search method const result = await pluginScope.search('@plugin', 'test', 'en') - // Should return empty array instead of throwing error expect(result).toEqual([]) - expect(mockPostMarketplace).toHaveBeenCalledWith('/plugins/search/advanced', { - body: { - page: 1, - page_size: 10, - query: 'test', - type: 'plugin', - }, - }) + expect(mockSearchPlugins).toHaveBeenCalledWith('test') }) it('should return empty array when user has no plugin data', async () => { - // Mock marketplace returning empty data - mockPostMarketplace.mockResolvedValue({ - data: { plugins: [] }, - }) + // eslint-disable-next-line ts/no-explicit-any + mockSearchPlugins.mockResolvedValue({ data: { plugins: [] } } as any) const result = await pluginScope.search('@plugin', '', 'en') @@ -84,10 +61,8 @@ describe('GotoAnything Search Error Handling', () => { }) it('should return empty array when API returns unexpected data structure', async () => { - // Mock API returning unexpected data structure - mockPostMarketplace.mockResolvedValue({ - data: null, - }) + // eslint-disable-next-line ts/no-explicit-any + mockSearchPlugins.mockResolvedValue({ data: null } as any) const result = await pluginScope.search('@plugin', 'test', 'en') @@ -97,8 +72,7 @@ describe('GotoAnything Search Error Handling', () => { describe('Other search types error handling', () => { it('@app search should return empty array when API fails', async () => { - // Mock app API failure - mockFetchAppList.mockRejectedValue(new Error('API Error')) + mockSearchApps.mockRejectedValue(new Error('API Error')) const result = await appScope.search('@app', 'test', 'en') @@ -106,8 +80,7 @@ describe('GotoAnything Search Error Handling', () => { }) it('@knowledge search should return empty array when API fails', async () => { - // Mock knowledge API failure - mockFetchDatasets.mockRejectedValue(new Error('API Error')) + mockSearchDatasets.mockRejectedValue(new Error('API Error')) const result = await knowledgeScope.search('@knowledge', 'test', 'en') @@ -117,33 +90,30 @@ describe('GotoAnything Search Error Handling', () => { describe('Unified search entry error handling', () => { it('regular search (without @prefix) should return successful results even when partial APIs fail', async () => { - // Set app and knowledge success, plugin failure - mockFetchAppList.mockResolvedValue({ data: [], has_more: false, limit: 10, page: 1, total: 0 }) - mockFetchDatasets.mockResolvedValue({ data: [], has_more: false, limit: 10, page: 1, total: 0 }) - mockPostMarketplace.mockRejectedValue(new Error('Plugin API failed')) + // eslint-disable-next-line ts/no-explicit-any + mockSearchApps.mockResolvedValue({ data: [], has_more: false, limit: 10, page: 1, total: 0 } as any) + // eslint-disable-next-line ts/no-explicit-any + mockSearchDatasets.mockResolvedValue({ data: [], has_more: false, limit: 10, page: 1, total: 0 } as any) + mockSearchPlugins.mockRejectedValue(new Error('Plugin API failed')) const allScopes = [appScope, knowledgeScope, pluginScope] const result = await searchAnything('en', 'test', undefined, allScopes) - // Should return successful results even if plugin search fails expect(result).toEqual([]) expect(console.warn).toHaveBeenCalled() }) it('@plugin dedicated search should return empty array when API fails', async () => { - // Mock plugin API failure - mockPostMarketplace.mockRejectedValue(new Error('Plugin service unavailable')) + mockSearchPlugins.mockRejectedValue(new Error('Plugin service unavailable')) const allScopes = [appScope, knowledgeScope, pluginScope] const result = await searchAnything('en', '@plugin test', pluginScope, allScopes) - // Should return empty array instead of throwing error expect(result).toEqual([]) }) it('@app dedicated search should return empty array when API fails', async () => { - // Mock app API failure - mockFetchAppList.mockRejectedValue(new Error('App service unavailable')) + mockSearchApps.mockRejectedValue(new Error('App service unavailable')) const allScopes = [appScope, knowledgeScope, pluginScope] const result = await searchAnything('en', '@app test', appScope, allScopes) @@ -154,10 +124,9 @@ describe('GotoAnything Search Error Handling', () => { describe('Error handling consistency validation', () => { it('all search types should return empty array when encountering errors', async () => { - // Mock all APIs to fail - mockPostMarketplace.mockRejectedValue(new Error('Plugin API failed')) - mockFetchAppList.mockRejectedValue(new Error('App API failed')) - mockFetchDatasets.mockRejectedValue(new Error('Dataset API failed')) + mockSearchPlugins.mockRejectedValue(new Error('Plugin API failed')) + mockSearchApps.mockRejectedValue(new Error('App API failed')) + mockSearchDatasets.mockRejectedValue(new Error('Dataset API failed')) const actions = [ { name: '@plugin', scope: pluginScope }, @@ -174,7 +143,8 @@ describe('GotoAnything Search Error Handling', () => { describe('Edge case testing', () => { it('empty search term should be handled properly', async () => { - mockPostMarketplace.mockResolvedValue({ data: { plugins: [] } }) + // eslint-disable-next-line ts/no-explicit-any + mockSearchPlugins.mockResolvedValue({ data: { plugins: [] } } as any) const allScopes = [appScope, knowledgeScope, pluginScope] const result = await searchAnything('en', '@plugin ', pluginScope, allScopes) @@ -185,7 +155,7 @@ describe('GotoAnything Search Error Handling', () => { const timeoutError = new Error('Network timeout') timeoutError.name = 'TimeoutError' - mockPostMarketplace.mockRejectedValue(timeoutError) + mockSearchPlugins.mockRejectedValue(timeoutError) const allScopes = [appScope, knowledgeScope, pluginScope] const result = await searchAnything('en', '@plugin test', pluginScope, allScopes) @@ -194,7 +164,7 @@ describe('GotoAnything Search Error Handling', () => { it('JSON parsing errors should be handled correctly', async () => { const parseError = new SyntaxError('Unexpected token in JSON') - mockPostMarketplace.mockRejectedValue(parseError) + mockSearchPlugins.mockRejectedValue(parseError) const allScopes = [appScope, knowledgeScope, pluginScope] const result = await searchAnything('en', '@plugin test', pluginScope, allScopes) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx index 986170728f..9f6df25aa6 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx @@ -57,7 +57,7 @@ const RangeSelector: FC = ({ {selected && (