diff --git a/apps/gateway/src/app.module.spec.ts b/apps/gateway/src/app.module.spec.ts index 191a00ad..87472b90 100644 --- a/apps/gateway/src/app.module.spec.ts +++ b/apps/gateway/src/app.module.spec.ts @@ -12,6 +12,7 @@ interface ComposedModuleGraph { federationModule: unknown; bootLogLines: readonly string[]; mosaicConfig: MosaicConfig; + resolvedConfigPath: string; } type StorageTier = 'local' | 'standalone' | 'federated'; @@ -205,6 +206,7 @@ async function loadModuleGraphFromDotenv( delete process.env['MOSAIC_STORAGE_TIER']; delete process.env['DATABASE_URL']; delete process.env['VALKEY_URL']; + delete process.env['MOSAIC_GATEWAY_HOME']; await options.setup?.(fixture); @@ -235,7 +237,7 @@ async function loadModuleGraphFromDotenv( expect(process.env['MOSAIC_STORAGE_TIER']).toBe(options.inheritedTier); } - await import('./env.js'); + const envModule = await import('./env.js'); expect(process.env['MOSAIC_STORAGE_TIER']).toBe( options.expectedProcessTier ?? options.rootTier, ); @@ -270,6 +272,7 @@ async function loadModuleGraphFromDotenv( args.map((value: unknown): string => String(value)).join(' '), ), mosaicConfig: configProvider.useFactory(), + resolvedConfigPath: envModule.resolveGatewayConfigPath(), }; } finally { cwdSpy?.mockRestore(); @@ -360,20 +363,53 @@ describe('AppModule federation gating', (): void => { ); it( - 'anchored monorepo-root config wins gateway-local config and registers FederationModule', + 'anchored gateway-local config wins monorepo-root config and registers FederationModule', async (): Promise => { + let gatewayLocalConfigPath = ''; const graph = await loadModuleGraphFromDotenv({ rootTier: 'local', setup: async (fixture: ModuleGraphFixture): Promise => { + gatewayLocalConfigPath = fixture.gatewayLocalConfigPath; await writeFixture( - fixture.monorepoRootConfigPath, + fixture.gatewayLocalConfigPath, configJson('federated'), fixture.tempRoot, ); - await writeFixture(fixture.gatewayLocalConfigPath, configJson('local'), fixture.tempRoot); + await writeFixture(fixture.monorepoRootConfigPath, configJson('local'), fixture.tempRoot); }, }); + expect(graph.resolvedConfigPath).toBe(gatewayLocalConfigPath); + expect(graph.mosaicConfig.tier).toBe('federated'); + expect(graph.imports).toContain(graph.federationModule); + expectBootLogLine(graph.bootLogLines, 'federated', 'mosaic.config.json'); + }, + MODULE_IMPORT_TIMEOUT_MS, + ); + + it( + 'resolves the daemon-installed GATEWAY_HOME/mosaic.config.json ahead of gateway-local and monorepo-root configs', + async (): Promise => { + let daemonConfigPath = ''; + const graph = await loadModuleGraphFromDotenv({ + rootEnvMode: 'absent', + setup: async (fixture: ModuleGraphFixture): Promise => { + const externalGatewayHome = join(fixture.tempRoot, 'external-gateway-home'); + daemonConfigPath = join(externalGatewayHome, 'mosaic.config.json'); + await writeFixture(daemonConfigPath, configJson('federated'), fixture.tempRoot); + await writeFixture( + fixture.gatewayLocalConfigPath, + configJson('standalone'), + fixture.tempRoot, + ); + await writeFixture(fixture.monorepoRootConfigPath, configJson('local'), fixture.tempRoot); + process.env['MOSAIC_GATEWAY_HOME'] = externalGatewayHome; + process.env['DATABASE_URL'] = 'postgresql://fixture.invalid/mosaic'; + }, + }); + + expect(graph.resolvedConfigPath).toBe(daemonConfigPath); + expect(graph.mosaicConfig.tier).toBe('federated'); expect(graph.imports).toContain(graph.federationModule); expectBootLogLine(graph.bootLogLines, 'federated', 'mosaic.config.json'); }, diff --git a/apps/gateway/src/env.ts b/apps/gateway/src/env.ts index 17211003..4422343f 100644 --- a/apps/gateway/src/env.ts +++ b/apps/gateway/src/env.ts @@ -34,15 +34,23 @@ export function resolveGatewayDotenvPaths( } export function resolveGatewayConfigPath(anchor: string = here): string { - const monorepoRootConfig = resolve(anchor, '../../..', 'mosaic.config.json'); + // GATEWAY_HOME is daemon-created 0700; its env override adds no authority because env can set MOSAIC_STORAGE_TIER. + const gatewayHome = resolve( + process.env['MOSAIC_GATEWAY_HOME'] ?? join(homedir(), '.config', 'mosaic', 'gateway'), + ); + const daemonConfig = join(gatewayHome, 'mosaic.config.json'); const gatewayLocalConfig = resolve(anchor, '..', 'mosaic.config.json'); + const monorepoRootConfig = resolve(anchor, '../../..', 'mosaic.config.json'); - if (existsSync(monorepoRootConfig)) { - return monorepoRootConfig; + if (existsSync(daemonConfig)) { + return daemonConfig; } if (existsSync(gatewayLocalConfig)) { return gatewayLocalConfig; } + if (existsSync(monorepoRootConfig)) { + return monorepoRootConfig; + } return monorepoRootConfig; }