import 'reflect-metadata'; import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import * as nodeOs from 'node:os'; import { dirname, isAbsolute, join, relative, resolve } from 'node:path'; import * as nodeUrl from 'node:url'; import type { MosaicConfig } from '@mosaicstack/config'; import type * as MosaicStorage from '@mosaicstack/storage'; import { describe, expect, it, vi, type MockInstance } from 'vitest'; // Each case uses vi.resetModules() and re-imports the full gateway graph for distinct ambient FS/env; CI needs headroom, while this still guards genuine hangs. const MODULE_IMPORT_TIMEOUT_MS = 120_000; function snapshotProcessEnv(): Record { return { ...process.env }; } function restoreProcessEnv(snapshot: Record): void { for (const key of Object.keys(process.env)) { if (!(key in snapshot)) { delete process.env[key]; } } for (const [key, value] of Object.entries(snapshot)) { if (value === undefined) { delete process.env[key]; continue; } process.env[key] = value; } } function expectPathUnderTempRoot(path: string, tempRoot: string): void { const relativePath = relative(tempRoot, path); expect(relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath))).toBe( true, ); } async function writeFixture(path: string, contents: string, tempRoot: string): Promise { expectPathUnderTempRoot(path, tempRoot); await mkdir(dirname(path), { recursive: true }); await writeFile(path, contents, 'utf8'); } interface BootstrapPreflightResult { capturedConfig: MosaicConfig | undefined; } async function runBootstrapPreflight( anchoredConfigContents: string, ambientConfigContents: string, ): Promise { const originalEnv = snapshotProcessEnv(); const tempRoot = await mkdtemp(join(nodeOs.tmpdir(), 'mosaic-gateway-main-preflight-')); let cwdSpy: ReturnType | undefined; let exitSpy: MockInstance | undefined; let consoleInfoSpy: ReturnType | undefined; let capturedConfig: MosaicConfig | undefined; try { const anchor = join(tempRoot, 'anchored', 'apps', 'gateway', 'src'); const homePath = join(tempRoot, 'home'); const cwdPath = join(tempRoot, 'ambient', 'cwd'); const monorepoRootConfigPath = resolve(anchor, '../../..', 'mosaic.config.json'); await mkdir(anchor, { recursive: true }); await mkdir(cwdPath, { recursive: true }); await writeFixture(monorepoRootConfigPath, anchoredConfigContents, tempRoot); await writeFixture(join(cwdPath, 'mosaic.config.json'), ambientConfigContents, tempRoot); process.env['HOME'] = homePath; process.env['BETTER_AUTH_SECRET'] = 'fixture-secret'; delete process.env['MOSAIC_STORAGE_TIER']; delete process.env['DATABASE_URL']; delete process.env['VALKEY_URL']; consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation((): void => undefined); const exitMock = vi.fn(); exitSpy = vi.spyOn(process, 'exit').mockImplementation(exitMock); vi.resetModules(); vi.doMock('node:os', () => ({ ...nodeOs, homedir: (): string => homePath })); vi.doMock('node:url', () => ({ ...nodeUrl, fileURLToPath: (url: string | URL): string => { const actualPath = nodeUrl.fileURLToPath(url); if ( actualPath.endsWith('/apps/gateway/src/env.ts') || actualPath.endsWith('/apps/gateway/src/env.js') ) { return join(anchor, 'env.ts'); } return actualPath; }, })); cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(cwdPath); vi.doMock('./tracing.js', () => ({})); const preflightSentinel = new Error('preflight-capture-sentinel'); vi.doMock('@mosaicstack/storage', async () => { const actual = await vi.importActual('@mosaicstack/storage'); return { ...actual, detectAndAssertTier: vi.fn((config: MosaicConfig): Promise => { capturedConfig = config; throw preflightSentinel; }), }; }); await import('./main.js'); await vi.waitFor((): void => { expect(exitSpy).toHaveBeenCalled(); }); return { capturedConfig }; } finally { cwdSpy?.mockRestore(); exitSpy?.mockRestore(); consoleInfoSpy?.mockRestore(); vi.doUnmock('@mosaicstack/storage'); vi.doUnmock('./tracing.js'); vi.doUnmock('node:url'); vi.doUnmock('node:os'); vi.resetModules(); restoreProcessEnv(originalEnv); await rm(tempRoot, { recursive: true, force: true }); } } describe('main bootstrap preflight config anchoring', (): void => { it( 'passes the anchored monorepo-root config to detectAndAssertTier, not an ambient cwd config', async (): Promise => { const anchoredConfig = JSON.stringify({ tier: 'local', storage: { type: 'pglite', dataDir: '.mosaic/storage-pglite' }, queue: { type: 'local', dataDir: '.mosaic/queue' }, memory: { type: 'keyword' }, }); const ambientConfig = JSON.stringify({ tier: 'federated', storage: { type: 'postgres', url: 'postgresql://ambient-attacker.invalid/mosaic', enableVector: true, }, queue: { type: 'bullmq' }, memory: { type: 'pgvector' }, }); const { capturedConfig } = await runBootstrapPreflight(anchoredConfig, ambientConfig); expect(capturedConfig?.tier).toBe('local'); expect(capturedConfig?.storage).not.toEqual( expect.objectContaining({ url: 'postgresql://ambient-attacker.invalid/mosaic' }), ); }, MODULE_IMPORT_TIMEOUT_MS, ); });