11/**
22 * Integration tests for the change-stream observe engine against a REAL MongoDB.
33 *
4- * These require a mongod binary (downloaded on first run by mongodb-memory-server) and start a single-node
5- * replica set (change streams require a replica set). They are slower than the pure-unit tests and live in
6- * the normal suite for now; if they become too slow they can be split into their own jest config.
4+ * Moved out of `server/collections/changeStream/__tests__/` into the shared `integration` jest project:
5+ * instead of booting its own `MongoMemoryReplSet` per file, it connects to the single replica set started
6+ * once by `__mocks__/integration-global-setup.js` (change streams require a replica set). Run via
7+ * `yarn unit:integration`.
78 */
8- import { MongoClient , Collection , ChangeStreamDocument } from 'mongodb'
9- import { MongoMemoryReplSet } from 'mongodb-memory-server'
9+ import { MongoClient , ChangeStreamDocument } from 'mongodb'
1010import { Meteor } from 'meteor/meteor'
11- import { protectString , ProtectedString } from '@sofie-automation/corelib/dist/protectedString'
12- import { MongoQuery , MongoFieldSpecifier } from '@sofie-automation/corelib/dist/mongo'
13- import { CollectionChangeFeed , createCollectionChangeStream , ChangeStreamLike } from '../collectionChangeFeed'
14- import { ObserveMultiplexer , ObserveMultiplexerDeps } from '../observeMultiplexer'
15-
16- interface TestDoc {
17- _id : ProtectedString < any >
18- name ?: string
19- val ?: number
20- studioId ?: string
21- }
22- const id = ( s : string ) => protectString < any > ( s )
23-
24- async function waitFor ( predicate : ( ) => boolean , timeoutMs = 5000 ) : Promise < void > {
25- const start = Date . now ( )
26- while ( ! predicate ( ) ) {
27- if ( Date . now ( ) - start > timeoutMs ) throw new Error ( 'waitFor timed out' )
28- await new Promise ( ( r ) => setTimeout ( r , 20 ) )
29- }
30- }
11+ import {
12+ CollectionChangeFeed ,
13+ createCollectionChangeStream ,
14+ ChangeStreamLike ,
15+ } from '../../collections/changeStream/collectionChangeFeed'
16+ import { ObserveMultiplexer , ObserveMultiplexerDeps } from '../../collections/changeStream/observeMultiplexer'
17+ import { connectSharedMongoClient , freshCollection } from './_integrationDb'
18+ import { TestDoc , id , waitFor , newSink , makeMultiplexer , subscribeChanges } from './_observeHelpers'
3119
3220describe ( 'change-stream engine (integration)' , ( ) => {
33- let replSet : MongoMemoryReplSet
3421 let client : MongoClient
35- let collectionCounter = 0
3622
3723 // Every observe handle created in a test is tracked here and stopped in afterEach, which cascades to
3824 // stopping the underlying change feed (otherwise a closed client would restart-loop forever).
3925 const openHandles : Meteor . LiveQueryHandle [ ] = [ ]
4026
4127 beforeAll ( async ( ) => {
42- replSet = await MongoMemoryReplSet . create ( { replSet : { count : 1 } } )
43- client = new MongoClient ( replSet . getUri ( ) , { ignoreUndefined : true } )
44- await client . connect ( )
28+ client = await connectSharedMongoClient ( )
4529 } , 120000 )
4630
4731 afterEach ( ( ) => {
@@ -51,63 +35,13 @@ describe('change-stream engine (integration)', () => {
5135
5236 afterAll ( async ( ) => {
5337 await client ?. close ( )
54- await replSet ?. stop ( )
5538 } )
5639
57- function makeMultiplexer (
58- collection : Collection < any > ,
59- selector : MongoQuery < TestDoc > ,
60- projection : MongoFieldSpecifier < TestDoc > | undefined
61- ) : ObserveMultiplexer < TestDoc > {
62- const feed = new CollectionChangeFeed (
63- collection . collectionName ,
64- ( ) => createCollectionChangeStream ( client , client . db ( ) , collection . collectionName ) ,
65- ( ) => undefined
66- )
67- const deps : ObserveMultiplexerDeps < TestDoc > = {
68- snapshot : async ( ) => collection . find ( selector as any ) . toArray ( ) as unknown as Promise < TestDoc [ ] > ,
69- subscribeFeed : ( onChange , onReconnect ) =>
70- feed . subscribe ( onChange as ( c : ChangeStreamDocument < any > ) => void , onReconnect ) ,
71- }
72- return new ObserveMultiplexer < TestDoc > ( selector , projection , deps , ( ) => undefined )
73- }
74-
75- async function subscribeChanges (
76- m : ObserveMultiplexer < TestDoc > ,
77- sink : {
78- added : Array < [ any , any ] >
79- changed : Array < [ any , any ] >
80- removed : any [ ]
81- }
82- ) {
83- const handle = await m . addObserveChangesSubscriber (
84- {
85- added : ( i : any , f : any ) => sink . added . push ( [ i , f ] ) ,
86- changed : ( i : any , f : any ) => sink . changed . push ( [ i , f ] ) ,
87- removed : ( i : any ) => sink . removed . push ( i ) ,
88- } as any ,
89- false
90- )
91- openHandles . push ( handle )
92- return handle
93- }
94-
95- function newSink ( ) {
96- return { added : [ ] as Array < [ any , any ] > , changed : [ ] as Array < [ any , any ] > , removed : [ ] as any [ ] }
97- }
98-
99- async function freshCollection ( seed : TestDoc [ ] = [ ] ) : Promise < Collection < any > > {
100- const name = `obs_test_${ collectionCounter ++ } `
101- const collection = client . db ( ) . collection ( name )
102- if ( seed . length ) await collection . insertMany ( seed as any [ ] )
103- return collection
104- }
105-
10640 test ( 'initial snapshot, insert, update, delete' , async ( ) => {
107- const collection = await freshCollection ( [ { _id : id ( 'A' ) , name : 'a' , val : 1 } ] )
41+ const collection = await freshCollection < TestDoc > ( client , [ { _id : id ( 'A' ) , name : 'a' , val : 1 } ] )
10842 const sink = newSink ( )
109- const m = makeMultiplexer ( collection , { } , undefined )
110- await subscribeChanges ( m , sink )
43+ const m = makeMultiplexer ( client , collection , { } , undefined )
44+ await subscribeChanges ( m , sink , openHandles )
11145
11246 expect ( sink . added ) . toEqual ( [ [ id ( 'A' ) , { name : 'a' , val : 1 } ] ] )
11347
@@ -125,10 +59,10 @@ describe('change-stream engine (integration)', () => {
12559 } )
12660
12761 test ( 'selector enter/leave and projection' , async ( ) => {
128- const collection = await freshCollection ( )
62+ const collection = await freshCollection < TestDoc > ( client )
12963 const sink = newSink ( )
130- const m = makeMultiplexer ( collection , { studioId : 's1' } , { studioId : 1 , val : 1 } )
131- await subscribeChanges ( m , sink )
64+ const m = makeMultiplexer ( client , collection , { studioId : 's1' } , { studioId : 1 , val : 1 } )
65+ await subscribeChanges ( m , sink , openHandles )
13266
13367 // insert outside selector → nothing; then move it in → added
13468 await collection . insertOne ( { _id : id ( 'X' ) , studioId : 's2' , val : 1 , name : 'secret' } as any )
@@ -150,7 +84,7 @@ describe('change-stream engine (integration)', () => {
15084 } )
15185
15286 test ( 'two observers with identical args share one change stream' , async ( ) => {
153- const collection = await freshCollection ( [ { _id : id ( 'A' ) , val : 1 } ] )
87+ const collection = await freshCollection < TestDoc > ( client , [ { _id : id ( 'A' ) , val : 1 } ] )
15488 let watchCount = 0
15589
15690 const feed = new CollectionChangeFeed (
@@ -192,11 +126,11 @@ describe('change-stream engine (integration)', () => {
192126 } )
193127
194128 test ( 'observe() delivers full documents on add/change/remove' , async ( ) => {
195- const collection = await freshCollection ( [ { _id : id ( 'A' ) , val : 1 } ] )
129+ const collection = await freshCollection < TestDoc > ( client , [ { _id : id ( 'A' ) , val : 1 } ] )
196130 const added : any [ ] = [ ]
197131 const changed : Array < [ any , any ] > = [ ]
198132 const removed : any [ ] = [ ]
199- const m = makeMultiplexer ( collection , { } , undefined )
133+ const m = makeMultiplexer ( client , collection , { } , undefined )
200134 openHandles . push (
201135 await m . addObserveSubscriber (
202136 {
@@ -223,10 +157,10 @@ describe('change-stream engine (integration)', () => {
223157 } )
224158
225159 test ( 'a replaceOne is observed as a changed transition' , async ( ) => {
226- const collection = await freshCollection ( [ { _id : id ( 'A' ) , name : 'a' , val : 1 } ] )
160+ const collection = await freshCollection < TestDoc > ( client , [ { _id : id ( 'A' ) , name : 'a' , val : 1 } ] )
227161 const sink = newSink ( )
228- const m = makeMultiplexer ( collection , { } , undefined )
229- await subscribeChanges ( m , sink )
162+ const m = makeMultiplexer ( client , collection , { } , undefined )
163+ await subscribeChanges ( m , sink , openHandles )
230164 expect ( sink . added ) . toEqual ( [ [ id ( 'A' ) , { name : 'a' , val : 1 } ] ] )
231165
232166 await collection . replaceOne ( { _id : id ( 'A' ) } , { name : 'b' , val : 1 } as any )
@@ -235,7 +169,7 @@ describe('change-stream engine (integration)', () => {
235169 } )
236170
237171 test ( 'recovers writes made while the change stream is down (reconnect re-sync)' , async ( ) => {
238- const collection = await freshCollection ( [ { _id : id ( 'A' ) , val : 1 } ] )
172+ const collection = await freshCollection < TestDoc > ( client , [ { _id : id ( 'A' ) , val : 1 } ] )
239173 const sink = newSink ( )
240174
241175 // A controllable stream: it wraps the REAL change stream, but lets the test inject a synthetic
0 commit comments