|
1 | 1 | // Mattermost tests cover monitor websocket plugin behavior. |
| 2 | +import { once } from "node:events"; |
2 | 3 | import { beforeEach, describe, expect, it, vi } from "vitest"; |
| 4 | +import { WebSocketServer } from "ws"; |
3 | 5 | import type { RuntimeEnv } from "../../runtime-api.js"; |
4 | 6 | import { |
5 | 7 | createMattermostConnectOnce, |
| 8 | + MATTERMOST_WEBSOCKET_MAX_PAYLOAD_BYTES, |
6 | 9 | type MattermostWebSocketLike, |
7 | 10 | WebSocketClosedBeforeOpenError, |
8 | 11 | } from "./monitor-websocket.js"; |
@@ -194,6 +197,78 @@ describe("mattermost websocket monitor", () => { |
194 | 197 | expect(countMatching(patches, (patch) => patch.connected === false)).toBe(2); |
195 | 198 | }); |
196 | 199 |
|
| 200 | + it("accepts large valid post envelopes and rejects oversized websocket payloads", async () => { |
| 201 | + const server = new WebSocketServer({ host: "127.0.0.1", port: 0 }); |
| 202 | + await once(server, "listening"); |
| 203 | + const address = server.address(); |
| 204 | + if (!address || typeof address === "string") { |
| 205 | + throw new Error("expected TCP websocket server address"); |
| 206 | + } |
| 207 | + |
| 208 | + const quotedCardBody = '"'.repeat(380_000); |
| 209 | + const largeProps = { cards: [{ body: quotedCardBody }] }; |
| 210 | + const largePostEnvelope = JSON.stringify({ |
| 211 | + event: "posted", |
| 212 | + data: { |
| 213 | + post: JSON.stringify({ |
| 214 | + id: "post-large", |
| 215 | + message: "large Mattermost integration post", |
| 216 | + props: largeProps, |
| 217 | + }), |
| 218 | + }, |
| 219 | + }); |
| 220 | + expect(JSON.stringify(largeProps).length).toBeLessThan(800_000); |
| 221 | + expect(Buffer.byteLength(largePostEnvelope)).toBeGreaterThan(1024 * 1024); |
| 222 | + expect(Buffer.byteLength(largePostEnvelope)).toBeLessThan( |
| 223 | + MATTERMOST_WEBSOCKET_MAX_PAYLOAD_BYTES, |
| 224 | + ); |
| 225 | + |
| 226 | + const runtime = testRuntime(); |
| 227 | + const onPosted = vi.fn(async () => {}); |
| 228 | + server.on("connection", (socket) => { |
| 229 | + socket.once("message", () => { |
| 230 | + socket.send( |
| 231 | + JSON.stringify({ |
| 232 | + event: "posted", |
| 233 | + data: { |
| 234 | + post: JSON.stringify({ |
| 235 | + id: "post-1", |
| 236 | + message: "normal Mattermost post", |
| 237 | + }), |
| 238 | + }, |
| 239 | + }), |
| 240 | + ); |
| 241 | + socket.send(largePostEnvelope); |
| 242 | + socket.send(Buffer.alloc(MATTERMOST_WEBSOCKET_MAX_PAYLOAD_BYTES + 1, 0x78)); |
| 243 | + }); |
| 244 | + }); |
| 245 | + |
| 246 | + try { |
| 247 | + await createMattermostConnectOnce({ |
| 248 | + wsUrl: `ws://127.0.0.1:${address.port}`, |
| 249 | + botToken: "token", |
| 250 | + runtime, |
| 251 | + nextSeq: () => 1, |
| 252 | + onPosted, |
| 253 | + })(); |
| 254 | + } finally { |
| 255 | + server.close(); |
| 256 | + await once(server, "close"); |
| 257 | + } |
| 258 | + |
| 259 | + expect(onPosted).toHaveBeenCalledWith( |
| 260 | + expect.objectContaining({ id: "post-1", message: "normal Mattermost post" }), |
| 261 | + expect.any(Object), |
| 262 | + ); |
| 263 | + expect(onPosted).toHaveBeenCalledWith( |
| 264 | + expect.objectContaining({ id: "post-large", props: largeProps }), |
| 265 | + expect.any(Object), |
| 266 | + ); |
| 267 | + expect(runtime.error).toHaveBeenCalledWith( |
| 268 | + expect.stringContaining("Max payload size exceeded"), |
| 269 | + ); |
| 270 | + }); |
| 271 | + |
197 | 272 | it("dispatches reaction events to the reaction handler", async () => { |
198 | 273 | const socket = new FakeWebSocket(); |
199 | 274 | const onPosted = vi.fn(async () => {}); |
|
0 commit comments