From fe1ebfd8a17316793f554ef086f70068c22954dc Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sat, 20 Apr 2024 08:58:39 +0200 Subject: [PATCH 01/10] Update deps --- mix.exs | 15 +++++++++++++-- mix.lock | 25 +++++++++++++++---------- test/event_test.exs | 2 +- test/plug_capture_test.exs | 6 ++---- test/support/test_error_view.ex | 10 +++++++--- 5 files changed, 38 insertions(+), 20 deletions(-) diff --git a/mix.exs b/mix.exs index d86a8a9b..38f1050a 100644 --- a/mix.exs +++ b/mix.exs @@ -100,8 +100,10 @@ defmodule Sentry.Mixfile do {:ex_doc, "~> 0.29.0", only: :dev}, {:excoveralls, "~> 0.17.1", only: [:test]}, {:phoenix, "~> 1.5", only: [:test]}, - {:phoenix_html, "~> 2.0", only: [:test]} - ] ++ maybe_oban_optional_dependency() ++ maybe_quantum_optional_dependency() + {:phoenix_html, "~> 4.0", only: [:test]} + ] ++ + maybe_oban_optional_dependency() ++ + maybe_quantum_optional_dependency() ++ maybe_phoenix_live_view_optional_dependency() end # TODO: Remove this once we drop support for Elixir < 1.13. @@ -122,6 +124,15 @@ defmodule Sentry.Mixfile do end end + # TODO: Remove this once we drop support for Elixir < 1.13. + defp maybe_phoenix_live_view_optional_dependency do + if Version.match?(System.version(), "~> 1.13") do + [{:phoenix_live_view, "~> 0.20", only: [:test]}] + else + [] + end + end + defp package do [ files: ["lib", "LICENSE", "mix.exs", "README.md", "CHANGELOG.md"], diff --git a/mix.lock b/mix.lock index c8f84db7..0da2f305 100644 --- a/mix.lock +++ b/mix.lock @@ -1,9 +1,10 @@ %{ "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, + "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, - "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, + "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, - "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, + "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, "crontab": {:hex, :crontab, "1.1.13", "3bad04f050b9f7f1c237809e42223999c150656a6b2afbbfef597d56df2144c5", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "d67441bec989640e3afb94e123f45a2bc42d76e02988c9613885dc3d01cf7085"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, @@ -22,23 +23,27 @@ "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, + "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, "nimble_ownership": {:hex, :nimble_ownership, "0.3.0", "29514f8779b26f50f9c07109677c98c0cc0b8025e89f82964dafa9cf7d657ec0", [:mix], [], "hexpm", "76c605106bc1e60f5b028b20203a1e0c90b4350b08e4b8a33f68bb50dcb6e837"}, "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "oban": {:hex, :oban, "2.17.6", "bac1dacd836edbf6a200ddd880db10faa2d39bb2e550ec6d19b3eb9c43852c2a", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "623f3554212e9a776e015156c47f076d66c7b74115ac47a7d3acba0294e65acb"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, - "phoenix": {:hex, :phoenix, "1.5.8", "71cfa7a9bb9a37af4df98939790642f210e35f696b935ca6d9d9c55a884621a4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "35ded0a32f4836168c7ab6c33b88822eccd201bcd9492125a9bea4c54332d955"}, - "phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, - "plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, + "phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"}, + "phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.14", "70fa101aa0539e81bed4238777498f6215e9dda3461bdaa067cad6908110c364", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "82f6d006c5264f979ed5eb75593d808bbe39020f20df2e78426f4f2d570e2402"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, + "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, + "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, "quantum": {:hex, :quantum, "3.5.3", "ee38838a07761663468145f489ad93e16a79440bebd7c0f90dc1ec9850776d99", [:mix], [{:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.14 or ~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_registry, "~> 0.2", [hex: :telemetry_registry, repo: "hexpm", optional: false]}], "hexpm", "500fd3fa77dcd723ed9f766d4a175b684919ff7b6b8cfd9d7d0564d58eba8734"}, - "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, "telemetry_registry": {:hex, :telemetry_registry, "0.2.1", "fe648a691f2128e4279d993cd010994c67f282354dc061e697bf070d4b87b480", [:mix, :rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4221cefbcadd0b3e7076960339223742d973f1371bc20f3826af640257bc3690"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"}, } diff --git a/test/event_test.exs b/test/event_test.exs index 7cd5a41e..7aa75c88 100644 --- a/test/event_test.exs +++ b/test/event_test.exs @@ -414,7 +414,7 @@ defmodule Sentry.EventTest do exception = RuntimeError.exception("error") event = Sentry.Event.transform_exception(exception, []) - assert ["asn1", "bypass", "certifi", "compiler" | _rest] = + assert ["asn1", "bypass" | _rest] = event.modules |> Map.keys() |> Enum.sort() diff --git a/test/plug_capture_test.exs b/test/plug_capture_test.exs index 2b3dcb69..218d088f 100644 --- a/test/plug_capture_test.exs +++ b/test/plug_capture_test.exs @@ -188,10 +188,8 @@ defmodule Sentry.PlugCaptureTest do end test "does not send Phoenix.Router.NoRouteError" do - assert_raise Phoenix.Router.NoRouteError, ~r"no route found for GET /not_found", fn -> - conn(:get, "/not_found") - |> call_phoenix_endpoint() - end + conn(:get, "/not_found") + |> call_phoenix_endpoint() end test "scrubs Phoenix.ActionClauseError", %{bypass: bypass} do diff --git a/test/support/test_error_view.ex b/test/support/test_error_view.ex index 7c84867c..29dcd7e4 100644 --- a/test/support/test_error_view.ex +++ b/test/support/test_error_view.ex @@ -1,5 +1,7 @@ defmodule Sentry.ErrorView do - import Phoenix.HTML, only: [sigil_E: 2, raw: 1] + use Phoenix.Component + + import Phoenix.HTML, only: [raw: 1] def render(_, _) do case Sentry.get_last_event_id_and_source() do @@ -8,11 +10,13 @@ defmodule Sentry.ErrorView do %{title: "Testing", eventId: event_id} |> Jason.encode!() - ~E""" + assigns = %{opts: opts} + + ~H""" """ From 41fa4273f21182182a6f3a2cb18aeaabe909c4e1 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sat, 20 Apr 2024 10:06:57 +0200 Subject: [PATCH 02/10] Add LiveView hook Closes #484. --- .formatter.exs | 2 +- lib/sentry/live_view_hook.ex | 151 +++++++++++++++++++++++++++ mix.exs | 15 +-- mix.lock | 1 + pages/setup-with-plug-and-phoenix.md | 14 +++ test/sentry/live_view_hook_test.exs | 133 +++++++++++++++++++++++ 6 files changed, 309 insertions(+), 7 deletions(-) create mode 100644 lib/sentry/live_view_hook.ex create mode 100644 test/sentry/live_view_hook_test.exs diff --git a/.formatter.exs b/.formatter.exs index 66646b82..a640d13b 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,5 +1,5 @@ [ - import_deps: [:plug], + import_deps: [:plug, :phoenix, :phoenix_live_view], inputs: [ "lib/**/*.ex", "config/*.exs", diff --git a/lib/sentry/live_view_hook.ex b/lib/sentry/live_view_hook.ex new file mode 100644 index 00000000..31323e16 --- /dev/null +++ b/lib/sentry/live_view_hook.ex @@ -0,0 +1,151 @@ +if Code.ensure_loaded?(Phoenix.LiveView) do + defmodule Sentry.LiveViewHook do + @moduledoc """ + A module that provides a `Phoenix.LiveView` hook to add Sentry context and breadcrumbs. + + This module sets context and breadcrumbs for the live view process through + `Sentry.Context`. It sets things like: + + * The request URL + * The user agent and user's IP address + * Breadcrumbs for events that happen within LiveView + + To make this module work best, you'll need to fetch information from the LiveView's + WebSocket. You can do that when calling the `socket/3` macro in your Phoenix endpoint. + For example: + + socket "/live", Phoenix.LiveView.Socket, + websocket: [connect_info: [:peer_data, :uri, :user_agent]] + + ## Examples + + defmodule MyApp.UserLive do + use Phoenix.LiveView + + on_mount Sentry.LiveViewHook + + # ... + end + + You can do the same at the router level: + + live_session :default, on_mounbt: Sentry.LiveViewHook do + scope "..." do + # ... + end + end + + You can also set this in your `MyAppWeb` module, so that all LiveViews that + `use MyAppWeb, :live_view` will have this hook. + """ + + import Phoenix.LiveView, only: [attach_hook: 4, get_connect_info: 2] + + alias Sentry.Context + + require Logger + + # See also: + # https://develop.sentry.dev/sdk/event-payloads/request/ + + @doc false + @spec on_mount(:default, map(), map(), struct()) :: {:cont, struct()} + def on_mount(:default, params, _session, socket), do: on_mount(params, socket) + + ## Helpers + + defp on_mount(_options, params, %Phoenix.LiveView.Socket{} = socket) do + Context.set_extra_context(%{socket_id: socket.id}) + Context.set_request_context(%{url: socket.host_uri}) + + Context.add_breadcrumb(%{ + category: "web.live_view.mount", + message: "Mounted live view", + data: params + }) + + if uri = get_connect_info(socket, :uri) do + Context.set_request_context(%{url: URI.to_string(uri)}) + end + + if user_agent = get_connect_info(socket, :user_agent) do + Context.set_request_context(%{user_agent: user_agent}) + end + + # :peer_data returns t:Plug.Conn.Adapter.peer_data/0. + # https://hexdocs.pm/plug/Plug.Conn.Adapter.html#t:peer_data/0 + if ip_address = socket |> get_connect_info(:peer_data) |> get_safe_ip_address() do + Context.set_user_context(%{ip_address: ip_address}) + end + + socket + |> maybe_attach_hook_handle_params() + |> attach_hook(__MODULE__, :handle_event, &handle_event_hook/3) + |> attach_hook(__MODULE__, :handle_info, &handle_info_hook/2) + catch + # We must NEVER raise an error in a hook, as it will crash the LiveView process + # and we don't want Sentry to be responsible for that. + kind, reason -> + Logger.error( + "Sentry.LiveView.on_mount hook errored out: #{Exception.format(kind, reason)}", + event_source: :logger + ) + + {:cont, socket} + else + socket -> {:cont, socket} + end + + defp handle_event_hook(event, params, socket) do + Context.add_breadcrumb(%{ + category: "web.live_view.event", + message: inspect(event), + data: %{event: event, params: params} + }) + + {:cont, socket} + end + + defp handle_info_hook(message, socket) do + Context.add_breadcrumb(%{ + category: "web.live_view.info", + message: inspect(message, pretty: true) + }) + + {:cont, socket} + end + + defp handle_params_hook(params, uri, socket) do + Context.set_extra_context(%{socket_id: socket.id}) + Context.set_request_context(%{url: uri}) + + Context.add_breadcrumb(%{ + category: "web.live_view.params", + message: "#{uri}", + data: %{params: params, uri: uri} + }) + + {:cont, socket} + end + + defp maybe_attach_hook_handle_params(socket) do + case socket.parent_pid do + nil -> attach_hook(socket, __MODULE__, :handle_params, &handle_params_hook/3) + pid when is_pid(pid) -> socket + end + end + + defp get_safe_ip_address(%{ip_address: ip} = _peer_data) do + case :inet.ntoa(ip) do + ip_address when is_list(ip_address) -> List.to_string(ip_address) + {:error, _reason} -> nil + end + catch + _kind, _reason -> nil + end + + defp get_safe_ip_address(_peer_data) do + nil + end + end +end diff --git a/mix.exs b/mix.exs index 38f1050a..d573abfb 100644 --- a/mix.exs +++ b/mix.exs @@ -46,7 +46,7 @@ defmodule Sentry.Mixfile do "Upgrade Guides": [~r{^pages/upgrade}] ], groups_for_modules: [ - "Plug and Phoenix": [Sentry.PlugCapture, Sentry.PlugContext], + "Plug and Phoenix": [Sentry.PlugCapture, Sentry.PlugContext, Sentry.LiveViewHook], Loggers: [Sentry.LoggerBackend, Sentry.LoggerHandler], "Data Structures": [Sentry.Attachment, Sentry.CheckIn], HTTP: [Sentry.HTTPClient, Sentry.HackneyClient], @@ -99,11 +99,11 @@ defmodule Sentry.Mixfile do {:dialyxir, "~> 1.0", only: [:test, :dev], runtime: false}, {:ex_doc, "~> 0.29.0", only: :dev}, {:excoveralls, "~> 0.17.1", only: [:test]}, - {:phoenix, "~> 1.5", only: [:test]}, - {:phoenix_html, "~> 4.0", only: [:test]} + # Required by Phoenix.LiveView's testing + {:floki, ">= 0.30.0", only: :test} ] ++ maybe_oban_optional_dependency() ++ - maybe_quantum_optional_dependency() ++ maybe_phoenix_live_view_optional_dependency() + maybe_quantum_optional_dependency() ++ maybe_phoenix_optional_dependencies() end # TODO: Remove this once we drop support for Elixir < 1.13. @@ -125,9 +125,12 @@ defmodule Sentry.Mixfile do end # TODO: Remove this once we drop support for Elixir < 1.13. - defp maybe_phoenix_live_view_optional_dependency do + defp maybe_phoenix_optional_dependencies do if Version.match?(System.version(), "~> 1.13") do - [{:phoenix_live_view, "~> 0.20", only: [:test]}] + [ + {:phoenix, "~> 1.6", optional: true}, + {:phoenix_live_view, "~> 0.20", optional: true} + ] else [] end diff --git a/mix.lock b/mix.lock index 0da2f305..51bf0ba5 100644 --- a/mix.lock +++ b/mix.lock @@ -15,6 +15,7 @@ "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, "excoveralls": {:hex, :excoveralls, "0.17.1", "83fa7906ef23aa7fc8ad7ee469c357a63b1b3d55dd701ff5b9ce1f72442b2874", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "95bc6fda953e84c60f14da4a198880336205464e75383ec0f570180567985ae0"}, + "floki": {:hex, :floki, "0.36.1", "712b7f2ba19a4d5a47dfe3e74d81876c95bbcbee44fe551f0af3d2a388abb3da", [:mix], [], "hexpm", "21ba57abb8204bcc70c439b423fc0dd9f0286de67dc82773a14b0200ada0995f"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, diff --git a/pages/setup-with-plug-and-phoenix.md b/pages/setup-with-plug-and-phoenix.md index a485e526..00271923 100644 --- a/pages/setup-with-plug-and-phoenix.md +++ b/pages/setup-with-plug-and-phoenix.md @@ -24,6 +24,20 @@ If you are using Phoenix: + plug Sentry.PlugContext ``` +If you're also using [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view), consider also setting up your LiveViews to use the `Sentry.LiveViewHook` hook: + +```elixir +defmodule MyAppWeb do + def live_view do + quote do + use Phoenix.LiveView + + on_mount Sentry.LiveViewHook + end + end +end +``` + ### Capturing User Feedback If you would like to capture user feedback as described [here](https://docs.sentry.io/platforms/elixir/enriching-events/user-feedback/), the `Sentry.get_last_event_id_and_source/0` function can be used to see if Sentry has sent an event within the current Plug process (and get the source of that event). `:plug` will be the source for events coming from `Sentry.PlugCapture`. The options described in the Sentry documentation linked above can be encoded into the response as well. diff --git a/test/sentry/live_view_hook_test.exs b/test/sentry/live_view_hook_test.exs new file mode 100644 index 00000000..949c17fa --- /dev/null +++ b/test/sentry/live_view_hook_test.exs @@ -0,0 +1,133 @@ +defmodule SentryTest.ErrorView do + def render(template, assigns) do + dbg() + "OK" + end +end + +defmodule SentryTest.Live do + use Phoenix.LiveView + + on_mount Sentry.LiveViewHook + + def render(assigns) do + ~H""" +

