625 lines
21 KiB
TypeScript
625 lines
21 KiB
TypeScript
import 'reflect-metadata';
|
|
import { mkdtemp, mkdir, readFile, 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 { MODULE_METADATA } from '@nestjs/common/constants.js';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import type { MosaicConfig } from '@mosaicstack/config';
|
|
|
|
interface ComposedModuleGraph {
|
|
imports: readonly unknown[];
|
|
federationModule: unknown;
|
|
bootLogLines: readonly string[];
|
|
mosaicConfig: MosaicConfig;
|
|
resolvedConfigPath: string;
|
|
}
|
|
|
|
type StorageTier = 'local' | 'standalone' | 'federated';
|
|
|
|
interface ModuleGraphFixture {
|
|
tempRoot: string;
|
|
anchor: string;
|
|
homePath: string;
|
|
cwdPath: string;
|
|
monorepoRootEnvPath: string;
|
|
gatewayLocalEnvPath: string;
|
|
daemonEnvPath: string;
|
|
monorepoRootConfigPath: string;
|
|
gatewayLocalConfigPath: string;
|
|
}
|
|
|
|
interface ModuleGraphFixtureOptions {
|
|
rootEnvMode?: 'present' | 'absent';
|
|
rootTier?: StorageTier;
|
|
rootEnvContents?: string;
|
|
redactionMarker?: string;
|
|
gatewayLocalTier?: StorageTier;
|
|
gatewayLocalEnvContents?: string;
|
|
daemonEnvContents?: string;
|
|
inheritedTier?: StorageTier;
|
|
expectedProcessTier?: string;
|
|
setup?: (fixture: ModuleGraphFixture) => Promise<void>;
|
|
}
|
|
|
|
// 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;
|
|
const MONOREPO_ROOT_DOTENV_LABEL = 'monorepo-root .env';
|
|
const DAEMON_DOTENV_LABEL = 'daemon .env';
|
|
|
|
function configJson(tier: StorageTier): string {
|
|
if (tier === 'local') {
|
|
return JSON.stringify({
|
|
tier,
|
|
storage: { type: 'pglite', dataDir: '.mosaic/storage-pglite' },
|
|
queue: { type: 'local', dataDir: '.mosaic/queue' },
|
|
memory: { type: 'keyword' },
|
|
});
|
|
}
|
|
|
|
return JSON.stringify({
|
|
tier,
|
|
storage: { type: 'postgres', url: 'postgresql://fixture.invalid/mosaic' },
|
|
queue: { type: 'bullmq' },
|
|
memory: { type: tier === 'federated' ? 'pgvector' : 'keyword' },
|
|
});
|
|
}
|
|
|
|
function snapshotProcessEnv(): Record<string, string | undefined> {
|
|
return { ...process.env };
|
|
}
|
|
|
|
function restoreProcessEnv(snapshot: Record<string, string | undefined>): 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<void> {
|
|
expectPathUnderTempRoot(path, tempRoot);
|
|
await mkdir(dirname(path), { recursive: true });
|
|
await writeFile(path, contents, 'utf8');
|
|
}
|
|
|
|
interface ConfigModuleProvider {
|
|
provide: string;
|
|
useFactory: () => MosaicConfig;
|
|
}
|
|
|
|
function isConfigModuleProvider(value: unknown): value is ConfigModuleProvider {
|
|
if (typeof value !== 'object' || value === null) {
|
|
return false;
|
|
}
|
|
|
|
if (!('provide' in value) || typeof value.provide !== 'string') {
|
|
return false;
|
|
}
|
|
|
|
return 'useFactory' in value && typeof value.useFactory === 'function';
|
|
}
|
|
|
|
function singleBootLogLine(bootLogLines: readonly string[]): string {
|
|
expect(bootLogLines).toHaveLength(1);
|
|
const [bootLogLine] = bootLogLines;
|
|
if (bootLogLine === undefined) {
|
|
throw new Error('Expected a single boot log line');
|
|
}
|
|
|
|
return bootLogLine;
|
|
}
|
|
|
|
function expectBootLogLine(
|
|
bootLogLines: readonly string[],
|
|
tier: StorageTier,
|
|
source: string,
|
|
): void {
|
|
const bootLogLine = singleBootLogLine(bootLogLines);
|
|
|
|
expect(bootLogLine).toContain(`storage tier=${tier}`);
|
|
expect(bootLogLine).toContain(`source=${source}`);
|
|
}
|
|
|
|
async function loadModuleGraphFromDotenv(
|
|
options: ModuleGraphFixtureOptions,
|
|
): Promise<ComposedModuleGraph> {
|
|
const originalEnv = snapshotProcessEnv();
|
|
const tempRoot = await mkdtemp(join(nodeOs.tmpdir(), 'mosaic-gateway-module-'));
|
|
let consoleInfoSpy: ReturnType<typeof vi.spyOn> | undefined;
|
|
let cwdSpy: ReturnType<typeof vi.spyOn> | undefined;
|
|
|
|
try {
|
|
const anchor = join(tempRoot, 'anchored', 'apps', 'gateway', 'src');
|
|
const homePath = join(tempRoot, 'home');
|
|
const cwdPath = join(tempRoot, 'ambient', 'parent', 'cwd');
|
|
const fixture: ModuleGraphFixture = {
|
|
tempRoot,
|
|
anchor,
|
|
homePath,
|
|
cwdPath,
|
|
monorepoRootEnvPath: resolve(anchor, '../../..', '.env'),
|
|
gatewayLocalEnvPath: resolve(anchor, '..', '.env'),
|
|
daemonEnvPath: join(homePath, '.config', 'mosaic', 'gateway', '.env'),
|
|
monorepoRootConfigPath: resolve(anchor, '../../..', 'mosaic.config.json'),
|
|
gatewayLocalConfigPath: resolve(anchor, '..', 'mosaic.config.json'),
|
|
};
|
|
consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation((): void => undefined);
|
|
|
|
for (const path of Object.values(fixture)) {
|
|
expectPathUnderTempRoot(path, tempRoot);
|
|
}
|
|
|
|
await mkdir(anchor, { recursive: true });
|
|
await mkdir(cwdPath, { recursive: true });
|
|
|
|
if ((options.rootEnvMode ?? 'present') === 'absent') {
|
|
if (
|
|
options.rootEnvContents !== undefined ||
|
|
options.rootTier !== undefined ||
|
|
options.redactionMarker !== undefined
|
|
) {
|
|
throw new Error('Expected no root env fixture values when rootEnvMode is absent');
|
|
}
|
|
} else {
|
|
if (options.rootEnvContents === undefined && options.rootTier === undefined) {
|
|
throw new Error('Expected rootTier or rootEnvContents');
|
|
}
|
|
|
|
const rootFixture = options.rootEnvContents ?? `MOSAIC_STORAGE_TIER=${options.rootTier}\n`;
|
|
const rootFixtureWithMarker = options.redactionMarker
|
|
? `${rootFixture}BETTER_AUTH_SECRET=${options.redactionMarker}\n`
|
|
: rootFixture;
|
|
await writeFixture(fixture.monorepoRootEnvPath, rootFixtureWithMarker, tempRoot);
|
|
}
|
|
|
|
if (options.daemonEnvContents !== undefined) {
|
|
await writeFixture(fixture.daemonEnvPath, options.daemonEnvContents, tempRoot);
|
|
}
|
|
|
|
if (options.gatewayLocalEnvContents !== undefined) {
|
|
await writeFixture(fixture.gatewayLocalEnvPath, options.gatewayLocalEnvContents, tempRoot);
|
|
} else if (options.gatewayLocalTier !== undefined) {
|
|
await writeFixture(
|
|
fixture.gatewayLocalEnvPath,
|
|
`MOSAIC_STORAGE_TIER=${options.gatewayLocalTier}\n`,
|
|
tempRoot,
|
|
);
|
|
}
|
|
|
|
process.env['HOME'] = homePath;
|
|
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);
|
|
|
|
if (options.inheritedTier !== undefined) {
|
|
process.env['MOSAIC_STORAGE_TIER'] = options.inheritedTier;
|
|
}
|
|
|
|
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);
|
|
|
|
if (options.inheritedTier === undefined) {
|
|
expect(process.env['MOSAIC_STORAGE_TIER']).toBeUndefined();
|
|
} else {
|
|
expect(process.env['MOSAIC_STORAGE_TIER']).toBe(options.inheritedTier);
|
|
}
|
|
|
|
const envModule = await import('./env.js');
|
|
expect(process.env['MOSAIC_STORAGE_TIER']).toBe(
|
|
options.expectedProcessTier ?? options.rootTier,
|
|
);
|
|
|
|
const { AppModule } = await import('./app.module.js');
|
|
const { FederationModule } = await import('./federation/federation.module.js');
|
|
const imports: unknown = Reflect.getMetadata(MODULE_METADATA.IMPORTS, AppModule);
|
|
|
|
if (!Array.isArray(imports)) {
|
|
throw new Error('AppModule imports metadata is not an array');
|
|
}
|
|
|
|
const { ConfigModule, MOSAIC_CONFIG } = await import('./config/config.module.js');
|
|
const providers: unknown = Reflect.getMetadata(MODULE_METADATA.PROVIDERS, ConfigModule);
|
|
|
|
if (!Array.isArray(providers)) {
|
|
throw new Error('ConfigModule providers metadata is not an array');
|
|
}
|
|
|
|
const configProvider = providers
|
|
.filter(isConfigModuleProvider)
|
|
.find((provider: ConfigModuleProvider): boolean => provider.provide === MOSAIC_CONFIG);
|
|
|
|
if (!configProvider) {
|
|
throw new Error('MOSAIC_CONFIG provider factory not found');
|
|
}
|
|
|
|
return {
|
|
imports,
|
|
federationModule: FederationModule,
|
|
bootLogLines: consoleInfoSpy.mock.calls.map((args: readonly unknown[]): string =>
|
|
args.map((value: unknown): string => String(value)).join(' '),
|
|
),
|
|
mosaicConfig: configProvider.useFactory(),
|
|
resolvedConfigPath: envModule.resolveGatewayConfigPath(),
|
|
};
|
|
} finally {
|
|
cwdSpy?.mockRestore();
|
|
vi.doUnmock('node:url');
|
|
vi.doUnmock('node:os');
|
|
vi.resetModules();
|
|
consoleInfoSpy?.mockRestore();
|
|
restoreProcessEnv(originalEnv);
|
|
await rm(tempRoot, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
describe('AppModule federation gating', (): void => {
|
|
it('loads dotenv before tracing and AppModule evaluation', async (): Promise<void> => {
|
|
const mainSource = await readFile(new URL('./main.ts', import.meta.url), 'utf8');
|
|
const envImportIndex = mainSource.indexOf("import './env.js';");
|
|
const tracingImportIndex = mainSource.indexOf("import './tracing.js';");
|
|
const appModuleImportIndex = mainSource.indexOf("import { AppModule } from './app.module.js';");
|
|
|
|
expect(envImportIndex).toBeGreaterThan(-1);
|
|
expect(envImportIndex).toBeLessThan(tracingImportIndex);
|
|
expect(envImportIndex).toBeLessThan(appModuleImportIndex);
|
|
});
|
|
|
|
it(
|
|
'ignores ambient cwd/.env and cwd/../.env files',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootTier: 'local',
|
|
setup: async (fixture: ModuleGraphFixture): Promise<void> => {
|
|
await writeFixture(
|
|
join(fixture.cwdPath, '.env'),
|
|
'MOSAIC_STORAGE_TIER=federated\n',
|
|
fixture.tempRoot,
|
|
);
|
|
await writeFixture(
|
|
resolve(fixture.cwdPath, '..', '.env'),
|
|
'MOSAIC_STORAGE_TIER=federated\n',
|
|
fixture.tempRoot,
|
|
);
|
|
},
|
|
});
|
|
|
|
expect(graph.imports).not.toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'local', MONOREPO_ROOT_DOTENV_LABEL);
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'ignores an ambient cwd/mosaic.config.json federated config',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootTier: 'local',
|
|
setup: async (fixture: ModuleGraphFixture): Promise<void> => {
|
|
await writeFixture(
|
|
join(fixture.cwdPath, 'mosaic.config.json'),
|
|
configJson('federated'),
|
|
fixture.tempRoot,
|
|
);
|
|
},
|
|
});
|
|
|
|
expect(graph.imports).not.toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'local', MONOREPO_ROOT_DOTENV_LABEL);
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'ignores an ambient cwd/../../mosaic.config.json federated config',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootTier: 'local',
|
|
setup: async (fixture: ModuleGraphFixture): Promise<void> => {
|
|
await writeFixture(
|
|
resolve(fixture.cwdPath, '../..', 'mosaic.config.json'),
|
|
configJson('federated'),
|
|
fixture.tempRoot,
|
|
);
|
|
},
|
|
});
|
|
|
|
expect(graph.imports).not.toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'local', MONOREPO_ROOT_DOTENV_LABEL);
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'anchored gateway-local config wins monorepo-root config and registers FederationModule',
|
|
async (): Promise<void> => {
|
|
let gatewayLocalConfigPath = '';
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootTier: 'local',
|
|
setup: async (fixture: ModuleGraphFixture): Promise<void> => {
|
|
gatewayLocalConfigPath = fixture.gatewayLocalConfigPath;
|
|
await writeFixture(
|
|
fixture.gatewayLocalConfigPath,
|
|
configJson('federated'),
|
|
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<void> => {
|
|
let daemonConfigPath = '';
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootEnvMode: 'absent',
|
|
setup: async (fixture: ModuleGraphFixture): Promise<void> => {
|
|
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');
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'logs mosaic.config.json when anchored config and env tiers are both federated',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootTier: 'federated',
|
|
setup: async (fixture: ModuleGraphFixture): Promise<void> => {
|
|
await writeFixture(
|
|
fixture.monorepoRootConfigPath,
|
|
configJson('federated'),
|
|
fixture.tempRoot,
|
|
);
|
|
},
|
|
});
|
|
|
|
expect(graph.imports).toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'federated', 'mosaic.config.json');
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'logs standalone from a monorepo-root .env DATABASE_URL fallback',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootEnvContents: 'DATABASE_URL=fixture-database-url\n',
|
|
});
|
|
|
|
expect(graph.imports).not.toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'standalone', MONOREPO_ROOT_DOTENV_LABEL);
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'attributes an invalid monorepo-root dotenv tier to the default',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootEnvContents: 'MOSAIC_STORAGE_TIER=invalid\n',
|
|
expectedProcessTier: 'invalid',
|
|
});
|
|
|
|
expect(graph.imports).not.toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'local', 'default');
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'attributes DATABASE_URL fallback to daemon .env ahead of inherited local tier',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootEnvMode: 'absent',
|
|
daemonEnvContents: 'DATABASE_URL=fixture-database-url\n',
|
|
inheritedTier: 'local',
|
|
expectedProcessTier: 'local',
|
|
});
|
|
|
|
expect(graph.imports).not.toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'standalone', DAEMON_DOTENV_LABEL);
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'daemon .env wins over monorepo-root and gateway-local tier values',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootTier: 'local',
|
|
gatewayLocalTier: 'federated',
|
|
daemonEnvContents: 'MOSAIC_STORAGE_TIER=standalone\n',
|
|
expectedProcessTier: 'standalone',
|
|
});
|
|
|
|
expect(graph.imports).not.toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'standalone', DAEMON_DOTENV_LABEL);
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'inherits process.env.MOSAIC_STORAGE_TIER over daemon, monorepo-root, and gateway-local dotenv values',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootTier: 'local',
|
|
gatewayLocalTier: 'federated',
|
|
daemonEnvContents: 'MOSAIC_STORAGE_TIER=federated\n',
|
|
inheritedTier: 'standalone',
|
|
expectedProcessTier: 'standalone',
|
|
});
|
|
|
|
expect(graph.imports).not.toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'standalone', 'process environment');
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'gateway-local .env configures the tier and source when the monorepo-root .env is absent',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootEnvMode: 'absent',
|
|
gatewayLocalTier: 'federated',
|
|
expectedProcessTier: 'federated',
|
|
});
|
|
|
|
expect(graph.imports).toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'federated', 'gateway-local .env');
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'monorepo-root .env wins over gateway-local tier values',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootTier: 'standalone',
|
|
gatewayLocalTier: 'federated',
|
|
});
|
|
|
|
expect(graph.imports).not.toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'standalone', MONOREPO_ROOT_DOTENV_LABEL);
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it.each(['local', 'standalone'] as const)(
|
|
'does not register FederationModule for the %s tier',
|
|
async (tier): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({ rootTier: tier });
|
|
|
|
expect(graph.imports).not.toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, tier, MONOREPO_ROOT_DOTENV_LABEL);
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'registers FederationModule when federated tier is supplied by the anchored monorepo root .env',
|
|
async (): Promise<void> => {
|
|
const redactionMarker = 'redaction-fixture-marker';
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootTier: 'federated',
|
|
redactionMarker,
|
|
});
|
|
|
|
expect(graph.imports).toContain(graph.federationModule);
|
|
expectBootLogLine(graph.bootLogLines, 'federated', MONOREPO_ROOT_DOTENV_LABEL);
|
|
expect(singleBootLogLine(graph.bootLogLines)).not.toContain(redactionMarker);
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'MOSAIC_CONFIG provider ignores an ambient cwd/mosaic.config.json config',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootTier: 'local',
|
|
setup: async (fixture: ModuleGraphFixture): Promise<void> => {
|
|
await writeFixture(
|
|
join(fixture.cwdPath, 'mosaic.config.json'),
|
|
JSON.stringify({
|
|
tier: 'federated',
|
|
storage: {
|
|
type: 'postgres',
|
|
url: 'postgresql://ambient-attacker.invalid/mosaic',
|
|
enableVector: true,
|
|
},
|
|
queue: { type: 'bullmq' },
|
|
memory: { type: 'pgvector' },
|
|
}),
|
|
fixture.tempRoot,
|
|
);
|
|
},
|
|
});
|
|
|
|
expect(graph.mosaicConfig.tier).toBe('local');
|
|
expect(graph.mosaicConfig.storage).not.toEqual(
|
|
expect.objectContaining({ url: 'postgresql://ambient-attacker.invalid/mosaic' }),
|
|
);
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
|
|
it(
|
|
'MOSAIC_CONFIG provider resolves from the anchored monorepo-root mosaic.config.json',
|
|
async (): Promise<void> => {
|
|
const graph = await loadModuleGraphFromDotenv({
|
|
rootTier: 'local',
|
|
setup: async (fixture: ModuleGraphFixture): Promise<void> => {
|
|
await writeFixture(
|
|
fixture.monorepoRootConfigPath,
|
|
configJson('federated'),
|
|
fixture.tempRoot,
|
|
);
|
|
},
|
|
});
|
|
|
|
expect(graph.mosaicConfig.tier).toBe('federated');
|
|
expect(graph.mosaicConfig.storage).toEqual(
|
|
expect.objectContaining({ url: 'postgresql://fixture.invalid/mosaic' }),
|
|
);
|
|
},
|
|
MODULE_IMPORT_TIMEOUT_MS,
|
|
);
|
|
});
|