mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-31 21:39:54 +00:00
446 lines
15 KiB
TypeScript
446 lines
15 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { useRegistrySearchGateway } from '@/services/gateway/registrySearchGateway'
|
|
import { useAlgoliaSearchProvider } from '@/services/providers/algoliaSearchProvider'
|
|
import { useComfyRegistrySearchProvider } from '@/services/providers/registrySearchProvider'
|
|
|
|
// Mock the provider modules to control their behavior
|
|
vi.mock('@/services/providers/algoliaSearchProvider')
|
|
vi.mock('@/services/providers/registrySearchProvider')
|
|
|
|
describe('useRegistrySearchGateway', () => {
|
|
let consoleWarnSpy: any
|
|
let consoleInfoSpy: any
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {})
|
|
})
|
|
|
|
afterEach(() => {
|
|
consoleWarnSpy.mockRestore()
|
|
consoleInfoSpy.mockRestore()
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
describe('Provider initialization', () => {
|
|
it('should initialize with both providers', () => {
|
|
const mockAlgoliaProvider = {
|
|
searchPacks: vi.fn(),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi.fn(),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
vi.mocked(useAlgoliaSearchProvider).mockReturnValue(mockAlgoliaProvider)
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
|
|
expect(useAlgoliaSearchProvider).toHaveBeenCalled()
|
|
expect(useComfyRegistrySearchProvider).toHaveBeenCalled()
|
|
expect(gateway).toBeDefined()
|
|
})
|
|
|
|
it('should handle Algolia initialization failure gracefully', () => {
|
|
vi.mocked(useAlgoliaSearchProvider).mockImplementation(() => {
|
|
throw new Error('Algolia init failed')
|
|
})
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi
|
|
.fn()
|
|
.mockResolvedValue({ nodePacks: [], querySuggestions: [] }),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
|
|
// Gateway should still work with just the Registry provider
|
|
expect(gateway).toBeDefined()
|
|
expect(typeof gateway.searchPacks).toBe('function')
|
|
|
|
// Verify it can still search using the fallback provider
|
|
return expect(
|
|
gateway.searchPacks('test', { pageSize: 10, pageNumber: 0 })
|
|
).resolves.toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe('Search functionality', () => {
|
|
it('should use Algolia provider by default and fallback on failure', async () => {
|
|
const algoliaResult = {
|
|
nodePacks: [{ id: 'algolia-1', name: 'Algolia Pack' }],
|
|
querySuggestions: []
|
|
}
|
|
const registryResult = {
|
|
nodePacks: [{ id: 'registry-1', name: 'Registry Pack' }],
|
|
querySuggestions: []
|
|
}
|
|
|
|
const mockAlgoliaProvider = {
|
|
searchPacks: vi
|
|
.fn()
|
|
.mockResolvedValueOnce(algoliaResult)
|
|
.mockRejectedValueOnce(new Error('Algolia failed')),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi.fn().mockResolvedValue(registryResult),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
vi.mocked(useAlgoliaSearchProvider).mockReturnValue(mockAlgoliaProvider)
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
|
|
// First call should use Algolia
|
|
const result1 = await gateway.searchPacks('test', {
|
|
pageSize: 10,
|
|
pageNumber: 0
|
|
})
|
|
expect(result1.nodePacks[0].name).toBe('Algolia Pack')
|
|
|
|
// Second call should fallback to Registry when Algolia fails
|
|
const result2 = await gateway.searchPacks('test', {
|
|
pageSize: 10,
|
|
pageNumber: 0
|
|
})
|
|
expect(result2.nodePacks[0].name).toBe('Registry Pack')
|
|
})
|
|
|
|
it('should throw error when all providers fail', async () => {
|
|
const mockAlgoliaProvider = {
|
|
searchPacks: vi.fn().mockRejectedValue(new Error('Algolia failed')),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi.fn().mockRejectedValue(new Error('Registry failed')),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
vi.mocked(useAlgoliaSearchProvider).mockReturnValue(mockAlgoliaProvider)
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
|
|
await expect(
|
|
gateway.searchPacks('test', { pageSize: 10, pageNumber: 0 })
|
|
).rejects.toThrow('All search providers failed')
|
|
})
|
|
})
|
|
|
|
describe('Circuit breaker functionality', () => {
|
|
it('should switch to fallback provider after failure and log warnings', async () => {
|
|
const registryResult = {
|
|
nodePacks: [{ id: 'registry-1', name: 'Registry Pack' }],
|
|
querySuggestions: []
|
|
}
|
|
|
|
// Create mock that fails
|
|
const mockAlgoliaProvider = {
|
|
searchPacks: vi.fn().mockRejectedValue(new Error('Algolia failed')),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi.fn().mockResolvedValue(registryResult),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
vi.mocked(useAlgoliaSearchProvider).mockReturnValue(mockAlgoliaProvider)
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
|
|
// First call should try Algolia, fail, and use Registry
|
|
const result = await gateway.searchPacks('test', {
|
|
pageSize: 10,
|
|
pageNumber: 0
|
|
})
|
|
|
|
expect(mockAlgoliaProvider.searchPacks).toHaveBeenCalledTimes(1)
|
|
expect(mockRegistryProvider.searchPacks).toHaveBeenCalledTimes(1)
|
|
expect(result.nodePacks[0].name).toBe('Registry Pack')
|
|
|
|
// Circuit breaker behavior is internal implementation detail
|
|
// We only test the observable behavior (fallback works)
|
|
})
|
|
|
|
it('should have circuit breaker timeout mechanism', () => {
|
|
// This test verifies that the constants exist for circuit breaker behavior
|
|
// The actual circuit breaker logic is tested in integration with real provider behavior
|
|
expect(typeof useRegistrySearchGateway).toBe('function')
|
|
|
|
// We can test that the gateway logs circuit breaker behavior
|
|
const mockAlgoliaProvider = {
|
|
searchPacks: vi.fn().mockRejectedValue(new Error('Persistent failure')),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi
|
|
.fn()
|
|
.mockResolvedValue({ nodePacks: [], querySuggestions: [] }),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
vi.mocked(useAlgoliaSearchProvider).mockReturnValue(mockAlgoliaProvider)
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
expect(gateway).toBeDefined()
|
|
})
|
|
})
|
|
|
|
describe('Cache management', () => {
|
|
it('should clear cache for all providers', () => {
|
|
const mockAlgoliaProvider = {
|
|
searchPacks: vi.fn(),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi.fn(),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
vi.mocked(useAlgoliaSearchProvider).mockReturnValue(mockAlgoliaProvider)
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
gateway.clearSearchCache()
|
|
|
|
expect(mockAlgoliaProvider.clearSearchCache).toHaveBeenCalled()
|
|
expect(mockRegistryProvider.clearSearchCache).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should handle cache clear failures gracefully', () => {
|
|
const mockAlgoliaProvider = {
|
|
searchPacks: vi.fn(),
|
|
clearSearchCache: vi.fn().mockImplementation(() => {
|
|
throw new Error('Cache clear failed')
|
|
}),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi.fn(),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
vi.mocked(useAlgoliaSearchProvider).mockReturnValue(mockAlgoliaProvider)
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
|
|
// Should not throw when clearing cache even if one provider fails
|
|
expect(() => gateway.clearSearchCache()).not.toThrow()
|
|
|
|
// Should still attempt to clear cache for all providers
|
|
expect(mockAlgoliaProvider.clearSearchCache).toHaveBeenCalled()
|
|
expect(mockRegistryProvider.clearSearchCache).toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('Sort functionality', () => {
|
|
it('should use sort fields from active provider', () => {
|
|
const algoliaFields = [
|
|
{ id: 'downloads', label: 'Downloads', direction: 'desc' }
|
|
]
|
|
|
|
const mockAlgoliaProvider = {
|
|
searchPacks: vi.fn(),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue(algoliaFields)
|
|
}
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi.fn(),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
vi.mocked(useAlgoliaSearchProvider).mockReturnValue(mockAlgoliaProvider)
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
const sortFields = gateway.getSortableFields()
|
|
|
|
expect(sortFields).toEqual(algoliaFields)
|
|
})
|
|
|
|
it('should switch sort fields when provider changes', async () => {
|
|
const algoliaFields = [
|
|
{ id: 'downloads', label: 'Downloads', direction: 'desc' }
|
|
]
|
|
const registryFields = [{ id: 'name', label: 'Name', direction: 'asc' }]
|
|
|
|
const mockAlgoliaProvider = {
|
|
searchPacks: vi.fn().mockRejectedValue(new Error('Algolia failed')),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue(algoliaFields)
|
|
}
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi
|
|
.fn()
|
|
.mockResolvedValue({ nodePacks: [], querySuggestions: [] }),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue(registryFields)
|
|
}
|
|
|
|
vi.mocked(useAlgoliaSearchProvider).mockReturnValue(mockAlgoliaProvider)
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
|
|
// Initially should use Algolia's sort fields
|
|
expect(gateway.getSortableFields()).toEqual(algoliaFields)
|
|
|
|
// Force a search to trigger provider switch
|
|
await gateway.searchPacks('test', { pageSize: 10, pageNumber: 0 })
|
|
|
|
// Now should use Registry's sort fields
|
|
expect(gateway.getSortableFields()).toEqual(registryFields)
|
|
})
|
|
|
|
it('should delegate getSortValue to active provider', () => {
|
|
const mockAlgoliaProvider = {
|
|
searchPacks: vi.fn(),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn().mockReturnValue(100),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi.fn(),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
vi.mocked(useAlgoliaSearchProvider).mockReturnValue(mockAlgoliaProvider)
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
const pack = { id: '1', name: 'Test Pack' }
|
|
|
|
const value = gateway.getSortValue(pack, 'downloads')
|
|
|
|
expect(mockAlgoliaProvider.getSortValue).toHaveBeenCalledWith(
|
|
pack,
|
|
'downloads'
|
|
)
|
|
expect(value).toBe(100)
|
|
})
|
|
})
|
|
|
|
describe('Provider recovery', () => {
|
|
it('should use fallback provider when primary fails', async () => {
|
|
const algoliaError = new Error('Algolia service unavailable')
|
|
const registryResult = {
|
|
nodePacks: [{ id: 'registry-1', name: 'Registry Pack' }],
|
|
querySuggestions: []
|
|
}
|
|
|
|
const mockAlgoliaProvider = {
|
|
searchPacks: vi.fn().mockRejectedValue(algoliaError),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
const mockRegistryProvider = {
|
|
searchPacks: vi.fn().mockResolvedValue(registryResult),
|
|
clearSearchCache: vi.fn(),
|
|
getSortValue: vi.fn(),
|
|
getSortableFields: vi.fn().mockReturnValue([])
|
|
}
|
|
|
|
vi.mocked(useAlgoliaSearchProvider).mockReturnValue(mockAlgoliaProvider)
|
|
vi.mocked(useComfyRegistrySearchProvider).mockReturnValue(
|
|
mockRegistryProvider
|
|
)
|
|
|
|
const gateway = useRegistrySearchGateway()
|
|
|
|
// Should fallback to Registry when Algolia fails
|
|
const result = await gateway.searchPacks('test', {
|
|
pageSize: 10,
|
|
pageNumber: 0
|
|
})
|
|
|
|
expect(result.nodePacks[0].name).toBe('Registry Pack')
|
|
expect(mockAlgoliaProvider.searchPacks).toHaveBeenCalledTimes(1)
|
|
expect(mockRegistryProvider.searchPacks).toHaveBeenCalledTimes(1)
|
|
|
|
// The gateway successfully handled the failure and returned results
|
|
})
|
|
})
|
|
})
|