Testing Sentry hooks

+ """ + end + + def mount(_params, _session, socket) do + {:ok, socket} + end + + def handle_event("refresh", _params, socket) do + {:noreply, socket} + end + + def handle_info(:test_message, socket) do + {:noreply, socket} + end +end + +defmodule SentryTest.Router do + use Phoenix.Router + import Phoenix.LiveView.Router + + scope "/" do + live "/hook_test", SentryTest.Live + end +end + +defmodule SentryTest.Endpoint do + use Phoenix.Endpoint, otp_app: :sentry + + socket "/live", Phoenix.LiveView.Socket, + websocket: [connect_info: [:peer_data, :uri, :user_agent]] + + plug SentryTest.Router +end + +defmodule Sentry.LiveViewHookTest do + use Sentry.Case, async: true + + import Phoenix.ConnTest + import Phoenix.LiveViewTest + + @endpoint SentryTest.Endpoint + + setup_all do + Application.put_env(:sentry, SentryTest.Endpoint, + secret_key_base: "TMnue44VMTf1VmyD6SYKR30cqKpHluHOFZGXcVkC33hKVVKTVZ1HBQLLLLLLLLLL", + live_view: [signing_salt: "F8ftIAbYdeTzhwgl"] + ) + + pid = start_supervised!(SentryTest.Endpoint) + Process.link(pid) + :ok + end + + setup do + %{conn: build_conn()} + end + + test "attaches the right context", %{conn: conn} do + conn = + conn + |> Plug.Conn.put_req_header("user-agent", "sentry-testing 1.0") + + {:ok, view, html} = live(conn, "/hook_test") + assert html =~ "

Testing Sentry hooks

" + + context1 = get_sentry_context(view) + + assert "phx-" <> _ = context1.extra.socket_id + assert context1.request.url == "http://www.example.com/hook_test" + assert context1.request.user_agent == "sentry-testing 1.0" + + assert [ + %{category: "web.live_view.params"} = params_breadcrumb, + %{category: "web.live_view.mount"} = mount_breadcrumb + ] = context1.breadcrumbs + + assert mount_breadcrumb.message == "Mounted live view" + assert mount_breadcrumb.data == %{} + + assert params_breadcrumb.message == "http://www.example.com/hook_test" + assert params_breadcrumb.data == %{params: %{}, uri: "http://www.example.com/hook_test"} + + # Send an event and test the new breadcrumb. + + assert render_hook(view, :refresh, %{force: true}) =~ "Testing Sentry hooks" + + context2 = get_sentry_context(view) + assert Map.take(context1, [:extra, :request]) == Map.take(context2, [:extra, :request]) + assert [event_breadcrumb, ^params_breadcrumb, ^mount_breadcrumb] = context2.breadcrumbs + assert event_breadcrumb.category == "web.live_view.event" + assert event_breadcrumb.message == ~s("refresh") + assert event_breadcrumb.data == %{params: %{"force" => true}, event: "refresh"} + + # Send a message and test the new breadcrumb. + send(view.pid, :test_message) + assert render(view) =~ "Testing Sentry hooks" + + context3 = get_sentry_context(view) + assert Map.take(context1, [:extra, :request]) == Map.take(context3, [:extra, :request]) + + assert [info_breadcrumb, ^event_breadcrumb, ^params_breadcrumb, ^mount_breadcrumb] = + context3.breadcrumbs + + assert info_breadcrumb.category == "web.live_view.info" + assert info_breadcrumb.message == ~s(:test_message) + end + + defp get_sentry_context(view) do + {:dictionary, pdict} = Process.info(view.pid, :dictionary) + + assert {:ok, sentry_context} = + pdict + |> Keyword.fetch!(:"$logger_metadata$") + |> Map.fetch(Sentry.Context.__logger_metadata_key__()) + + sentry_context + end +end From fda8e9c881c8049e857879872b40cff35e975dc0 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sat, 20 Apr 2024 10:07:45 +0200 Subject: [PATCH 03/10] Docs since --- lib/sentry/live_view_hook.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/sentry/live_view_hook.ex b/lib/sentry/live_view_hook.ex index 31323e16..59c7d6a5 100644 --- a/lib/sentry/live_view_hook.ex +++ b/lib/sentry/live_view_hook.ex @@ -3,6 +3,8 @@ if Code.ensure_loaded?(Phoenix.LiveView) do @moduledoc """ A module that provides a `Phoenix.LiveView` hook to add Sentry context and breadcrumbs. + *Available since v10.5.0.* + This module sets context and breadcrumbs for the live view process through `Sentry.Context`. It sets things like: @@ -39,6 +41,8 @@ if Code.ensure_loaded?(Phoenix.LiveView) do `use MyAppWeb, :live_view` will have this hook. """ + @moduledoc since: "10.5.0" + import Phoenix.LiveView, only: [attach_hook: 4, get_connect_info: 2] alias Sentry.Context From ddc8ccb01a0ff86660a5a45d89e76ce30d2a6f14 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sat, 20 Apr 2024 10:10:26 +0200 Subject: [PATCH 04/10] FIXUP --- test/sentry/live_view_hook_test.exs | 191 ++++++++++++++-------------- 1 file changed, 97 insertions(+), 94 deletions(-) diff --git a/test/sentry/live_view_hook_test.exs b/test/sentry/live_view_hook_test.exs index 949c17fa..73f7263f 100644 --- a/test/sentry/live_view_hook_test.exs +++ b/test/sentry/live_view_hook_test.exs @@ -1,133 +1,136 @@ -defmodule SentryTest.ErrorView do - def render(template, assigns) do - dbg() - "OK" +# TODO: Oban requires Elixir 1.13+, remove this once we depend on that too. +if Version.match?(System.version(), "~> 1.13") do + defmodule SentryTest.ErrorView do + def render(template, assigns) do + dbg() + "OK" + end end -end -defmodule SentryTest.Live do - use Phoenix.LiveView + defmodule SentryTest.Live do + use Phoenix.LiveView - on_mount Sentry.LiveViewHook + on_mount Sentry.LiveViewHook - def render(assigns) do - ~H""" -

