mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
feat: Wire authentication header system with auth stores
- Create AuthHeaderProvider that integrates with Firebase and API key stores - Add core extension to register auth provider during preInit - Implement automatic auth header injection for all HTTP requests - Add comprehensive unit and integration tests - Include examples showing migration from manual to automatic auth This completes the header registration system by connecting it to the actual authentication mechanisms in ComfyUI. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
180
src/examples/authHeaderAutoInjection.ts
Normal file
180
src/examples/authHeaderAutoInjection.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Example showing how authentication headers are automatically injected
|
||||
* with the new header registration system.
|
||||
*
|
||||
* Before: Services had to manually retrieve and add auth headers
|
||||
* After: Headers are automatically injected via the network adapters
|
||||
*/
|
||||
import {
|
||||
createAxiosWithHeaders,
|
||||
fetchWithHeaders
|
||||
} from '@/services/networkClientAdapter'
|
||||
|
||||
// ============================================
|
||||
// BEFORE: Manual header management
|
||||
// ============================================
|
||||
|
||||
// This is how services used to handle auth headers:
|
||||
/*
|
||||
import axios from 'axios'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
|
||||
export async function oldWayToMakeRequest() {
|
||||
// Had to manually get auth headers
|
||||
const authHeaders = await useFirebaseAuthStore().getAuthHeader()
|
||||
|
||||
if (!authHeaders) {
|
||||
throw new Error('Not authenticated')
|
||||
}
|
||||
|
||||
// Had to manually add headers to each request
|
||||
const response = await axios.get('/api/data', {
|
||||
headers: {
|
||||
...authHeaders,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// AFTER: Automatic header injection
|
||||
// ============================================
|
||||
|
||||
// With the new system, auth headers are automatically injected:
|
||||
|
||||
/**
|
||||
* Example 1: Using fetchWithHeaders
|
||||
* Headers are automatically injected - no manual auth handling needed
|
||||
*/
|
||||
export async function modernFetchExample() {
|
||||
// Just make the request - auth headers are added automatically!
|
||||
const response = await fetchWithHeaders('/api/data', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
// Auth headers are automatically added by the AuthHeaderProvider
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Request failed: ${response.status}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 2: Using createAxiosWithHeaders
|
||||
* Create an axios client that automatically injects headers
|
||||
*/
|
||||
export function createModernApiClient() {
|
||||
// Create a client with automatic header injection
|
||||
const client = createAxiosWithHeaders({
|
||||
baseURL: '/api',
|
||||
timeout: 30000
|
||||
})
|
||||
|
||||
return {
|
||||
async getData() {
|
||||
// No need to manually add auth headers!
|
||||
const response = await client.get('/data')
|
||||
return response.data
|
||||
},
|
||||
|
||||
async postData(data: any) {
|
||||
// Auth headers are automatically included
|
||||
const response = await client.post('/data', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async updateData(id: string, data: any) {
|
||||
// Works with all HTTP methods
|
||||
const response = await client.put(`/data/${id}`, data)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 3: Real-world service refactoring
|
||||
* Shows how to update an existing service to use automatic headers
|
||||
*/
|
||||
|
||||
// Before: CustomerEventsService with manual auth
|
||||
/*
|
||||
class OldCustomerEventsService {
|
||||
private async makeRequest(url: string) {
|
||||
const authHeaders = await useFirebaseAuthStore().getAuthHeader()
|
||||
if (!authHeaders) {
|
||||
throw new Error('Authentication required')
|
||||
}
|
||||
|
||||
return axios.get(url, { headers: authHeaders })
|
||||
}
|
||||
|
||||
async getEvents() {
|
||||
return this.makeRequest('/customers/events')
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// After: CustomerEventsService with automatic auth
|
||||
class ModernCustomerEventsService {
|
||||
private client = createAxiosWithHeaders({
|
||||
baseURL: '/api'
|
||||
})
|
||||
|
||||
async getEvents() {
|
||||
// Auth headers are automatically included!
|
||||
const response = await this.client.get('/customers/events')
|
||||
return response.data
|
||||
}
|
||||
|
||||
async getEventDetails(eventId: string) {
|
||||
// No manual auth handling needed
|
||||
const response = await this.client.get(`/customers/events/${eventId}`)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Benefits of the new system:
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 1. Cleaner code - no auth header boilerplate
|
||||
* 2. Consistent auth handling across all services
|
||||
* 3. Automatic token refresh (handled by Firebase SDK)
|
||||
* 4. Fallback to API key when Firebase auth unavailable
|
||||
* 5. Easy to add new header providers (debug headers, etc.)
|
||||
* 6. Headers can be conditionally applied based on URL/method
|
||||
* 7. Priority system allows overriding headers when needed
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// How it works behind the scenes:
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 1. During app initialization (preInit hook), the AuthHeadersExtension
|
||||
* registers the AuthHeaderProvider with the header registry
|
||||
*
|
||||
* 2. When you use fetchWithHeaders or createAxiosWithHeaders, they
|
||||
* automatically query the header registry for all registered providers
|
||||
*
|
||||
* 3. The AuthHeaderProvider checks for Firebase token first, then
|
||||
* falls back to API key if needed
|
||||
*
|
||||
* 4. Headers are merged and added to the request automatically
|
||||
*
|
||||
* 5. If authentication fails, the request proceeds without auth headers
|
||||
* (the backend will handle the 401/403 response)
|
||||
*/
|
||||
|
||||
export const examples = {
|
||||
modernFetchExample,
|
||||
createModernApiClient,
|
||||
ModernCustomerEventsService
|
||||
}
|
||||
29
src/extensions/core/authHeaders.ts
Normal file
29
src/extensions/core/authHeaders.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { AuthHeaderProvider } from '@/providers/authHeaderProvider'
|
||||
import { app } from '@/scripts/app'
|
||||
import { headerRegistry } from '@/services/headerRegistry'
|
||||
|
||||
/**
|
||||
* Core extension that registers authentication header providers.
|
||||
* This ensures all HTTP requests automatically include authentication headers.
|
||||
*/
|
||||
app.registerExtension({
|
||||
name: 'Comfy.AuthHeaders',
|
||||
|
||||
/**
|
||||
* Register authentication header provider in the pre-init phase.
|
||||
* This ensures headers are available before any network activity.
|
||||
*/
|
||||
async preInit(_app) {
|
||||
console.log('[AuthHeaders] Registering authentication header provider')
|
||||
|
||||
// Register the auth header provider with high priority
|
||||
// This ensures auth headers are added to all requests
|
||||
headerRegistry.registerHeaderProvider(new AuthHeaderProvider(), {
|
||||
priority: 1000 // High priority to ensure auth headers are applied
|
||||
})
|
||||
|
||||
console.log(
|
||||
'[AuthHeaders] Authentication headers will be automatically injected'
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import './authHeaders'
|
||||
import './clipspace'
|
||||
import './contextMenuFilter'
|
||||
import './dynamicPrompts'
|
||||
|
||||
64
src/providers/authHeaderProvider.ts
Normal file
64
src/providers/authHeaderProvider.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
import type {
|
||||
HeaderMap,
|
||||
HeaderProviderContext,
|
||||
IHeaderProvider
|
||||
} from '@/types/headerTypes'
|
||||
|
||||
/**
|
||||
* Header provider for authentication headers.
|
||||
* Automatically adds Firebase Bearer tokens or API keys to outgoing requests.
|
||||
*
|
||||
* Priority order:
|
||||
* 1. Firebase Bearer token (if user is authenticated)
|
||||
* 2. API key (if configured)
|
||||
* 3. No authentication header
|
||||
*/
|
||||
export class AuthHeaderProvider implements IHeaderProvider {
|
||||
async provideHeaders(_context: HeaderProviderContext): Promise<HeaderMap> {
|
||||
// Try to get Firebase auth header first (includes fallback to API key)
|
||||
const authHeader = await useFirebaseAuthStore().getAuthHeader()
|
||||
|
||||
if (authHeader) {
|
||||
return authHeader
|
||||
}
|
||||
|
||||
// No authentication available
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Header provider specifically for API key authentication.
|
||||
* Only provides API key headers, ignoring Firebase auth.
|
||||
* Useful for specific endpoints that require API key auth.
|
||||
*/
|
||||
export class ApiKeyHeaderProvider implements IHeaderProvider {
|
||||
provideHeaders(_context: HeaderProviderContext): HeaderMap {
|
||||
const apiKeyHeader = useApiKeyAuthStore().getAuthHeader()
|
||||
return apiKeyHeader || {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Header provider specifically for Firebase Bearer token authentication.
|
||||
* Only provides Firebase auth headers, ignoring API keys.
|
||||
* Useful for specific endpoints that require Firebase auth.
|
||||
*/
|
||||
export class FirebaseAuthHeaderProvider implements IHeaderProvider {
|
||||
async provideHeaders(_context: HeaderProviderContext): Promise<HeaderMap> {
|
||||
const firebaseStore = useFirebaseAuthStore()
|
||||
|
||||
// Only get Firebase token, not the fallback API key
|
||||
const token = await firebaseStore.getIdToken()
|
||||
|
||||
if (token) {
|
||||
return {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
}
|
||||
83
tests-ui/tests/extension/authHeadersExtension.test.ts
Normal file
83
tests-ui/tests/extension/authHeadersExtension.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { AuthHeaderProvider } from '@/providers/authHeaderProvider'
|
||||
import { headerRegistry } from '@/services/headerRegistry'
|
||||
|
||||
// Mock the providers module
|
||||
vi.mock('@/providers/authHeaderProvider', () => ({
|
||||
AuthHeaderProvider: vi.fn()
|
||||
}))
|
||||
|
||||
// Mock headerRegistry
|
||||
vi.mock('@/services/headerRegistry', () => ({
|
||||
headerRegistry: {
|
||||
registerHeaderProvider: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
// Mock app
|
||||
const mockApp = {
|
||||
registerExtension: vi.fn()
|
||||
}
|
||||
|
||||
vi.mock('@/scripts/app', () => ({
|
||||
app: mockApp
|
||||
}))
|
||||
|
||||
describe('authHeaders extension', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Reset module cache to ensure fresh imports
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
it('should register extension with correct name', async () => {
|
||||
// Import the extension (this will call app.registerExtension)
|
||||
await import('@/extensions/core/authHeaders')
|
||||
|
||||
expect(mockApp.registerExtension).toHaveBeenCalledOnce()
|
||||
const extensionConfig = mockApp.registerExtension.mock.calls[0][0]
|
||||
expect(extensionConfig.name).toBe('Comfy.AuthHeaders')
|
||||
})
|
||||
|
||||
it('should register auth header provider in preInit hook', async () => {
|
||||
// Import the extension
|
||||
await import('@/extensions/core/authHeaders')
|
||||
|
||||
const extensionConfig = mockApp.registerExtension.mock.calls[0][0]
|
||||
expect(extensionConfig.preInit).toBeDefined()
|
||||
|
||||
// Call the preInit hook
|
||||
await extensionConfig.preInit({})
|
||||
|
||||
// Verify AuthHeaderProvider was instantiated
|
||||
expect(AuthHeaderProvider).toHaveBeenCalledOnce()
|
||||
|
||||
// Verify header provider was registered with high priority
|
||||
expect(headerRegistry.registerHeaderProvider).toHaveBeenCalledWith(
|
||||
expect.any(Object), // The AuthHeaderProvider instance
|
||||
{ priority: 1000 }
|
||||
)
|
||||
})
|
||||
|
||||
it('should log initialization messages', async () => {
|
||||
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
// Import the extension
|
||||
await import('@/extensions/core/authHeaders')
|
||||
|
||||
const extensionConfig = mockApp.registerExtension.mock.calls[0][0]
|
||||
|
||||
// Call the preInit hook
|
||||
await extensionConfig.preInit({})
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[AuthHeaders] Registering authentication header provider'
|
||||
)
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[AuthHeaders] Authentication headers will be automatically injected'
|
||||
)
|
||||
|
||||
consoleLogSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
234
tests-ui/tests/integration/authHeaderIntegration.test.ts
Normal file
234
tests-ui/tests/integration/authHeaderIntegration.test.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import axios from 'axios'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { AuthHeaderProvider } from '@/providers/authHeaderProvider'
|
||||
import { headerRegistry } from '@/services/headerRegistry'
|
||||
import {
|
||||
createAxiosWithHeaders,
|
||||
fetchWithHeaders
|
||||
} from '@/services/networkClientAdapter'
|
||||
|
||||
// Mock stores
|
||||
const mockFirebaseAuthStore = {
|
||||
getAuthHeader: vi.fn(),
|
||||
getIdToken: vi.fn()
|
||||
}
|
||||
|
||||
const mockApiKeyAuthStore = {
|
||||
getAuthHeader: vi.fn()
|
||||
}
|
||||
|
||||
vi.mock('@/stores/firebaseAuthStore', () => ({
|
||||
useFirebaseAuthStore: () => mockFirebaseAuthStore
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/apiKeyAuthStore', () => ({
|
||||
useApiKeyAuthStore: () => mockApiKeyAuthStore
|
||||
}))
|
||||
|
||||
// Mock fetch
|
||||
const mockFetch = vi.fn()
|
||||
global.fetch = mockFetch
|
||||
|
||||
// Mock axios
|
||||
vi.mock('axios')
|
||||
const mockedAxios = axios as any
|
||||
|
||||
describe('Auth Header Integration', () => {
|
||||
let authProviderRegistration: any
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Reset fetch mock
|
||||
mockFetch.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ success: true })
|
||||
})
|
||||
|
||||
// Reset axios mock
|
||||
mockedAxios.create.mockReturnValue({
|
||||
interceptors: {
|
||||
request: {
|
||||
use: vi.fn()
|
||||
},
|
||||
response: {
|
||||
use: vi.fn()
|
||||
}
|
||||
},
|
||||
defaults: {
|
||||
headers: {
|
||||
common: {},
|
||||
get: {},
|
||||
post: {},
|
||||
put: {},
|
||||
patch: {},
|
||||
delete: {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Register auth header provider
|
||||
authProviderRegistration = headerRegistry.registerHeaderProvider(
|
||||
new AuthHeaderProvider(),
|
||||
{ priority: 1000 }
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
// Unregister the provider
|
||||
authProviderRegistration.unregister()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe('fetchWithHeaders integration', () => {
|
||||
it('should automatically add Firebase auth headers to fetch requests', async () => {
|
||||
const mockAuthHeader = { Authorization: 'Bearer firebase-token-123' }
|
||||
mockFirebaseAuthStore.getAuthHeader.mockResolvedValue(mockAuthHeader)
|
||||
|
||||
await fetchWithHeaders('https://api.example.com/data')
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'https://api.example.com/data',
|
||||
expect.objectContaining({
|
||||
headers: expect.any(Headers)
|
||||
})
|
||||
)
|
||||
|
||||
// Verify the auth header was added
|
||||
const callArgs = mockFetch.mock.calls[0]
|
||||
const headers = callArgs[1].headers as Headers
|
||||
expect(headers.get('Authorization')).toBe('Bearer firebase-token-123')
|
||||
})
|
||||
|
||||
it('should automatically add API key headers when Firebase is not available', async () => {
|
||||
const mockApiKeyHeader = { 'X-API-KEY': 'test-api-key' }
|
||||
mockFirebaseAuthStore.getAuthHeader.mockResolvedValue(mockApiKeyHeader)
|
||||
|
||||
await fetchWithHeaders('https://api.example.com/data')
|
||||
|
||||
const callArgs = mockFetch.mock.calls[0]
|
||||
const headers = callArgs[1].headers as Headers
|
||||
expect(headers.get('X-API-KEY')).toBe('test-api-key')
|
||||
})
|
||||
|
||||
it('should merge auth headers with existing headers', async () => {
|
||||
const mockAuthHeader = { Authorization: 'Bearer firebase-token-123' }
|
||||
mockFirebaseAuthStore.getAuthHeader.mockResolvedValue(mockAuthHeader)
|
||||
|
||||
await fetchWithHeaders('https://api.example.com/data', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Custom-Header': 'custom-value'
|
||||
}
|
||||
})
|
||||
|
||||
const callArgs = mockFetch.mock.calls[0]
|
||||
const headers = callArgs[1].headers as Headers
|
||||
expect(headers.get('Authorization')).toBe('Bearer firebase-token-123')
|
||||
expect(headers.get('Content-Type')).toBe('application/json')
|
||||
expect(headers.get('X-Custom-Header')).toBe('custom-value')
|
||||
})
|
||||
|
||||
it('should not add headers when no auth is available', async () => {
|
||||
mockFirebaseAuthStore.getAuthHeader.mockResolvedValue(null)
|
||||
|
||||
await fetchWithHeaders('https://api.example.com/data')
|
||||
|
||||
const callArgs = mockFetch.mock.calls[0]
|
||||
const headers = callArgs[1].headers as Headers
|
||||
expect(headers.get('Authorization')).toBeNull()
|
||||
expect(headers.get('X-API-KEY')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('createAxiosWithHeaders integration', () => {
|
||||
it('should setup interceptor to add auth headers', async () => {
|
||||
const mockInstance = {
|
||||
interceptors: {
|
||||
request: {
|
||||
use: vi.fn()
|
||||
},
|
||||
response: {
|
||||
use: vi.fn()
|
||||
}
|
||||
},
|
||||
defaults: {
|
||||
headers: {
|
||||
common: {},
|
||||
get: {},
|
||||
post: {},
|
||||
put: {},
|
||||
patch: {},
|
||||
delete: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mockedAxios.create.mockReturnValue(mockInstance)
|
||||
|
||||
createAxiosWithHeaders({ baseURL: 'https://api.example.com' })
|
||||
|
||||
// Verify interceptor was registered
|
||||
expect(mockInstance.interceptors.request.use).toHaveBeenCalledOnce()
|
||||
|
||||
// Get the interceptor function
|
||||
const interceptorCall =
|
||||
mockInstance.interceptors.request.use.mock.calls[0]
|
||||
const requestInterceptor = interceptorCall[0]
|
||||
|
||||
// Test the interceptor
|
||||
const mockAuthHeader = { Authorization: 'Bearer firebase-token-123' }
|
||||
mockFirebaseAuthStore.getAuthHeader.mockResolvedValue(mockAuthHeader)
|
||||
|
||||
const config = {
|
||||
url: '/test',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
const modifiedConfig = await requestInterceptor(config)
|
||||
|
||||
expect(modifiedConfig.headers.Authorization).toBe(
|
||||
'Bearer firebase-token-123'
|
||||
)
|
||||
expect(modifiedConfig.headers['Content-Type']).toBe('application/json')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Multiple providers with priority', () => {
|
||||
it('should apply headers in priority order', async () => {
|
||||
// Register a second provider with higher priority
|
||||
const customProvider = {
|
||||
provideHeaders: vi.fn().mockResolvedValue({
|
||||
'X-Custom': 'high-priority',
|
||||
Authorization: 'Bearer custom-token' // This should override the auth provider
|
||||
})
|
||||
}
|
||||
|
||||
const customRegistration = headerRegistry.registerHeaderProvider(
|
||||
customProvider,
|
||||
{ priority: 2000 } // Higher priority than auth provider
|
||||
)
|
||||
|
||||
// Auth provider returns different token
|
||||
mockFirebaseAuthStore.getAuthHeader.mockResolvedValue({
|
||||
Authorization: 'Bearer firebase-token'
|
||||
})
|
||||
|
||||
await fetchWithHeaders('https://api.example.com/data')
|
||||
|
||||
const callArgs = mockFetch.mock.calls[0]
|
||||
const headers = callArgs[1].headers as Headers
|
||||
|
||||
// Higher priority provider should win
|
||||
expect(headers.get('Authorization')).toBe('Bearer custom-token')
|
||||
expect(headers.get('X-Custom')).toBe('high-priority')
|
||||
|
||||
customRegistration.dispose()
|
||||
})
|
||||
})
|
||||
})
|
||||
144
tests-ui/tests/providers/authHeaderProvider.test.ts
Normal file
144
tests-ui/tests/providers/authHeaderProvider.test.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
ApiKeyHeaderProvider,
|
||||
AuthHeaderProvider,
|
||||
FirebaseAuthHeaderProvider
|
||||
} from '@/providers/authHeaderProvider'
|
||||
import type { HeaderProviderContext } from '@/types/headerTypes'
|
||||
|
||||
// Mock stores
|
||||
const mockFirebaseAuthStore = {
|
||||
getAuthHeader: vi.fn(),
|
||||
getIdToken: vi.fn()
|
||||
}
|
||||
|
||||
const mockApiKeyAuthStore = {
|
||||
getAuthHeader: vi.fn()
|
||||
}
|
||||
|
||||
vi.mock('@/stores/firebaseAuthStore', () => ({
|
||||
useFirebaseAuthStore: () => mockFirebaseAuthStore
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/apiKeyAuthStore', () => ({
|
||||
useApiKeyAuthStore: () => mockApiKeyAuthStore
|
||||
}))
|
||||
|
||||
describe('authHeaderProvider', () => {
|
||||
const mockContext: HeaderProviderContext = {
|
||||
url: 'https://api.example.com/test',
|
||||
method: 'GET'
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe('AuthHeaderProvider', () => {
|
||||
it('should provide Firebase auth header when available', async () => {
|
||||
const provider = new AuthHeaderProvider()
|
||||
const mockAuthHeader = { Authorization: 'Bearer firebase-token-123' }
|
||||
|
||||
mockFirebaseAuthStore.getAuthHeader.mockResolvedValue(mockAuthHeader)
|
||||
|
||||
const headers = await provider.provideHeaders(mockContext)
|
||||
|
||||
expect(headers).toEqual(mockAuthHeader)
|
||||
expect(mockFirebaseAuthStore.getAuthHeader).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('should provide API key header when Firebase auth is not available', async () => {
|
||||
const provider = new AuthHeaderProvider()
|
||||
const mockApiKeyHeader = { 'X-API-KEY': 'test-api-key' }
|
||||
|
||||
// Firebase returns null, but includes API key as fallback
|
||||
mockFirebaseAuthStore.getAuthHeader.mockResolvedValue(mockApiKeyHeader)
|
||||
|
||||
const headers = await provider.provideHeaders(mockContext)
|
||||
|
||||
expect(headers).toEqual(mockApiKeyHeader)
|
||||
})
|
||||
|
||||
it('should return empty object when no auth is available', async () => {
|
||||
const provider = new AuthHeaderProvider()
|
||||
|
||||
mockFirebaseAuthStore.getAuthHeader.mockResolvedValue(null)
|
||||
|
||||
const headers = await provider.provideHeaders(mockContext)
|
||||
|
||||
expect(headers).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe('ApiKeyHeaderProvider', () => {
|
||||
it('should provide API key header when available', () => {
|
||||
const provider = new ApiKeyHeaderProvider()
|
||||
const mockApiKeyHeader = { 'X-API-KEY': 'test-api-key' }
|
||||
|
||||
mockApiKeyAuthStore.getAuthHeader.mockReturnValue(mockApiKeyHeader)
|
||||
|
||||
const headers = provider.provideHeaders(mockContext)
|
||||
|
||||
expect(headers).toEqual(mockApiKeyHeader)
|
||||
expect(mockApiKeyAuthStore.getAuthHeader).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('should return empty object when no API key is available', () => {
|
||||
const provider = new ApiKeyHeaderProvider()
|
||||
|
||||
mockApiKeyAuthStore.getAuthHeader.mockReturnValue(null)
|
||||
|
||||
const headers = provider.provideHeaders(mockContext)
|
||||
|
||||
expect(headers).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe('FirebaseAuthHeaderProvider', () => {
|
||||
it('should provide Firebase auth header when available', async () => {
|
||||
const provider = new FirebaseAuthHeaderProvider()
|
||||
const mockToken = 'firebase-token-456'
|
||||
|
||||
mockFirebaseAuthStore.getIdToken.mockResolvedValue(mockToken)
|
||||
|
||||
const headers = await provider.provideHeaders(mockContext)
|
||||
|
||||
expect(headers).toEqual({
|
||||
Authorization: `Bearer ${mockToken}`
|
||||
})
|
||||
expect(mockFirebaseAuthStore.getIdToken).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('should return empty object when no Firebase token is available', async () => {
|
||||
const provider = new FirebaseAuthHeaderProvider()
|
||||
|
||||
mockFirebaseAuthStore.getIdToken.mockResolvedValue(null)
|
||||
|
||||
const headers = await provider.provideHeaders(mockContext)
|
||||
|
||||
expect(headers).toEqual({})
|
||||
})
|
||||
|
||||
it('should not fall back to API key', async () => {
|
||||
const provider = new FirebaseAuthHeaderProvider()
|
||||
|
||||
// Firebase has no token
|
||||
mockFirebaseAuthStore.getIdToken.mockResolvedValue(null)
|
||||
// API key is available
|
||||
mockApiKeyAuthStore.getAuthHeader.mockReturnValue({
|
||||
'X-API-KEY': 'test-key'
|
||||
})
|
||||
|
||||
const headers = await provider.provideHeaders(mockContext)
|
||||
|
||||
expect(headers).toEqual({})
|
||||
// Should not call API key store
|
||||
expect(mockApiKeyAuthStore.getAuthHeader).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user