Files
ComfyUI_frontend/tests-ui/tests/services/customerEventsService.test.ts
Terry Jia 5bbed91295 usage log table (#4288)
Co-authored-by: github-actions <github-actions@github.com>
2025-06-30 12:13:01 -07:00

427 lines
12 KiB
TypeScript

import axios from 'axios'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
EventType,
useCustomerEventsService
} from '@/services/customerEventsService'
// Hoist the mocks to avoid hoisting issues
const mockAxiosInstance = vi.hoisted(() => ({
get: vi.fn()
}))
const mockFirebaseAuthStore = vi.hoisted(() => ({
getAuthHeader: vi.fn()
}))
const mockI18n = vi.hoisted(() => ({
d: vi.fn()
}))
// Mock dependencies
vi.mock('axios', () => ({
default: {
create: vi.fn(() => mockAxiosInstance),
isAxiosError: vi.fn()
}
}))
vi.mock('@/stores/firebaseAuthStore', () => ({
useFirebaseAuthStore: vi.fn(() => mockFirebaseAuthStore)
}))
vi.mock('vue-i18n', () => ({
useI18n: vi.fn(() => mockI18n)
}))
vi.mock('@/utils/typeGuardUtil', () => ({
isAbortError: vi.fn()
}))
describe('useCustomerEventsService', () => {
let service: ReturnType<typeof useCustomerEventsService>
const mockAuthHeaders = {
Authorization: 'Bearer mock-token',
'Content-Type': 'application/json'
}
const mockEventsResponse = {
events: [
{
event_id: 'event-1',
event_type: 'credit_added',
params: {
amount: 1000,
transaction_id: 'txn-123',
payment_method: 'stripe'
},
createdAt: '2024-01-01T10:00:00Z'
},
{
event_id: 'event-2',
event_type: 'api_usage_completed',
params: {
api_name: 'Image Generation',
model: 'sdxl-base',
duration: 5000,
cost: 50
},
createdAt: '2024-01-02T10:00:00Z'
}
],
total: 2,
page: 1,
limit: 10,
totalPages: 1
}
beforeEach(() => {
vi.clearAllMocks()
// Setup default mocks
mockFirebaseAuthStore.getAuthHeader.mockResolvedValue(mockAuthHeaders)
mockI18n.d.mockImplementation((date, options) => {
// Mock i18n date formatting
if (options?.month === 'short') {
return date.toLocaleString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
}
return date.toLocaleString()
})
service = useCustomerEventsService()
})
describe('initialization', () => {
it('should initialize with default state', () => {
expect(service.isLoading.value).toBe(false)
expect(service.error.value).toBeNull()
})
it('should initialize i18n date formatter', () => {
expect(mockI18n.d).toBeDefined()
})
})
describe('getMyEvents', () => {
it('should fetch events successfully', async () => {
mockAxiosInstance.get.mockResolvedValue({ data: mockEventsResponse })
const result = await service.getMyEvents({
page: 1,
limit: 10
})
expect(mockFirebaseAuthStore.getAuthHeader).toHaveBeenCalled()
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/customers/events', {
params: { page: 1, limit: 10 },
headers: mockAuthHeaders
})
expect(result).toEqual(mockEventsResponse)
expect(service.isLoading.value).toBe(false)
expect(service.error.value).toBeNull()
})
it('should use default parameters when none provided', async () => {
mockAxiosInstance.get.mockResolvedValue({ data: mockEventsResponse })
await service.getMyEvents()
expect(mockAxiosInstance.get).toHaveBeenCalledWith('/customers/events', {
params: { page: 1, limit: 10 },
headers: mockAuthHeaders
})
})
it('should return null when auth headers are missing', async () => {
mockFirebaseAuthStore.getAuthHeader.mockResolvedValue(null)
const result = await service.getMyEvents()
expect(result).toBeNull()
expect(service.error.value).toBe('Authentication header is missing')
expect(mockAxiosInstance.get).not.toHaveBeenCalled()
})
it('should handle 400 errors', async () => {
const errorResponse = {
response: {
status: 400,
data: { message: 'Invalid input' }
}
}
mockAxiosInstance.get.mockRejectedValue(errorResponse)
vi.mocked(axios.isAxiosError).mockReturnValue(true)
const result = await service.getMyEvents()
expect(result).toBeNull()
expect(service.error.value).toBe('Invalid input, object invalid')
})
it('should handle 404 errors', async () => {
const errorResponse = {
response: {
status: 404,
data: { message: 'Not found' }
}
}
mockAxiosInstance.get.mockRejectedValue(errorResponse)
vi.mocked(axios.isAxiosError).mockReturnValue(true)
const result = await service.getMyEvents()
expect(result).toBeNull()
expect(service.error.value).toBe('Not found')
})
it('should handle network errors', async () => {
const networkError = new Error('Network Error')
mockAxiosInstance.get.mockRejectedValue(networkError)
vi.mocked(axios.isAxiosError).mockReturnValue(false)
const result = await service.getMyEvents()
expect(result).toBeNull()
expect(service.error.value).toBe(
'Fetching customer events failed: Network Error'
)
})
})
describe('formatEventType', () => {
it('should format known event types correctly', () => {
expect(service.formatEventType(EventType.CREDIT_ADDED)).toBe(
'Credits Added'
)
expect(service.formatEventType(EventType.ACCOUNT_CREATED)).toBe(
'Account Created'
)
expect(service.formatEventType(EventType.API_USAGE_COMPLETED)).toBe(
'API Usage'
)
})
it('should return the original string for unknown event types', () => {
expect(service.formatEventType('unknown_event')).toBe('unknown_event')
})
})
describe('getEventSeverity', () => {
it('should return correct severity for known event types', () => {
expect(service.getEventSeverity(EventType.CREDIT_ADDED)).toBe('success')
expect(service.getEventSeverity(EventType.ACCOUNT_CREATED)).toBe('info')
expect(service.getEventSeverity(EventType.API_USAGE_COMPLETED)).toBe(
'warning'
)
})
it('should return default severity for unknown event types', () => {
expect(service.getEventSeverity('unknown_event')).toBe('info')
})
})
describe('formatAmount', () => {
it('should format amounts correctly', () => {
expect(service.formatAmount(1000)).toBe('10.00')
expect(service.formatAmount(2550)).toBe('25.50')
expect(service.formatAmount(100)).toBe('1.00')
})
it('should handle undefined amounts', () => {
expect(service.formatAmount(undefined)).toBe('0.00')
expect(service.formatAmount(0)).toBe('0.00')
})
})
describe('formatDate', () => {
it('should use i18n date formatter', () => {
const dateString = '2024-01-01T10:00:00Z'
service.formatDate(dateString)
expect(mockI18n.d).toHaveBeenCalledWith(new Date(dateString), {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
})
it('should return formatted date string', () => {
const dateString = '2024-01-01T10:00:00Z'
const result = service.formatDate(dateString)
expect(typeof result).toBe('string')
expect(result.length).toBeGreaterThan(0)
})
})
describe('hasAdditionalInfo', () => {
it('should return true when event has additional parameters', () => {
const event = {
event_id: 'test',
event_type: 'api_usage_completed',
params: {
api_name: 'test-api',
model: 'test-model',
duration: 1000,
extra_param: 'extra_value'
},
createdAt: '2024-01-01T10:00:00Z'
}
expect(service.hasAdditionalInfo(event)).toBe(true)
})
it('should return false when event only has known parameters', () => {
const event = {
event_id: 'test',
event_type: 'api_usage_completed',
params: {
amount: 1000,
api_name: 'test-api',
model: 'test-model'
},
createdAt: '2024-01-01T10:00:00Z'
}
expect(service.hasAdditionalInfo(event)).toBe(false)
})
it('should return false when params is undefined', () => {
const event = {
event_id: 'test',
event_type: 'account_created',
params: undefined,
createdAt: '2024-01-01T10:00:00Z'
}
expect(service.hasAdditionalInfo(event)).toBe(false)
})
})
describe('getTooltipContent', () => {
it('should generate HTML tooltip content for all parameters', () => {
const event = {
event_id: 'test',
event_type: 'api_usage_completed',
params: {
transaction_id: 'txn-123',
duration: 5000,
status: 'completed'
},
createdAt: '2024-01-01T10:00:00Z'
}
const result = service.getTooltipContent(event)
expect(result).toContain('<strong>Transaction Id:</strong> txn-123')
expect(result).toContain('<strong>Duration:</strong> 5,000')
expect(result).toContain('<strong>Status:</strong> completed')
expect(result).toContain('<br>')
})
it('should return empty string when no parameters', () => {
const event = {
event_id: 'test',
event_type: 'account_created',
params: {},
createdAt: '2024-01-01T10:00:00Z'
}
expect(service.getTooltipContent(event)).toBe('')
})
it('should handle undefined params', () => {
const event = {
event_id: 'test',
event_type: 'account_created',
params: undefined,
createdAt: '2024-01-01T10:00:00Z'
}
expect(service.getTooltipContent(event)).toBe('')
})
})
describe('formatJsonKey', () => {
it('should format keys correctly', () => {
expect(service.formatJsonKey('transaction_id')).toBe('Transaction Id')
expect(service.formatJsonKey('api_name')).toBe('Api Name')
expect(service.formatJsonKey('simple')).toBe('Simple')
})
})
describe('formatJsonValue', () => {
it('should format numbers with commas', () => {
expect(service.formatJsonValue(1000)).toBe('1,000')
expect(service.formatJsonValue(1234567)).toBe('1,234,567')
})
it('should format date strings', () => {
const dateString = '2024-01-01T10:00:00Z'
const result = service.formatJsonValue(dateString)
expect(typeof result).toBe('string')
expect(result).not.toBe(dateString) // Should be formatted
})
})
describe('error handling edge cases', () => {
it('should handle non-Error objects', async () => {
const stringError = 'String error'
mockAxiosInstance.get.mockRejectedValue(stringError)
vi.mocked(axios.isAxiosError).mockReturnValue(false)
const result = await service.getMyEvents()
expect(result).toBeNull()
expect(service.error.value).toBe(
'Fetching customer events failed: String error'
)
})
it('should reset error state on new request', async () => {
// First request fails
mockAxiosInstance.get.mockRejectedValueOnce(new Error('First error'))
await service.getMyEvents()
expect(service.error.value).toBeTruthy()
// Second request succeeds
mockAxiosInstance.get.mockResolvedValueOnce({ data: mockEventsResponse })
await service.getMyEvents()
expect(service.error.value).toBeNull()
})
})
describe('EventType enum', () => {
it('should have correct enum values', () => {
expect(EventType.CREDIT_ADDED).toBe('credit_added')
expect(EventType.ACCOUNT_CREATED).toBe('account_created')
expect(EventType.API_USAGE_STARTED).toBe('api_usage_started')
expect(EventType.API_USAGE_COMPLETED).toBe('api_usage_completed')
})
})
describe('edge cases for formatting functions', () => {
it('formatJsonKey should handle empty strings', () => {
expect(service.formatJsonKey('')).toBe('')
})
it('formatJsonKey should handle single words', () => {
expect(service.formatJsonKey('test')).toBe('Test')
})
it('formatAmount should handle very large numbers', () => {
expect(service.formatAmount(999999999)).toBe('9999999.99')
})
})
})