Add tests for unified storage initialization

This commit is contained in:
2026-01-18 18:01:54 +00:00
parent 91969e8494
commit e3e3dbf602
2 changed files with 162 additions and 20 deletions

View File

@@ -0,0 +1,145 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const {
callOrder,
mockFlaskGet,
mockIndexedGet,
mockSQLiteGet,
mockSparkGet,
MockFlaskBackendAdapter,
MockIndexedDBAdapter,
MockSQLiteAdapter,
MockSparkKVAdapter
} = vi.hoisted(() => {
const callOrder: string[] = []
const mockFlaskGet = vi.fn<[], Promise<unknown>>()
const mockIndexedGet = vi.fn<[], Promise<unknown>>()
const mockSQLiteGet = vi.fn<[], Promise<unknown>>()
const mockSparkGet = vi.fn<[], Promise<unknown>>()
class MockFlaskBackendAdapter {
constructor() {
callOrder.push('flask')
}
get = mockFlaskGet
}
class MockIndexedDBAdapter {
constructor() {
callOrder.push('indexeddb')
}
get = mockIndexedGet
}
class MockSQLiteAdapter {
constructor() {
callOrder.push('sqlite')
}
get = mockSQLiteGet
}
class MockSparkKVAdapter {
constructor() {
callOrder.push('sparkkv')
}
get = mockSparkGet
}
return {
callOrder,
mockFlaskGet,
mockIndexedGet,
mockSQLiteGet,
mockSparkGet,
MockFlaskBackendAdapter,
MockIndexedDBAdapter,
MockSQLiteAdapter,
MockSparkKVAdapter
}
})
vi.mock('./unified-storage-adapters', () => ({
FlaskBackendAdapter: MockFlaskBackendAdapter,
IndexedDBAdapter: MockIndexedDBAdapter,
SQLiteAdapter: MockSQLiteAdapter,
SparkKVAdapter: MockSparkKVAdapter
}))
const createLocalStorageMock = () => {
const store = new Map<string, string>()
return {
getItem: vi.fn((key: string) => store.get(key) ?? null),
setItem: vi.fn((key: string, value: string) => {
store.set(key, value)
}),
removeItem: vi.fn((key: string) => {
store.delete(key)
}),
clear: vi.fn(() => {
store.clear()
})
}
}
describe('UnifiedStorage.detectAndInitialize', () => {
let localStorageMock: ReturnType<typeof createLocalStorageMock>
beforeEach(() => {
vi.resetModules()
callOrder.length = 0
mockFlaskGet.mockReset()
mockIndexedGet.mockReset()
mockSQLiteGet.mockReset()
mockSparkGet.mockReset()
localStorageMock = createLocalStorageMock()
vi.stubGlobal('localStorage', localStorageMock)
vi.stubGlobal('window', { spark: undefined })
if (!(import.meta as { env?: Record<string, string | undefined> }).env) {
;(import.meta as { env?: Record<string, string | undefined> }).env = {}
}
})
it('tries Flask before IndexedDB when prefer-flask is set', async () => {
localStorageMock.setItem('codeforge-prefer-flask', 'true')
mockFlaskGet.mockRejectedValue(new Error('flask down'))
mockIndexedGet.mockResolvedValue(undefined)
vi.stubGlobal('indexedDB', {})
const { unifiedStorage } = await import('./unified-storage')
await unifiedStorage.getBackend()
expect(callOrder[0]).toBe('flask')
expect(callOrder).toContain('indexeddb')
})
it('falls back to IndexedDB when Flask initialization fails', async () => {
localStorageMock.setItem('codeforge-prefer-flask', 'true')
mockFlaskGet.mockRejectedValue(new Error('flask down'))
mockIndexedGet.mockResolvedValue(undefined)
vi.stubGlobal('indexedDB', {})
const { unifiedStorage } = await import('./unified-storage')
const backend = await unifiedStorage.getBackend()
expect(backend).toBe('indexeddb')
})
it('honors prefer-sqlite when configured', async () => {
localStorageMock.setItem('codeforge-prefer-sqlite', 'true')
mockSQLiteGet.mockResolvedValue(undefined)
delete (globalThis as { indexedDB?: unknown }).indexedDB
const { unifiedStorage } = await import('./unified-storage')
const backend = await unifiedStorage.getBackend()
expect(backend).toBe('sqlite')
expect(callOrder).toContain('sqlite')
})
})

View File

@@ -19,6 +19,23 @@ class UnifiedStorage {
const flaskEnvUrl = import.meta.env.VITE_FLASK_BACKEND_URL
const preferSQLite = localStorage.getItem('codeforge-prefer-sqlite') === 'true'
if (preferFlask || flaskEnvUrl) {
try {
console.log('[Storage] Flask backend explicitly configured, attempting to initialize...')
const flaskAdapter = new FlaskBackendAdapter(flaskEnvUrl)
await Promise.race([
flaskAdapter.get('_health_check'),
new Promise((_, reject) => setTimeout(() => reject(new Error('Flask connection timeout')), 2000))
])
this.adapter = flaskAdapter
this.backend = 'flask'
console.log('[Storage] ✓ Using Flask backend')
return
} catch (error) {
console.warn('[Storage] Flask backend not available, falling back to IndexedDB:', error)
}
}
if (typeof indexedDB !== 'undefined') {
try {
console.log('[Storage] Initializing default IndexedDB backend...')
@@ -33,26 +50,6 @@ class UnifiedStorage {
}
}
if (preferFlask || flaskEnvUrl) {
try {
console.log('[Storage] Flask backend explicitly configured, attempting to initialize...')
const flaskAdapter = new FlaskBackendAdapter(flaskEnvUrl)
const testResponse = await Promise.race([
flaskAdapter.get('_health_check'),
new Promise((_, reject) => setTimeout(() => reject(new Error('Flask connection timeout')), 2000))
])
this.adapter = flaskAdapter
this.backend = 'flask'
console.log('[Storage] ✓ Using Flask backend')
return
} catch (error) {
console.warn('[Storage] Flask backend not available, already using IndexedDB:', error)
if (this.adapter && this.backend === 'indexeddb') {
return
}
}
}
if (preferSQLite) {
try {
console.log('[Storage] SQLite fallback, attempting to initialize...')