|
60 | 60 | "scopeSetFingerprint", |
61 | 61 | "scopeSetUser", |
62 | 62 | "scopeAddBytesAttachment", |
| 63 | + "scopeAddFileAttachment", |
63 | 64 | "scopeClear", |
64 | 65 | "logTrace", |
65 | 66 | "logDebug", |
|
72 | 73 | "lastEventId", |
73 | 74 | "addBreadcrumb", |
74 | 75 | "addBytesAttachment", |
| 76 | + "addFileAttachment", |
75 | 77 | "clearAttachments", |
76 | | - "pushAttachmentData", |
77 | 78 | "storeBytes", |
78 | 79 | "takeBytes", |
79 | 80 | "releaseBytes", |
@@ -110,8 +111,24 @@ try { |
110 | 111 |
|
111 | 112 | console.log("\n🧪 Functional tests:"); |
112 | 113 |
|
| 114 | + // Stands in for the C++ layer: hands back fixed bytes, leaving "user://missing.txt" unread like a missing file. |
| 115 | + const readAttachmentPaths = []; |
| 116 | + const readAttachment = (request) => { |
| 117 | + readAttachmentPaths.push(request.path); |
| 118 | + if (request.path !== "user://missing.txt") { |
| 119 | + request.data = new Uint8Array([ 1, 2, 3, 4 ]); |
| 120 | + } |
| 121 | + }; |
| 122 | + |
113 | 123 | runTest("init()", () => { |
114 | | - bridge.init(() => {}, null, null, "https://test@sentry.io/123", false, "1.0.0", "1", "production", 1.0, 100, false, false, false, "0.1.0"); |
| 124 | + bridge.init(() => {}, null, null, readAttachment, "https://test@sentry.io/123", false, "1.0.0", "1", "production", 1.0, 100, false, false, false, "0.1.0"); |
| 125 | + }); |
| 126 | + |
| 127 | + // Observes what actually goes out with an event. The bridge registers its own handler during |
| 128 | + // init() and handlers run in registration order, so this one sees the resolved and filtered list. |
| 129 | + const sentAttachments = []; |
| 130 | + bridge.createScope().getClient().on("beforeSendEvent", (_event, hint) => { |
| 131 | + sentAttachments.push(hint?.attachments ?? []); |
115 | 132 | }); |
116 | 133 |
|
117 | 134 | runTest("isEnabled()", () => { |
@@ -229,6 +246,47 @@ try { |
229 | 246 | assertEqual(attachments[0].data.length, 3, "scopeAddBytesAttachment should carry the bytes"); |
230 | 247 | }); |
231 | 248 |
|
| 249 | + runTest("scopeAddFileAttachment()", () => { |
| 250 | + const scope = bridge.createScope(); |
| 251 | + bridge.scopeAddFileAttachment(scope, "user://save.dat", "save.dat", "application/octet-stream", ""); |
| 252 | + const attachments = scope.getScopeData().attachments; |
| 253 | + assertEqual(attachments.length, 1, "scopeAddFileAttachment should add the attachment to the scope"); |
| 254 | + assertEqual(attachments[0].filename, "save.dat", "scopeAddFileAttachment should set the filename"); |
| 255 | + assertEqual(attachments[0].data.length, 0, "scopeAddFileAttachment should leave the bytes for capture time"); |
| 256 | + assertEqual(attachments[0].__godotPath, "user://save.dat", |
| 257 | + "scopeAddFileAttachment should mark the attachment with the path to read at capture time"); |
| 258 | + }); |
| 259 | + |
| 260 | + runTest("file attachments resolve on capture", () => { |
| 261 | + const scope = bridge.createScope(); |
| 262 | + bridge.scopeAddFileAttachment(scope, "user://save.dat", "save.dat", "application/octet-stream", ""); |
| 263 | + |
| 264 | + const readCountBefore = readAttachmentPaths.length; |
| 265 | + bridge.captureEvent({ message : "Event with a file attachment" }, scope); |
| 266 | + assertEqual(readAttachmentPaths.length, readCountBefore + 1, "captureEvent should read the file once"); |
| 267 | + assertEqual(readAttachmentPaths[readAttachmentPaths.length - 1], "user://save.dat", "captureEvent should read the attachment path"); |
| 268 | + |
| 269 | + const sent = sentAttachments[sentAttachments.length - 1]; |
| 270 | + assertEqual(sent.length, 1, "captureEvent should send the resolved attachment"); |
| 271 | + assertEqual(sent[0].data.length, 4, "the sent attachment should carry the bytes that were read"); |
| 272 | + assertEqual(sent[0].filename, "save.dat", "resolving should keep the filename"); |
| 273 | + assertEqual(sent[0].__godotPath, undefined, "the path marker should not be sent with the event"); |
| 274 | + |
| 275 | + const kept = scope.getScopeData().attachments[0]; |
| 276 | + assertEqual(kept.data.length, 0, "the scope's own attachment should not keep the bytes"); |
| 277 | + assertEqual(kept.__godotPath, "user://save.dat", |
| 278 | + "the scope should keep the path so the next capture reads the file again"); |
| 279 | + }); |
| 280 | + |
| 281 | + runTest("unreadable file attachments are dropped", () => { |
| 282 | + const scope = bridge.createScope(); |
| 283 | + bridge.scopeAddFileAttachment(scope, "user://missing.txt", "missing.txt", "", ""); |
| 284 | + bridge.captureEvent({ message : "Event with a missing file attachment" }, scope); |
| 285 | + |
| 286 | + const sent = sentAttachments[sentAttachments.length - 1]; |
| 287 | + assertEqual(sent.length, 0, "an attachment the C++ layer could not read should not be sent"); |
| 288 | + }); |
| 289 | + |
232 | 290 | runTest("scopeClear()", () => { |
233 | 291 | const scope = bridge.createScope(); |
234 | 292 | bridge.scopeSetContext(scope, "test-context", '{"key": "value"}'); |
@@ -306,19 +364,17 @@ try { |
306 | 364 | assertEqual(bridge.takeBytes(id), undefined, "releaseBytes should discard bytes"); |
307 | 365 | }); |
308 | 366 |
|
309 | | - runTest("pushAttachmentData()", () => { |
310 | | - const attachments = []; |
311 | | - bridge.pushAttachmentData(attachments, new Uint8Array([ 1, 2, 3 ]), "test.bin", "application/octet-stream", "event.attachment"); |
312 | | - assertEqual(attachments.length, 1, "should push one attachment"); |
313 | | - assertEqual(attachments[0].filename, "test.bin", "filename should match"); |
314 | | - assertEqual(attachments[0].bytes.length, 3, "bytes length should match"); |
315 | | - assertEqual(attachments[0].contentType, "application/octet-stream", "contentType should match"); |
316 | | - assertEqual(attachments[0].attachmentType, "event.attachment", "attachmentType should match"); |
| 367 | + // Globally added file attachments go through the same resolution as the scoped ones above. |
| 368 | + runTest("addFileAttachment()", () => { |
| 369 | + bridge.addFileAttachment("user://global.dat", "global.dat", "application/json", "event.view_hierarchy"); |
| 370 | + bridge.captureEvent({ message : "Event with a global file attachment" }, bridge.createScope()); |
317 | 371 |
|
318 | | - bridge.pushAttachmentData(attachments, new Uint8Array([ 4, 5 ]), "test2.bin"); |
319 | | - assertEqual(attachments.length, 2, "should push second attachment"); |
320 | | - assertEqual(attachments[1].contentType, undefined, "optional contentType should be undefined"); |
321 | | - assertEqual(attachments[1].attachmentType, undefined, "optional attachmentType should be undefined"); |
| 372 | + const sent = sentAttachments[sentAttachments.length - 1]; |
| 373 | + const globalAttachment = sent.find((attachment) => attachment.filename === "global.dat"); |
| 374 | + assert(globalAttachment, "addFileAttachment should send the attachment with the event"); |
| 375 | + assertEqual(globalAttachment.data.length, 4, "the global attachment should carry the bytes that were read"); |
| 376 | + assertEqual(globalAttachment.contentType, "application/json", "addFileAttachment should set the content type"); |
| 377 | + assertEqual(globalAttachment.attachmentType, "event.view_hierarchy", "addFileAttachment should set the attachment type"); |
322 | 378 | }); |
323 | 379 |
|
324 | 380 | runTest("addBytesAttachment()", () => { |
|
0 commit comments