From 4e2c23737ecb66c300d60d52bffc0e9568b771c2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 20 Aug 2025 12:30:02 +0200 Subject: [PATCH] backend-test-utils: add update method for mockServices.rootConfig Signed-off-by: Patrik Oldsberg --- .changeset/giant-zebras-peel.md | 5 +++ packages/backend-test-utils/package.json | 2 ++ packages/backend-test-utils/report.api.md | 6 +++- .../src/services/mockServices.test.ts | 34 +++++++++++++++++++ .../src/services/mockServices.ts | 14 ++++++-- yarn.lock | 2 ++ 6 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 .changeset/giant-zebras-peel.md diff --git a/.changeset/giant-zebras-peel.md b/.changeset/giant-zebras-peel.md new file mode 100644 index 0000000000..c08caaddb7 --- /dev/null +++ b/.changeset/giant-zebras-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': minor +--- + +The `mockServices.rootConfig()` instance now has an `update` method that can be used to test configuration subscriptions and updates. diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 5383f8a756..b4a6a48462 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -71,6 +71,7 @@ "fs-extra": "^11.0.0", "keyv": "^5.2.1", "knex": "^3.0.0", + "lodash": "^4.17.21", "mysql2": "^3.0.0", "pg": "^8.11.3", "pg-connection-string": "^2.3.0", @@ -84,6 +85,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@types/jest": "*", + "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "supertest": "^7.0.0" } diff --git a/packages/backend-test-utils/report.api.md b/packages/backend-test-utils/report.api.md index eeb5009641..02fa9d8587 100644 --- a/packages/backend-test-utils/report.api.md +++ b/packages/backend-test-utils/report.api.md @@ -305,7 +305,11 @@ export namespace mockServices { ) => ServiceMock; } // (undocumented) - export function rootConfig(options?: rootConfig.Options): RootConfigService; + export function rootConfig( + options?: rootConfig.Options, + ): RootConfigService & { + update(options: { data: JsonObject }): void; + }; // (undocumented) export namespace rootConfig { // (undocumented) diff --git a/packages/backend-test-utils/src/services/mockServices.test.ts b/packages/backend-test-utils/src/services/mockServices.test.ts index 80d6e4c6f1..f5ba684608 100644 --- a/packages/backend-test-utils/src/services/mockServices.test.ts +++ b/packages/backend-test-utils/src/services/mockServices.test.ts @@ -30,3 +30,37 @@ describe('mockServices', () => { expect(mockServices[key].factory()).toEqual(expect.any(Object)); }); }); + +describe('mockServices.rootConfig()', () => { + it('should notify about updates', async () => { + const config = mockServices.rootConfig(); + + const fooConfig = config.getConfig('foo'); + + const rootListener = jest.fn(); + const fooListener = jest.fn(); + + config.subscribe?.(rootListener); + fooConfig.subscribe?.(fooListener); + + expect(rootListener).toHaveBeenCalledTimes(0); + expect(fooListener).toHaveBeenCalledTimes(0); + + config.update({ data: { foo: { bar: 1, baz: 2 } } }); + + expect(rootListener).toHaveBeenCalledTimes(1); + expect(fooListener).toHaveBeenCalledTimes(1); + + config.update({ data: { foo: { baz: 2, bar: 1 } } }); + + // Doesn't notify, no changes + expect(rootListener).toHaveBeenCalledTimes(1); + expect(fooListener).toHaveBeenCalledTimes(1); + + config.update({ data: { foo: { bar: 1, baz: 2 }, unrelated: 'key' } }); + + // Notifies all listeners, even if an unrelated key was changed + expect(rootListener).toHaveBeenCalledTimes(2); + expect(fooListener).toHaveBeenCalledTimes(2); + }); +}); diff --git a/packages/backend-test-utils/src/services/mockServices.ts b/packages/backend-test-utils/src/services/mockServices.ts index 1d3a1578cc..f7d57d3fb1 100644 --- a/packages/backend-test-utils/src/services/mockServices.ts +++ b/packages/backend-test-utils/src/services/mockServices.ts @@ -58,6 +58,8 @@ import { MockEventsService } from './MockEventsService'; import { MockPermissionsService } from './MockPermissionsService'; import { simpleMock } from './simpleMock'; import { MockSchedulerService } from './MockSchedulerService'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { ObservableConfigProxy } from '../../../config-loader/src/sources/ObservableConfigProxy'; /** @internal */ function createLoggerMock() { @@ -137,8 +139,16 @@ function simpleFactoryWithOptions< * ``` */ export namespace mockServices { - export function rootConfig(options?: rootConfig.Options): RootConfigService { - return new ConfigReader(options?.data, 'mock-config'); + export function rootConfig( + options?: rootConfig.Options, + ): RootConfigService & { update(options: { data: JsonObject }): void } { + const config = ObservableConfigProxy.create(new AbortController()); + config.setConfig(new ConfigReader(options?.data ?? {}, 'mock-config')); + return Object.assign(config, { + update({ data }: { data: JsonObject }): void { + config.setConfig(new ConfigReader(data, 'mock-config')); + }, + }); } export namespace rootConfig { export type Options = { data?: JsonObject }; diff --git a/yarn.lock b/yarn.lock index 48ad24aa59..b7c187d56d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2755,6 +2755,7 @@ __metadata: "@types/express-serve-static-core": "npm:^4.17.5" "@types/jest": "npm:*" "@types/keyv": "npm:^4.2.0" + "@types/lodash": "npm:^4.14.151" "@types/qs": "npm:^6.9.6" "@types/supertest": "npm:^2.0.8" better-sqlite3: "npm:^12.0.0" @@ -2763,6 +2764,7 @@ __metadata: fs-extra: "npm:^11.0.0" keyv: "npm:^5.2.1" knex: "npm:^3.0.0" + lodash: "npm:^4.17.21" mysql2: "npm:^3.0.0" pg: "npm:^8.11.3" pg-connection-string: "npm:^2.3.0"