Testing Sentry hooks

- """ - end + def render(assigns) do + ~H""" +

Testing Sentry hooks

+ """ + end - def mount(_params, _session, socket) do - {:ok, socket} - end + def mount(_params, _session, socket) do + {:ok, socket} + end - def handle_event("refresh", _params, socket) do - {:noreply, socket} - end + def handle_event("refresh", _params, socket) do + {:noreply, socket} + end - def handle_info(:test_message, socket) do - {:noreply, socket} + def handle_info(:test_message, socket) do + {:noreply, socket} + end end -end -defmodule SentryTest.Router do - use Phoenix.Router - import Phoenix.LiveView.Router + defmodule SentryTest.Router do + use Phoenix.Router + import Phoenix.LiveView.Router - scope "/" do - live "/hook_test", SentryTest.Live + scope "/" do + live "/hook_test", SentryTest.Live + end end -end -defmodule SentryTest.Endpoint do - use Phoenix.Endpoint, otp_app: :sentry + defmodule SentryTest.Endpoint do + use Phoenix.Endpoint, otp_app: :sentry - socket "/live", Phoenix.LiveView.Socket, - websocket: [connect_info: [:peer_data, :uri, :user_agent]] + socket "/live", Phoenix.LiveView.Socket, + websocket: [connect_info: [:peer_data, :uri, :user_agent]] - plug SentryTest.Router -end + plug SentryTest.Router + end -defmodule Sentry.LiveViewHookTest do - use Sentry.Case, async: true + defmodule Sentry.LiveViewHookTest do + use Sentry.Case, async: true - import Phoenix.ConnTest - import Phoenix.LiveViewTest + import Phoenix.ConnTest + import Phoenix.LiveViewTest - @endpoint SentryTest.Endpoint + @endpoint SentryTest.Endpoint - setup_all do - Application.put_env(:sentry, SentryTest.Endpoint, - secret_key_base: "TMnue44VMTf1VmyD6SYKR30cqKpHluHOFZGXcVkC33hKVVKTVZ1HBQLLLLLLLLLL", - live_view: [signing_salt: "F8ftIAbYdeTzhwgl"] - ) + setup_all do + Application.put_env(:sentry, SentryTest.Endpoint, + secret_key_base: "TMnue44VMTf1VmyD6SYKR30cqKpHluHOFZGXcVkC33hKVVKTVZ1HBQLLLLLLLLLL", + live_view: [signing_salt: "F8ftIAbYdeTzhwgl"] + ) - pid = start_supervised!(SentryTest.Endpoint) - Process.link(pid) - :ok - end + pid = start_supervised!(SentryTest.Endpoint) + Process.link(pid) + :ok + end - setup do - %{conn: build_conn()} - end + setup do + %{conn: build_conn()} + end - test "attaches the right context", %{conn: conn} do - conn = - conn - |> Plug.Conn.put_req_header("user-agent", "sentry-testing 1.0") + test "attaches the right context", %{conn: conn} do + conn = + conn + |> Plug.Conn.put_req_header("user-agent", "sentry-testing 1.0") - {:ok, view, html} = live(conn, "/hook_test") - assert html =~ "

Testing Sentry hooks

" + {:ok, view, html} = live(conn, "/hook_test") + assert html =~ "

Testing Sentry hooks

" - context1 = get_sentry_context(view) + context1 = get_sentry_context(view) - assert "phx-" <> _ = context1.extra.socket_id - assert context1.request.url == "http://www.example.com/hook_test" - assert context1.request.user_agent == "sentry-testing 1.0" + assert "phx-" <> _ = context1.extra.socket_id + assert context1.request.url == "http://www.example.com/hook_test" + assert context1.request.user_agent == "sentry-testing 1.0" - assert [ - %{category: "web.live_view.params"} = params_breadcrumb, - %{category: "web.live_view.mount"} = mount_breadcrumb - ] = context1.breadcrumbs + assert [ + %{category: "web.live_view.params"} = params_breadcrumb, + %{category: "web.live_view.mount"} = mount_breadcrumb + ] = context1.breadcrumbs - assert mount_breadcrumb.message == "Mounted live view" - assert mount_breadcrumb.data == %{} + assert mount_breadcrumb.message == "Mounted live view" + assert mount_breadcrumb.data == %{} - assert params_breadcrumb.message == "http://www.example.com/hook_test" - assert params_breadcrumb.data == %{params: %{}, uri: "http://www.example.com/hook_test"} + assert params_breadcrumb.message == "http://www.example.com/hook_test" + assert params_breadcrumb.data == %{params: %{}, uri: "http://www.example.com/hook_test"} - # Send an event and test the new breadcrumb. + # Send an event and test the new breadcrumb. - assert render_hook(view, :refresh, %{force: true}) =~ "Testing Sentry hooks" + assert render_hook(view, :refresh, %{force: true}) =~ "Testing Sentry hooks" - context2 = get_sentry_context(view) - assert Map.take(context1, [:extra, :request]) == Map.take(context2, [:extra, :request]) - assert [event_breadcrumb, ^params_breadcrumb, ^mount_breadcrumb] = context2.breadcrumbs - assert event_breadcrumb.category == "web.live_view.event" - assert event_breadcrumb.message == ~s("refresh") - assert event_breadcrumb.data == %{params: %{"force" => true}, event: "refresh"} + context2 = get_sentry_context(view) + assert Map.take(context1, [:extra, :request]) == Map.take(context2, [:extra, :request]) + assert [event_breadcrumb, ^params_breadcrumb, ^mount_breadcrumb] = context2.breadcrumbs + assert event_breadcrumb.category == "web.live_view.event" + assert event_breadcrumb.message == ~s("refresh") + assert event_breadcrumb.data == %{params: %{"force" => true}, event: "refresh"} - # Send a message and test the new breadcrumb. - send(view.pid, :test_message) - assert render(view) =~ "Testing Sentry hooks" + # Send a message and test the new breadcrumb. + send(view.pid, :test_message) + assert render(view) =~ "Testing Sentry hooks" - context3 = get_sentry_context(view) - assert Map.take(context1, [:extra, :request]) == Map.take(context3, [:extra, :request]) + context3 = get_sentry_context(view) + assert Map.take(context1, [:extra, :request]) == Map.take(context3, [:extra, :request]) - assert [info_breadcrumb, ^event_breadcrumb, ^params_breadcrumb, ^mount_breadcrumb] = - context3.breadcrumbs + assert [info_breadcrumb, ^event_breadcrumb, ^params_breadcrumb, ^mount_breadcrumb] = + context3.breadcrumbs - assert info_breadcrumb.category == "web.live_view.info" - assert info_breadcrumb.message == ~s(:test_message) - end + assert info_breadcrumb.category == "web.live_view.info" + assert info_breadcrumb.message == ~s(:test_message) + end - defp get_sentry_context(view) do - {:dictionary, pdict} = Process.info(view.pid, :dictionary) + defp get_sentry_context(view) do + {:dictionary, pdict} = Process.info(view.pid, :dictionary) - assert {:ok, sentry_context} = - pdict - |> Keyword.fetch!(:"$logger_metadata$") - |> Map.fetch(Sentry.Context.__logger_metadata_key__()) + assert {:ok, sentry_context} = + pdict + |> Keyword.fetch!(:"$logger_metadata$") + |> Map.fetch(Sentry.Context.__logger_metadata_key__()) - sentry_context + sentry_context + end end end From 83b318653e09640fad26873a0e8c0a05eba363c5 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sat, 20 Apr 2024 10:16:06 +0200 Subject: [PATCH 05/10] More fixes --- lib/sentry/live_view_hook.ex | 2 +- test/plug_capture_test.exs | 5 ++++ test/support/test_error_view.ex | 41 ++++++++++++++++++--------------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/lib/sentry/live_view_hook.ex b/lib/sentry/live_view_hook.ex index 59c7d6a5..8f78b300 100644 --- a/lib/sentry/live_view_hook.ex +++ b/lib/sentry/live_view_hook.ex @@ -58,7 +58,7 @@ if Code.ensure_loaded?(Phoenix.LiveView) do ## Helpers - defp on_mount(_options, params, %Phoenix.LiveView.Socket{} = socket) do + defp on_mount(params, %Phoenix.LiveView.Socket{} = socket) do Context.set_extra_context(%{socket_id: socket.id}) Context.set_request_context(%{url: socket.host_uri}) diff --git a/test/plug_capture_test.exs b/test/plug_capture_test.exs index 218d088f..f9ccb210 100644 --- a/test/plug_capture_test.exs +++ b/test/plug_capture_test.exs @@ -125,6 +125,11 @@ defmodule Sentry.PlugCaptureTest do end end + # TODO: Oban requires Elixir 1.13+, remove this once we depend on that too. + if not Version.match?(System.version(), "~> 1.13") do + @tag :skip + end + describe "with a Phoenix endpoint" do @describetag :capture_log diff --git a/test/support/test_error_view.ex b/test/support/test_error_view.ex index 29dcd7e4..21fe8eb4 100644 --- a/test/support/test_error_view.ex +++ b/test/support/test_error_view.ex @@ -1,27 +1,30 @@ -defmodule Sentry.ErrorView do - use Phoenix.Component +# TODO: Oban requires Elixir 1.13+, remove this once we depend on that too. +if Version.match?(System.version(), "~> 1.13") do + defmodule Sentry.ErrorView do + use Phoenix.Component - import Phoenix.HTML, only: [raw: 1] + import Phoenix.HTML, only: [raw: 1] - def render(_, _) do - case Sentry.get_last_event_id_and_source() do - {event_id, :plug} -> - opts = - %{title: "Testing", eventId: event_id} - |> Jason.encode!() + def render(_, _) do + case Sentry.get_last_event_id_and_source() do + {event_id, :plug} -> + opts = + %{title: "Testing", eventId: event_id} + |> Jason.encode!() - assigns = %{opts: opts} + assigns = %{opts: opts} - ~H""" - - - """ + ~H""" + + + """ - _ -> - "error" + _ -> + "error" + end end end end From 1454c0981f6120997cc9ff9f2d42c4c4b3a6b853 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sat, 20 Apr 2024 10:24:22 +0200 Subject: [PATCH 06/10] FIXUP --- test/plug_capture_test.exs | 458 ++++++++++++++-------------- test/sentry/live_view_hook_test.exs | 2 +- 2 files changed, 231 insertions(+), 229 deletions(-) diff --git a/test/plug_capture_test.exs b/test/plug_capture_test.exs index f9ccb210..bf1fc4de 100644 --- a/test/plug_capture_test.exs +++ b/test/plug_capture_test.exs @@ -1,298 +1,300 @@ -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 +# TODO: LV requires Elixir 1.13+, remove this once we depend on that too. +if Version.match?(System.version(), "~> 1.13") do + 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 - plug Plug.Parsers, parsers: [:json], pass: ["*/*"], json_decoder: Jason - plug Sentry.PlugContext - plug PhoenixRouter - end + defmodule PhoenixRouter do + use Phoenix.Router - defmodule Scrubber do - def scrub_conn(conn) do - conn + 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 - end - defmodule PhoenixEndpointWithScrubber do - use Sentry.PlugCapture, scrubber: {Scrubber, :scrub_conn, []} - use Phoenix.Endpoint, otp_app: :sentry - use Plug.Debugger, otp_app: :sentry + defmodule PhoenixEndpoint do + use Sentry.PlugCapture + use Phoenix.Endpoint, otp_app: :sentry + use Plug.Debugger, otp_app: :sentry - plug Plug.Parsers, parsers: [:json], pass: ["*/*"], json_decoder: Jason - 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) + plug Plug.Parsers, parsers: [:json], pass: ["*/*"], json_decoder: Jason + plug Sentry.PlugContext + plug PhoenixRouter + end - event = decode_event_from_envelope!(body) + defmodule Scrubber do + def scrub_conn(conn) do + conn + end + end - 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) + defmodule PhoenixEndpointWithScrubber do + use Sentry.PlugCapture, scrubber: {Scrubber, :scrub_conn, []} + use Phoenix.Endpoint, otp_app: :sentry + use Plug.Debugger, otp_app: :sentry - assert_raise(Plug.Conn.WrapperError, "** (RuntimeError) Error", fn -> - conn(:get, "/error_route") - |> call_plug_app() - end) + plug Plug.Parsers, parsers: [:json], pass: ["*/*"], json_decoder: Jason + plug Sentry.PlugContext + plug PhoenixRouter 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()) + setup do + bypass = Bypass.open() + put_test_config(dsn: "http://public:secret@localhost:#{bypass.port}/1") + %{bypass: bypass} 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) + 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 - catch_exit(conn(:get, "/exit_route") |> call_plug_app()) - 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) - 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() + catch_throw(conn(:get, "/throw_route") |> 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) + 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) - conn = conn(:get, "/error_route") + catch_exit(conn(:get, "/exit_route") |> call_plug_app()) + end - assert_raise Plug.Conn.WrapperError, "** (RuntimeError) Error", fn -> - call_plug_app(conn) + 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 - 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"} + 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 - end - # TODO: Oban requires Elixir 1.13+, remove this once we depend on that too. - if not Version.match?(System.version(), "~> 1.13") do - @tag :skip - end - - describe "with a Phoenix endpoint" do - @describetag :capture_log + describe "with a Phoenix endpoint" do + @describetag :capture_log - setup do - Application.put_env(:sentry, PhoenixEndpoint, - render_errors: [view: Sentry.ErrorView, accepts: ~w(html)] - ) + setup do + Application.put_env(:sentry, PhoenixEndpoint, + render_errors: [view: Sentry.ErrorView, accepts: ~w(html)] + ) - pid = start_supervised!(PhoenixEndpoint) - Process.link(pid) + pid = start_supervised!(PhoenixEndpoint) + Process.link(pid) - :ok - end + :ok + end - test "reports raised exceptions", %{bypass: bypass} do - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) + 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) + 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) + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end) - assert_raise RuntimeError, "PhoenixError", fn -> - conn(:get, "/error_route") - |> call_phoenix_endpoint() + assert_raise RuntimeError, "PhoenixError", fn -> + conn(:get, "/error_route") + |> call_phoenix_endpoint() + end end - end - test "reports exits", %{bypass: bypass} do - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) + 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) + 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) + 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 + 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) + 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) + 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) + 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 + 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 "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() + 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) + 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_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_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 + assert event["culprit"] == + "Sentry.PlugCaptureTest.PhoenixController.action_clause_error/2" - test "can render feedback form in Phoenix ErrorView", %{bypass: bypass} do - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) + assert [exception] = event["exception"] + assert exception["type"] == "Phoenix.ActionClauseError" + assert exception["value"] =~ ~s(params: %{"password" => "*********"}) + end - _event = decode_event_from_envelope!(body) + test "can render feedback form in Phoenix ErrorView", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) - Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) - end) + _event = decode_event_from_envelope!(body) - conn = conn(:get, "/error_route") + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end) - assert_raise RuntimeError, "PhoenixError", fn -> call_phoenix_endpoint(conn) end + conn = conn(:get, "/error_route") - {event_id, _} = Sentry.get_last_event_id_and_source() + assert_raise RuntimeError, "PhoenixError", fn -> call_phoenix_endpoint(conn) end - 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 + {event_id, _} = Sentry.get_last_event_id_and_source() - 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() + 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 - end - test "modifies conn with custom scrubber", %{bypass: bypass} do - Application.put_env(:sentry, PhoenixEndpointWithScrubber, - render_errors: [view: Sentry.ErrorView, accepts: ~w(html)] - ) + 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) + pid = start_supervised!(PhoenixEndpointWithScrubber) + Process.link(pid) - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) + Bypass.expect(bypass, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) - event = decode_event_from_envelope!(body) + 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) + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end) - assert_raise RuntimeError, "PhoenixError", fn -> - conn(:get, "/error_route") - |> Plug.run([{PhoenixEndpointWithScrubber, []}]) + assert_raise RuntimeError, "PhoenixError", fn -> + conn(:get, "/error_route") + |> Plug.run([{PhoenixEndpointWithScrubber, []}]) + end end end - end - defp call_plug_app(conn), do: Plug.run(conn, [{Sentry.ExamplePlugApplication, []}]) + defp call_plug_app(conn), do: Plug.run(conn, [{Sentry.ExamplePlugApplication, []}]) - defp call_phoenix_endpoint(conn), do: Plug.run(conn, [{PhoenixEndpoint, []}]) + 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 + defp decode_event_from_envelope!(envelope) do + assert [{%{"type" => "event"}, event}] = decode_envelope!(envelope) + event + end end end diff --git a/test/sentry/live_view_hook_test.exs b/test/sentry/live_view_hook_test.exs index 73f7263f..8f090fb9 100644 --- a/test/sentry/live_view_hook_test.exs +++ b/test/sentry/live_view_hook_test.exs @@ -1,4 +1,4 @@ -# TODO: Oban requires Elixir 1.13+, remove this once we depend on that too. +# TODO: LV requires Elixir 1.13+, remove this once we depend on that too. if Version.match?(System.version(), "~> 1.13") do defmodule SentryTest.ErrorView do def render(template, assigns) do From 3413adf9b9dd2a6556a588eac84314733fe77879 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sat, 20 Apr 2024 10:31:43 +0200 Subject: [PATCH 07/10] More --- .github/workflows/main.yml | 4 +- mix.exs | 40 +- test/plug_capture_test.exs | 455 +++++++++--------- test/sentry/integrations/oban/cron_test.exs | 271 ++++++----- .../integrations/oban/error_reporter_test.exs | 95 ++-- .../sentry/integrations/quantum/cron_test.exs | 299 ++++++------ test/sentry/live_view_hook_test.exs | 191 ++++---- test/support/test_error_view.ex | 41 +- 8 files changed, 675 insertions(+), 721 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c884b974..443a4bfe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,8 +31,8 @@ jobs: otp: '25.3' # Oldest supported Elixir/Erlang pair. - - elixir: '1.11.4' - otp: '21.3' + - elixir: '1.13.4-otp-22' + otp: '22.3.4' steps: - name: Check out this repository diff --git a/mix.exs b/mix.exs index d573abfb..8c616294 100644 --- a/mix.exs +++ b/mix.exs @@ -91,6 +91,8 @@ defmodule Sentry.Mixfile do # Optional dependencies {:hackney, "~> 1.8", optional: true}, {:jason, "~> 1.1", optional: true}, + {:phoenix, "~> 1.6", optional: true}, + {:phoenix_live_view, "~> 0.20", optional: true}, {:plug, "~> 1.6", optional: true}, {:telemetry, "~> 0.4 or ~> 1.0", optional: true}, @@ -100,40 +102,10 @@ defmodule Sentry.Mixfile do {:ex_doc, "~> 0.29.0", only: :dev}, {:excoveralls, "~> 0.17.1", only: [:test]}, # Required by Phoenix.LiveView's testing - {:floki, ">= 0.30.0", only: :test} - ] ++ - maybe_oban_optional_dependency() ++ - maybe_quantum_optional_dependency() ++ maybe_phoenix_optional_dependencies() - end - - # TODO: Remove this once we drop support for Elixir < 1.13. - defp maybe_oban_optional_dependency do - if Version.match?(System.version(), "~> 1.13") do - [{:oban, "~> 2.17 and >= 2.17.6", only: [:test]}] - else - [] - end - end - - # TODO: Remove this once we drop support for Elixir < 1.12. - defp maybe_quantum_optional_dependency do - if Version.match?(System.version(), "~> 1.12") do - [{:quantum, "~> 3.0", only: [:test]}] - else - [] - end - end - - # TODO: Remove this once we drop support for Elixir < 1.13. - defp maybe_phoenix_optional_dependencies do - if Version.match?(System.version(), "~> 1.13") do - [ - {:phoenix, "~> 1.6", optional: true}, - {:phoenix_live_view, "~> 0.20", optional: true} - ] - else - [] - end + {:floki, ">= 0.30.0", only: :test}, + {:oban, "~> 2.17 and >= 2.17.6", only: [:test]}, + {:quantum, "~> 3.0", only: [:test]} + ] end defp package do diff --git a/test/plug_capture_test.exs b/test/plug_capture_test.exs index bf1fc4de..226fc2cf 100644 --- a/test/plug_capture_test.exs +++ b/test/plug_capture_test.exs @@ -1,300 +1,297 @@ -# TODO: LV requires Elixir 1.13+, remove this once we depend on that too. -if Version.match?(System.version(), "~> 1.13") do - 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 Sentry.PlugCaptureTest do + use Sentry.Case + use Plug.Test - defmodule PhoenixRouter do - use Phoenix.Router + import Sentry.TestHelpers - 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 PhoenixController do + use Phoenix.Controller - defmodule PhoenixEndpoint do - use Sentry.PlugCapture - use Phoenix.Endpoint, otp_app: :sentry - use Plug.Debugger, otp_app: :sentry + 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 - plug Plug.Parsers, parsers: [:json], pass: ["*/*"], json_decoder: Jason - plug Sentry.PlugContext - plug PhoenixRouter - end + defmodule PhoenixRouter do + use Phoenix.Router - defmodule Scrubber do - def scrub_conn(conn) do - conn - end + 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 + + plug Plug.Parsers, parsers: [:json], pass: ["*/*"], json_decoder: Jason + 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 - defmodule PhoenixEndpointWithScrubber do - use Sentry.PlugCapture, scrubber: {Scrubber, :scrub_conn, []} - use Phoenix.Endpoint, otp_app: :sentry - use Plug.Debugger, otp_app: :sentry + plug Plug.Parsers, parsers: [:json], pass: ["*/*"], json_decoder: Jason + 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 - plug Plug.Parsers, parsers: [:json], pass: ["*/*"], json_decoder: Jason - plug Sentry.PlugContext - plug PhoenixRouter + 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 - setup do - bypass = Bypass.open() - put_test_config(dsn: "http://public:secret@localhost:#{bypass.port}/1") - %{bypass: bypass} + 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 - 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 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) - 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_exit(conn(:get, "/exit_route") |> call_plug_app()) + end - catch_throw(conn(:get, "/throw_route") |> call_plug_app()) + 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 "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) + 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) - catch_exit(conn(:get, "/exit_route") |> call_plug_app()) - end + conn = conn(:get, "/error_route") - 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 + assert_raise Plug.Conn.WrapperError, "** (RuntimeError) Error", fn -> + call_plug_app(conn) 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 + 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 + describe "with a Phoenix endpoint" do + @describetag :capture_log - setup do - Application.put_env(:sentry, PhoenixEndpoint, - render_errors: [view: Sentry.ErrorView, accepts: ~w(html)] - ) + setup do + Application.put_env(:sentry, PhoenixEndpoint, + render_errors: [view: Sentry.ErrorView, accepts: ~w(html)] + ) - pid = start_supervised!(PhoenixEndpoint) - Process.link(pid) + pid = start_supervised!(PhoenixEndpoint) + Process.link(pid) - :ok - end + :ok + end - test "reports raised exceptions", %{bypass: bypass} do - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) + 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) + 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) + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end) - assert_raise RuntimeError, "PhoenixError", fn -> - conn(:get, "/error_route") - |> call_phoenix_endpoint() - 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) + 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) + 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) + 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 + 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) + 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) + 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) + 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 + 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 "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() + 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) + 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_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_receive {^ref, sentry_body} + event = decode_event_from_envelope!(sentry_body) - assert event["culprit"] == - "Sentry.PlugCaptureTest.PhoenixController.action_clause_error/2" + 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 + 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) + 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) + _event = decode_event_from_envelope!(body) - Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) - end) + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end) - conn = conn(:get, "/error_route") + conn = conn(:get, "/error_route") - assert_raise RuntimeError, "PhoenixError", fn -> call_phoenix_endpoint(conn) end + assert_raise RuntimeError, "PhoenixError", fn -> call_phoenix_endpoint(conn) end - {event_id, _} = Sentry.get_last_event_id_and_source() + {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 + 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 + 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)] - ) + 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) + pid = start_supervised!(PhoenixEndpointWithScrubber) + Process.link(pid) - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) + Bypass.expect(bypass, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) - event = decode_event_from_envelope!(body) + 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) + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end) - assert_raise RuntimeError, "PhoenixError", fn -> - conn(:get, "/error_route") - |> Plug.run([{PhoenixEndpointWithScrubber, []}]) - 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_plug_app(conn), do: Plug.run(conn, [{Sentry.ExamplePlugApplication, []}]) - defp call_phoenix_endpoint(conn), do: Plug.run(conn, [{PhoenixEndpoint, []}]) + 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 + defp decode_event_from_envelope!(envelope) do + assert [{%{"type" => "event"}, event}] = decode_envelope!(envelope) + event end end diff --git a/test/sentry/integrations/oban/cron_test.exs b/test/sentry/integrations/oban/cron_test.exs index 64a0c636..67f91345 100644 --- a/test/sentry/integrations/oban/cron_test.exs +++ b/test/sentry/integrations/oban/cron_test.exs @@ -1,158 +1,112 @@ -# TODO: Oban requires Elixir 1.13+, remove this once we depend on that too. -if Version.match?(System.version(), "~> 1.13") do - defmodule Sentry.Integrations.Oban.CronTest do - use Sentry.Case, async: false +defmodule Sentry.Integrations.Oban.CronTest do + use Sentry.Case, async: false - import Sentry.TestHelpers + import Sentry.TestHelpers - setup_all do - Sentry.Integrations.Oban.Cron.attach_telemetry_handler() - end + setup_all do + Sentry.Integrations.Oban.Cron.attach_telemetry_handler() + end - setup do - bypass = Bypass.open() + setup do + bypass = Bypass.open() - put_test_config( - dsn: "http://public:secret@localhost:#{bypass.port}/1", - dedup_events: false, - environment_name: "test" - ) + put_test_config( + dsn: "http://public:secret@localhost:#{bypass.port}/1", + dedup_events: false, + environment_name: "test" + ) - %{bypass: bypass} - end + %{bypass: bypass} + end - for event_type <- [:start, :stop, :exception] do - test "ignores #{event_type} events without a cron meta", %{bypass: bypass} do - Bypass.down(bypass) - :telemetry.execute([:oban, :job, unquote(event_type)], %{}, %{job: %Oban.Job{}}) - end + for event_type <- [:start, :stop, :exception] do + test "ignores #{event_type} events without a cron meta", %{bypass: bypass} do + Bypass.down(bypass) + :telemetry.execute([:oban, :job, unquote(event_type)], %{}, %{job: %Oban.Job{}}) + end - test "ignores #{event_type} events without a cron_expr meta", %{bypass: bypass} do - Bypass.down(bypass) + test "ignores #{event_type} events without a cron_expr meta", %{bypass: bypass} do + Bypass.down(bypass) - :telemetry.execute([:oban, :job, unquote(event_type)], %{}, %{ - job: %Oban.Job{meta: %{"cron" => true}} - }) - end + :telemetry.execute([:oban, :job, unquote(event_type)], %{}, %{ + job: %Oban.Job{meta: %{"cron" => true}} + }) + end - test "ignores #{event_type} events with a cron expr of @reboot", %{bypass: bypass} do - Bypass.down(bypass) + test "ignores #{event_type} events with a cron expr of @reboot", %{bypass: bypass} do + Bypass.down(bypass) - :telemetry.execute([:oban, :job, unquote(event_type)], %{}, %{ - job: %Oban.Job{meta: %{"cron" => true, "cron_expr" => "@reboot"}} - }) - end + :telemetry.execute([:oban, :job, unquote(event_type)], %{}, %{ + job: %Oban.Job{meta: %{"cron" => true, "cron_expr" => "@reboot"}} + }) + end - test "ignores #{event_type} events with a cron expr that is not a string", %{bypass: bypass} do - Bypass.down(bypass) + test "ignores #{event_type} events with a cron expr that is not a string", %{bypass: bypass} do + Bypass.down(bypass) - :telemetry.execute([:oban, :job, unquote(event_type)], %{}, %{ - job: %Oban.Job{meta: %{"cron" => true, "cron_expr" => 123}} - }) - end + :telemetry.execute([:oban, :job, unquote(event_type)], %{}, %{ + job: %Oban.Job{meta: %{"cron" => true, "cron_expr" => 123}} + }) end + end - test "captures start events with monitor config", %{bypass: bypass} do - test_pid = self() - ref = make_ref() + test "captures start events with monitor config", %{bypass: bypass} do + test_pid = self() + ref = make_ref() - Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - assert [{headers, check_in_body}] = decode_envelope!(body) + Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + assert [{headers, check_in_body}] = decode_envelope!(body) - assert headers["type"] == "check_in" + assert headers["type"] == "check_in" - assert check_in_body["check_in_id"] == "oban-123" - assert check_in_body["status"] == "in_progress" - assert check_in_body["monitor_slug"] == "sentry-my-worker" - assert check_in_body["duration"] == nil - assert check_in_body["environment"] == "test" + assert check_in_body["check_in_id"] == "oban-123" + assert check_in_body["status"] == "in_progress" + assert check_in_body["monitor_slug"] == "sentry-my-worker" + assert check_in_body["duration"] == nil + assert check_in_body["environment"] == "test" - assert check_in_body["monitor_config"] == %{ - "schedule" => %{ - "type" => "interval", - "value" => 1, - "unit" => "day" - } + assert check_in_body["monitor_config"] == %{ + "schedule" => %{ + "type" => "interval", + "value" => 1, + "unit" => "day" } + } - send(test_pid, {ref, :done}) - - Plug.Conn.send_resp(conn, 200, ~s<{"id": "1923"}>) - end) - - :telemetry.execute([:oban, :job, :start], %{}, %{ - job: %Oban.Job{ - worker: "Sentry.MyWorker", - id: 123, - meta: %{"cron" => true, "cron_expr" => "@daily"} - } - }) - - assert_receive {^ref, :done}, 1000 - end - - for {oban_state, expected_status} <- [ - success: "ok", - failure: "error", - cancelled: "ok", - discard: "ok", - snoozed: "ok" - ], - {frequency, expected_unit} <- [ - {"@hourly", "hour"}, - {"@daily", "day"}, - {"@weekly", "week"}, - {"@monthly", "month"}, - {"@yearly", "year"}, - {"@annually", "year"} - ] do - test "captures stop events with monitor config and state of #{inspect(oban_state)} and frequency of #{frequency}", - %{bypass: bypass} do - test_pid = self() - ref = make_ref() - - Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - assert [{headers, check_in_body}] = decode_envelope!(body) - - assert headers["type"] == "check_in" - - assert check_in_body["check_in_id"] == "oban-942" - assert check_in_body["status"] == unquote(expected_status) - assert check_in_body["monitor_slug"] == "sentry-my-worker" - assert check_in_body["duration"] == 12.099 - assert check_in_body["environment"] == "test" - - assert check_in_body["monitor_config"] == %{ - "schedule" => %{ - "type" => "interval", - "value" => 1, - "unit" => unquote(expected_unit) - } - } - - send(test_pid, {ref, :done}) + send(test_pid, {ref, :done}) - Plug.Conn.send_resp(conn, 200, ~s<{"id": "1923"}>) - end) + Plug.Conn.send_resp(conn, 200, ~s<{"id": "1923"}>) + end) - duration = System.convert_time_unit(12_099, :millisecond, :native) + :telemetry.execute([:oban, :job, :start], %{}, %{ + job: %Oban.Job{ + worker: "Sentry.MyWorker", + id: 123, + meta: %{"cron" => true, "cron_expr" => "@daily"} + } + }) - :telemetry.execute([:oban, :job, :stop], %{duration: duration}, %{ - state: unquote(oban_state), - job: %Oban.Job{ - worker: "Sentry.MyWorker", - id: 942, - meta: %{"cron" => true, "cron_expr" => unquote(frequency)} - } - }) - - assert_receive {^ref, :done}, 1000 - end - end + assert_receive {^ref, :done}, 1000 + end - test "captures exception events with monitor config", %{bypass: bypass} do + for {oban_state, expected_status} <- [ + success: "ok", + failure: "error", + cancelled: "ok", + discard: "ok", + snoozed: "ok" + ], + {frequency, expected_unit} <- [ + {"@hourly", "hour"}, + {"@daily", "day"}, + {"@weekly", "week"}, + {"@monthly", "month"}, + {"@yearly", "year"}, + {"@annually", "year"} + ] do + test "captures stop events with monitor config and state of #{inspect(oban_state)} and frequency of #{frequency}", + %{bypass: bypass} do test_pid = self() ref = make_ref() @@ -163,15 +117,16 @@ if Version.match?(System.version(), "~> 1.13") do assert headers["type"] == "check_in" assert check_in_body["check_in_id"] == "oban-942" - assert check_in_body["status"] == "error" + assert check_in_body["status"] == unquote(expected_status) assert check_in_body["monitor_slug"] == "sentry-my-worker" assert check_in_body["duration"] == 12.099 assert check_in_body["environment"] == "test" assert check_in_body["monitor_config"] == %{ "schedule" => %{ - "type" => "crontab", - "value" => "* 1 1 1 1" + "type" => "interval", + "value" => 1, + "unit" => unquote(expected_unit) } } @@ -182,16 +137,58 @@ if Version.match?(System.version(), "~> 1.13") do duration = System.convert_time_unit(12_099, :millisecond, :native) - :telemetry.execute([:oban, :job, :exception], %{duration: duration}, %{ - state: :success, + :telemetry.execute([:oban, :job, :stop], %{duration: duration}, %{ + state: unquote(oban_state), job: %Oban.Job{ worker: "Sentry.MyWorker", id: 942, - meta: %{"cron" => true, "cron_expr" => "* 1 1 1 1"} + meta: %{"cron" => true, "cron_expr" => unquote(frequency)} } }) assert_receive {^ref, :done}, 1000 end end + + test "captures exception events with monitor config", %{bypass: bypass} do + test_pid = self() + ref = make_ref() + + Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + assert [{headers, check_in_body}] = decode_envelope!(body) + + assert headers["type"] == "check_in" + + assert check_in_body["check_in_id"] == "oban-942" + assert check_in_body["status"] == "error" + assert check_in_body["monitor_slug"] == "sentry-my-worker" + assert check_in_body["duration"] == 12.099 + assert check_in_body["environment"] == "test" + + assert check_in_body["monitor_config"] == %{ + "schedule" => %{ + "type" => "crontab", + "value" => "* 1 1 1 1" + } + } + + send(test_pid, {ref, :done}) + + Plug.Conn.send_resp(conn, 200, ~s<{"id": "1923"}>) + end) + + duration = System.convert_time_unit(12_099, :millisecond, :native) + + :telemetry.execute([:oban, :job, :exception], %{duration: duration}, %{ + state: :success, + job: %Oban.Job{ + worker: "Sentry.MyWorker", + id: 942, + meta: %{"cron" => true, "cron_expr" => "* 1 1 1 1"} + } + }) + + assert_receive {^ref, :done}, 1000 + end end diff --git a/test/sentry/integrations/oban/error_reporter_test.exs b/test/sentry/integrations/oban/error_reporter_test.exs index 50013670..a4f770a6 100644 --- a/test/sentry/integrations/oban/error_reporter_test.exs +++ b/test/sentry/integrations/oban/error_reporter_test.exs @@ -1,56 +1,53 @@ -# TODO: Oban requires Elixir 1.13+, remove this once we depend on that too. -if Version.match?(System.version(), "~> 1.13") do - defmodule Sentry.Integrations.Oban.ErrorReporterTest do - use ExUnit.Case, async: true +defmodule Sentry.Integrations.Oban.ErrorReporterTest do + use ExUnit.Case, async: true - alias Sentry.Integrations.Oban.ErrorReporter + alias Sentry.Integrations.Oban.ErrorReporter - defmodule MyWorker do - use Oban.Worker + defmodule MyWorker do + use Oban.Worker - @impl Oban.Worker - def perform(%Oban.Job{}), do: :ok - end + @impl Oban.Worker + def perform(%Oban.Job{}), do: :ok + end - describe "handle_event/4" do - test "reports the correct error to Sentry" do - # Any worker is okay here, this is just an easier way to get a job struct. - job = - %{"id" => "123", "entity" => "user", "type" => "delete"} - |> MyWorker.new() - |> Ecto.Changeset.apply_action!(:validate) - |> Map.replace!(:unsaved_error, %{ - reason: %RuntimeError{message: "oops"}, - kind: :error, - stacktrace: [] - }) - - Sentry.Test.start_collecting() - - assert :ok = - ErrorReporter.handle_event( - [:oban, :job, :exception], - %{}, - %{job: job}, - :no_config - ) - - assert [event] = Sentry.Test.pop_sentry_reports() - assert event.original_exception == %RuntimeError{message: "oops"} - assert [%{stacktrace: %{frames: [stacktrace]}} = exception] = event.exception - - assert exception.type == "RuntimeError" - assert exception.value == "oops" - assert exception.mechanism.handled == true - assert stacktrace.module == MyWorker - - assert stacktrace.function == - "Sentry.Integrations.Oban.ErrorReporterTest.MyWorker.process/1" - - assert event.tags.oban_queue == "default" - assert event.tags.oban_state == "available" - assert event.tags.oban_worker == "Sentry.Integrations.Oban.ErrorReporterTest.MyWorker" - end + describe "handle_event/4" do + test "reports the correct error to Sentry" do + # Any worker is okay here, this is just an easier way to get a job struct. + job = + %{"id" => "123", "entity" => "user", "type" => "delete"} + |> MyWorker.new() + |> Ecto.Changeset.apply_action!(:validate) + |> Map.replace!(:unsaved_error, %{ + reason: %RuntimeError{message: "oops"}, + kind: :error, + stacktrace: [] + }) + + Sentry.Test.start_collecting() + + assert :ok = + ErrorReporter.handle_event( + [:oban, :job, :exception], + %{}, + %{job: job}, + :no_config + ) + + assert [event] = Sentry.Test.pop_sentry_reports() + assert event.original_exception == %RuntimeError{message: "oops"} + assert [%{stacktrace: %{frames: [stacktrace]}} = exception] = event.exception + + assert exception.type == "RuntimeError" + assert exception.value == "oops" + assert exception.mechanism.handled == true + assert stacktrace.module == MyWorker + + assert stacktrace.function == + "Sentry.Integrations.Oban.ErrorReporterTest.MyWorker.process/1" + + assert event.tags.oban_queue == "default" + assert event.tags.oban_state == "available" + assert event.tags.oban_worker == "Sentry.Integrations.Oban.ErrorReporterTest.MyWorker" end end end diff --git a/test/sentry/integrations/quantum/cron_test.exs b/test/sentry/integrations/quantum/cron_test.exs index faee1148..31c5ead2 100644 --- a/test/sentry/integrations/quantum/cron_test.exs +++ b/test/sentry/integrations/quantum/cron_test.exs @@ -1,157 +1,187 @@ -# TODO: Quantum requires Elixir 1.12+, remove this once we depend on that too. -if Version.match?(System.version(), "~> 1.12") do - defmodule Sentry.Integrations.Quantum.CronTest do - use Sentry.Case, async: false +defmodule Sentry.Integrations.Quantum.CronTest do + use Sentry.Case, async: false - import Sentry.TestHelpers + import Sentry.TestHelpers - defmodule Scheduler do - use Quantum, otp_app: :sentry - end + defmodule Scheduler do + use Quantum, otp_app: :sentry + end - setup_all do - Sentry.Integrations.Quantum.Cron.attach_telemetry_handler() - end + setup_all do + Sentry.Integrations.Quantum.Cron.attach_telemetry_handler() + end - setup do - bypass = Bypass.open() + setup do + bypass = Bypass.open() - put_test_config( - dsn: "http://public:secret@localhost:#{bypass.port}/1", - dedup_events: false, - environment_name: "test" - ) + put_test_config( + dsn: "http://public:secret@localhost:#{bypass.port}/1", + dedup_events: false, + environment_name: "test" + ) - %{bypass: bypass} + %{bypass: bypass} + end + + for event_type <- [:start, :stop, :exception] do + test "ignores #{event_type} events without a cron meta", %{bypass: bypass} do + Bypass.down(bypass) + + :telemetry.execute([:quantum, :job, unquote(event_type)], %{}, %{ + job: Scheduler.new_job(name: :test_job) + }) end - for event_type <- [:start, :stop, :exception] do - test "ignores #{event_type} events without a cron meta", %{bypass: bypass} do - Bypass.down(bypass) - - :telemetry.execute([:quantum, :job, unquote(event_type)], %{}, %{ - job: Scheduler.new_job(name: :test_job) - }) - end - - test "ignores #{event_type} events with a cron expr of @reboot", %{bypass: bypass} do - Bypass.down(bypass) - - :telemetry.execute([:quantum, :job, unquote(event_type)], %{}, %{ - job: - Scheduler.new_job( - name: :reboot_job, - schedule: Crontab.CronExpression.Parser.parse!("@reboot") - ) - }) - end + test "ignores #{event_type} events with a cron expr of @reboot", %{bypass: bypass} do + Bypass.down(bypass) + + :telemetry.execute([:quantum, :job, unquote(event_type)], %{}, %{ + job: + Scheduler.new_job( + name: :reboot_job, + schedule: Crontab.CronExpression.Parser.parse!("@reboot") + ) + }) end + end - test "captures start events with monitor config", %{bypass: bypass} do - test_pid = self() - ref = make_ref() + test "captures start events with monitor config", %{bypass: bypass} do + test_pid = self() + ref = make_ref() - Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - assert [{headers, check_in_body}] = decode_envelope!(body) + Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + assert [{headers, check_in_body}] = decode_envelope!(body) - assert headers["type"] == "check_in" + assert headers["type"] == "check_in" - assert check_in_body["check_in_id"] == "quantum-#{:erlang.phash2(ref)}" - assert check_in_body["status"] == "in_progress" - assert check_in_body["monitor_slug"] == "quantum-test-job" - assert check_in_body["duration"] == nil - assert check_in_body["environment"] == "test" + assert check_in_body["check_in_id"] == "quantum-#{:erlang.phash2(ref)}" + assert check_in_body["status"] == "in_progress" + assert check_in_body["monitor_slug"] == "quantum-test-job" + assert check_in_body["duration"] == nil + assert check_in_body["environment"] == "test" - assert check_in_body["monitor_config"] == %{ - "schedule" => %{ - "type" => "crontab", - "value" => "0 0 * * * *" - } + assert check_in_body["monitor_config"] == %{ + "schedule" => %{ + "type" => "crontab", + "value" => "0 0 * * * *" } + } - send(test_pid, {ref, :done}) + send(test_pid, {ref, :done}) - Plug.Conn.send_resp(conn, 200, ~s<{"id": "1923"}>) - end) + Plug.Conn.send_resp(conn, 200, ~s<{"id": "1923"}>) + end) - :telemetry.execute([:quantum, :job, :start], %{}, %{ - job: - Scheduler.new_job( - name: :test_job, - schedule: Crontab.CronExpression.Parser.parse!("@daily") - ), - telemetry_span_context: ref - }) + :telemetry.execute([:quantum, :job, :start], %{}, %{ + job: + Scheduler.new_job( + name: :test_job, + schedule: Crontab.CronExpression.Parser.parse!("@daily") + ), + telemetry_span_context: ref + }) - assert_receive {^ref, :done}, 1000 - end + assert_receive {^ref, :done}, 1000 + end - test "captures exception events with monitor config", %{bypass: bypass} do - test_pid = self() - ref = make_ref() + test "captures exception events with monitor config", %{bypass: bypass} do + test_pid = self() + ref = make_ref() - Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - assert [{headers, check_in_body}] = decode_envelope!(body) + Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + assert [{headers, check_in_body}] = decode_envelope!(body) - assert headers["type"] == "check_in" + assert headers["type"] == "check_in" - assert check_in_body["check_in_id"] == "quantum-#{:erlang.phash2(ref)}" - assert check_in_body["status"] == "error" - assert check_in_body["monitor_slug"] == "quantum-test-job" - assert check_in_body["duration"] == 12.099 - assert check_in_body["environment"] == "test" + assert check_in_body["check_in_id"] == "quantum-#{:erlang.phash2(ref)}" + assert check_in_body["status"] == "error" + assert check_in_body["monitor_slug"] == "quantum-test-job" + assert check_in_body["duration"] == 12.099 + assert check_in_body["environment"] == "test" - assert check_in_body["monitor_config"] == %{ - "schedule" => %{ - "type" => "crontab", - "value" => "0 0 * * * *" - } + assert check_in_body["monitor_config"] == %{ + "schedule" => %{ + "type" => "crontab", + "value" => "0 0 * * * *" } + } - send(test_pid, {ref, :done}) + send(test_pid, {ref, :done}) - Plug.Conn.send_resp(conn, 200, ~s<{"id": "1923"}>) - end) + Plug.Conn.send_resp(conn, 200, ~s<{"id": "1923"}>) + end) - duration = System.convert_time_unit(12_099, :millisecond, :native) + duration = System.convert_time_unit(12_099, :millisecond, :native) - :telemetry.execute([:quantum, :job, :exception], %{duration: duration}, %{ - job: - Scheduler.new_job( - name: :test_job, - schedule: Crontab.CronExpression.Parser.parse!("@daily") - ), - telemetry_span_context: ref - }) + :telemetry.execute([:quantum, :job, :exception], %{duration: duration}, %{ + job: + Scheduler.new_job( + name: :test_job, + schedule: Crontab.CronExpression.Parser.parse!("@daily") + ), + telemetry_span_context: ref + }) - assert_receive {^ref, :done}, 1000 - end + assert_receive {^ref, :done}, 1000 + end - test "captures stop events with monitor config", %{bypass: bypass} do - test_pid = self() - ref = make_ref() + test "captures stop events with monitor config", %{bypass: bypass} do + test_pid = self() + ref = make_ref() - Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - assert [{headers, check_in_body}] = decode_envelope!(body) + Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + assert [{headers, check_in_body}] = decode_envelope!(body) - assert headers["type"] == "check_in" + assert headers["type"] == "check_in" - assert check_in_body["check_in_id"] == "quantum-#{:erlang.phash2(ref)}" - assert check_in_body["status"] == "ok" - assert check_in_body["monitor_slug"] == "quantum-test-job" - assert check_in_body["duration"] == 12.099 - assert check_in_body["environment"] == "test" + assert check_in_body["check_in_id"] == "quantum-#{:erlang.phash2(ref)}" + assert check_in_body["status"] == "ok" + assert check_in_body["monitor_slug"] == "quantum-test-job" + assert check_in_body["duration"] == 12.099 + assert check_in_body["environment"] == "test" - assert check_in_body["monitor_config"] == %{ - "schedule" => %{ - "type" => "crontab", - "value" => "0 0 * * * *" - } + assert check_in_body["monitor_config"] == %{ + "schedule" => %{ + "type" => "crontab", + "value" => "0 0 * * * *" } + } + + send(test_pid, {ref, :done}) + + Plug.Conn.send_resp(conn, 200, ~s<{"id": "1923"}>) + end) + + duration = System.convert_time_unit(12_099, :millisecond, :native) + + :telemetry.execute([:quantum, :job, :stop], %{duration: duration}, %{ + job: + Scheduler.new_job( + name: :test_job, + schedule: Crontab.CronExpression.Parser.parse!("@daily") + ), + telemetry_span_context: ref + }) + + assert_receive {^ref, :done}, 1000 + end + for {job_name, expected_slug} <- [ + {:some_job, "quantum-some-job"}, + {MyApp.MyJob, "quantum-my-app-my-job"} + ] do + test "works for a job named #{inspect(job_name)}", %{bypass: bypass} do + test_pid = self() + ref = make_ref() + + Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + assert [{_headers, check_in_body}] = decode_envelope!(body) + + assert check_in_body["monitor_slug"] == unquote(expected_slug) send(test_pid, {ref, :done}) Plug.Conn.send_resp(conn, 200, ~s<{"id": "1923"}>) @@ -162,7 +192,7 @@ if Version.match?(System.version(), "~> 1.12") do :telemetry.execute([:quantum, :job, :stop], %{duration: duration}, %{ job: Scheduler.new_job( - name: :test_job, + name: unquote(job_name), schedule: Crontab.CronExpression.Parser.parse!("@daily") ), telemetry_span_context: ref @@ -170,38 +200,5 @@ if Version.match?(System.version(), "~> 1.12") do assert_receive {^ref, :done}, 1000 end - - for {job_name, expected_slug} <- [ - {:some_job, "quantum-some-job"}, - {MyApp.MyJob, "quantum-my-app-my-job"} - ] do - test "works for a job named #{inspect(job_name)}", %{bypass: bypass} do - test_pid = self() - ref = make_ref() - - Bypass.expect_once(bypass, "POST", "/api/1/envelope/", fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - assert [{_headers, check_in_body}] = decode_envelope!(body) - - assert check_in_body["monitor_slug"] == unquote(expected_slug) - send(test_pid, {ref, :done}) - - Plug.Conn.send_resp(conn, 200, ~s<{"id": "1923"}>) - end) - - duration = System.convert_time_unit(12_099, :millisecond, :native) - - :telemetry.execute([:quantum, :job, :stop], %{duration: duration}, %{ - job: - Scheduler.new_job( - name: unquote(job_name), - schedule: Crontab.CronExpression.Parser.parse!("@daily") - ), - telemetry_span_context: ref - }) - - assert_receive {^ref, :done}, 1000 - end - end end end diff --git a/test/sentry/live_view_hook_test.exs b/test/sentry/live_view_hook_test.exs index 8f090fb9..949c17fa 100644 --- a/test/sentry/live_view_hook_test.exs +++ b/test/sentry/live_view_hook_test.exs @@ -1,136 +1,133 @@ -# TODO: LV requires Elixir 1.13+, remove this once we depend on that too. -if Version.match?(System.version(), "~> 1.13") do - defmodule SentryTest.ErrorView do - def render(template, assigns) do - dbg() - "OK" - end +defmodule SentryTest.ErrorView do + def render(template, assigns) do + dbg() + "OK" end +end - defmodule SentryTest.Live do - use Phoenix.LiveView +defmodule SentryTest.Live do + use Phoenix.LiveView - on_mount Sentry.LiveViewHook + on_mount Sentry.LiveViewHook - def render(assigns) do - ~H""" -

