|
| 1 | +/** |
| 2 | + * Helpers shared by the change-stream integration tests (the moved engine test and the observe-parity test). |
| 3 | + * Extracted from the original `engine.integration.test.ts` so both can drive the real change-stream |
| 4 | + * multiplexer the same way. |
| 5 | + */ |
| 6 | +import { MongoClient, Collection, ChangeStreamDocument } from 'mongodb' |
| 7 | +import { protectString, ProtectedString } from '@sofie-automation/corelib/dist/protectedString' |
| 8 | +import { MongoQuery, MongoFieldSpecifier } from '@sofie-automation/corelib/dist/mongo' |
| 9 | +import { CollectionChangeFeed, createCollectionChangeStream } from '../../collections/changeStream/collectionChangeFeed' |
| 10 | +import { ObserveMultiplexer, ObserveMultiplexerDeps } from '../../collections/changeStream/observeMultiplexer' |
| 11 | +import type { ObserveViewShape } from '@sofie-automation/corelib/dist/memoryCollection/observeView' |
| 12 | + |
| 13 | +export interface TestDoc { |
| 14 | + _id: ProtectedString<any> |
| 15 | + name?: string |
| 16 | + val?: number |
| 17 | + studioId?: string |
| 18 | +} |
| 19 | + |
| 20 | +export const id = (s: string): ProtectedString<any> => protectString<any>(s) |
| 21 | + |
| 22 | +/** |
| 23 | + * Poll `predicate` until it is true or the timeout elapses (for awaiting async change-stream delivery). |
| 24 | + * Delivery is normally sub-second; the default allows for CI load while staying below the integration |
| 25 | + * project's jest `testTimeout`, so the poll (not jest) reports a genuine hang. |
| 26 | + */ |
| 27 | +export async function waitFor(predicate: () => boolean, timeoutMs = 15000): Promise<void> { |
| 28 | + const start = Date.now() |
| 29 | + while (!predicate()) { |
| 30 | + if (Date.now() - start > timeoutMs) throw new Error('waitFor timed out') |
| 31 | + await new Promise((r) => setTimeout(r, 20)) |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +export interface Sink { |
| 36 | + added: Array<[any, any]> |
| 37 | + changed: Array<[any, any]> |
| 38 | + removed: any[] |
| 39 | +} |
| 40 | + |
| 41 | +export function newSink(): Sink { |
| 42 | + return { added: [], changed: [], removed: [] } |
| 43 | +} |
| 44 | + |
| 45 | +/** Build an {@link ObserveMultiplexer} backed by a real change stream + snapshot against `collection`. */ |
| 46 | +export function makeMultiplexer( |
| 47 | + client: MongoClient, |
| 48 | + collection: Collection<any>, |
| 49 | + selector: MongoQuery<TestDoc>, |
| 50 | + projection: MongoFieldSpecifier<TestDoc> | undefined, |
| 51 | + shape?: ObserveViewShape<TestDoc> |
| 52 | +): ObserveMultiplexer<TestDoc> { |
| 53 | + const feed = new CollectionChangeFeed( |
| 54 | + collection.collectionName, |
| 55 | + () => createCollectionChangeStream(client, client.db(), collection.collectionName), |
| 56 | + () => undefined |
| 57 | + ) |
| 58 | + const deps: ObserveMultiplexerDeps<TestDoc> = { |
| 59 | + snapshot: async () => collection.find(selector as any).toArray() as unknown as Promise<TestDoc[]>, |
| 60 | + subscribeFeed: (onChange, onReconnect) => |
| 61 | + feed.subscribe(onChange as (c: ChangeStreamDocument<any>) => void, onReconnect), |
| 62 | + } |
| 63 | + return new ObserveMultiplexer<TestDoc>(selector, projection, shape, deps, () => undefined) |
| 64 | +} |
| 65 | + |
| 66 | +/** Subscribe an observeChanges sink to a multiplexer; teardown is driven by aborting `signal`. */ |
| 67 | +export async function subscribeChanges(m: ObserveMultiplexer<TestDoc>, sink: Sink, signal: AbortSignal): Promise<void> { |
| 68 | + await m.addObserveChangesSubscriber( |
| 69 | + { |
| 70 | + added: (i: any, f: any) => sink.added.push([i, f]), |
| 71 | + changed: (i: any, f: any) => sink.changed.push([i, f]), |
| 72 | + removed: (i: any) => sink.removed.push(i), |
| 73 | + } as any, |
| 74 | + signal, |
| 75 | + false |
| 76 | + ) |
| 77 | +} |
0 commit comments