This commit is contained in:
@@ -3,6 +3,7 @@ export {
|
||||
DEFAULT_LOCAL_CONFIG,
|
||||
DEFAULT_STANDALONE_CONFIG,
|
||||
DEFAULT_FEDERATED_CONFIG,
|
||||
MosaicConfigEnvironmentError,
|
||||
loadConfig,
|
||||
validateConfig,
|
||||
detectFromEnv,
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { MosaicConfigEnvironmentError as PublicMosaicConfigEnvironmentError } from '@mosaicstack/config';
|
||||
import { detectFromEnv } from './mosaic-config.js';
|
||||
|
||||
interface TypedSelectionFailure {
|
||||
readonly name: string;
|
||||
readonly code: string;
|
||||
}
|
||||
|
||||
describe('#1182 fail closed — a wrong answer must not be read as no answer', () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
it('exports the typed storage-tier refusal from the public @mosaicstack/config entry point', () => {
|
||||
expect(new PublicMosaicConfigEnvironmentError()).toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...originalEnv };
|
||||
delete process.env['DATABASE_URL'];
|
||||
delete process.env['VALKEY_URL'];
|
||||
delete process.env['MOSAIC_STORAGE_TIER'];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
it('FL-08 rejects an invalid explicit MOSAIC_STORAGE_TIER before local PGlite selection', () => {
|
||||
process.env['MOSAIC_STORAGE_TIER'] = 'federatd';
|
||||
let pgliteSelectionCount = 0;
|
||||
let failure: unknown;
|
||||
|
||||
try {
|
||||
const config = detectFromEnv();
|
||||
if (config.storage.type === 'pglite') {
|
||||
pgliteSelectionCount += 1;
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
failure = error;
|
||||
}
|
||||
|
||||
expect
|
||||
.soft(failure, 'invalid explicit storage tier must produce a typed selection failure')
|
||||
.toMatchObject({
|
||||
name: 'MosaicConfigEnvironmentError',
|
||||
code: 'invalid_storage_tier',
|
||||
} satisfies TypedSelectionFailure);
|
||||
expect(
|
||||
pgliteSelectionCount,
|
||||
'invalid explicit storage tier must fail before local PGlite is selected',
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it.each([undefined, ''])('preserves the local default for true absence: %j', (tier) => {
|
||||
if (tier === undefined) delete process.env['MOSAIC_STORAGE_TIER'];
|
||||
else process.env['MOSAIC_STORAGE_TIER'] = tier;
|
||||
|
||||
const config = detectFromEnv();
|
||||
expect(config.tier).toBe('local');
|
||||
expect(config.storage.type).toBe('pglite');
|
||||
});
|
||||
|
||||
it('anti-drift: storage selection validates a non-empty tier before the local default', () => {
|
||||
const source = readFileSync(new URL('./mosaic-config.ts', import.meta.url), 'utf8');
|
||||
|
||||
expect(
|
||||
/if \(tier !== undefined && tier !== ''[^]*throw new [A-Za-z]+Error/.test(source),
|
||||
'closed storage enums must reject invalid explicit input before DEFAULT_LOCAL_CONFIG',
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -20,6 +20,16 @@ export interface MosaicConfig {
|
||||
memory: MemoryConfigRef;
|
||||
}
|
||||
|
||||
/** Typed startup refusal for an invalid explicit storage-tier selection. */
|
||||
export class MosaicConfigEnvironmentError extends Error {
|
||||
readonly code = 'invalid_storage_tier' as const;
|
||||
|
||||
constructor() {
|
||||
super('Invalid MOSAIC_STORAGE_TIER; expected "local", "standalone", or "federated".');
|
||||
this.name = 'MosaicConfigEnvironmentError';
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Defaults */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -126,6 +136,10 @@ export function validateConfig(raw: unknown): MosaicConfig {
|
||||
export function detectFromEnv(): MosaicConfig {
|
||||
const tier = process.env['MOSAIC_STORAGE_TIER'];
|
||||
|
||||
if (tier !== undefined && tier !== '' && !VALID_TIERS.has(tier)) {
|
||||
throw new MosaicConfigEnvironmentError();
|
||||
}
|
||||
|
||||
if (tier === 'federated') {
|
||||
if (process.env['DATABASE_URL']) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user