-
-
Notifications
You must be signed in to change notification settings - Fork 200
/
Copy pathplug_capture_test.exs
301 lines (230 loc) · 9.35 KB
/
plug_capture_test.exs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
defmodule Sentry.PlugCaptureTest do
use Sentry.Case
use Plug.Test
import Sentry.TestHelpers
defmodule PhoenixController do
use Phoenix.Controller
def error(_conn, _params), do: raise("PhoenixError")
def exit(_conn, _params), do: exit(:test)
def throw(_conn, _params), do: throw(:test)
def action_clause_error(conn, %{"required_param" => true}), do: conn
def assigns(conn, _params), do: _test = conn.assigns2.test
end
defmodule PhoenixRouter do
use Phoenix.Router
get "/error_route", PhoenixController, :error
get "/exit_route", PhoenixController, :exit
get "/throw_route", PhoenixController, :throw
get "/action_clause_error", PhoenixController, :action_clause_error
get "/assigns_route", PhoenixController, :assigns
end
defmodule PhoenixEndpoint do
use Sentry.PlugCapture
use Phoenix.Endpoint, otp_app: :sentry
use Plug.Debugger, otp_app: :sentry
json_mod = if Code.ensure_loaded?(JSON), do: JSON, else: Jason
plug Plug.Parsers, parsers: [:json], pass: ["*/*"], json_decoder: json_mod
plug Sentry.PlugContext
plug PhoenixRouter
end
defmodule Scrubber do
def scrub_conn(conn) do
conn
end
end
defmodule PhoenixEndpointWithScrubber do
use Sentry.PlugCapture, scrubber: {Scrubber, :scrub_conn, []}
use Phoenix.Endpoint, otp_app: :sentry
use Plug.Debugger, otp_app: :sentry
json_mod = if Code.ensure_loaded?(JSON), do: JSON, else: Jason
plug Plug.Parsers, parsers: [:json], pass: ["*/*"], json_decoder: json_mod
plug Sentry.PlugContext
plug PhoenixRouter
end
setup do
bypass = Bypass.open()
put_test_config(dsn: "http://public:secret@localhost:#{bypass.port}/1")
%{bypass: bypass}
end
describe "with a Plug application" do
test "sends error to Sentry and uses Sentry.PlugContext to fill in context", %{
bypass: bypass
} do
Bypass.expect(bypass, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
event = decode_event_from_envelope!(body)
assert event["request"]["url"] == "http://www.example.com/error_route"
assert event["request"]["method"] == "GET"
assert event["request"]["query_string"] == ""
assert event["request"]["data"] == %{}
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
assert_raise(Plug.Conn.WrapperError, "** (RuntimeError) Error", fn ->
conn(:get, "/error_route")
|> call_plug_app()
end)
end
test "sends throws to Sentry", %{bypass: bypass} do
Bypass.expect(bypass, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
_event = decode_event_from_envelope!(body)
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
catch_throw(conn(:get, "/throw_route") |> call_plug_app())
end
test "sends exits to Sentry", %{bypass: bypass} do
Bypass.expect(bypass, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
_event = decode_event_from_envelope!(body)
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
catch_exit(conn(:get, "/exit_route") |> call_plug_app())
end
test "does not send error on unmatched routes", %{bypass: _bypass} do
assert_raise FunctionClauseError, ~r/no function clause matching/, fn ->
conn(:get, "/not_found")
|> call_plug_app()
end
end
test "can render feedback form", %{bypass: bypass} do
Bypass.expect(bypass, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
_event = decode_event_from_envelope!(body)
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
conn = conn(:get, "/error_route")
assert_raise Plug.Conn.WrapperError, "** (RuntimeError) Error", fn ->
call_plug_app(conn)
end
assert_received {:plug_conn, :sent}
{event_id, _} = Sentry.get_last_event_id_and_source()
assert {500, _headers, body} = sent_resp(conn)
assert body =~ "sentry-cdn"
assert body =~ event_id
assert body =~ ~s{"title":"Testing"}
end
end
describe "with a Phoenix endpoint" do
@describetag :capture_log
setup do
Application.put_env(:sentry, PhoenixEndpoint,
render_errors: [view: Sentry.ErrorView, accepts: ~w(html)]
)
pid = start_supervised!(PhoenixEndpoint)
Process.link(pid)
:ok
end
test "reports raised exceptions", %{bypass: bypass} do
Bypass.expect(bypass, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
event = decode_event_from_envelope!(body)
assert event["culprit"] == "Sentry.PlugCaptureTest.PhoenixController.error/2"
assert List.first(event["exception"])["type"] == "RuntimeError"
assert List.first(event["exception"])["value"] == "PhoenixError"
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
assert_raise RuntimeError, "PhoenixError", fn ->
conn(:get, "/error_route")
|> call_phoenix_endpoint()
end
end
test "reports exits", %{bypass: bypass} do
Bypass.expect(bypass, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
event = decode_event_from_envelope!(body)
assert event["culprit"] == "Sentry.PlugCaptureTest.PhoenixController.exit/2"
assert event["message"]["formatted"] == "Uncaught exit - :test"
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
catch_exit(conn(:get, "/exit_route") |> call_phoenix_endpoint())
end
test "reports throws", %{bypass: bypass} do
Bypass.expect(bypass, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
event = decode_event_from_envelope!(body)
assert event["culprit"] == "Sentry.PlugCaptureTest.PhoenixController.throw/2"
assert event["message"]["formatted"] == "Uncaught throw - :test"
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
catch_throw(conn(:get, "/throw_route") |> call_phoenix_endpoint())
end
test "does not send Phoenix.Router.NoRouteError" do
conn(:get, "/not_found")
|> call_phoenix_endpoint()
end
test "scrubs Phoenix.ActionClauseError", %{bypass: bypass} do
test_pid = self()
ref = make_ref()
Bypass.expect(bypass, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
send(test_pid, {ref, body})
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
assert_raise Phoenix.ActionClauseError, fn ->
conn(:get, "/action_clause_error?password=secret")
|> Plug.Conn.put_req_header("authorization", "yes")
|> call_phoenix_endpoint()
end
assert_receive {^ref, sentry_body}
event = decode_event_from_envelope!(sentry_body)
assert event["culprit"] ==
"Sentry.PlugCaptureTest.PhoenixController.action_clause_error/2"
assert [exception] = event["exception"]
assert exception["type"] == "Phoenix.ActionClauseError"
assert exception["value"] =~ ~s(params: %{"password" => "*********"})
end
test "can render feedback form in Phoenix ErrorView", %{bypass: bypass} do
Bypass.expect(bypass, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
_event = decode_event_from_envelope!(body)
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
conn = conn(:get, "/error_route")
assert_raise RuntimeError, "PhoenixError", fn -> call_phoenix_endpoint(conn) end
{event_id, _} = Sentry.get_last_event_id_and_source()
assert_received {:plug_conn, :sent}
assert {500, _headers, body} = sent_resp(conn)
assert body =~ "sentry-cdn"
assert body =~ event_id
assert body =~ ~s{"title":"Testing"}
end
test "handles Erlang error in Plug.Conn.WrapperError", %{bypass: bypass} do
Bypass.expect(bypass, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
event = decode_event_from_envelope!(body)
assert event["culprit"] == "Sentry.PlugCaptureTest.PhoenixController.assigns/2"
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
assert_raise KeyError, fn ->
conn(:get, "/assigns_route")
|> Plug.Conn.put_req_header("throw", "throw")
|> call_phoenix_endpoint()
end
end
test "modifies conn with custom scrubber", %{bypass: bypass} do
Application.put_env(:sentry, PhoenixEndpointWithScrubber,
render_errors: [view: Sentry.ErrorView, accepts: ~w(html)]
)
pid = start_supervised!(PhoenixEndpointWithScrubber)
Process.link(pid)
Bypass.expect(bypass, fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
event = decode_event_from_envelope!(body)
assert event["culprit"] == "Sentry.PlugCaptureTest.PhoenixController.error/2"
assert List.first(event["exception"])["type"] == "RuntimeError"
assert List.first(event["exception"])["value"] == "PhoenixError"
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
assert_raise RuntimeError, "PhoenixError", fn ->
conn(:get, "/error_route")
|> Plug.run([{PhoenixEndpointWithScrubber, []}])
end
end
end
defp call_plug_app(conn), do: Plug.run(conn, [{Sentry.ExamplePlugApplication, []}])
defp call_phoenix_endpoint(conn), do: Plug.run(conn, [{PhoenixEndpoint, []}])
defp decode_event_from_envelope!(envelope) do
assert [{%{"type" => "event"}, event}] = decode_envelope!(envelope)
event
end
end