Skip to content

Commit ebedcbf

Browse files
committed
feat: refactor ddp method registration into a MethodRegistry
1 parent 88f7116 commit ebedcbf

41 files changed

Lines changed: 1088 additions & 450 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { MethodRegistry } from '../../server/methodRegistry'
2+
import { registerAllApiMethods } from '../../server/methodRegistrations'
3+
4+
/**
5+
* Test helper: register all API methods on a fresh MethodRegistry and apply them to the (mock)
6+
* Meteor server, mirroring what `main.ts` does at startup.
7+
*
8+
* Call this in suites that exercise Meteor methods (via `MeteorCall`, `Meteor.callAsync`, or by
9+
* spying on `MeteorMock.mockMethods`). It is needed because the production registration now only
10+
* runs explicitly from `main.ts`, rather than as an import-time side effect of each API file.
11+
*/
12+
export function registerAllMethodsForTest(): MethodRegistry {
13+
const registry = new MethodRegistry()
14+
registerAllApiMethods(registry)
15+
registry.applyToMeteor()
16+
return registry
17+
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
2+
3+
exports[`MethodRegistry the full set of registered method names is stable (drift guard) 1`] = `
4+
[
5+
"blueprint.assignSystem",
6+
"client.callBackgroundPeripheralDeviceFunction",
7+
"client.callPeripheralDeviceAction",
8+
"client.callPeripheralDeviceFunction",
9+
"client.clientErrorReport",
10+
"client.clientLogNotification",
11+
"client.clientLogger",
12+
"debug_clearAllResetInstances",
13+
"debug_forceClearAllCaches",
14+
"debug_playlistRunBlueprints",
15+
"debug_previewTrigger",
16+
"debug_regenerateNextPartInstance",
17+
"debug_removeAllPlaylists",
18+
"debug_removePlaylist",
19+
"debug_segmentRunBlueprints",
20+
"debug_syncPlayheadInfinitesForNextPartInstance",
21+
"debug_updateTimeline",
22+
"externalMessages.remove",
23+
"externalMessages.retry",
24+
"externalMessages.toggleHold",
25+
"migration.fixupConfigForShowStyleBase",
26+
"migration.fixupConfigForStudio",
27+
"migration.forceMigration",
28+
"migration.getMigrationStatus",
29+
"migration.ignoreFixupConfigForShowStyleBase",
30+
"migration.ignoreFixupConfigForStudio",
31+
"migration.resetDatabaseVersions",
32+
"migration.runMigration",
33+
"migration.runUpgradeForCoreSystem",
34+
"migration.runUpgradeForShowStyleBase",
35+
"migration.runUpgradeForStudio",
36+
"migration.validateConfigForShowStyleBase",
37+
"migration.validateConfigForStudio",
38+
"mongo.insert",
39+
"mongo.remove",
40+
"mongo.update",
41+
"peripheralDevice.functionReply",
42+
"peripheralDevice.getPeripheralDevice",
43+
"peripheralDevice.initialize",
44+
"peripheralDevice.input.inputDeviceTrigger",
45+
"peripheralDevice.killProcess",
46+
"peripheralDevice.mediaScanner.clearMediaObjectCollection",
47+
"peripheralDevice.mediaScanner.getMediaObjectRevisions",
48+
"peripheralDevice.mediaScanner.updateMediaObject",
49+
"peripheralDevice.mos.roCreate",
50+
"peripheralDevice.mos.roDelete",
51+
"peripheralDevice.mos.roFullStory",
52+
"peripheralDevice.mos.roItemDelete",
53+
"peripheralDevice.mos.roItemInsert",
54+
"peripheralDevice.mos.roItemMove",
55+
"peripheralDevice.mos.roItemReplace",
56+
"peripheralDevice.mos.roItemStatus",
57+
"peripheralDevice.mos.roItemSwap",
58+
"peripheralDevice.mos.roMetadata",
59+
"peripheralDevice.mos.roReadyToAir",
60+
"peripheralDevice.mos.roReplace",
61+
"peripheralDevice.mos.roStatus",
62+
"peripheralDevice.mos.roStoryDelete",
63+
"peripheralDevice.mos.roStoryInsert",
64+
"peripheralDevice.mos.roStoryMove",
65+
"peripheralDevice.mos.roStoryReplace",
66+
"peripheralDevice.mos.roStoryStatus",
67+
"peripheralDevice.mos.roStorySwap",
68+
"peripheralDevice.packageManager.fetchPackageInfoMetadata",
69+
"peripheralDevice.packageManager.removeAllExpectedPackageWorkStatusOfDevice",
70+
"peripheralDevice.packageManager.removeAllPackageContainerPackageStatusesOfDevice",
71+
"peripheralDevice.packageManager.removeAllPackageContainerStatusesOfDevice",
72+
"peripheralDevice.packageManager.removePackageInfo",
73+
"peripheralDevice.packageManager.updateExpectedPackageWorkStatuses",
74+
"peripheralDevice.packageManager.updatePackageContainerPackageStatuses",
75+
"peripheralDevice.packageManager.updatePackageContainerStatuses",
76+
"peripheralDevice.packageManager.updatePackageInfo",
77+
"peripheralDevice.ping",
78+
"peripheralDevice.pingWithCommand",
79+
"peripheralDevice.playlist.playlistGet",
80+
"peripheralDevice.playout.playbackChanged",
81+
"peripheralDevice.playout.reportExternalEvents",
82+
"peripheralDevice.removePeripheralDevice",
83+
"peripheralDevice.reportResolveDone",
84+
"peripheralDevice.rundown.partCreate",
85+
"peripheralDevice.rundown.partDelete",
86+
"peripheralDevice.rundown.partUpdate",
87+
"peripheralDevice.rundown.rundownCreate",
88+
"peripheralDevice.rundown.rundownDelete",
89+
"peripheralDevice.rundown.rundownGet",
90+
"peripheralDevice.rundown.rundownList",
91+
"peripheralDevice.rundown.rundownMetaDataUpdate",
92+
"peripheralDevice.rundown.rundownUpdate",
93+
"peripheralDevice.rundown.segmentCreate",
94+
"peripheralDevice.rundown.segmentDelete",
95+
"peripheralDevice.rundown.segmentGet",
96+
"peripheralDevice.rundown.segmentRanksUpdate",
97+
"peripheralDevice.rundown.segmentUpdate",
98+
"peripheralDevice.spreadsheet.requestUserAuthToken",
99+
"peripheralDevice.spreadsheet.storeAccessToken",
100+
"peripheralDevice.status",
101+
"peripheralDevice.testMethod",
102+
"peripheralDevice.timeline.setTimelineTriggerTime",
103+
"peripheralDevice.unInitialize",
104+
"playout.shouldUpdateStudioBaseline",
105+
"playout.updateStudioBaseline",
106+
"rundown.rundownPlaylistNeedsResync",
107+
"rundownLayout.createRundownLayout",
108+
"rundownLayout.removeRundownLayout",
109+
"showstyles.getCreateAdlibTestingRundownOptions",
110+
"showstyles.importShowStyleVariant",
111+
"showstyles.importShowStyleVariantAsNew",
112+
"showstyles.insertBlueprint",
113+
"showstyles.insertShowStyleBase",
114+
"showstyles.insertShowStyleVariant",
115+
"showstyles.removeBlueprint",
116+
"showstyles.removeShowStyleBase",
117+
"showstyles.removeShowStyleVariant",
118+
"showstyles.reorderShowStyleVariant",
119+
"snapshot.debugSnaphot",
120+
"snapshot.removeSnaphot",
121+
"snapshot.restoreSnaphot",
122+
"snapshot.rundownPlaylistSnapshot",
123+
"snapshot.systemSnapshot",
124+
"studio.assignConfigToPeripheralDevice",
125+
"studio.insertStudio",
126+
"studio.removeStudio",
127+
"system.cleanupIndexes",
128+
"system.cleanupOldData",
129+
"system.doSystemBenchmark",
130+
"system.generateSingleUseToken",
131+
"system.getTranslationBundle",
132+
"system.runCronjob",
133+
"systemStatus.getDebugStates",
134+
"systemStatus.getSystemStatus",
135+
"systemTime.determineDiffTime",
136+
"systemTime.getTime",
137+
"systemTime.getTimeDiff",
138+
"triggeredActions.createTriggeredActions",
139+
"triggeredActions.removeTriggeredActions",
140+
"user.getUserPermissions",
141+
"userAction.DEBUG_crashStudioWorker",
142+
"userAction.activate",
143+
"userAction.activateAdlibTestingMode",
144+
"userAction.activateHold",
145+
"userAction.baselineAdLibPieceStart",
146+
"userAction.blurred",
147+
"userAction.bucketAdlibImport",
148+
"userAction.bucketAdlibStart",
149+
"userAction.bucketsModifyBucketAdLib",
150+
"userAction.bucketsModifyBucketAdLibAction",
151+
"userAction.bucketsSaveActionIntoBucket",
152+
"userAction.clearQuickLoop",
153+
"userAction.createAdlibTestingRundownForShowStyleVariant",
154+
"userAction.createBucket",
155+
"userAction.deactivate",
156+
"userAction.disableNextPiece",
157+
"userAction.emptyBucket",
158+
"userAction.executeAction",
159+
"userAction.executeUserChangeOperation",
160+
"userAction.focused",
161+
"userAction.forceResetAndActivate",
162+
"userAction.ingest.regenerateRundownPlaylist",
163+
"userAction.modifyBucket",
164+
"userAction.moveNext",
165+
"userAction.moveRundown",
166+
"userAction.packagemanager.abortExpectation",
167+
"userAction.packagemanager.restartAllExpectations",
168+
"userAction.packagemanager.restartExpectation",
169+
"userAction.packagemanager.restartPackageContainer",
170+
"userAction.pieceSetInOutPoints",
171+
"userAction.pieceTakeNow",
172+
"userAction.prepareForBroadcast",
173+
"userAction.queueNextSegment",
174+
"userAction.removeBucket",
175+
"userAction.removeBucketAdLib",
176+
"userAction.removeBucketAdLibAction",
177+
"userAction.removeRundown",
178+
"userAction.removeRundownPlaylist",
179+
"userAction.resetAndActivate",
180+
"userAction.resetRundownPlaylist",
181+
"userAction.restoreRundownOrder",
182+
"userAction.resyncRundown",
183+
"userAction.resyncRundownPlaylist",
184+
"userAction.saveEvaluation",
185+
"userAction.segmentAdLibPieceStart",
186+
"userAction.setNext",
187+
"userAction.setNextSegment",
188+
"userAction.setQuickLoopEnd",
189+
"userAction.setQuickLoopStart",
190+
"userAction.sourceLayerOnPartStop",
191+
"userAction.sourceLayerStickyPieceStart",
192+
"userAction.storeRundownSnapshot",
193+
"userAction.switchRouteSet",
194+
"userAction.system.disablePeripheralSubDevice",
195+
"userAction.system.restartCore",
196+
"userAction.take",
197+
"userAction.unsyncRundown",
198+
]
199+
`;

meteor/server/__tests__/cronjobs.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ jest.mock('../api/deviceTriggers/observer')
4848
const MAX_WAIT_TIME = 4 * 1000
4949

5050
import '../cronjobs'
51+
import { registerAllMethodsForTest } from '../../__mocks__/helpers/methods'
5152

52-
import '../api/peripheralDevice'
53+
registerAllMethodsForTest()
5354
import {
5455
CoreSystem,
5556
NrcsIngestDataCache,
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { MethodApiRegistration, MethodRegistry } from '../methodRegistry'
2+
import { METHOD_REGISTRATIONS, registerAllApiMethods } from '../methodRegistrations'
3+
4+
describe('MethodRegistry', () => {
5+
test('registers all API methods without error', () => {
6+
const registry = new MethodRegistry()
7+
// registerApi throws if a class method has no matching wire name, so this is itself an assertion
8+
// that every implemented method maps to an enum entry (the historical behaviour).
9+
expect(() => registerAllApiMethods(registry)).not.toThrow()
10+
})
11+
12+
test('registered names are unique and only use known wire names', () => {
13+
const registry = new MethodRegistry()
14+
registerAllApiMethods(registry)
15+
const names = registry.getAllMethodNames()
16+
17+
// No duplicate registrations across all APIs
18+
expect(new Set(names).size).toBe(names.length)
19+
20+
// Every non-debug registered name must be a value from one of the method-name enums.
21+
// (Debug methods are not enum-backed and are checked separately.)
22+
const enumWireNames = new Set<string>()
23+
for (const reg of Object.values<MethodApiRegistration<any>>(METHOD_REGISTRATIONS)) {
24+
for (const wireName of Object.values<string>(reg.methods)) enumWireNames.add(wireName)
25+
}
26+
for (const name of names) {
27+
if (name.startsWith('debug')) continue
28+
expect(enumWireNames.has(name)).toBe(true)
29+
}
30+
31+
// A handful of representative names must be present, guarding against an API silently
32+
// dropping out of the registry.
33+
expect(names).toEqual(
34+
expect.arrayContaining([
35+
'userAction.take',
36+
'playout.updateStudioBaseline',
37+
'peripheralDevice.initialize',
38+
'system.cleanupIndexes',
39+
])
40+
)
41+
})
42+
43+
test('the full set of registered method names is stable (drift guard)', () => {
44+
const registry = new MethodRegistry()
45+
registerAllApiMethods(registry)
46+
// If this snapshot changes, a method's wire name was added, removed or renamed. That must be a
47+
// deliberate change reviewed against the clients that depend on these exact names.
48+
expect([...registry.getAllMethodNames()].sort()).toMatchSnapshot()
49+
})
50+
})

meteor/server/api/ExternalMessageQueue.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ import { Meteor } from 'meteor/meteor'
22
import { check } from '../lib/check'
33
import { StatusCode } from '@sofie-automation/blueprints-integration'
44
import { deferAsync, getCurrentTime } from '../lib/lib'
5-
import { registerClassToMeteorMethods } from '../methods'
6-
import {
7-
NewExternalMessageQueueAPI,
8-
ExternalMessageQueueAPIMethods,
9-
} from '@sofie-automation/meteor-lib/dist/api/ExternalMessageQueue'
5+
import { NewExternalMessageQueueAPI } from '@sofie-automation/meteor-lib/dist/api/ExternalMessageQueue'
106
import { StatusObject, setSystemStatus } from '../systemStatus/systemStatus'
117
import { MethodContextAPI, MethodContext } from './methodContext'
128
import { ExternalMessageQueueObjId } from '@sofie-automation/corelib/dist/dataModel/Ids'
@@ -114,15 +110,14 @@ async function retry(context: MethodContext, messageId: ExternalMessageQueueObjI
114110
})
115111
// triggerdoMessageQueue(1000)
116112
}
117-
class ServerExternalMessageQueueAPI extends MethodContextAPI implements NewExternalMessageQueueAPI {
118-
async remove(messageId: ExternalMessageQueueObjId) {
113+
export class ServerExternalMessageQueueAPI extends MethodContextAPI implements NewExternalMessageQueueAPI {
114+
async remove(messageId: ExternalMessageQueueObjId): Promise<void> {
119115
return removeExternalMessage(this, messageId)
120116
}
121-
async toggleHold(messageId: ExternalMessageQueueObjId) {
117+
async toggleHold(messageId: ExternalMessageQueueObjId): Promise<void> {
122118
return toggleHold(this, messageId)
123119
}
124-
async retry(messageId: ExternalMessageQueueObjId) {
120+
async retry(messageId: ExternalMessageQueueObjId): Promise<void> {
125121
return retry(this, messageId)
126122
}
127123
}
128-
registerClassToMeteorMethods(ExternalMessageQueueAPIMethods, ServerExternalMessageQueueAPI, false)

meteor/server/api/__tests__/client.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import { MeteorCall } from '../methods'
1616
import { PeripheralDeviceId } from '@sofie-automation/corelib/dist/dataModel/Ids'
1717
import { PeripheralDeviceCommands, UserActionsLog } from '../../collections'
1818
import { SupressLogMessages } from '../../../__mocks__/suppressLogging'
19+
import { registerAllMethodsForTest } from '../../../__mocks__/helpers/methods'
1920

20-
require('../client') // include in order to create the Meteor methods needed
21+
registerAllMethodsForTest()
2122

2223
setLogLevel(LogLevel.INFO)
2324

meteor/server/api/__tests__/externalMessageQueue.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import { protectString } from '@sofie-automation/corelib/dist/protectedString'
88
import { getCurrentTime } from '../../lib/lib'
99
import { MeteorCall } from '../methods'
1010

11-
import '../ExternalMessageQueue'
11+
import { registerAllMethodsForTest } from '../../../__mocks__/helpers/methods'
1212
import { SupressLogMessages } from '../../../__mocks__/suppressLogging'
1313

14+
registerAllMethodsForTest()
15+
1416
describe('Test external message queue static methods', () => {
1517
let studioEnv: DefaultEnvironment
1618
beforeAll(async () => {

meteor/server/api/__tests__/peripheralDevice.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ import {
2222
StatusCode,
2323
} from '@sofie-automation/blueprints-integration'
2424
import { CreateFakeResult, QueueStudioJobSpy } from '../../../__mocks__/worker'
25+
import { registerAllMethodsForTest } from '../../../__mocks__/helpers/methods'
2526

2627
jest.mock('../../api/deviceTriggers/observer')
2728

28-
import '../peripheralDevice'
2929
import { OnTimelineTriggerTimeProps, StudioJobFunc, StudioJobs } from '@sofie-automation/corelib/dist/worker/studio'
3030
import { MeteorCall } from '../methods'
3131
import { PeripheralDeviceForDevice } from '@sofie-automation/shared-lib/dist/core/model/peripheralDevice'
@@ -50,6 +50,8 @@ import { SupressLogMessages } from '../../../__mocks__/suppressLogging'
5050
import { JSONBlobStringify } from '@sofie-automation/shared-lib/dist/lib/JSONBlob'
5151
import { PeripheralDeviceCommand } from '@sofie-automation/corelib/dist/dataModel/PeripheralDeviceCommand'
5252

53+
registerAllMethodsForTest()
54+
5355
const DEBUG = false
5456

5557
describe('test peripheralDevice general API methods', () => {

meteor/server/api/__tests__/rundownLayouts.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import { RundownLayouts } from '../../collections'
1313
import { SupressLogMessages } from '../../../__mocks__/suppressLogging'
1414
import { shelfLayoutsRouter } from '../rundownLayouts'
1515
import { callKoaRoute } from '../../../__mocks__/koa-util'
16+
import { registerAllMethodsForTest } from '../../../__mocks__/helpers/methods'
17+
18+
registerAllMethodsForTest()
1619

1720
describe('Rundown Layouts', () => {
1821
let env: DefaultEnvironment

meteor/server/api/__tests__/userActions/general.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import { getCurrentTime, sleep } from '../../../lib/lib'
55
import { MeteorCall } from '../../methods'
66
import { ClientAPI } from '@sofie-automation/meteor-lib/dist/api/client'
77
import { UserActionsLog } from '../../../collections'
8+
import { registerAllMethodsForTest } from '../../../../__mocks__/helpers/methods'
89

9-
require('../../system') // include so that we can call generateSingleUseToken()
10-
require('../../client') // include in order to create the Meteor methods needed
11-
require('../../userActions') // include in order to create the Meteor methods needed
10+
registerAllMethodsForTest()
1211

1312
describe('User Actions - General', () => {
1413
beforeEach(async () => {

0 commit comments

Comments
 (0)