diff --git a/lib/postoffice/messaging.ex b/lib/postoffice/messaging.ex index 4457ba04..da375341 100644 --- a/lib/postoffice/messaging.ex +++ b/lib/postoffice/messaging.ex @@ -204,4 +204,9 @@ defmodule Postoffice.Messaging do from(p in PublisherFailures, where: p.message_id == ^message_id) |> Repo.all() end + + def get_topic_origin_hosts() do + from(t in Topic, distinct: true, select: t.origin_host) + |> Repo.all() + end end diff --git a/lib/postoffice/rescuer/adapters/http.ex b/lib/postoffice/rescuer/adapters/http.ex new file mode 100644 index 00000000..1008870b --- /dev/null +++ b/lib/postoffice/rescuer/adapters/http.ex @@ -0,0 +1,21 @@ +defmodule Postoffice.Rescuer.Adapters.Http do + require Logger + + @behaviour Postoffice.Rescuer.Adapters.Impl + + @impl true + def list(host) do + Logger.info("Listing undelivered messages from #{host}") + HTTPoison.get(host) + end + + @impl true + def delete(host, message_id) do + build_message_path(host, message_id) + |> HTTPoison.delete() + end + + defp build_message_path(host, message_id) do + host <> message_id <> "/" + end +end diff --git a/lib/postoffice/rescuer/adapters/impl.ex b/lib/postoffice/rescuer/adapters/impl.ex new file mode 100644 index 00000000..a29610d3 --- /dev/null +++ b/lib/postoffice/rescuer/adapters/impl.ex @@ -0,0 +1,6 @@ +defmodule Postoffice.Rescuer.Adapters.Impl do + @moduledoc false + + @callback list(host :: String) :: {:ok, list} | {:error, reason :: String} + @callback delete(host :: String, message_id :: Integer) :: {:ok, status :: Atom } | {:error, reason :: String} +end diff --git a/lib/postoffice/rescuer/client.ex b/lib/postoffice/rescuer/client.ex new file mode 100644 index 00000000..b3b42b0e --- /dev/null +++ b/lib/postoffice/rescuer/client.ex @@ -0,0 +1,52 @@ +defmodule Postoffice.Rescuer.Client do + require Logger + + alias Postoffice.Rescuer.Adapters.Http + + def list(host) do + case impl().list(host) do + {:ok, %HTTPoison.Response{status_code: status_code, body: body}} + when status_code in 200..299 -> + Logger.info("Succesfully listed pending messages from #{host}") + {:ok, Poison.decode!(body)} + + {:ok, %HTTPoison.Response{status_code: status_code, body: body}} -> + Logger.info( + "Non successful response list pending messages from #{host} with status code: #{ + status_code + }" + ) + + {:error, []} + + {:error, %HTTPoison.Error{reason: reason}} -> + Logger.info("Error trying to list pending messages #{reason}") + {:error, []} + end + end + + def delete(host, message_id) do + case impl().delete(host, message_id) do + {:ok, %HTTPoison.Response{status_code: status_code}} + when status_code in 200..299 -> + Logger.info("Successfully deleted message #{message_id} from #{host}") + {:ok, :deleted} + {:ok, %HTTPoison.Response{status_code: status_code, body: body}} -> + Logger.info( + "Non successful response deleting message from #{host} with status code: #{ + status_code + }" + ) + + {:error, "Request status code #{status_code}"} + + {:error, %HTTPoison.Error{reason: reason}} -> + Logger.info("Error trying to delete message #{host}: #{reason}") + {:error, reason} + end + end + + defp impl do + Application.get_env(:postoffice, :rescuer_client, Http) + end +end diff --git a/test/postoffice/messaging_test.exs b/test/postoffice/messaging_test.exs index 335f6e83..c1ce0c8e 100644 --- a/test/postoffice/messaging_test.exs +++ b/test/postoffice/messaging_test.exs @@ -290,5 +290,19 @@ defmodule Postoffice.MessagingTest do loaded_publisher_failures = Messaging.get_publisher_failures_for_message(message.id) assert Kernel.length(loaded_publisher_failures) == 1 end + + test "get_topic_origin_hosts returns unique hosts" do + _topic = Fixtures.create_topic() + + _second_topic = + Fixtures.create_topic(%{ + name: "second_test", + origin_host: "example.com" + }) + + hosts = Messaging.get_topic_origin_hosts() + + assert Kernel.length(hosts) == 1 + end end end diff --git a/test/postoffice/rescuer/client_test.exs b/test/postoffice/rescuer/client_test.exs new file mode 100644 index 00000000..5bdf8146 --- /dev/null +++ b/test/postoffice/rescuer/client_test.exs @@ -0,0 +1,96 @@ +defmodule Postoffice.Rescuer.ClientTest do + use ExUnit.Case + + import Mox + + alias Postoffice.Rescuer.Adapters.HttpMock + alias Postoffice.Rescuer.Client + + @origin_host "http://fake_origin.host" + @wrong_message_id 9999 + @external_message_id 1 + @one_message_response "[{\"id\": 1, \"topic\": \"test\", \"payload\": {\"products\": [{\"code\": \"1234\"}, {\"code\": 2345}], \"reference\": 1234}, \"attributes\": null}]" + @two_messages_response "[{\"id\": 1, \"topic\": \"test\", \"payload\": {\"products\": [{\"code\": \"1234\"}, {\"code\": 2345}], \"reference\": 1234}, \"attributes\": null}, {\"id\": 2, \"topic\": \"test2\", \"payload\": {\"products\": [{\"code\": \"1234\"}, {\"code\": 2345}], \"reference\": 1234}, \"attributes\": null}]" + + setup [:set_mox_from_context, :verify_on_exit!] + + setup do + :ok = Ecto.Adapters.SQL.Sandbox.checkout(Postoffice.Repo) + end + + describe "list undelivered messages" do + test "non successful response from origin host" do + expect(HttpMock, :list, fn @origin_host -> + {:ok, %HTTPoison.Response{status_code: 300}} + end) + + {:error, pending_messages} = Client.list(@origin_host) + assert pending_messages == [] + end + + test "origin host returns an error" do + expect(HttpMock, :list, fn @origin_host -> + {:error, %HTTPoison.Error{reason: "test error"}} + end) + + {:error, pending_messages} = Client.list(@origin_host) + assert pending_messages == [] + end + + test "no pending messages returned" do + expect(HttpMock, :list, fn @origin_host -> + {:ok, %HTTPoison.Response{status_code: 200, body: "[]"}} + end) + + {:ok, pending_messages} = Client.list(@origin_host) + assert pending_messages == [] + end + + test "one pending message received" do + expect(HttpMock, :list, fn @origin_host -> + {:ok, %HTTPoison.Response{status_code: 200, body: @one_message_response}} + end) + + {:ok, pending_messages} = Client.list(@origin_host) + assert Kernel.length(pending_messages) == 1 + end + + test "two pending messages received" do + expect(HttpMock, :list, fn @origin_host -> + {:ok, %HTTPoison.Response{status_code: 200, body: @two_messages_response}} + end) + + {:ok, pending_messages} = Client.list(@origin_host) + assert Kernel.length(pending_messages) == 2 + end + end + + describe "delete undelivered messages" do + test "trying to delete non existing message" do + expect(HttpMock, :delete, fn @origin_host, @wrong_message_id -> + {:ok, %HTTPoison.Response{status_code: 404, body: ""}} + end) + + {:error, reason} = Client.delete(@origin_host, @wrong_message_id) + assert reason == "Request status code 404" + end + + test "errors are handled from our side" do + expect(HttpMock, :delete, fn @origin_host, @wrong_message_id -> + {:error, %HTTPoison.Error{reason: "Something weird happened"}} + end) + + {:error, reason} = Client.delete(@origin_host, @wrong_message_id) + assert reason == "Something weird happened" + end + + test "messages are successfuly deleted" do + expect(HttpMock, :delete, fn @origin_host, @external_message_id -> + {:ok, %HTTPoison.Response{status_code: 204, body: ""}} + end) + + {:ok, :deleted} = Client.delete(@origin_host, @external_message_id) + + end + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 7190543c..33d7ddc6 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -3,6 +3,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Postoffice.Repo, :manual) Mox.defmock(Postoffice.Adapters.HttpMock, for: Postoffice.Adapters.Impl) Mox.defmock(Postoffice.Adapters.PubsubMock, for: Postoffice.Adapters.Impl) +Mox.defmock(Postoffice.Rescuer.Adapters.HttpMock, for: Postoffice.Rescuer.Adapters.Impl) Application.put_env( :postoffice, @@ -15,3 +16,9 @@ Application.put_env( :pubsub_consumer_impl, Postoffice.Adapters.PubsubMock ) + +Application.put_env( + :postoffice, + :rescuer_client, + Postoffice.Rescuer.Adapters.HttpMock +)