Testing Sentry hooks

- """ - end + def render(assigns) do + ~H""" +

Testing Sentry hooks

+ """ + end - def mount(_params, _session, socket) do - {:ok, socket} - end + def mount(_params, _session, socket) do + {:ok, socket} + end - def handle_event("refresh", _params, socket) do - {:noreply, socket} - end + def handle_event("refresh", _params, socket) do + {:noreply, socket} + end - def handle_info(:test_message, socket) do - {:noreply, socket} - end + def handle_info(:test_message, socket) do + {:noreply, socket} end +end - defmodule SentryTest.Router do - use Phoenix.Router - import Phoenix.LiveView.Router +defmodule SentryTest.Router do + use Phoenix.Router + import Phoenix.LiveView.Router - scope "/" do - live "/hook_test", SentryTest.Live - end + scope "/" do + live "/hook_test", SentryTest.Live end +end - defmodule SentryTest.Endpoint do - use Phoenix.Endpoint, otp_app: :sentry +defmodule SentryTest.Endpoint do + use Phoenix.Endpoint, otp_app: :sentry - socket "/live", Phoenix.LiveView.Socket, - websocket: [connect_info: [:peer_data, :uri, :user_agent]] + socket "/live", Phoenix.LiveView.Socket, + websocket: [connect_info: [:peer_data, :uri, :user_agent]] - plug SentryTest.Router - end + plug SentryTest.Router +end - defmodule Sentry.LiveViewHookTest do - use Sentry.Case, async: true +defmodule Sentry.LiveViewHookTest do + use Sentry.Case, async: true - import Phoenix.ConnTest - import Phoenix.LiveViewTest + import Phoenix.ConnTest + import Phoenix.LiveViewTest - @endpoint SentryTest.Endpoint + @endpoint SentryTest.Endpoint - setup_all do - Application.put_env(:sentry, SentryTest.Endpoint, - secret_key_base: "TMnue44VMTf1VmyD6SYKR30cqKpHluHOFZGXcVkC33hKVVKTVZ1HBQLLLLLLLLLL", - live_view: [signing_salt: "F8ftIAbYdeTzhwgl"] - ) + setup_all do + Application.put_env(:sentry, SentryTest.Endpoint, + secret_key_base: "TMnue44VMTf1VmyD6SYKR30cqKpHluHOFZGXcVkC33hKVVKTVZ1HBQLLLLLLLLLL", + live_view: [signing_salt: "F8ftIAbYdeTzhwgl"] + ) - pid = start_supervised!(SentryTest.Endpoint) - Process.link(pid) - :ok - end + pid = start_supervised!(SentryTest.Endpoint) + Process.link(pid) + :ok + end - setup do - %{conn: build_conn()} - end + setup do + %{conn: build_conn()} + end - test "attaches the right context", %{conn: conn} do - conn = - conn - |> Plug.Conn.put_req_header("user-agent", "sentry-testing 1.0") + test "attaches the right context", %{conn: conn} do + conn = + conn + |> Plug.Conn.put_req_header("user-agent", "sentry-testing 1.0") - {:ok, view, html} = live(conn, "/hook_test") - assert html =~ "

Testing Sentry hooks

" + {:ok, view, html} = live(conn, "/hook_test") + assert html =~ "

Testing Sentry hooks

" - context1 = get_sentry_context(view) + context1 = get_sentry_context(view) - assert "phx-" <> _ = context1.extra.socket_id - assert context1.request.url == "http://www.example.com/hook_test" - assert context1.request.user_agent == "sentry-testing 1.0" + assert "phx-" <> _ = context1.extra.socket_id + assert context1.request.url == "http://www.example.com/hook_test" + assert context1.request.user_agent == "sentry-testing 1.0" - assert [ - %{category: "web.live_view.params"} = params_breadcrumb, - %{category: "web.live_view.mount"} = mount_breadcrumb - ] = context1.breadcrumbs + assert [ + %{category: "web.live_view.params"} = params_breadcrumb, + %{category: "web.live_view.mount"} = mount_breadcrumb + ] = context1.breadcrumbs - assert mount_breadcrumb.message == "Mounted live view" - assert mount_breadcrumb.data == %{} + assert mount_breadcrumb.message == "Mounted live view" + assert mount_breadcrumb.data == %{} - assert params_breadcrumb.message == "http://www.example.com/hook_test" - assert params_breadcrumb.data == %{params: %{}, uri: "http://www.example.com/hook_test"} + assert params_breadcrumb.message == "http://www.example.com/hook_test" + assert params_breadcrumb.data == %{params: %{}, uri: "http://www.example.com/hook_test"} - # Send an event and test the new breadcrumb. + # Send an event and test the new breadcrumb. - assert render_hook(view, :refresh, %{force: true}) =~ "Testing Sentry hooks" + assert render_hook(view, :refresh, %{force: true}) =~ "Testing Sentry hooks" - context2 = get_sentry_context(view) - assert Map.take(context1, [:extra, :request]) == Map.take(context2, [:extra, :request]) - assert [event_breadcrumb, ^params_breadcrumb, ^mount_breadcrumb] = context2.breadcrumbs - assert event_breadcrumb.category == "web.live_view.event" - assert event_breadcrumb.message == ~s("refresh") - assert event_breadcrumb.data == %{params: %{"force" => true}, event: "refresh"} + context2 = get_sentry_context(view) + assert Map.take(context1, [:extra, :request]) == Map.take(context2, [:extra, :request]) + assert [event_breadcrumb, ^params_breadcrumb, ^mount_breadcrumb] = context2.breadcrumbs + assert event_breadcrumb.category == "web.live_view.event" + assert event_breadcrumb.message == ~s("refresh") + assert event_breadcrumb.data == %{params: %{"force" => true}, event: "refresh"} - # Send a message and test the new breadcrumb. - send(view.pid, :test_message) - assert render(view) =~ "Testing Sentry hooks" + # Send a message and test the new breadcrumb. + send(view.pid, :test_message) + assert render(view) =~ "Testing Sentry hooks" - context3 = get_sentry_context(view) - assert Map.take(context1, [:extra, :request]) == Map.take(context3, [:extra, :request]) + context3 = get_sentry_context(view) + assert Map.take(context1, [:extra, :request]) == Map.take(context3, [:extra, :request]) - assert [info_breadcrumb, ^event_breadcrumb, ^params_breadcrumb, ^mount_breadcrumb] = - context3.breadcrumbs + assert [info_breadcrumb, ^event_breadcrumb, ^params_breadcrumb, ^mount_breadcrumb] = + context3.breadcrumbs - assert info_breadcrumb.category == "web.live_view.info" - assert info_breadcrumb.message == ~s(:test_message) - end + assert info_breadcrumb.category == "web.live_view.info" + assert info_breadcrumb.message == ~s(:test_message) + end - defp get_sentry_context(view) do - {:dictionary, pdict} = Process.info(view.pid, :dictionary) + defp get_sentry_context(view) do + {:dictionary, pdict} = Process.info(view.pid, :dictionary) - assert {:ok, sentry_context} = - pdict - |> Keyword.fetch!(:"$logger_metadata$") - |> Map.fetch(Sentry.Context.__logger_metadata_key__()) + assert {:ok, sentry_context} = + pdict + |> Keyword.fetch!(:"$logger_metadata$") + |> Map.fetch(Sentry.Context.__logger_metadata_key__()) - sentry_context - end + sentry_context end end diff --git a/test/support/test_error_view.ex b/test/support/test_error_view.ex index 21fe8eb4..29dcd7e4 100644 --- a/test/support/test_error_view.ex +++ b/test/support/test_error_view.ex @@ -1,30 +1,27 @@ -# TODO: Oban requires Elixir 1.13+, remove this once we depend on that too. -if Version.match?(System.version(), "~> 1.13") do - defmodule Sentry.ErrorView do - use Phoenix.Component +defmodule Sentry.ErrorView do + use Phoenix.Component - import Phoenix.HTML, only: [raw: 1] + import Phoenix.HTML, only: [raw: 1] - def render(_, _) do - case Sentry.get_last_event_id_and_source() do - {event_id, :plug} -> - opts = - %{title: "Testing", eventId: event_id} - |> Jason.encode!() + def render(_, _) do + case Sentry.get_last_event_id_and_source() do + {event_id, :plug} -> + opts = + %{title: "Testing", eventId: event_id} + |> Jason.encode!() - assigns = %{opts: opts} + assigns = %{opts: opts} - ~H""" - - - """ + ~H""" + + + """ - _ -> - "error" - end + _ -> + "error" end end end From 337bd242ffabcaebedcaec763d532affc7e73a51 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sat, 20 Apr 2024 10:43:23 +0200 Subject: [PATCH 08/10] FIXUP --- test/sentry/live_view_hook_test.exs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/test/sentry/live_view_hook_test.exs b/test/sentry/live_view_hook_test.exs index 949c17fa..605cce4b 100644 --- a/test/sentry/live_view_hook_test.exs +++ b/test/sentry/live_view_hook_test.exs @@ -1,16 +1,9 @@ -defmodule SentryTest.ErrorView do - def render(template, assigns) do - dbg() - "OK" - end -end - defmodule SentryTest.Live do use Phoenix.LiveView on_mount Sentry.LiveViewHook - def render(assigns) do + def render(_assigns) do ~H"""

