From 10bedeed415e83469a5afcce1abf96f4dd559753 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 30 Mar 2023 15:51:47 +0200 Subject: [PATCH] config-loader: MergedConfigSource tests + fixes Signed-off-by: Patrik Oldsberg --- .../src/sources/ConfigSources.ts | 4 +- .../src/sources/MergedConfigSource.test.ts | 107 ++++++++++++++++++ .../src/sources/MergedConfigSource.ts | 8 +- .../src/sources/__testUtils__/testUtils.ts | 14 +++ 4 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 packages/config-loader/src/sources/MergedConfigSource.test.ts diff --git a/packages/config-loader/src/sources/ConfigSources.ts b/packages/config-loader/src/sources/ConfigSources.ts index 02780e9345..6e90994c6e 100644 --- a/packages/config-loader/src/sources/ConfigSources.ts +++ b/packages/config-loader/src/sources/ConfigSources.ts @@ -18,7 +18,7 @@ import { Config, ConfigReader } from '@backstage/config'; import parseArgs from 'minimist'; import { EnvConfigSource } from './EnvConfigSource'; import { FileConfigSource } from './FileConfigSource'; -import { MergeConfigSource } from './MergedConfigSource'; +import { MergedConfigSource } from './MergedConfigSource'; import { RemoteConfigSource } from './RemoteConfigSource'; import { ConfigSource } from './types'; import { ObservableConfigProxy } from './ObservableConfigProxy'; @@ -63,7 +63,7 @@ export class ConfigSources { } static merge(sources: ConfigSource[]): ConfigSource { - return MergeConfigSource.fromConfigSources(sources); + return MergedConfigSource.from(sources); } static toConfig(source: ConfigSource): Promise { diff --git a/packages/config-loader/src/sources/MergedConfigSource.test.ts b/packages/config-loader/src/sources/MergedConfigSource.test.ts new file mode 100644 index 0000000000..e8a3b8b565 --- /dev/null +++ b/packages/config-loader/src/sources/MergedConfigSource.test.ts @@ -0,0 +1,107 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MergedConfigSource } from './MergedConfigSource'; +import { MutableConfigSource } from './MutableConfigSource'; +import { isResolved, readAll, simpleSource } from './__testUtils__/testUtils'; + +describe('MergeConfigSource', () => { + it('should forward from a single source', async () => { + const source = simpleSource([{ a: 1 }, { a: 2 }, { a: 3 }]); + const merged = MergedConfigSource.from([source]); + await expect(readAll(merged)).resolves.toEqual([ + [{ data: { a: 1 }, context: 'mock-source' }], + [{ data: { a: 2 }, context: 'mock-source' }], + [{ data: { a: 3 }, context: 'mock-source' }], + ]); + }); + + it('should forward from multiple sources', async () => { + const sourceA = simpleSource([{ a: 1 }, { a: 2 }, { a: 3 }], 'a'); + const sourceB = simpleSource([{ b: 1 }, { b: 2 }], 'b'); + const sourceC = simpleSource([{ c: 1 }], 'c'); + const merged = MergedConfigSource.from([sourceA, sourceB, sourceC]); + await expect(readAll(merged)).resolves.toEqual([ + [ + { data: { a: 1 }, context: 'a' }, + { data: { b: 1 }, context: 'b' }, + { data: { c: 1 }, context: 'c' }, + ], + [ + { data: { a: 2 }, context: 'a' }, + { data: { b: 1 }, context: 'b' }, + { data: { c: 1 }, context: 'c' }, + ], + [ + { data: { a: 3 }, context: 'a' }, + { data: { b: 1 }, context: 'b' }, + { data: { c: 1 }, context: 'c' }, + ], + [ + { data: { a: 3 }, context: 'a' }, + { data: { b: 2 }, context: 'b' }, + { data: { c: 1 }, context: 'c' }, + ], + ]); + }); + + it('should forward from multiple sources at difference pace', async () => { + const sourceA = MutableConfigSource.create({ context: 'a' }); + const sourceB = MutableConfigSource.create({ context: 'b' }); + const merged = MergedConfigSource.from([sourceA, sourceB]); + + const it = merged.readConfigData(); + + const first = it.next(); + await expect(isResolved(first, { wait: true })).resolves.toBe(false); + sourceA.setData({ a: 1 }); + await expect(isResolved(first, { wait: true })).resolves.toBe(false); + sourceB.setData({ b: 1 }); + + await expect(first).resolves.toEqual({ + value: { + data: [ + { data: { a: 1 }, context: 'a' }, + { data: { b: 1 }, context: 'b' }, + ], + }, + done: false, + }); + + sourceB.setData({ b: 2 }); + + await expect(it.next()).resolves.toEqual({ + value: { + data: [ + { data: { a: 1 }, context: 'a' }, + { data: { b: 2 }, context: 'b' }, + ], + }, + done: false, + }); + + const last = it.next(); + await expect(isResolved(last, { wait: true })).resolves.toBe(false); + sourceA.close(); + await expect(isResolved(last, { wait: true })).resolves.toBe(false); + sourceB.close(); + await expect(isResolved(last, { wait: true })).resolves.toBe(true); + + await expect(last).resolves.toEqual({ + done: true, + }); + }); +}); diff --git a/packages/config-loader/src/sources/MergedConfigSource.ts b/packages/config-loader/src/sources/MergedConfigSource.ts index 4aeab4cecf..9bf5b20b38 100644 --- a/packages/config-loader/src/sources/MergedConfigSource.ts +++ b/packages/config-loader/src/sources/MergedConfigSource.ts @@ -21,9 +21,9 @@ import { ReadConfigDataOptions, } from './types'; -export class MergeConfigSource implements ConfigSource { - static fromConfigSources(sources: ConfigSource[]): ConfigSource { - return new MergeConfigSource(sources); +export class MergedConfigSource implements ConfigSource { + static from(sources: ConfigSource[]): ConfigSource { + return new MergedConfigSource(sources); } private constructor(private readonly sources: ConfigSource[]) {} @@ -53,7 +53,7 @@ export class MergeConfigSource implements ConfigSource { while (results.some(Boolean)) { try { - const [i, result] = (await Promise.race(results))!; + const [i, result] = (await Promise.race(results.filter(Boolean)))!; if (result.done) { results[i] = undefined; } else { diff --git a/packages/config-loader/src/sources/__testUtils__/testUtils.ts b/packages/config-loader/src/sources/__testUtils__/testUtils.ts index 6b954a8489..2701a80f52 100644 --- a/packages/config-loader/src/sources/__testUtils__/testUtils.ts +++ b/packages/config-loader/src/sources/__testUtils__/testUtils.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { JsonObject } from '@backstage/types'; import { ConfigSource, ConfigSourceData } from '../types'; export function isResolved( @@ -49,3 +50,16 @@ export async function readAll( return results; } + +export function simpleSource( + data: JsonObject[], + context: string = 'mock-source', +): ConfigSource { + return { + async *readConfigData() { + for (const d of data) { + yield { data: [{ data: d, context }] }; + } + }, + }; +}