import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const mocks = vi.hoisted(() => ({ initializeApp: vi.fn((_options: unknown, name = '[DEFAULT]') => ({ name })), getApps: vi.fn((): Array<{ name: string }> => []), getApp: vi.fn(), connectAuth: vi.fn(), connectFirestore: vi.fn(), })); vi.mock('firebase/app', () => mocks); vi.mock('firebase/auth', () => ({ getAuth: vi.fn(() => ({})), GoogleAuthProvider: class {}, connectAuthEmulator: mocks.connectAuth })); vi.mock('firebase/firestore', () => ({ getFirestore: vi.fn(() => ({})), connectFirestoreEmulator: mocks.connectFirestore })); describe('isolated Firebase QA client', () => { beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); mocks.getApps.mockReturnValue([]); }); afterEach(() => vi.unstubAllEnvs()); it('initializes production project when explicitly configured', async () => { vi.stubEnv('NEXT_PUBLIC_FIREBASE_EMULATORS', 'false'); vi.stubEnv('NEXT_PUBLIC_FIREBASE_PROJECT_ID', 'motoapp-d22e0'); vi.stubEnv('FIREBASE_PROJECT_ID', 'motoapp-d22e0'); vi.stubEnv('NEXT_PUBLIC_FIREBASE_API_KEY', 'test-production-api-key'); await import('../lib/firebase-client'); expect(mocks.initializeApp).toHaveBeenCalledWith(expect.objectContaining({ projectId: 'motoapp-d22e0' })); expect(mocks.connectAuth).not.toHaveBeenCalled(); }); it('rejects unconfigured Firebase client when emulators are off', async () => { vi.stubEnv('NEXT_PUBLIC_FIREBASE_EMULATORS', 'false'); vi.stubEnv('NEXT_PUBLIC_FIREBASE_PROJECT_ID', ''); vi.stubEnv('NEXT_PUBLIC_FIREBASE_API_KEY', ''); await expect(import('../lib/firebase-client')).rejects.toThrow('Missing required Firebase client configuration'); expect(mocks.initializeApp).not.toHaveBeenCalled(); }); it('rejects a production project before initializing Firebase', async () => { vi.stubEnv('NEXT_PUBLIC_FIREBASE_EMULATORS', 'true'); vi.stubEnv('NEXT_PUBLIC_FIREBASE_QA_PROJECT_ID', 'motoapp-d22e0'); await expect(import('../lib/firebase-client')).rejects.toThrow('demo- project'); expect(mocks.initializeApp).not.toHaveBeenCalled(); }); it('connects an isolated named demo app to both loopback emulators', async () => { vi.stubEnv('NEXT_PUBLIC_FIREBASE_EMULATORS', 'true'); vi.stubEnv('NEXT_PUBLIC_FIREBASE_QA_PROJECT_ID', 'demo-motorsignal-qa'); await import('../lib/firebase-client'); expect(mocks.initializeApp).toHaveBeenCalledWith(expect.objectContaining({ projectId: 'demo-motorsignal-qa' }), 'motorsignal-local-qa'); expect(mocks.connectAuth).toHaveBeenCalledWith(expect.anything(), 'http://127.0.0.1:9099'); expect(mocks.connectFirestore).toHaveBeenCalledWith(expect.anything(), '127.0.0.1', 8088); }); it('supports dedicated QA emulator ports without changing production defaults', async () => { vi.stubEnv('NEXT_PUBLIC_FIREBASE_EMULATORS', 'true'); vi.stubEnv('NEXT_PUBLIC_FIREBASE_QA_PROJECT_ID', 'demo-motorsignal-web-qa'); vi.stubEnv('NEXT_PUBLIC_FIREBASE_AUTH_EMULATOR_PORT', '9199'); vi.stubEnv('NEXT_PUBLIC_FIREBASE_FIRESTORE_EMULATOR_PORT', '8188'); await import('../lib/firebase-client'); expect(mocks.connectAuth).toHaveBeenCalledWith(expect.anything(), 'http://127.0.0.1:9199'); expect(mocks.connectFirestore).toHaveBeenCalledWith(expect.anything(), '127.0.0.1', 8188); }); it('rejects invalid dedicated QA emulator ports', async () => { vi.stubEnv('NEXT_PUBLIC_FIREBASE_EMULATORS', 'true'); vi.stubEnv('NEXT_PUBLIC_FIREBASE_QA_PROJECT_ID', 'demo-motorsignal-web-qa'); vi.stubEnv('NEXT_PUBLIC_FIREBASE_AUTH_EMULATOR_PORT', '99999'); await expect(import('../lib/firebase-client')).rejects.toThrow('valid TCP ports'); expect(mocks.initializeApp).not.toHaveBeenCalled(); }); it('reuses an already connected QA app during module reload', async () => { vi.stubEnv('NEXT_PUBLIC_FIREBASE_EMULATORS', 'true'); vi.stubEnv('NEXT_PUBLIC_FIREBASE_QA_PROJECT_ID', 'demo-motorsignal-qa'); mocks.getApps.mockReturnValue([{ name: 'motorsignal-local-qa' }]); await import('../lib/firebase-client'); expect(mocks.initializeApp).not.toHaveBeenCalled(); expect(mocks.connectFirestore).not.toHaveBeenCalled(); }); });