Files
ComfyUI_frontend/tests-ui/tests/services/fileNameMappingService.test.ts
Richard Yu d83af149b0 wip
2025-09-02 16:56:40 -07:00

397 lines
11 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import { api } from '@/scripts/api'
import {
type FileNameMapping,
FileNameMappingService
} from '@/services/fileNameMappingService'
// Mock api module
vi.mock('@/scripts/api', () => ({
api: {
fetchApi: vi.fn()
}
}))
describe('FileNameMappingService', () => {
let service: FileNameMappingService
beforeEach(() => {
vi.clearAllMocks()
// Create a new instance for each test to avoid cache pollution
service = new FileNameMappingService()
})
describe('getMapping', () => {
it('should fetch mappings from API', async () => {
const mockData: FileNameMapping = {
'abc123.png': 'vacation_photo.png',
'def456.jpg': 'profile_picture.jpg'
}
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData
} as any)
const result = await service.getMapping('input')
expect(api.fetchApi).toHaveBeenCalledWith('/files/mappings')
expect(result).toEqual(mockData)
})
it('should cache mappings and not refetch within TTL', async () => {
const mockData: FileNameMapping = {
'abc123.png': 'vacation_photo.png'
}
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData
} as any)
// First call
await service.getMapping('input')
expect(api.fetchApi).toHaveBeenCalledTimes(1)
// Second call should use cache
const result = await service.getMapping('input')
expect(api.fetchApi).toHaveBeenCalledTimes(1)
expect(result).toEqual(mockData)
})
it('should return empty object on API failure', async () => {
vi.mocked(api.fetchApi).mockRejectedValue(new Error('Network error'))
const result = await service.getMapping('input')
expect(result).toEqual({})
})
it('should return empty object on non-200 response', async () => {
vi.mocked(api.fetchApi).mockResolvedValue({
ok: false,
status: 404,
statusText: 'Not Found'
} as any)
const result = await service.getMapping('input')
expect(result).toEqual({})
})
})
describe('getCachedMapping', () => {
it('should return empty object if no cached data', () => {
const result = service.getCachedMapping('input')
expect(result).toEqual({})
})
it('should return cached data after successful fetch', async () => {
const mockData: FileNameMapping = {
'abc123.png': 'vacation_photo.png'
}
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData
} as any)
await service.getMapping('input')
const result = service.getCachedMapping('input')
expect(result).toEqual(mockData)
})
})
describe('getCachedReverseMapping', () => {
it('should return reverse mapping (human -> hash)', async () => {
const mockData: FileNameMapping = {
'abc123.png': 'vacation_photo.png',
'def456.jpg': 'profile_picture.jpg'
}
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData
} as any)
await service.getMapping('input')
const reverseMapping = service.getCachedReverseMapping('input')
expect(reverseMapping).toEqual({
'vacation_photo.png': 'abc123.png',
'profile_picture.jpg': 'def456.jpg'
})
})
it('should return empty object if no cached data', () => {
const result = service.getCachedReverseMapping('input')
expect(result).toEqual({})
})
})
describe('getHashFromHumanName', () => {
it('should convert human name to hash', async () => {
const mockData: FileNameMapping = {
'abc123.png': 'vacation_photo.png'
}
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData
} as any)
await service.getMapping('input')
const hash = service.getHashFromHumanName('vacation_photo.png', 'input')
expect(hash).toBe('abc123.png')
})
it('should return original name if no mapping exists', async () => {
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => ({})
} as any)
await service.getMapping('input')
const result = service.getHashFromHumanName('unknown.png', 'input')
expect(result).toBe('unknown.png')
})
})
describe('getHumanReadableName', () => {
it('should convert hash to human-readable name', async () => {
const mockData: FileNameMapping = {
'abc123.png': 'vacation_photo.png'
}
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData
} as any)
const humanName = await service.getHumanReadableName(
'abc123.png',
'input'
)
expect(humanName).toBe('vacation_photo.png')
})
it('should return hash if no mapping exists', async () => {
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => ({})
} as any)
const result = await service.getHumanReadableName('xyz789.png', 'input')
expect(result).toBe('xyz789.png')
})
})
describe('refreshMapping', () => {
it('should invalidate cache and fetch fresh data', async () => {
const mockData1: FileNameMapping = {
'abc123.png': 'old_photo.png'
}
const mockData2: FileNameMapping = {
'def456.png': 'new_photo.png'
}
vi.mocked(api.fetchApi)
.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockData1
} as any)
.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => mockData2
} as any)
// First fetch
await service.getMapping('input')
expect(service.getCachedMapping('input')).toEqual(mockData1)
// Refresh should fetch new data
const refreshedData = await service.refreshMapping('input')
expect(api.fetchApi).toHaveBeenCalledTimes(2)
expect(refreshedData).toEqual(mockData2)
expect(service.getCachedMapping('input')).toEqual(mockData2)
})
})
describe('invalidateCache', () => {
it('should clear cache for specific file type', async () => {
const mockData: FileNameMapping = {
'abc123.png': 'photo.png'
}
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData
} as any)
await service.getMapping('input')
expect(service.getCachedMapping('input')).toEqual(mockData)
service.invalidateCache('input')
expect(service.getCachedMapping('input')).toEqual({})
})
it('should clear all caches when no type specified', async () => {
const mockData: FileNameMapping = {
'abc123.png': 'photo.png'
}
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData
} as any)
await service.getMapping('input')
await service.getMapping('output')
service.invalidateCache()
expect(service.getCachedMapping('input')).toEqual({})
expect(service.getCachedMapping('output')).toEqual({})
})
})
describe('ensureMappingsLoaded', () => {
it('should preload mappings for immediate synchronous access', async () => {
const mockData: FileNameMapping = {
'abc123.png': 'photo.png'
}
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData
} as any)
// Ensure mappings are loaded
await service.ensureMappingsLoaded('input')
// Should be available synchronously
const cached = service.getCachedMapping('input')
expect(cached).toEqual(mockData)
})
it('should not throw on API failure', async () => {
vi.mocked(api.fetchApi).mockRejectedValue(new Error('Network error'))
// Should not throw
await expect(service.ensureMappingsLoaded('input')).resolves.not.toThrow()
// Should have empty mapping
expect(service.getCachedMapping('input')).toEqual({})
})
})
describe('applyMappingToArray', () => {
it('should apply mapping to array of filenames', async () => {
const mockData: FileNameMapping = {
'abc123.png': 'vacation.png',
'def456.jpg': 'profile.jpg'
}
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData
} as any)
const result = await service.applyMappingToArray(
['abc123.png', 'def456.jpg', 'unknown.gif'],
'input'
)
expect(result).toEqual(['vacation.png', 'profile.jpg', 'unknown.gif'])
})
it('should return original array on API failure', async () => {
vi.mocked(api.fetchApi).mockRejectedValue(new Error('Network error'))
const input = ['abc123.png', 'def456.jpg']
const result = await service.applyMappingToArray(input, 'input')
expect(result).toEqual(input)
})
})
describe('edge cases', () => {
it('should handle invalid JSON response gracefully', async () => {
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => {
throw new Error('Invalid JSON')
}
} as any)
const result = await service.getMapping('input')
expect(result).toEqual({})
})
it('should filter out invalid entries from response', async () => {
const mockData = {
'valid.png': 'photo.png',
invalid: 123, // Invalid value type - will be filtered
123: 'number_key', // Numeric key becomes string "123" in JS
'another_valid.jpg': 'image.jpg'
}
vi.mocked(api.fetchApi).mockResolvedValue({
ok: true,
status: 200,
json: async () => mockData
} as any)
const result = await service.getMapping('input')
// Should filter out non-string values but keep string keys (including coerced numeric keys)
expect(result).toEqual({
'valid.png': 'photo.png',
'123': 'number_key', // Numeric key becomes string
'another_valid.jpg': 'image.jpg'
})
})
it('should handle null or array responses', async () => {
// Test null response
vi.mocked(api.fetchApi).mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => null
} as any)
let result = await service.getMapping('input')
expect(result).toEqual({})
// Test array response
vi.mocked(api.fetchApi).mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => []
} as any)
result = await service.getMapping('output')
expect(result).toEqual({})
})
})
})