import { assert, describe, it } from "@effect/vitest" import { assertNone } from "@effect/vitest/utils" import { Cause, Effect } from "effect" import { causeResponseStripped, HttpServerError, InternalError, isHttpServerError, RequestParseError, ResponseError, RouteNotFound, ServeError } from "effect/unstable/http/HttpServerError" import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest" import * as Respondable from "effect/unstable/http/HttpServerRespondable" import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" const makeRequest = (method: string, url: string) => HttpServerRequest.fromWeb(new Request(url, { method })) const makeResponse = (status: number) => HttpServerResponse.empty({ status }) describe("HttpServerError", () => { describe("reason messages", () => { it("formats request reason messages", () => { const request = makeRequest("POST", "http://example.com/widgets") const parseError = new RequestParseError({ request, description: "invalid body" }) const routeNotFound = new RouteNotFound({ request }) assert.strictEqual(parseError.message, "RequestParseError (POST /widgets): invalid body") assert.strictEqual(routeNotFound.message, "RouteNotFound (POST /widgets)") }) it("formats response reason messages", () => { const request = makeRequest("GET", "http://example.com/tea") const response = makeResponse(418) const responseError = new ResponseError({ request, response, description: "short and stout" }) assert.strictEqual(responseError.message, "ResponseError (418 GET /tea): short and stout") }) }) describe("wrapper behavior", () => { it("delegates message and accessors", () => { const request = makeRequest("GET", "http://example.com/ok") const response = makeResponse(503) const reason = new ResponseError({ request, response }) const error = new HttpServerError({ reason }) assert.strictEqual(error.message, reason.message) assert.strictEqual(error.request, request) assert.strictEqual(error.response, response) assert.strictEqual(error.reason, reason) }) it("forwards the reason cause", () => { const request = makeRequest("GET", "http://example.com/boom") const cause = new Error("boom") const reason = new InternalError({ request, cause }) const error = new HttpServerError({ reason }) assert.strictEqual(reason.cause, cause) assert.strictEqual(error.cause, cause) }) }) describe("respondable mapping", () => { it.effect("maps reasons to status codes", () => Effect.gen(function*() { const request = makeRequest("GET", "http://example.com/status") const response = makeResponse(418) const cases: ReadonlyArray = [ [new RequestParseError({ request }), 400], [new RouteNotFound({ request }), 404], [new InternalError({ request }), 500], [new ResponseError({ request, response }), 500], [new HttpServerError({ reason: new RequestParseError({ request }) }), 400] ] for (const [respondable, status] of cases) { const resolved = yield* Respondable.toResponse(respondable) assert.strictEqual(resolved.status, status) } })) }) describe("isHttpServerError", () => { it("matches wrapper only", () => { const request = makeRequest("GET", "http://example.com/check") const wrapper = new HttpServerError({ reason: new RouteNotFound({ request }) }) assert.isTrue(isHttpServerError(wrapper)) assert.isFalse(isHttpServerError(new RouteNotFound({ request }))) assert.isFalse(isHttpServerError(new ServeError({ cause: new Error("boom") }))) assert.isFalse(isHttpServerError({ _tag: "HttpServerError" })) }) }) describe("causeResponseStripped", () => { it("returns none cause for response defects", () => { const response = makeResponse(418) const [out, cause] = causeResponseStripped(Cause.die(response)) assert.strictEqual(out, response) assertNone(cause) }) it("returns some cause for non-response failures", () => { const request = makeRequest("GET", "http://example.com/fail") const [out, cause] = causeResponseStripped(Cause.fail(new RouteNotFound({ request }))) assert.strictEqual(out.status, 500) assert.strictEqual(cause._tag, "Some") }) }) })