Skip to content

Commit eb78ae4

Browse files
authored
fix: DDP method calls returning 404 for void methods (#40057)
Signed-off-by: Abhinav Kumar <abhinav@avitechlab.com>
1 parent a6c863a commit eb78ae4

7 files changed

Lines changed: 132 additions & 8 deletions

File tree

.changeset/empty-spiders-vanish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/ddp-streamer': patch
3+
---
4+
5+
Fixes an issue where some DDP method calls could incorrectly return a 404 error.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import server from '@rocket.chat/jest-presets/server';
2+
import type { Config } from 'jest';
3+
4+
export default {
5+
preset: server.preset,
6+
testMatch: ['<rootDir>/src/**/*.spec.(ts|js|mjs)'],
7+
} satisfies Config;

ee/apps/ddp-streamer/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"build": "tsc -p tsconfig.json",
1616
"lint": "eslint .",
1717
"ms": "TRANSPORTER=${TRANSPORTER:-TCP} MONGO_URL=${MONGO_URL:-mongodb://localhost:3001/meteor} ts-node --files src/service.ts",
18-
"test": "echo \"Error: no test specified\" && exit 1",
18+
"test": "jest",
19+
"testunit": "jest",
1920
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
2021
},
2122
"dependencies": {
@@ -50,14 +51,17 @@
5051
"devDependencies": {
5152
"@rocket.chat/apps-engine": "workspace:^",
5253
"@rocket.chat/ddp-client": "workspace:~",
54+
"@rocket.chat/jest-presets": "workspace:*",
5355
"@types/ejson": "^2.2.2",
56+
"@types/jest": "~30.0.0",
5457
"@types/node": "~22.16.5",
5558
"@types/polka": "^0.5.8",
5659
"@types/prometheus-gc-stats": "^0.6.4",
5760
"@types/underscore": "^1.13.0",
5861
"@types/uuid": "^10.0.0",
5962
"@types/ws": "^8.18.1",
6063
"eslint": "~9.39.3",
64+
"jest": "~30.2.0",
6165
"pino-pretty": "13.1.3",
6266
"ts-node": "^10.9.2",
6367
"typescript": "~5.9.3"
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { MeteorService } from '@rocket.chat/core-services';
2+
import WebSocket from 'ws';
3+
4+
import { Server } from './Server';
5+
import type { IPacket } from './types/IPacket';
6+
7+
jest.mock('@rocket.chat/core-services', () => ({
8+
...jest.requireActual('@rocket.chat/core-services'),
9+
MeteorService: {
10+
callMethodWithToken: jest.fn(),
11+
},
12+
}));
13+
14+
jest.mock('@rocket.chat/logger', () => ({
15+
Logger: jest.fn().mockReturnValue({
16+
error: jest.fn(),
17+
}),
18+
}));
19+
20+
const mockCallMethodWithToken = jest.mocked(MeteorService.callMethodWithToken);
21+
22+
function makeClient(readyState: number = WebSocket.OPEN) {
23+
return {
24+
ws: { readyState },
25+
userId: 'user1',
26+
userToken: 'token1',
27+
send: jest.fn(),
28+
} as unknown as Parameters<Server['call']>[0];
29+
}
30+
31+
function makePacket(method: string, id = 'test-id'): IPacket {
32+
return { msg: 'method', method, id, params: [] } as unknown as IPacket;
33+
}
34+
35+
describe('Server.call', () => {
36+
let server: Server;
37+
38+
beforeEach(() => {
39+
server = new Server();
40+
jest.clearAllMocks();
41+
});
42+
43+
describe('when the method is delegated to MeteorService', () => {
44+
it('returns the result value from MeteorService', async () => {
45+
mockCallMethodWithToken.mockResolvedValue({ result: 'some-value' } as any);
46+
const client = makeClient();
47+
const resultSpy = jest.spyOn(server, 'result');
48+
49+
await server.call(client, makePacket('someMethod'));
50+
51+
expect(resultSpy).toHaveBeenCalledWith(client, expect.objectContaining({ id: 'test-id' }), 'some-value');
52+
});
53+
54+
it('does not return an error when the method returns void', async () => {
55+
mockCallMethodWithToken.mockResolvedValue({ result: undefined } as any);
56+
const client = makeClient();
57+
const resultSpy = jest.spyOn(server, 'result');
58+
59+
await server.call(client, makePacket('setAvatarFromService'));
60+
61+
expect(resultSpy).toHaveBeenCalledWith(client, expect.objectContaining({ id: 'test-id' }), undefined);
62+
});
63+
64+
it('calls result with an error when MeteorService throws', async () => {
65+
mockCallMethodWithToken.mockRejectedValue(new Error('boom'));
66+
const client = makeClient();
67+
const resultSpy = jest.spyOn(server, 'result');
68+
69+
await server.call(client, makePacket('someMethod'));
70+
71+
expect(resultSpy).toHaveBeenCalledWith(client, expect.objectContaining({ id: 'test-id' }), null, expect.any(Error));
72+
});
73+
});
74+
75+
describe('when the method is registered locally', () => {
76+
it('returns the result value from the local method', async () => {
77+
server.methods({ localMethod: async () => 'local-result' });
78+
const client = makeClient();
79+
const resultSpy = jest.spyOn(server, 'result');
80+
81+
await server.call(client, makePacket('localMethod'));
82+
83+
expect(resultSpy).toHaveBeenCalledWith(client, expect.objectContaining({ id: 'test-id' }), 'local-result');
84+
});
85+
86+
it('does not return an error when the local method returns void', async () => {
87+
server.methods({ voidMethod: async () => undefined });
88+
const client = makeClient();
89+
const resultSpy = jest.spyOn(server, 'result');
90+
91+
await server.call(client, makePacket('voidMethod'));
92+
93+
expect(resultSpy).toHaveBeenCalledWith(client, expect.objectContaining({ id: 'test-id' }), undefined);
94+
});
95+
});
96+
97+
describe('when the client WebSocket is not open', () => {
98+
it('does nothing', async () => {
99+
const client = makeClient(WebSocket.CLOSED);
100+
const resultSpy = jest.spyOn(server, 'result');
101+
102+
await server.call(client, makePacket('anyMethod'));
103+
104+
expect(resultSpy).not.toHaveBeenCalled();
105+
expect(mockCallMethodWithToken).not.toHaveBeenCalled();
106+
});
107+
});
108+
});

ee/apps/ddp-streamer/src/Server.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,7 @@ export class Server extends EventEmitter {
6868
// if method was not defined on DDP Streamer we fall back to Meteor
6969
if (!this._methods.has(packet.method)) {
7070
const result = await MeteorService.callMethodWithToken(client.userId, client.userToken, packet.method, packet.params);
71-
if (result?.result) {
72-
return this.result(client, packet, result.result);
73-
}
74-
75-
throw new MeteorError(404, `Method '${packet.method}' not found`);
71+
return this.result(client, packet, result.result);
7672
}
7773

7874
const fn = this._methods.get(packet.method);

ee/apps/ddp-streamer/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"extends": "@rocket.chat/tsconfig/server.json",
33
"compilerOptions": {
44
"strictPropertyInitialization": false, // TODO: Remove this line
5-
"outDir": "./dist"
5+
"outDir": "./dist",
6+
"types": ["jest", "node"]
67
},
78
"include": ["./src/**/*", "./definition"],
89
"exclude": ["./dist", "./ecosystem.config.js"]

yarn.lock

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9248,6 +9248,7 @@ __metadata:
92489248
"@rocket.chat/ddp-client": "workspace:~"
92499249
"@rocket.chat/emitter": "npm:^0.32.0"
92509250
"@rocket.chat/instance-status": "workspace:^"
9251+
"@rocket.chat/jest-presets": "workspace:*"
92519252
"@rocket.chat/logger": "workspace:^"
92529253
"@rocket.chat/model-typings": "workspace:^"
92539254
"@rocket.chat/models": "workspace:^"
@@ -9256,6 +9257,7 @@ __metadata:
92569257
"@rocket.chat/string-helpers": "npm:~0.32.0"
92579258
"@rocket.chat/tracing": "workspace:^"
92589259
"@types/ejson": "npm:^2.2.2"
9260+
"@types/jest": "npm:~30.0.0"
92599261
"@types/node": "npm:~22.16.5"
92609262
"@types/polka": "npm:^0.5.8"
92619263
"@types/prometheus-gc-stats": "npm:^0.6.4"
@@ -9267,6 +9269,7 @@ __metadata:
92679269
eslint: "npm:~9.39.3"
92689270
eventemitter3: "npm:^5.0.4"
92699271
jaeger-client: "npm:^3.19.0"
9272+
jest: "npm:~30.2.0"
92709273
mem: "npm:^8.1.1"
92719274
moleculer: "npm:^0.14.35"
92729275
mongodb: "npm:6.16.0"
@@ -9671,7 +9674,7 @@ __metadata:
96719674
languageName: unknown
96729675
linkType: soft
96739676

9674-
"@rocket.chat/jest-presets@workspace:^, @rocket.chat/jest-presets@workspace:packages/jest-presets, @rocket.chat/jest-presets@workspace:~":
9677+
"@rocket.chat/jest-presets@workspace:*, @rocket.chat/jest-presets@workspace:^, @rocket.chat/jest-presets@workspace:packages/jest-presets, @rocket.chat/jest-presets@workspace:~":
96759678
version: 0.0.0-use.local
96769679
resolution: "@rocket.chat/jest-presets@workspace:packages/jest-presets"
96779680
dependencies:

0 commit comments

Comments
 (0)