Skip to content

Commit

Permalink
Move envelope decoding to test helpers (#672)
Browse files Browse the repository at this point in the history
* Move from_binary/1 to test helpers, all tests pass

* Finish up

* Dialyzer
  • Loading branch information
whatyouhide authored Dec 10, 2023
1 parent a8e67d8 commit cb24caf
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 244 deletions.
83 changes: 0 additions & 83 deletions lib/sentry/envelope.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,87 +56,4 @@ defmodule Sentry.Envelope do
error
end
end

@doc """
Decodes the envelope from its binary representation.
"""
@spec from_binary(String.t()) :: {:ok, t()} | {:error, :invalid_envelope}
def from_binary(binary) when is_binary(binary) do
json_library = Config.json_library()

[raw_headers | raw_items] = String.split(binary, "\n")

with {:ok, headers} <- json_library.decode(raw_headers),
{:ok, items} <- decode_items(raw_items, json_library) do
{:ok,
%__MODULE__{
event_id: headers["event_id"] || nil,
items: items
}}
else
{:error, _json_error} -> {:error, :invalid_envelope}
end
end

#
# Decoding
#

# Steps over the item pairs in the envelope body. The item header is decoded
# first so it can be used to decode the item following it.
defp decode_items(raw_items, json_library) do
items =
raw_items
|> Enum.chunk_every(2, 2, :discard)
|> Enum.map(fn [k, v] ->
with {:ok, item_header} <- json_library.decode(k),
{:ok, item} <- decode_item(item_header, v, json_library) do
item
else
{:error, _reason} = error -> throw(error)
end
end)

{:ok, items}
catch
{:error, reason} -> {:error, reason}
end

defp decode_item(%{"type" => "event"}, data, json_library) do
result = json_library.decode(data)

case result do
{:ok, fields} ->
{:ok,
%Event{
breadcrumbs: fields["breadcrumbs"],
culprit: fields["culprit"],
environment: fields["environment"],
event_id: fields["event_id"],
source: fields["event_source"],
exception: List.wrap(fields["exception"]),
extra: fields["extra"],
fingerprint: fields["fingerprint"],
level: fields["level"],
message: fields["message"],
modules: fields["modules"],
original_exception: fields["original_exception"],
platform: fields["platform"],
release: fields["release"],
request: fields["request"],
server_name: fields["server_name"],
tags: fields["tags"],
timestamp: fields["timestamp"],
user: fields["user"]
}}

{:error, e} ->
{:error, "Failed to decode event item: #{e}"}
end
end

defp decode_item(%{"type" => type}, _data, _json_library),
do: {:error, "unexpected item type '#{type}'"}

defp decode_item(_, _data, _json_library), do: {:error, "Missing item type header"}
end
129 changes: 1 addition & 128 deletions test/envelope_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,134 +3,7 @@ defmodule Sentry.EnvelopeTest do

import Sentry.TestHelpers

alias Sentry.{Envelope, Event, Interfaces}

describe "from_binary/1" do
test "parses envelope with empty headers" do
raw = "{}\n"

{:ok, envelope} = Envelope.from_binary(raw)

assert envelope.event_id == nil
assert envelope.items == []
end

test "parses envelope with only headers" do
raw = "{\"event_id\":\"12c2d058d58442709aa2eca08bf20986\"}\n"

{:ok, envelope} = Envelope.from_binary(raw)

assert envelope.event_id == "12c2d058d58442709aa2eca08bf20986"
assert envelope.items == []
end

test "parses envelope containing an event" do
event = %Event{
breadcrumbs: [],
environment: "test",
event_id: "1d208b37d9904203918a9c2125ea91fa",
source: nil,
exception: [],
extra: %{},
fingerprint: ["{{ default }}"],
level: "error",
message: "hello",
modules: %{
bypass: "2.1.0",
certifi: "2.6.1",
cowboy: "2.8.0",
cowboy_telemetry: "0.3.1",
cowlib: "2.9.1",
dialyxir: "1.1.0",
erlex: "0.2.6",
hackney: "1.17.4",
idna: "6.1.1",
jason: "1.2.2",
metrics: "1.0.1",
mime: "1.5.0",
mimerl: "1.2.0",
parse_trans: "3.3.1",
phoenix: "1.5.8",
phoenix_html: "2.14.3",
phoenix_pubsub: "2.0.0",
plug: "1.11.1",
plug_cowboy: "2.4.1",
plug_crypto: "1.2.2",
ranch: "1.7.1",
ssl_verify_fun: "1.1.6",
telemetry: "0.4.2",
unicode_util_compat: "0.7.0"
},
original_exception: nil,
platform: :elixir,
release: nil,
request: %Interfaces.Request{},
server_name: "john-linux",
tags: %{},
timestamp: "2021-10-09T03:53:22",
user: %{}
}

{:ok, raw_envelope} =
[event]
|> Envelope.new()
|> Envelope.to_binary()

{:ok, envelope} = Envelope.from_binary(raw_envelope)

assert envelope.event_id == event.event_id

assert envelope.items == [
%Event{
breadcrumbs: [],
environment: "test",
event_id: "1d208b37d9904203918a9c2125ea91fa",
exception: [],
extra: %{},
fingerprint: ["{{ default }}"],
level: "error",
message: "hello",
modules: %{
"bypass" => "2.1.0",
"certifi" => "2.6.1",
"cowboy" => "2.8.0",
"cowboy_telemetry" => "0.3.1",
"cowlib" => "2.9.1",
"dialyxir" => "1.1.0",
"erlex" => "0.2.6",
"hackney" => "1.17.4",
"idna" => "6.1.1",
"jason" => "1.2.2",
"metrics" => "1.0.1",
"mime" => "1.5.0",
"mimerl" => "1.2.0",
"parse_trans" => "3.3.1",
"phoenix" => "1.5.8",
"phoenix_html" => "2.14.3",
"phoenix_pubsub" => "2.0.0",
"plug" => "1.11.1",
"plug_cowboy" => "2.4.1",
"plug_crypto" => "1.2.2",
"ranch" => "1.7.1",
"ssl_verify_fun" => "1.1.6",
"telemetry" => "0.4.2",
"unicode_util_compat" => "0.7.0"
},
platform: "elixir",
release: nil,
request: %{},
server_name: "john-linux",
tags: %{},
timestamp: "2021-10-09T03:53:22",
user: %{}
}
]
end

test "returns an error if headers are missing" do
assert {:error, :invalid_envelope} = Envelope.from_binary("no newlines here")
end
end
alias Sentry.{Envelope, Event}

describe "to_binary/1" do
test "encodes an envelope" do
Expand Down
39 changes: 19 additions & 20 deletions test/plug_capture_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ defmodule Sentry.PlugCaptureTest do

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"] == %{}
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)

