Compare commits

...

5 Commits

Author SHA1 Message Date
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
bymyself
81ddfc2f58 [fix] Update firebaseAuthStore tests to mock axios client
- Move axios mock setup before store import to ensure client is mocked
- Set up default successful responses for customer API endpoints
- Fix TypeScript issues with isAxiosError mock type
2025-08-15 14:40:29 -07:00
bymyself
e19f0b2da9 [fix] Add missing await for response.json() calls in template workflows 2025-08-15 14:14:03 -07:00
bymyself
afecff6c94 [feat] Complete network call consolidation to use consistent clients
Template Workflows:
- Replace direct fetch with api.fetchApi() for API endpoints
- Keep direct fetch for static file URLs (already using api.fileURL())

Model Exporter:
- Add logic to distinguish ComfyUI URLs from external URLs
- Use api.apiURL() for ComfyUI URLs, direct fetch for external URLs
- Maintain existing download functionality

Other files already following correct patterns:
- Upload Audio: Already using api.fetchApi()
- 3D Loading Utils: Already using api.fetchApi() (fetch call is for blob conversion)
- Download Utility: Uses direct fetch for external URLs (correct)

All network calls now use consistent client patterns where appropriate.
2025-08-15 13:34:37 -07:00
bymyself
5afeee258f [feat] Consolidate firebaseAuthStore network calls to use axios client
- Replace all direct fetch() calls with axios client instance
- Maintain exact same error handling behavior and logic flow
- Use consistent pattern with other services (customerEventsService)
- All customer API endpoints now use centralized client
- Prepares for header registration system implementation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-15 12:28:52 -07:00
4 changed files with 140 additions and 77 deletions

View File

@@ -160,12 +160,15 @@ export function useTemplateWorkflows() {
*/
const fetchTemplateJson = async (id: string, sourceModule: string) => {
if (sourceModule === 'default') {
// Default templates provided by frontend are served on this separate endpoint
return fetch(api.fileURL(`/templates/${id}.json`)).then((r) => r.json())
// Default templates provided by frontend are served as static files
const response = await fetch(api.fileURL(`/templates/${id}.json`))
return await response.json()
} else {
return fetch(
api.apiURL(`/workflow_templates/${sourceModule}/${id}.json`)
).then((r) => r.json())
// Custom node templates served via API
const response = await api.fetchApi(
`/workflow_templates/${sourceModule}/${id}.json`
)
return await response.json()
}
}

View File

@@ -4,6 +4,7 @@ import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter'
import { STLExporter } from 'three/examples/jsm/exporters/STLExporter'
import { t } from '@/i18n'
import { api } from '@/scripts/api'
import { useToastStore } from '@/stores/toastStore'
export class ModelExporter {
@@ -36,7 +37,18 @@ export class ModelExporter {
desiredFilename: string
): Promise<void> {
try {
const response = await fetch(url)
// Check if this is a ComfyUI relative URL
const isComfyUrl = url.startsWith('/') || url.includes('/view?')
let response: Response
if (isComfyUrl) {
// Use ComfyUI API client for internal URLs
response = await fetch(api.apiURL(url))
} else {
// Use direct fetch for external URLs
response = await fetch(url)
}
const blob = await response.blob()
const link = document.createElement('a')

View File

@@ -1,3 +1,4 @@
import axios from 'axios'
import {
type Auth,
GithubAuthProvider,
@@ -44,6 +45,14 @@ export class FirebaseAuthStoreError extends Error {
}
}
// Customer API client - follows the same pattern as other services
const customerApiClient = axios.create({
baseURL: COMFY_API_BASE_URL,
headers: {
'Content-Type': 'application/json'
}
})
export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
// State
const loading = ref(false)
@@ -129,27 +138,27 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
)
}
const response = await fetch(`${COMFY_API_BASE_URL}/customers/balance`, {
headers: {
...authHeader,
'Content-Type': 'application/json'
let balanceData
try {
const response = await customerApiClient.get('/customers/balance', {
headers: authHeader
})
balanceData = response.data
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
if (error.response.status === 404) {
// Customer not found is expected for new users
return null
}
const errorData = error.response.data
throw new FirebaseAuthStoreError(
t('toastMessages.failedToFetchBalance', {
error: errorData.message
})
)
}
})
if (!response.ok) {
if (response.status === 404) {
// Customer not found is expected for new users
return null
}
const errorData = await response.json()
throw new FirebaseAuthStoreError(
t('toastMessages.failedToFetchBalance', {
error: errorData.message
})
)
throw error
}
const balanceData = await response.json()
// Update the last balance update time
lastBalanceUpdateTime.value = new Date()
balance.value = balanceData
@@ -165,23 +174,26 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated'))
}
const createCustomerRes = await fetch(`${COMFY_API_BASE_URL}/customers`, {
method: 'POST',
headers: {
...authHeader,
'Content-Type': 'application/json'
}
})
if (!createCustomerRes.ok) {
throw new FirebaseAuthStoreError(
t('toastMessages.failedToCreateCustomer', {
error: createCustomerRes.statusText
})
let createCustomerResJson: CreateCustomerResponse
try {
const createCustomerRes = await customerApiClient.post(
'/customers',
{},
{
headers: authHeader
}
)
createCustomerResJson = createCustomerRes.data
} catch (error) {
if (axios.isAxiosError(error)) {
throw new FirebaseAuthStoreError(
t('toastMessages.failedToCreateCustomer', {
error: error.response?.statusText || error.message
})
)
}
throw error
}
const createCustomerResJson: CreateCustomerResponse =
await createCustomerRes.json()
if (!createCustomerResJson?.id) {
throw new FirebaseAuthStoreError(
t('toastMessages.failedToCreateCustomer', {
@@ -282,25 +294,26 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
customerCreated.value = true
}
const response = await fetch(`${COMFY_API_BASE_URL}/customers/credit`, {
method: 'POST',
headers: {
...authHeader,
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBodyContent)
})
if (!response.ok) {
const errorData = await response.json()
throw new FirebaseAuthStoreError(
t('toastMessages.failedToInitiateCreditPurchase', {
error: errorData.message
})
try {
const response = await customerApiClient.post(
'/customers/credit',
requestBodyContent,
{
headers: authHeader
}
)
return response.data
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
const errorData = error.response.data
throw new FirebaseAuthStoreError(
t('toastMessages.failedToInitiateCreditPurchase', {
error: errorData.message
})
)
}
throw error
}
return response.json()
}
const initiateCreditPurchase = async (
@@ -316,27 +329,26 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated'))
}
const response = await fetch(`${COMFY_API_BASE_URL}/customers/billing`, {
method: 'POST',
headers: {
...authHeader,
'Content-Type': 'application/json'
},
...(requestBody && {
body: JSON.stringify(requestBody)
})
})
if (!response.ok) {
const errorData = await response.json()
throw new FirebaseAuthStoreError(
t('toastMessages.failedToAccessBillingPortal', {
error: errorData.message
})
try {
const response = await customerApiClient.post(
'/customers/billing',
requestBody,
{
headers: authHeader
}
)
return response.data
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
const errorData = error.response.data
throw new FirebaseAuthStoreError(
t('toastMessages.failedToAccessBillingPortal', {
error: errorData.message
})
)
}
throw error
}
return response.json()
}
return {

View File

@@ -1,3 +1,4 @@
import axios from 'axios'
import * as firebaseAuth from 'firebase/auth'
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
@@ -5,6 +6,30 @@ 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)
@@ -91,7 +116,18 @@ describe('useFirebaseAuthStore', () => {
}
beforeEach(() => {
vi.resetAllMocks()
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)