config-loader: MergedConfigSource tests + fixes

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-03-30 15:51:47 +02:00
parent bc67fe43cd
commit 10bedeed41
4 changed files with 127 additions and 6 deletions
@@ -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<LiveConfig> {
@@ -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,
});
});
});
@@ -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 {
@@ -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 }] };
}
},
};
}