Files
ComfyUI_frontend/tests-ui/tests/store/firebaseAuthStore.test.ts
bymyself 2e80a5789c [test] Fix axios mocking in firebaseAuthStore tests
Use vi.mock() with factory function to ensure axios mock is set up before module imports. This fixes test failures caused by the store creating axios client at module level.

Fixes test execution in CI
2025-08-15 15:37:06 -07:00

454 lines
13 KiB
TypeScript

import axios from 'axios'
import * as firebaseAuth from 'firebase/auth'
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import * as vuefire from 'vuefire'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
// Mock axios before any imports that use it
vi.mock('axios', () => {
const mockAxiosInstance = {
get: vi.fn().mockResolvedValue({
data: { balance: { credits: 0 } },
status: 200
}),
post: vi.fn().mockResolvedValue({
data: { id: 'test-customer-id' },
status: 201
})
}
return {
default: {
create: vi.fn().mockReturnValue(mockAxiosInstance),
isAxiosError: vi.fn().mockImplementation(() => false)
}
}
})
const mockedAxios = vi.mocked(axios)
const mockAxiosInstance = mockedAxios.create() as any
// Mock fetch
const mockFetch = vi.fn()
vi.stubGlobal('fetch', mockFetch)
// Mock successful API responses
const mockCreateCustomerResponse = {
ok: true,
statusText: 'OK',
json: () => Promise.resolve({ id: 'test-customer-id' })
}
const mockFetchBalanceResponse = {
ok: true,
json: () => Promise.resolve({ balance: 0 })
}
const mockAddCreditsResponse = {
ok: true,
statusText: 'OK'
}
const mockAccessBillingPortalResponse = {
ok: true,
statusText: 'OK'
}
vi.mock('vuefire', () => ({
useFirebaseAuth: vi.fn()
}))
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key: string) => key
}),
createI18n: () => ({
global: {
t: (key: string) => key
}
})
}))
vi.mock('firebase/auth', () => ({
signInWithEmailAndPassword: vi.fn(),
createUserWithEmailAndPassword: vi.fn(),
signOut: vi.fn(),
onAuthStateChanged: vi.fn(),
signInWithPopup: vi.fn(),
GoogleAuthProvider: class {
setCustomParameters = vi.fn()
},
GithubAuthProvider: class {
setCustomParameters = vi.fn()
},
browserLocalPersistence: 'browserLocalPersistence',
setPersistence: vi.fn().mockResolvedValue(undefined)
}))
// Mock useToastStore
vi.mock('@/stores/toastStore', () => ({
useToastStore: () => ({
add: vi.fn()
})
}))
// Mock useDialogService
vi.mock('@/services/dialogService', () => ({
useDialogService: () => ({
showSettingsDialog: vi.fn()
})
}))
describe('useFirebaseAuthStore', () => {
let store: ReturnType<typeof useFirebaseAuthStore>
let authStateCallback: (user: any) => void
const mockAuth = {
/* mock Auth object */
}
const mockUser = {
uid: 'test-user-id',
email: 'test@example.com',
getIdToken: vi.fn().mockResolvedValue('mock-id-token')
}
beforeEach(() => {
vi.clearAllMocks()
// Reset axios mock responses to defaults
mockAxiosInstance.get.mockResolvedValue({
data: { balance: { credits: 0 } },
status: 200
})
mockAxiosInstance.post.mockResolvedValue({
data: { id: 'test-customer-id' },
status: 201
})
;(mockedAxios.isAxiosError as any).mockReturnValue(false)
// Mock useFirebaseAuth to return our mock auth object
vi.mocked(vuefire.useFirebaseAuth).mockReturnValue(mockAuth as any)
// Mock onAuthStateChanged to capture the callback and simulate initial auth state
vi.mocked(firebaseAuth.onAuthStateChanged).mockImplementation(
(_, callback) => {
authStateCallback = callback as (user: any) => void
// Call the callback with our mock user
;(callback as (user: any) => void)(mockUser)
// Return an unsubscribe function
return vi.fn()
}
)
// Mock fetch responses
mockFetch.mockImplementation((url: string) => {
if (url.endsWith('/customers')) {
return Promise.resolve(mockCreateCustomerResponse)
}
if (url.endsWith('/customers/balance')) {
return Promise.resolve(mockFetchBalanceResponse)
}
if (url.endsWith('/customers/credit')) {
return Promise.resolve(mockAddCreditsResponse)
}
if (url.endsWith('/customers/billing-portal')) {
return Promise.resolve(mockAccessBillingPortalResponse)
}
return Promise.reject(new Error('Unexpected API call'))
})
// Initialize Pinia
setActivePinia(createPinia())
store = useFirebaseAuthStore()
// Reset and set up getIdToken mock
mockUser.getIdToken.mockReset()
mockUser.getIdToken.mockResolvedValue('mock-id-token')
})
it('should initialize with the current user', () => {
expect(store.currentUser).toEqual(mockUser)
expect(store.isAuthenticated).toBe(true)
expect(store.userEmail).toBe('test@example.com')
expect(store.userId).toBe('test-user-id')
expect(store.loading).toBe(false)
})
it('should set persistence to local storage on initialization', () => {
expect(firebaseAuth.setPersistence).toHaveBeenCalledWith(
mockAuth,
firebaseAuth.browserLocalPersistence
)
})
it('should properly clean up error state between operations', async () => {
// First, cause an error
const mockError = new Error('Invalid password')
vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockRejectedValueOnce(
mockError
)
try {
await store.login('test@example.com', 'wrong-password')
} catch (e) {
// Error expected
}
// Now, succeed on next attempt
vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockResolvedValueOnce({
user: mockUser
} as any)
await store.login('test@example.com', 'correct-password')
})
describe('login', () => {
it('should login with valid credentials', async () => {
const mockUserCredential = { user: mockUser }
vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockResolvedValue(
mockUserCredential as any
)
const result = await store.login('test@example.com', 'password')
expect(firebaseAuth.signInWithEmailAndPassword).toHaveBeenCalledWith(
mockAuth,
'test@example.com',
'password'
)
expect(result).toEqual(mockUserCredential)
expect(store.loading).toBe(false)
})
it('should handle login errors', async () => {
const mockError = new Error('Invalid password')
vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockRejectedValue(
mockError
)
await expect(
store.login('test@example.com', 'wrong-password')
).rejects.toThrow('Invalid password')
expect(firebaseAuth.signInWithEmailAndPassword).toHaveBeenCalledWith(
mockAuth,
'test@example.com',
'wrong-password'
)
expect(store.loading).toBe(false)
})
it('should handle concurrent login attempts correctly', async () => {
// Set up multiple login promises
const mockUserCredential = { user: mockUser }
vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockResolvedValue(
mockUserCredential as any
)
const loginPromise1 = store.login('user1@example.com', 'password1')
const loginPromise2 = store.login('user2@example.com', 'password2')
// Resolve both promises
await Promise.all([loginPromise1, loginPromise2])
// Verify the loading state is reset
expect(store.loading).toBe(false)
})
})
describe('register', () => {
it('should register a new user', async () => {
const mockUserCredential = { user: mockUser }
vi.mocked(firebaseAuth.createUserWithEmailAndPassword).mockResolvedValue(
mockUserCredential as any
)
const result = await store.register('new@example.com', 'password')
expect(firebaseAuth.createUserWithEmailAndPassword).toHaveBeenCalledWith(
mockAuth,
'new@example.com',
'password'
)
expect(result).toEqual(mockUserCredential)
expect(store.loading).toBe(false)
})
it('should handle registration errors', async () => {
const mockError = new Error('Email already in use')
vi.mocked(firebaseAuth.createUserWithEmailAndPassword).mockRejectedValue(
mockError
)
await expect(
store.register('existing@example.com', 'password')
).rejects.toThrow('Email already in use')
expect(firebaseAuth.createUserWithEmailAndPassword).toHaveBeenCalledWith(
mockAuth,
'existing@example.com',
'password'
)
expect(store.loading).toBe(false)
})
})
describe('logout', () => {
it('should sign out the user', async () => {
vi.mocked(firebaseAuth.signOut).mockResolvedValue(undefined)
await store.logout()
expect(firebaseAuth.signOut).toHaveBeenCalledWith(mockAuth)
})
it('should handle logout errors', async () => {
const mockError = new Error('Network error')
vi.mocked(firebaseAuth.signOut).mockRejectedValue(mockError)
await expect(store.logout()).rejects.toThrow('Network error')
expect(firebaseAuth.signOut).toHaveBeenCalledWith(mockAuth)
})
})
describe('getIdToken', () => {
it('should return the user ID token', async () => {
// FIX 2: Reset the mock and set a specific return value
mockUser.getIdToken.mockReset()
mockUser.getIdToken.mockResolvedValue('mock-id-token')
const token = await store.getIdToken()
expect(mockUser.getIdToken).toHaveBeenCalled()
expect(token).toBe('mock-id-token')
})
it('should return null when no user is logged in', async () => {
// Simulate logged out state
authStateCallback(null)
const token = await store.getIdToken()
expect(token).toBeNull()
})
it('should return null for token after login and logout sequence', async () => {
// Setup mock for login
const mockUserCredential = { user: mockUser }
vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockResolvedValue(
mockUserCredential as any
)
// Login
await store.login('test@example.com', 'password')
// Simulate successful auth state update after login
authStateCallback(mockUser)
// Verify we're logged in and can get a token
mockUser.getIdToken.mockReset()
mockUser.getIdToken.mockResolvedValue('mock-id-token')
expect(await store.getIdToken()).toBe('mock-id-token')
// Setup mock for logout
vi.mocked(firebaseAuth.signOut).mockResolvedValue(undefined)
// Logout
await store.logout()
// Simulate successful auth state update after logout
authStateCallback(null)
// Verify token is null after logout
const tokenAfterLogout = await store.getIdToken()
expect(tokenAfterLogout).toBeNull()
})
})
describe('social authentication', () => {
describe('loginWithGoogle', () => {
it('should sign in with Google', async () => {
const mockUserCredential = { user: mockUser }
vi.mocked(firebaseAuth.signInWithPopup).mockResolvedValue(
mockUserCredential as any
)
const result = await store.loginWithGoogle()
expect(firebaseAuth.signInWithPopup).toHaveBeenCalledWith(
mockAuth,
expect.any(firebaseAuth.GoogleAuthProvider)
)
expect(result).toEqual(mockUserCredential)
expect(store.loading).toBe(false)
})
it('should handle Google sign in errors', async () => {
const mockError = new Error('Google authentication failed')
vi.mocked(firebaseAuth.signInWithPopup).mockRejectedValue(mockError)
await expect(store.loginWithGoogle()).rejects.toThrow(
'Google authentication failed'
)
expect(firebaseAuth.signInWithPopup).toHaveBeenCalledWith(
mockAuth,
expect.any(firebaseAuth.GoogleAuthProvider)
)
expect(store.loading).toBe(false)
})
})
describe('loginWithGithub', () => {
it('should sign in with Github', async () => {
const mockUserCredential = { user: mockUser }
vi.mocked(firebaseAuth.signInWithPopup).mockResolvedValue(
mockUserCredential as any
)
const result = await store.loginWithGithub()
expect(firebaseAuth.signInWithPopup).toHaveBeenCalledWith(
mockAuth,
expect.any(firebaseAuth.GithubAuthProvider)
)
expect(result).toEqual(mockUserCredential)
expect(store.loading).toBe(false)
})
it('should handle Github sign in errors', async () => {
const mockError = new Error('Github authentication failed')
vi.mocked(firebaseAuth.signInWithPopup).mockRejectedValue(mockError)
await expect(store.loginWithGithub()).rejects.toThrow(
'Github authentication failed'
)
expect(firebaseAuth.signInWithPopup).toHaveBeenCalledWith(
mockAuth,
expect.any(firebaseAuth.GithubAuthProvider)
)
expect(store.loading).toBe(false)
})
})
it('should handle concurrent social login attempts correctly', async () => {
const mockUserCredential = { user: mockUser }
vi.mocked(firebaseAuth.signInWithPopup).mockResolvedValue(
mockUserCredential as any
)
const googleLoginPromise = store.loginWithGoogle()
const githubLoginPromise = store.loginWithGithub()
await Promise.all([googleLoginPromise, githubLoginPromise])
expect(store.loading).toBe(false)
})
})
})