import { assert, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import { HttpClient, HttpClientError, HttpClientResponse } from "effect/unstable/http"; import { buildDiscordReleaseAnnouncement, isDiscordReleaseAnnouncementError, postDiscordWebhook, } from "./notify-discord-release.ts"; const latestAnnouncement = { target: "latest", roleId: "222222222222222222", releaseName: "T3 Code v1.2.3", version: "1.2.3", tag: "v1.2.3", releaseUrl: new URL("https://github.com/t3dotgg/t3-code/releases/tag/v1.2.3"), timestamp: "2026-05-01T01:41:00.000Z", } as const; const webhookUrl = new URL("https://discord.com/api/webhooks/123456/secret-token"); it("builds a prerelease Discord announcement for nightly subscribers", () => { assert.deepStrictEqual( buildDiscordReleaseAnnouncement({ target: "prerelease", roleId: "111111111111111111", releaseName: "T3 Code Nightly 1.2.4-nightly.20260501.17 (abcdef123456)", version: "1.2.4-nightly.20260501.17", tag: "v1.2.4-nightly.20260501.17", releaseUrl: new URL( "https://github.com/t3dotgg/t3-code/releases/tag/v1.2.4-nightly.20260501.17", ), timestamp: "2026-05-01T01:41:00.000Z", }), { content: "<@&111111111111111111> Prerelease published: T3 Code Nightly 1.2.4-nightly.20260501.17 (abcdef123456)", allowed_mentions: { roles: ["111111111111111111"], }, embeds: [ { title: "T3 Code Nightly 1.2.4-nightly.20260501.17 (abcdef123456)", url: "https://github.com/t3dotgg/t3-code/releases/tag/v1.2.4-nightly.20260501.17", description: "A new T3 Code prerelease is available for nightly testers.", color: 0x5865f2, fields: [ { name: "Version", value: "1.2.4-nightly.20260501.17", inline: true, }, { name: "Tag", value: "v1.2.4-nightly.20260501.17", inline: true, }, ], timestamp: "2026-05-01T01:41:00.000Z", }, ], }, ); }); it("builds a latest Discord announcement for stable subscribers", () => { assert.deepStrictEqual(buildDiscordReleaseAnnouncement(latestAnnouncement), { content: "<@&222222222222222222> Latest published: T3 Code v1.2.3", allowed_mentions: { roles: ["222222222222222222"], }, embeds: [ { title: "T3 Code v1.2.3", url: "https://github.com/t3dotgg/t3-code/releases/tag/v1.2.3", description: "A new T3 Code latest release is available.", color: 0x2ecc71, fields: [ { name: "Version", value: "1.2.3", inline: true, }, { name: "Tag", value: "v1.2.3", inline: true, }, ], timestamp: "2026-05-01T01:41:00.000Z", }, ], }); }); it.effect("preserves webhook request context and the full client cause chain", () => { const payload = buildDiscordReleaseAnnouncement(latestAnnouncement); const requestCause = new Error("request encoder unavailable"); let clientError: HttpClientError.HttpClientError | undefined; const httpClientLayer = Layer.succeed( HttpClient.HttpClient, HttpClient.make((request) => { clientError = new HttpClientError.HttpClientError({ reason: new HttpClientError.EncodeError({ request, cause: requestCause, }), }); return Effect.fail(clientError); }), ); return Effect.gen(function* () { const error = yield* postDiscordWebhook(webhookUrl, payload, latestAnnouncement).pipe( Effect.provide(httpClientLayer), Effect.flip, ); if (error._tag !== "DiscordReleaseWebhookRequestError") { assert.fail(`Unexpected error: ${error._tag}`); } assert.equal(error.target, "latest"); assert.equal(error.releaseName, latestAnnouncement.releaseName); assert.equal(error.version, latestAnnouncement.version); assert.equal(error.tag, latestAnnouncement.tag); assert.equal(error.releaseUrl, latestAnnouncement.releaseUrl.href); assert.equal(error.webhookOrigin, webhookUrl.origin); assert.equal(error.webhookPathnameSegmentCount, 4); assert.equal(error.contentLength, payload.content.length); assert.equal(error.embedCount, 1); assert.equal(error.allowedRoleMentionCount, 1); assert.equal(error.hasRoleMentionSyntax, true); assert.equal(error.cause, clientError); assert.equal((error.cause as HttpClientError.HttpClientError).cause, requestCause); assert.ok(!error.message.includes(requestCause.message)); assert.equal(isDiscordReleaseAnnouncementError(error), true); }); }); it.effect("preserves a non-success response error with structured status context", () => { const payload = buildDiscordReleaseAnnouncement(latestAnnouncement); const httpClientLayer = Layer.succeed( HttpClient.HttpClient, HttpClient.make((request) => Effect.succeed( HttpClientResponse.fromWeb(request, new Response("invalid webhook", { status: 400 })), ), ), ); return Effect.gen(function* () { const error = yield* postDiscordWebhook(webhookUrl, payload, latestAnnouncement).pipe( Effect.provide(httpClientLayer), Effect.flip, ); if (error._tag !== "DiscordReleaseWebhookResponseError") { assert.fail(`Unexpected error: ${error._tag}`); } assert.equal(error.target, "latest"); assert.equal(error.tag, latestAnnouncement.tag); assert.equal(error.webhookOrigin, webhookUrl.origin); assert.equal(error.webhookPathnameSegmentCount, 4); assert.equal(error.status, 400); if (!HttpClientError.isHttpClientError(error.cause)) { assert.fail("Expected HttpClientError cause"); } assert.equal(error.cause.reason._tag, "StatusCodeError"); assert.ok(!error.message.includes(error.cause.message)); assert.equal(isDiscordReleaseAnnouncementError(error), true); }); });