Expand All @@ -63,9 +63,7 @@ defmodule Sentry.PlugCaptureTest do
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)

Expand All @@ -75,9 +73,7 @@ defmodule Sentry.PlugCaptureTest do
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)

Expand All @@ -94,9 +90,7 @@ defmodule Sentry.PlugCaptureTest do
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)

Expand Down Expand Up @@ -135,10 +129,10 @@ defmodule Sentry.PlugCaptureTest do

event = decode_event_from_envelope!(body)

assert event.culprit == "Sentry.PlugCaptureTest.PhoenixController.error/2"
assert event["culprit"] == "Sentry.PlugCaptureTest.PhoenixController.error/2"

assert List.first(event.exception)["type"] == "RuntimeError"
assert List.first(event.exception)["value"] == "PhoenixError"
assert List.first(event["exception"])["type"] == "RuntimeError"
assert List.first(event["exception"])["value"] == "PhoenixError"

Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
Expand All @@ -155,8 +149,8 @@ defmodule Sentry.PlugCaptureTest do

event = decode_event_from_envelope!(body)

assert event.culprit == "Sentry.PlugCaptureTest.PhoenixController.exit/2"
assert event.message == "Uncaught exit - :test"
assert event["culprit"] == "Sentry.PlugCaptureTest.PhoenixController.exit/2"
assert event["message"] == "Uncaught exit - :test"
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)

Expand All @@ -169,8 +163,8 @@ defmodule Sentry.PlugCaptureTest do

event = decode_event_from_envelope!(body)

assert event.culprit == "Sentry.PlugCaptureTest.PhoenixController.throw/2"
assert event.message == "Uncaught throw - :test"
assert event["culprit"] == "Sentry.PlugCaptureTest.PhoenixController.throw/2"
assert event["message"] == "Uncaught throw - :test"
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)

Expand Down Expand Up @@ -203,8 +197,8 @@ defmodule Sentry.PlugCaptureTest do
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 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
Expand Down Expand Up @@ -235,7 +229,7 @@ defmodule Sentry.PlugCaptureTest 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"
assert event["culprit"] == "Sentry.PlugCaptureTest.PhoenixController.assigns/2"
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)

Expand All @@ -250,4 +244,9 @@ defmodule Sentry.PlugCaptureTest do
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
12 changes: 6 additions & 6 deletions test/sentry/client_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ defmodule Sentry.ClientTest do
Bypass.expect(bypass, fn conn ->
assert {:ok, body, conn} = Plug.Conn.read_body(conn)

event = decode_event_from_envelope!(body)
assert [{%{"type" => "event"}, event}] = decode_envelope!(body)

assert event.extra == %{"key" => "value"}
assert event.user["id"] == 1
assert event["extra"] == %{"key" => "value"}
assert event["user"]["id"] == 1

Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end)
Expand Down Expand Up @@ -224,15 +224,15 @@ defmodule Sentry.ClientTest do
event =
fn ->
assert_receive {^ref, body}, 1000
decode_event_from_envelope!(body)
assert [{%{"type" => "event"}, event}] = decode_envelope!(body)
event
end
|> Stream.repeatedly()
|> Stream.reject(&is_nil/1)
|> Stream.take(10)
|> Enum.at(0)

assert %Event{} = event
assert event.message == "Something went wrong"
assert event["message"] == "Something went wrong"
end

test "dedupes events", %{bypass: bypass} do
Expand Down
Loading

0 comments on commit cb24caf

Please sign in to comment.