142 lines · 4.3 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import { Effect, Layer } from "effect";import { HttpClient, HttpClientRequest, HttpClientResponse, UrlParams } from "effect/unstable/http"; import { issueRequest } from "./request";import type { RenderedAuth } from "./template"; /* A capturing in-memory HttpClient: records the request it was handed and * returns a canned web Response. No network. */const capturingClient = (  capture: { request?: HttpClientRequest.HttpClientRequest },  response: Response,): Layer.Layer<HttpClient.HttpClient> =>  Layer.succeed(    HttpClient.HttpClient,    HttpClient.make((request: HttpClientRequest.HttpClientRequest) => {      capture.request = request;      return Effect.succeed(HttpClientResponse.fromWeb(request, response));    }),  ); const noAuth: RenderedAuth = { headers: {}, queryParams: {} }; describe("issueRequest", () => {  it.effect("issues a GET, merges auth headers + query, parses JSON body", () =>    Effect.gen(function* () {      const capture: { request?: HttpClientRequest.HttpClientRequest } = {};      const layer = capturingClient(        capture,        new Response('{"hello":"world"}', {          status: 200,          headers: { "content-type": "application/json" },        }),      );       const result = yield* issueRequest(        {          baseUrl: "https://api.example.com",          method: "GET",          path: "/things",          query: { limit: 5 },          headers: { "x-trace": "abc" },          auth: {            headers: { Authorization: "Bearer tok" },            queryParams: { api_key: "k" },          },          defaultHeaders: { "x-default": "d" },        },        layer,      );       expect(result.status).toBe(200);      expect(result.body).toEqual({ hello: "world" });       const sent = capture.request!;      expect(sent.method).toBe("GET");      expect(sent.url).toContain("https://api.example.com/things");      const params = UrlParams.toString(sent.urlParams);      expect(params).toContain("limit=5");      expect(params).toContain("api_key=k");      expect(sent.headers["authorization"]).toBe("Bearer tok");      expect(sent.headers["x-trace"]).toBe("abc");      expect(sent.headers["x-default"]).toBe("d");    }),  );   it.effect("returns non-2xx upstream responses as data, not an error", () =>    Effect.gen(function* () {      const capture: { request?: HttpClientRequest.HttpClientRequest } = {};      const layer = capturingClient(        capture,        new Response("nope", {          status: 404,          headers: { "content-type": "text/plain" },        }),      );       const result = yield* issueRequest(        {          baseUrl: "https://api.example.com",          method: "GET",          path: "/missing",          auth: noAuth,        },        layer,      );       expect(result.status).toBe(404);      expect(result.body).toBe("nope");    }),  );   it.effect("JSON-encodes object bodies on POST", () =>    Effect.gen(function* () {      const capture: { request?: HttpClientRequest.HttpClientRequest } = {};      const layer = capturingClient(capture, new Response(null, { status: 204 }));       const result = yield* issueRequest(        {          baseUrl: "https://api.example.com",          method: "POST",          path: "things",          body: { name: "n" },          auth: noAuth,        },        layer,      );       expect(result.status).toBe(204);      expect(result.body).toBe(null);      const sent = capture.request!;      expect(sent.method).toBe("POST");      // path without leading slash is still joined under the base      expect(sent.url).toContain("https://api.example.com/things");    }),  );   it.effect("treats an absolute path as the full URL (ignores baseUrl)", () =>    Effect.gen(function* () {      const capture: { request?: HttpClientRequest.HttpClientRequest } = {};      const layer = capturingClient(        capture,        new Response("{}", {          status: 200,          headers: { "content-type": "application/json" },        }),      );       yield* issueRequest(        {          baseUrl: "https://api.example.com",          method: "GET",          path: "https://other.example.org/x",          auth: noAuth,        },        layer,      );       expect(capture.request!.url).toContain("https://other.example.org/x");    }),  );});