From 26eb9233c435ac6c4e2ef86f937bf6b624d2e3f3 Mon Sep 17 00:00:00 2001 From: Maria Garcia Luque Date: Wed, 20 May 2026 11:12:08 +0200 Subject: [PATCH 1/2] [STEP-2.10-006A] Add setServiceUrl() to EnvironmentService - Add _serviceOverrides Map for runtime service URL overrides - Add setServiceUrl(service, url) method with protectedFields support - Update getApiUrl() priority: serviceOverrides > config > apiBaseUrlOverride > base - Clear serviceOverrides in loadConfig(), setEnvironment(), reset() - Add 'services' to ProtectedField union type - Add 9 unit tests (516 total, 0 failures) --- .../environment/environment.service.spec.ts | 58 ++++++++++++++++++- .../lib/environment/environment.service.ts | 35 ++++++++++- .../src/lib/environment/environment.types.ts | 3 +- 3 files changed, 91 insertions(+), 5 deletions(-) diff --git a/packages/core/src/lib/environment/environment.service.spec.ts b/packages/core/src/lib/environment/environment.service.spec.ts index 793153f..f1505d5 100644 --- a/packages/core/src/lib/environment/environment.service.spec.ts +++ b/packages/core/src/lib/environment/environment.service.spec.ts @@ -33,7 +33,7 @@ const MULTI_ENV_CONFIG: EnvironmentConfig = { const PROTECTED_CONFIG: EnvironmentConfig = { ...MULTI_ENV_CONFIG, - protectedFields: ['currentEnv', 'apiBaseUrl', 'flags'], + protectedFields: ['currentEnv', 'apiBaseUrl', 'flags', 'services'], }; // --------------------------------------------------------------------------- @@ -248,6 +248,54 @@ describe('EnvironmentService (injected config)', () => { }); }); + // --- setServiceUrl --- + + describe('setServiceUrl', () => { + it('should override a service URL', () => { + service.setServiceUrl('lending', 'http://custom-lending.com'); + expect(service.getApiUrl('lending')).toBe('http://custom-lending.com'); + }); + + it('should take priority over config service URLs', () => { + expect(service.getApiUrl('lending')).toBe('http://localhost:3001'); + service.setServiceUrl('lending', 'http://override.com'); + expect(service.getApiUrl('lending')).toBe('http://override.com'); + }); + + it('should allow adding a new service not in config', () => { + service.setServiceUrl('payments', 'http://payments.local'); + expect(service.getApiUrl('payments')).toBe('http://payments.local'); + }); + + it('should not affect apiBaseUrl resolution', () => { + service.setServiceUrl('lending', 'http://custom.com'); + expect(service.getApiUrl()).toBe('http://localhost:3000'); + }); + + it('should not affect other services', () => { + service.setServiceUrl('lending', 'http://custom.com'); + expect(service.getApiUrl('auth')).toBe('http://localhost:4000'); + }); + + it('should be cleared on setEnvironment', () => { + service.setServiceUrl('lending', 'http://custom.com'); + service.setEnvironment('staging'); + expect(service.getApiUrl('lending')).toBe('https://lending.stg.firefly.com'); + }); + + it('should be cleared on reset', () => { + service.setServiceUrl('lending', 'http://custom.com'); + service.reset(); + expect(service.getApiUrl('lending')).toBe('http://localhost:3001'); + }); + + it('should be cleared on loadConfig', () => { + service.setServiceUrl('lending', 'http://custom.com'); + service.loadConfig(MULTI_ENV_CONFIG); + expect(service.getApiUrl('lending')).toBe('http://localhost:3001'); + }); + }); + // --- reset --- describe('reset', () => { @@ -331,6 +379,14 @@ describe('EnvironmentService (protectedFields)', () => { expect.stringContaining('flags'), ); }); + + it('setServiceUrl should be blocked and log warning', () => { + service.setServiceUrl('lending', 'http://hacked.com'); + expect(service.getApiUrl('lending')).toBe('http://localhost:3001'); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('services'), + ); + }); }); // --------------------------------------------------------------------------- diff --git a/packages/core/src/lib/environment/environment.service.ts b/packages/core/src/lib/environment/environment.service.ts index 2c8ea23..19eff81 100644 --- a/packages/core/src/lib/environment/environment.service.ts +++ b/packages/core/src/lib/environment/environment.service.ts @@ -72,6 +72,9 @@ export class EnvironmentService { /** Runtime overrides for individual flags (set by `setFlag()`). */ private readonly _flagOverrides = new Map(); + /** Runtime overrides for individual service URLs (set by `setServiceUrl()`). */ + private readonly _serviceOverrides = new Map(); + // --------------------------------------------------------------------------- // Public signals (readonly) // --------------------------------------------------------------------------- @@ -113,6 +116,7 @@ export class EnvironmentService { this._originalEnv = config.default; this._apiBaseUrlOverride = null; this._flagOverrides.clear(); + this._serviceOverrides.clear(); } /** @@ -168,9 +172,16 @@ export class EnvironmentService { getApiUrl(service?: string): string { const entry = this._getCurrentEntry(); - // Service-specific URL takes priority - if (service && entry.services?.[service]) { - return entry.services[service]; + if (service) { + // Runtime service override takes highest priority + if (this._serviceOverrides.has(service)) { + return this._serviceOverrides.get(service)!; + } + + // Config service-specific URL + if (entry.services?.[service]) { + return entry.services[service]; + } } // apiBaseUrl override takes priority over config @@ -239,6 +250,7 @@ export class EnvironmentService { this._currentEnv.set(env); this._apiBaseUrlOverride = null; this._flagOverrides.clear(); + this._serviceOverrides.clear(); } /** @@ -271,6 +283,22 @@ export class EnvironmentService { this._flagOverrides.set(key, value); } + /** + * Override a single service URL at runtime. + * + * Overrides take priority over the environment's `services` map + * and over `apiBaseUrl` for the specified service. + * + * Blocked when `'services'` is in `protectedFields`. + * + * @param service - Service name + * @param url - Service URL + */ + setServiceUrl(service: string, url: string): void { + if (this._isProtected('services')) return; + this._serviceOverrides.set(service, url); + } + /** * Reset the service to the state after the last `loadConfig()` call. * @@ -284,6 +312,7 @@ export class EnvironmentService { } this._apiBaseUrlOverride = null; this._flagOverrides.clear(); + this._serviceOverrides.clear(); } // --------------------------------------------------------------------------- diff --git a/packages/core/src/lib/environment/environment.types.ts b/packages/core/src/lib/environment/environment.types.ts index fc965f3..474b0df 100644 --- a/packages/core/src/lib/environment/environment.types.ts +++ b/packages/core/src/lib/environment/environment.types.ts @@ -68,8 +68,9 @@ export interface EnvironmentEntry { * - `'currentEnv'` — blocks `setEnvironment()` * - `'apiBaseUrl'` — blocks `setApiBaseUrl()` * - `'flags'` — blocks `setFlag()` + * - `'services'` — blocks `setServiceUrl()` */ -export type ProtectedField = 'currentEnv' | 'apiBaseUrl' | 'flags'; +export type ProtectedField = 'currentEnv' | 'apiBaseUrl' | 'flags' | 'services'; // --------------------------------------------------------------------------- // Module configuration From efaf06300e6778d1e3d07673ab41864dd6428755 Mon Sep 17 00:00:00 2001 From: Maria Garcia Luque Date: Wed, 20 May 2026 11:13:05 +0200 Subject: [PATCH 2/2] [STEP-2.10-006A] Release @fireflyframework/core 0.10.1 - Add setServiceUrl() to EnvironmentService - Add 'services' to ProtectedField - Update getApiUrl() resolution priority --- packages/core/CHANGELOG.md | 8 ++++++++ packages/core/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 3f17af4..37c9857 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.10.1] - 2026-05-20 + +### Added +- `setServiceUrl(service, url)` — runtime override of individual service URLs +- `'services'` added to `ProtectedField` union type — blocks `setServiceUrl()` when listed in `protectedFields` +- `getApiUrl(service)` now checks runtime service overrides first (priority: serviceOverrides > config services > apiBaseUrlOverride > apiBaseUrl) +- Service URL overrides are cleared on `setEnvironment()`, `loadConfig()`, and `reset()` + ## [0.10.0] - 2026-05-20 ### Added diff --git a/packages/core/package.json b/packages/core/package.json index e127aca..579080b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@fireflyframework/core", - "version": "0.10.0", + "version": "0.10.1", "publishConfig": { "registry": "https://npm.pkg.github.com" },