diff --git a/.credo.exs b/.credo.exs index 3641623..1dd1422 100644 --- a/.credo.exs +++ b/.credo.exs @@ -142,6 +142,7 @@ {Credo.Check.Warning.UnusedStringOperation}, {Credo.Check.Warning.UnusedTupleOperation}, {Credo.Check.Warning.RaiseInsideRescue}, + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, false}, # # Controversial and experimental checks (opt-in, just remove `, false`) diff --git a/CHANGELOG.md b/CHANGELOG.md index e74517b..aafdc3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.1.0 - 2021-02-03 + +### Added + +- Initial implementation for publishing connection telemetry + ## 0.0.0 - 2021-02-03 ### Added diff --git a/config/config.exs b/config/config.exs index 7b2fcaf..dce06c2 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,3 +1,7 @@ use Mix.Config +if Mix.env() == :test do + import_config "test.exs" +end + # Generated by Elixir.Gaas.Generators.Simple.ConfigConfig diff --git a/config/test.exs b/config/test.exs new file mode 100644 index 0000000..2e4d753 --- /dev/null +++ b/config/test.exs @@ -0,0 +1,6 @@ +use Mix.Config + +config :phoenix, json_library: Jason + +config :slipstream_honeycomb, + honeycomb_sender: HoneycombSenderMock diff --git a/lib/slipstream/honeycomb/connection.ex b/lib/slipstream/honeycomb/connection.ex new file mode 100644 index 0000000..31c91b3 --- /dev/null +++ b/lib/slipstream/honeycomb/connection.ex @@ -0,0 +1,104 @@ +defmodule Slipstream.Honeycomb.Connection do + @moduledoc """ + A GenServer that collects telemetry events from Slipstream connections and + emits them to Honeycomb + """ + + @sender Application.get_env( + :slipstream_honeycomb, + :honeycomb_sender, + Opencensus.Honeycomb.Sender + ) + + alias Opencensus.Honeycomb.Event + + @event_names [ + ~w[slipstream connection connect stop]a, + ~w[slipstream connection handle stop]a + ] + + use GenServer + + @doc false + def start_link(_args) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @doc false + @impl GenServer + def init(state) do + {:ok, state, {:continue, :telemetry_attach}} + end + + @doc false + @impl GenServer + def handle_continue(:telemetry_attach, state) do + :telemetry.attach_many( + "slipstream-honeycomb-connection-exporter", + @event_names, + &handle_event/4, + state + ) + + {:noreply, state} + end + + def handle_event(event, measurements, metadata, _state) do + GenServer.cast(__MODULE__, {event, measurements, metadata}) + end + + @impl GenServer + def handle_cast({event, measurements, metadata}, state) do + {event, measurements, metadata} + |> map_to_event() + |> send_event() + + {:noreply, state} + end + + defp map_to_event( + {~w[slipstream connection connect stop]a, %{duration: duration}, + metadata} + ) do + %Event{ + time: metadata.start_time, + data: %{ + state: inspect(metadata.state), + traceId: metadata.trace_id, + id: metadata.connection_id, + durationMs: convert_time(duration) + } + } + end + + defp map_to_event( + {~w[slipstream connection handle stop]a, %{duration: duration}, + metadata} + ) do + %Event{ + time: metadata.start_time, + data: %{ + state: inspect(metadata.state), + traceId: metadata.trace_id, + parentId: metadata.connection_id, + id: metadata.span_id, + durationMs: convert_time(duration), + raw_message: inspect(metadata.raw_message), + message: inspect(metadata.message), + events: inspect(metadata.events), + built_events: inspect(metadata.built_events), + return: metadata.return + } + } + end + + defp send_event(event) do + @sender.send_batch([event]) + end + + defp convert_time(time) do + # nanoseconds but with decimals! + # if we were to convert directly to msec, it'd be an integer :( + System.convert_time_unit(time, :native, :microsecond) / 1_000 + end +end diff --git a/lib/slipstream_honeycomb.ex b/lib/slipstream_honeycomb.ex deleted file mode 100644 index 4c63604..0000000 --- a/lib/slipstream_honeycomb.ex +++ /dev/null @@ -1,7 +0,0 @@ -defmodule SlipstreamHoneycomb do - @moduledoc """ - An adapter between slipstream telemetry and honeycomb events - """ -end - -# Generated by Elixir.Gaas.Generators.Simple.BaseModule diff --git a/mix.exs b/mix.exs index 88bbfc8..3b4f35f 100644 --- a/mix.exs +++ b/mix.exs @@ -34,7 +34,7 @@ defmodule SlipstreamHoneycomb.MixProject do ] end - defp elixirc_paths(:test), do: ["lib", "test/fixtures"] + defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_), do: ["lib"] def application do @@ -43,11 +43,15 @@ defmodule SlipstreamHoneycomb.MixProject do defp deps do [ + {:slipstream, "~> 0.3"}, + {:telemetry, "~> 0.4"}, + {:opencensus_honeycomb, "~> 0.2.0"}, # docs {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, # test - {:bless, "~> 1.0"}, - {:convene, "~> 0.2", organization: "cuatro", only: [:dev, :test]}, + {:bless, "~> 1.0", only: [:dev, :test]}, + {:mox, "~> 1.0", only: :test}, + {:credo, "~> 1.0", only: [:dev, :test]}, {:excoveralls, "~> 0.7", only: :test} ] end diff --git a/mix.lock b/mix.lock index 367b456..c808b29 100644 --- a/mix.lock +++ b/mix.lock @@ -3,20 +3,36 @@ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.5.3", "70bdd7e7188c804f3a30ee0e7c99655bc35d8ac41c23e12325f36ab449b70651", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "ed516acb3929b101208a9d700062d520f3953da3b6b918d866106ffa980e1c10"}, "convene": {:hex, :convene, "0.2.2", "2495b032aff9a8d5b87b5db83c64e65a90e61d438dda2f72fc951d2093ceaea3", [:mix], [{:credo, "~> 1.4", [hex: :credo, repo: "hexpm", optional: false]}], "hexpm:cuatro", "83e0a113b38315091e5d65fc12f02f1f518b317eee9bf9dfe3dd01efb64ece51"}, + "counters": {:hex, :counters, "0.2.1", "aa3d97e88f92573488987193d0f48efce0f3b2cd1443bf4ee760bc7f99322f0c", [:mix, :rebar3], [], "hexpm", "a020c714992f7db89178d27e1f39909e5d77da3b80daa4d6015877ed0c94b8ab"}, + "cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm", "1e1a3d176d52daebbecbbcdfd27c27726076567905c2a9d7398c54da9d225761"}, "credo": {:hex, :credo, "1.5.4", "9914180105b438e378e94a844ec3a5088ae5875626fc945b7c1462b41afc3198", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cf51af45eadc0a3f39ba13b56fdac415c91b34f7b7533a13dc13550277141bc4"}, + "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"}, "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, "excoveralls": {:hex, :excoveralls, "0.13.4", "7b0baee01fe150ef81153e6ffc0fc68214737f54570dc257b3ca4da8e419b812", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "faae00b3eee35cdf0342c10b669a7c91f942728217d2a7c7f644b24d391e6190"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "gun": {:hex, :gun, "1.3.3", "cf8b51beb36c22b9c8df1921e3f2bc4d2b1f68b49ad4fbc64e91875aa14e16b4", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "3106ce167f9c9723f849e4fb54ea4a4d814e3996ae243a1c828b256e749041e0"}, "hackney": {:hex, :hackney, "1.17.0", "717ea195fd2f898d9fe9f1ce0afcc2621a41ecfe137fae57e7fe6e9484b9aa99", [:rebar3], [{:certifi, "~>2.5", [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", [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", "64c22225f1ea8855f584720c0e5b3cd14095703af1c9fbc845ba042811dc671c"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mox": {:hex, :mox, "1.0.0", "4b3c7005173f47ff30641ba044eb0fe67287743eec9bd9545e37f3002b0a9f8b", [:mix], [], "hexpm", "201b0a20b7abdaaab083e9cf97884950f8a30a1350a1da403b3145e213c6f4df"}, + "nimble_options": {:hex, :nimble_options, "0.3.5", "a4f6820cdcb4ee444afd78635f323e58e8a5ddf2fbbe9b9d283a99f972034bae", [:mix], [], "hexpm", "f5507cc90033a8d12769522009c80aa9164af6bab245dbd4ad421d008455f1e1"}, "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "opencensus": {:hex, :opencensus, "0.9.3", "a7bb2771b40593f8513fc14eb70d822e88bea1b2fb552e19a1e2e6f98371427a", [:rebar3], [{:counters, "~> 0.2.1", [hex: :counters, repo: "hexpm", optional: false]}, {:ctx, "~> 0.5", [hex: :ctx, repo: "hexpm", optional: false]}, {:wts, "~> 0.3", [hex: :wts, repo: "hexpm", optional: false]}], "hexpm", "9495b4fa817f10a7cdf8b69cca929470aeda0633a04ea18cff6a0a9ea03a03e9"}, + "opencensus_honeycomb": {:hex, :opencensus_honeycomb, "0.2.2", "71ed8039fe573be25100777b47afc2893e58d64ee9164d8b69156166bd9bd751", [:mix], [{:hackney, "~> 1.15", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:opencensus, "~> 0.9.2", [hex: :opencensus, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5b08dff93f34a4d56f2038382bd1c4ddcd2661c14e90b80a501e874f169a39d3"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, + "phoenix": {:hex, :phoenix, "1.5.7", "2923bb3af924f184459fe4fa4b100bd25fa6468e69b2803dfae82698269aa5e0", [: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", "774cd64417c5a3788414fdbb2be2eb9bcd0c048d9e6ad11a0c1fd67b7c0d0978"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, + "plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [: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", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, + "slipstream": {:hex, :slipstream, "0.3.0", "e2e86234c8a7347360138fa3423df2ebcc3ab21662a61c3e3df4fb36cb90d5c0", [:mix], [{:gun, "~> 1.0", [hex: :gun, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "09d80ea7193e7ba559cb2d263fbc6b20e16a76a192442bf5a860ca6df4585e06"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "wts": {:hex, :wts, "0.4.0", "62d9dc400ad29f0d233f0665b9c75c8f8eb0a8af75eec921056ba4a73b0605a2", [:rebar3], [], "hexpm", "711bb675de2ce2b3ebab80a613ac93b994f74df44af4cff7970dc9eebe724869"}, } diff --git a/test/slipstream/honeycomb/connection_test.exs b/test/slipstream/honeycomb/connection_test.exs new file mode 100644 index 0000000..9d8e040 --- /dev/null +++ b/test/slipstream/honeycomb/connection_test.exs @@ -0,0 +1,72 @@ +defmodule Slipstream.Honeycomb.ConnectionTest do + use ExUnit.Case + + import Mox + setup :verify_on_exit! + @sender Application.fetch_env!(:slipstream_honeycomb, :honeycomb_sender) + + test "honeycomb events are emitted on telemetry events" do + pid = start_supervised!(Slipstream.Honeycomb.Connection) + test_proc = self() + + expect(@sender, :send_batch, 2, fn [event] -> + send(test_proc, {:send_event, event}) + + {:ok, 1} + end) + |> allow(self(), pid) + + # ideally we'd have slipstream do the actual emitting here, but I don't + # wanna go through all the melarky to set up a phoenix endpoint just for + # this test + metadata = %{ + state: %{}, + connection_id: "foo", + trace_id: "bar", + start_time: DateTime.utc_now() + } + + duration = System.monotonic_time() - System.monotonic_time() + + :telemetry.execute( + [:slipstream, :connection, :connect, :stop], + %{duration: duration}, + metadata + ) + + assert_receive {:send_event, event} + + assert event.time == metadata.start_time + assert event.data.state == "%{}" + assert event.data.traceId == metadata.trace_id + assert event.data.id == metadata.connection_id + + metadata = %{ + state: %{}, + connection_id: "foo", + span_id: "baz", + trace_id: "bar", + start_time: DateTime.utc_now(), + raw_message: :connect, + message: :connect, + events: [], + built_events: [], + return: {:noreply, %{}} + } + + :telemetry.execute( + [:slipstream, :connection, :handle, :stop], + %{duration: duration}, + metadata + ) + + assert_receive {:send_event, event} + + assert event.time == metadata.start_time + assert event.data.state == "%{}" + assert event.data.traceId == metadata.trace_id + assert event.data.parentId == metadata.connection_id + assert event.data.id == metadata.span_id + assert event.data.raw_message == ":connect" + end +end diff --git a/test/slipstream_honeycomb_test.exs b/test/slipstream_honeycomb_test.exs deleted file mode 100644 index 1a7f021..0000000 --- a/test/slipstream_honeycomb_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule SlipstreamHoneycombTest do - use ExUnit.Case -end - -# Generated by Elixir.Gaas.Generators.Simple.BaseModuleTest diff --git a/test/support/honeycomb_sender_behaviour.ex b/test/support/honeycomb_sender_behaviour.ex new file mode 100644 index 0000000..36322a6 --- /dev/null +++ b/test/support/honeycomb_sender_behaviour.ex @@ -0,0 +1,11 @@ +defmodule Slipstream.Honeycomb.SenderBehaviour do + @moduledoc """ + A behaviour for `Opencensus.Honeycomb.Sender` that defines the `send_batch/1` + function + + Used by mox to allow us to test emitting these events without side effects + """ + + @callback send_batch([Opencensus.Honeycomb.Event.t()]) :: + {:ok, integer()} | {:error, Exception.t()} +end diff --git a/test/support/mocks.ex b/test/support/mocks.ex new file mode 100644 index 0000000..6f0308b --- /dev/null +++ b/test/support/mocks.ex @@ -0,0 +1 @@ +Mox.defmock(HoneycombSenderMock, for: Slipstream.Honeycomb.SenderBehaviour)