Testing Sentry hooks

""" From 2bd49e5e28816ceb2ed92a30aeccc68ed80a4df9 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sun, 21 Apr 2024 10:02:47 +0200 Subject: [PATCH 09/10] FIXUP --- test/sentry/live_view_hook_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sentry/live_view_hook_test.exs b/test/sentry/live_view_hook_test.exs index 605cce4b..a9551dcf 100644 --- a/test/sentry/live_view_hook_test.exs +++ b/test/sentry/live_view_hook_test.exs @@ -3,7 +3,7 @@ defmodule SentryTest.Live do on_mount Sentry.LiveViewHook - def render(_assigns) do + def render(assigns) do ~H"""

Testing Sentry hooks

""" From 3fc8b0b3c2ef41ee3a1380d8a4dd4ce62ba00da7 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Sun, 21 Apr 2024 10:26:29 +0200 Subject: [PATCH 10/10] FIXUOP --- lib/sentry/live_view_hook.ex | 2 +- test/sentry/live_view_hook_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/sentry/live_view_hook.ex b/lib/sentry/live_view_hook.ex index 8f78b300..9b6418a9 100644 --- a/lib/sentry/live_view_hook.ex +++ b/lib/sentry/live_view_hook.ex @@ -73,7 +73,7 @@ if Code.ensure_loaded?(Phoenix.LiveView) do end if user_agent = get_connect_info(socket, :user_agent) do - Context.set_request_context(%{user_agent: user_agent}) + Context.set_extra_context(%{user_agent: user_agent}) end # :peer_data returns t:Plug.Conn.Adapter.peer_data/0. diff --git a/test/sentry/live_view_hook_test.exs b/test/sentry/live_view_hook_test.exs index a9551dcf..1bf44ee8 100644 --- a/test/sentry/live_view_hook_test.exs +++ b/test/sentry/live_view_hook_test.exs @@ -75,7 +75,7 @@ defmodule Sentry.LiveViewHookTest do assert "phx-" <> _ = context1.extra.socket_id assert context1.request.url == "http://www.example.com/hook_test" - assert context1.request.user_agent == "sentry-testing 1.0" + assert context1.extra.user_agent == "sentry-testing 1.0" assert [ %{category: "web.live_view.params"} = params_breadcrumb,