1import { assert, describe, it } from "@effect/vitest"
2import { assertNone } from "@effect/vitest/utils"
3import { Cause, Effect } from "effect"
4import {
5 causeResponseStripped,
6 HttpServerError,
7 InternalError,
8 isHttpServerError,
9 RequestParseError,
10 ResponseError,
11 RouteNotFound,
12 ServeError
13} from "effect/http/HttpServerError"
14import * as HttpServerRequest from "effect/http/HttpServerRequest"
15import * as Respondable from "effect/http/HttpServerRespondable"
16import * as HttpServerResponse from "effect/http/HttpServerResponse"
17
18const makeRequest = (method: string, url: string) => HttpServerRequest.fromWeb(new Request(url, { method }))
19const makeResponse = (status: number) => HttpServerResponse.empty({ status })
20
21describe("HttpServerError", () => {
22 describe("reason messages", () => {
23 it("formats request reason messages", () => {
24 const request = makeRequest("POST", "http://example.com/widgets")
25 const parseError = new RequestParseError({
26 request,
27 description: "invalid body"
28 })
29 const routeNotFound = new RouteNotFound({ request })
30
31 assert.strictEqual(parseError.message, "RequestParseError (POST /widgets): invalid body")
32 assert.strictEqual(routeNotFound.message, "RouteNotFound (POST /widgets)")
33 })
34
35 it("formats response reason messages", () => {
36 const request = makeRequest("GET", "http://example.com/tea")
37 const response = makeResponse(418)
38 const responseError = new ResponseError({
39 request,
40 response,
41 description: "short and stout"
42 })
43
44 assert.strictEqual(responseError.message, "ResponseError (418 GET /tea): short and stout")
45 })
46 })
47
48 describe("wrapper behavior", () => {
49 it("delegates message and accessors", () => {
50 const request = makeRequest("GET", "http://example.com/ok")
51 const response = makeResponse(503)
52 const reason = new ResponseError({ request, response })
53 const error = new HttpServerError({ reason })
54
55 assert.strictEqual(error.message, reason.message)
56 assert.strictEqual(error.request, request)
57 assert.strictEqual(error.response, response)
58 assert.strictEqual(error.reason, reason)
59 })
60
61 it("forwards the reason cause", () => {
62 const request = makeRequest("GET", "http://example.com/boom")
63 const cause = new Error("boom")
64 const reason = new InternalError({ request, cause })
65 const error = new HttpServerError({ reason })
66
67 assert.strictEqual(reason.cause, cause)
68 assert.strictEqual(error.cause, cause)
69 })
70 })
71
72 describe("respondable mapping", () => {
73 it.effect("maps reasons to status codes", () =>
74 Effect.gen(function*() {
75 const request = makeRequest("GET", "http://example.com/status")
76 const response = makeResponse(418)
77 const cases: ReadonlyArray<readonly [Respondable.Respondable, number]> = [
78 [new RequestParseError({ request }), 400],
79 [new RouteNotFound({ request }), 404],
80 [new InternalError({ request }), 500],
81 [new ResponseError({ request, response }), 500],
82 [new HttpServerError({ reason: new RequestParseError({ request }) }), 400]
83 ]
84
85 for (const [respondable, status] of cases) {
86 const resolved = yield* Respondable.toResponse(respondable)
87 assert.strictEqual(resolved.status, status)
88 }
89 }))
90 })
91
92 describe("isHttpServerError", () => {
93 it("matches wrapper only", () => {
94 const request = makeRequest("GET", "http://example.com/check")
95 const wrapper = new HttpServerError({
96 reason: new RouteNotFound({ request })
97 })
98
99 assert.isTrue(isHttpServerError(wrapper))
100 assert.isFalse(isHttpServerError(new RouteNotFound({ request })))
101 assert.isFalse(isHttpServerError(new ServeError({ cause: new Error("boom") })))
102 assert.isFalse(isHttpServerError({ _tag: "HttpServerError" }))
103 })
104 })
105
106 describe("causeResponseStripped", () => {
107 it("returns none cause for response defects", () => {
108 const response = makeResponse(418)
109 const [out, cause] = causeResponseStripped(Cause.die(response))
110 assert.strictEqual(out, response)
111 assertNone(cause)
112 })
113
114 it("returns some cause for non-response failures", () => {
115 const request = makeRequest("GET", "http://example.com/fail")
116 const [out, cause] = causeResponseStripped(Cause.fail(new RouteNotFound({ request })))
117 assert.strictEqual(out.status, 500)
118 assert.strictEqual(cause._tag, "Some")
119 })
120 })
121})