| 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Exit from "effect/Exit"; |
| 4 | import * as Schema from "effect/Schema"; |
| 5 | |
| 6 | import { |
| 7 | ChatAttachment, |
| 8 | isProviderSendTurnSupportedImageMimeType, |
| 9 | PROVIDER_SEND_TURN_MAX_FILE_BYTES, |
| 10 | SnapShotAccessibility, |
| 11 | } from "./chatAttachment.ts"; |
| 12 | |
| 13 | const decodeAttachment = Schema.decodeUnknownEffect(ChatAttachment); |
| 14 | const decodeSnapShotAccessibility = Schema.decodeUnknownEffect(SnapShotAccessibility); |
| 15 | |
| 16 | // Attachments ride on persisted events and thread streams with no client |
| 17 | // version negotiation. A type this build does not know must decode instead of |
| 18 | // failing the whole message. |
| 19 | it.effect("tolerates attachment types from newer builds", () => |
| 20 | Effect.gen(function* () { |
| 21 | const attachment = yield* decodeAttachment({ |
| 22 | type: "somethingnew", |
| 23 | id: "thread-1-00000000-0000-4000-8000-000000000003-glb", |
| 24 | name: "scene.glb", |
| 25 | mimeType: "model/gltf-binary", |
| 26 | sizeBytes: 12, |
| 27 | }); |
| 28 | assert.strictEqual(attachment.type, "somethingnew"); |
| 29 | }), |
| 30 | ); |
| 31 | |
| 32 | // The tolerant member must not catch malformed known attachments: a file over |
| 33 | // the size cap or an image with a bad mime has to fail its own schema, not |
| 34 | // slide through the open one with those constraints unchecked. |
| 35 | it.effect("rejects malformed known attachment types instead of tolerating them", () => |
| 36 | Effect.gen(function* () { |
| 37 | const base = { |
| 38 | id: "thread-1-00000000-0000-4000-8000-000000000003-pdf", |
| 39 | name: "report.pdf", |
| 40 | mimeType: "application/pdf", |
| 41 | }; |
| 42 | const oversizedFile = yield* Effect.exit( |
| 43 | decodeAttachment({ ...base, type: "file", sizeBytes: PROVIDER_SEND_TURN_MAX_FILE_BYTES + 1 }), |
| 44 | ); |
| 45 | assert.strictEqual(Exit.isFailure(oversizedFile), true); |
| 46 | const badMimeImage = yield* Effect.exit( |
| 47 | decodeAttachment({ ...base, type: "image", mimeType: "application/pdf", sizeBytes: 12 }), |
| 48 | ); |
| 49 | assert.strictEqual(Exit.isFailure(badMimeImage), true); |
| 50 | }), |
| 51 | ); |
| 52 | |
| 53 | it.effect("rejects accessibility trees above the serialized payload limit", () => |
| 54 | Effect.gen(function* () { |
| 55 | const result = yield* Effect.exit( |
| 56 | decodeSnapShotAccessibility({ |
| 57 | format: "element-tree", |
| 58 | coordinateSpace: "captured-image", |
| 59 | imageSize: { width: 800, height: 600 }, |
| 60 | truncated: false, |
| 61 | root: { |
| 62 | role: "window", |
| 63 | bounds: { x: 0, y: 0, width: 800, height: 600 }, |
| 64 | children: Array.from({ length: 10 }, () => ({ |
| 65 | role: "text", |
| 66 | value: "x".repeat(8_000), |
| 67 | bounds: null, |
| 68 | children: [], |
| 69 | })), |
| 70 | }, |
| 71 | }), |
| 72 | ); |
| 73 | |
| 74 | assert.strictEqual(Exit.isFailure(result), true); |
| 75 | }), |
| 76 | ); |
| 77 | |
| 78 | it("isProviderSendTurnSupportedImageMimeType accepts raster formats and rejects svg", () => { |
| 79 | assert.strictEqual(isProviderSendTurnSupportedImageMimeType("image/png"), true); |
| 80 | assert.strictEqual(isProviderSendTurnSupportedImageMimeType("IMAGE/JPEG"), true); |
| 81 | assert.strictEqual(isProviderSendTurnSupportedImageMimeType("image/svg+xml"), false); |
| 82 | }); |