145 lines · 5.3 KB · TypeScript
RawDownload
import { expect, it } from "@effect/vitest";import * as Effect from "effect/Effect";import { HttpClientRequest, HttpClientResponse } from "effect/unstable/http";import * as HttpClientError from "effect/unstable/http/HttpClientError"; import { filterRelayResponse, relayRequestError, shouldRetryCloudLink } from "./relayResponse.ts"; const response = (  status: number,  body: string | Record<string, unknown>,  headers?: Record<string, string>,) =>  HttpClientResponse.fromWeb(    HttpClientRequest.post("https://relay.example.test/v1/client/environment-links"),    typeof body === "string"      ? new Response(body, { status, ...(headers ? { headers } : {}) })      : Response.json(body, { status, ...(headers ? { headers } : {}) }),  ); it("reports a transport failure category without exposing request or cause details", () => {  const error = relayRequestError(    new HttpClientError.HttpClientError({      reason: new HttpClientError.TransportError({        request: HttpClientRequest.post("https://relay.example.test/link?token=private-token"),        description: "private transport details",        cause: new Error("private cause details"),      }),    }),  );   expect(error._tag).toBe("EnvironmentHttpInternalServerError");  expect(error.message).toContain("TransportError");  expect(error.message).toContain("network connection");  expect(error.message).not.toContain("relay.example.test");  expect(error.message).not.toContain("private");  expect(shouldRetryCloudLink(error)).toBe(true);}); it.effect("reports the tunnel limit and relay trace instead of a generic 403", () =>  Effect.gen(function* () {    const error = yield* filterRelayResponse(      response(403, {        _tag: "RelayEnvironmentLinkLimitExceededError",        code: "environment_link_limit_exceeded",        maxTunnels: 3,        traceId: "trace-limit",      }),    ).pipe(Effect.mapError(relayRequestError), Effect.flip);     expect(error._tag).toBe("EnvironmentHttpForbiddenError");    expect(error.message).toContain("at most 3 tunnels");    expect(error.message).toContain("Unlink an unused environment");    expect(error.message).toContain("Trace ID: trace-limit");  }),); it.effect("makes revoked authorization actionable and non-retryable", () =>  Effect.gen(function* () {    const error = yield* filterRelayResponse(      response(401, {        _tag: "RelayAuthInvalidError",        code: "auth_invalid",        reason: "invalid_bearer",        traceId: "trace-auth",      }),    ).pipe(Effect.mapError(relayRequestError), Effect.flip);     expect(error._tag).toBe("EnvironmentHttpUnauthorizedError");    expect(error.message).toContain("invalid_bearer");    expect(error.message).toContain("t3 connect login");    expect(error.message).toContain("Trace ID: trace-auth");  }),); it.effect("reports an unrecognized access denial without printing its response body", () =>  Effect.gen(function* () {    const error = yield* filterRelayResponse(      response(403, "<html>private upstream details</html>", {        "content-type": "text/html",        "cf-ray": "abcdef1234-IAD",      }),    ).pipe(Effect.flip);     expect(error._tag).toBe("EnvironmentHttpForbiddenError");    expect(error.message).toContain("HTTP 403");    expect(error.message).toContain("proxy or firewall");    expect(error.message).toContain("Cloudflare Ray ID: abcdef1234-IAD");    expect(error.message).not.toContain("private upstream details");  }),); it.effect.each([408, 429, 500, 502, 503, 504])(  "keeps transient HTTP %s failures retryable",  (status) =>    Effect.gen(function* () {      const error = yield* filterRelayResponse(response(status, "unavailable")).pipe(Effect.flip);      expect(error._tag).toBe("EnvironmentHttpInternalServerError");      expect(error.message).toContain(`HTTP ${status}`);    }),); it.effect.each([  { status: 401, attempts: 1 },  { status: 403, attempts: 1 },  { status: 429, attempts: 2 },  { status: 503, attempts: 2 },])("stops rejected startup links but retries temporary failures: $status", ({ status, attempts }) =>  Effect.gen(function* () {    let requests = 0;    const result = yield* Effect.suspend(() => {      requests++;      return filterRelayResponse(response(requests === 1 ? status : 200, "{}"));    }).pipe(      Effect.mapError(relayRequestError),      Effect.retry({ while: shouldRetryCloudLink, times: 1 }),      Effect.result,    );    expect(requests).toBe(attempts);    expect(result._tag).toBe(attempts === 1 ? "Failure" : "Success");  }),); it.effect("keeps the relay failure reason and trace when tunnel cleanup fails", () =>  Effect.gen(function* () {    const error = yield* filterRelayResponse(      response(500, {        _tag: "RelayInternalError",        code: "internal_error",        reason: "upstream_unavailable",        traceId: "trace-cleanup",      }),    ).pipe(Effect.flip);     expect(error._tag).toBe("EnvironmentHttpInternalServerError");    expect(error.message).toContain("upstream_unavailable");    expect(error.message).toContain("Trace ID: trace-cleanup");  }),); it.effect("leaves successful response bodies available to their decoder", () =>  Effect.gen(function* () {    const result = yield* filterRelayResponse(response(200, '{"ok":true}'));    expect(yield* result.json).toEqual({ ok: true });  }),);