From dc72a8809559395b505991d705c93cc301b98a67 Mon Sep 17 00:00:00 2001 From: kamalogudah Date: Sun, 7 Jun 2020 17:20:17 +0300 Subject: [PATCH 1/6] Work on Refactoring Tests --- lib/at_ex.ex | 2 +- lib/at_ex/gateway/Sms/bulk.ex | 64 ++++++ lib/at_ex/gateway/Sms/premium.ex | 196 +++++++++++++++++ lib/at_ex/gateway/base_http.ex | 1 + lib/at_ex/gateway/sms.ex | 208 +----------------- lib/at_ex/gateway/voice.ex | 3 + test/at_ex/gateway/Sms/bulk_test.exs | 101 +++++++++ .../{sms_test.exs => Sms/premium_test.exs} | 67 ++---- 8 files changed, 384 insertions(+), 258 deletions(-) create mode 100644 lib/at_ex/gateway/Sms/bulk.ex create mode 100644 lib/at_ex/gateway/Sms/premium.ex create mode 100644 lib/at_ex/gateway/voice.ex create mode 100644 test/at_ex/gateway/Sms/bulk_test.exs rename test/at_ex/gateway/{sms_test.exs => Sms/premium_test.exs} (79%) diff --git a/lib/at_ex.ex b/lib/at_ex.ex index 9852761..5067f53 100644 --- a/lib/at_ex.ex +++ b/lib/at_ex.ex @@ -97,7 +97,7 @@ defmodule AtEx do } }} """ - defdelegate send_sms(map), to: Sms + defdelegate send_sms(map), to: Sms.Bulk @doc """ diff --git a/lib/at_ex/gateway/Sms/bulk.ex b/lib/at_ex/gateway/Sms/bulk.ex new file mode 100644 index 0000000..a676596 --- /dev/null +++ b/lib/at_ex/gateway/Sms/bulk.ex @@ -0,0 +1,64 @@ +defmodule AtEx.Gateway.Sms.Bulk do + @moduledoc """ + This module holds the implementation for the HTTP Gateway that runs calls against the Africas Talking API + SMS endpoint, use it to POST and GET requests to the SMS endpoint + """ + use AtEx.Gateway.Base, url: "https://api.sandbox.africastalking.com/version1" + + @doc """ + This function builds and runs a post request to send an SMS via the Africa's talking SMS endpoint, this + function accepts a map of parameters that should always contain the `to` address and the `message` to be + sent + + ## Parameters + attrs: - a map containing a `to` and `message` key optionally it may also contain `from`, bulk_sms, enqueue, key_word + link_id and retry_hours keys, see the docs at https://build.at-labs.io/docs/sms%2Fsending for how to use these keys + """ + @spec send_sms(map()) :: {:ok, term()} | {:error, term()} + def send_sms(attrs) do + username = Application.get_env(:at_ex, :username) + + params = + attrs + |> Map.put(:username, username) + + with {:ok, %{status: 201} = res} <- post("/messaging", params) do + {:ok, Jason.decode!(res.body)} + else + {:ok, val} -> + {:error, %{status: val.status, message: val.body}} + + {:error, message} -> + {:error, message} + end + end + + @doc """ + This function makes a get request to fetch an SMS via the Africa's talking SMS endpoint, this + function accepts an map of parameters that optionally accepts `lastReceivedId` of the message. + sent + + ## Parameters + attrs: - an empty map or a map containing optionally `lastReceivedId` of the message to be fetched, see the docs at https://build.at-labs.io/docs/sms%2Ffetch_messages for how to use these keys + """ + + @spec fetch_sms(map()) :: {:error, any()} | {:ok, any()} + def fetch_sms(attrs) do + username = Application.get_env(:at_ex, :username) + + params = + attrs + |> Map.put(:username, username) + |> Map.to_list() + + with {:ok, %{status: 200} = res} <- get("/messaging", query: params) do + {:ok, Jason.decode!(res.body)} + else + {:ok, val} -> + {:error, %{status: val.status, message: val.body}} + + {:error, message} -> + {:error, message} + end + end +end diff --git a/lib/at_ex/gateway/Sms/premium.ex b/lib/at_ex/gateway/Sms/premium.ex new file mode 100644 index 0000000..800e984 --- /dev/null +++ b/lib/at_ex/gateway/Sms/premium.ex @@ -0,0 +1,196 @@ +defmodule AtEx.Gateway.Sms.PremiumSubscriptions do + use AtEx.Gateway.Base, url: "https://api.sandbox.africastalking.com/version1" + # Checkout token endpoints + @live_token_url "https://api.africastalking.com/checkout/token" + @sandbox_token_url "https://api.sandbox.africastalking.com/checkout/token" + + # Using this system for delivery of which URL to use (sandbox or live) + # determined by whether we ar in production or development or test + # Selection of the live URL can be forced by setting an environment + # variable FORCE_TOKEN_LIVE=YES + defp get_token_url do + cond do + Mix.env() == :prod -> @live_token_url + System.get_env("FORCE_TOKEN_LIVE") == "YES" -> @live_token_url + true -> @sandbox_token_url + end + end + + @doc """ + This function fetches the checkout token from the checkout token endpoint + THIS IS DIFFERENT THAN THE SMS ENDPOINT!! + + phone_number: - a string representing a valid phone number in EL64 + (+ with all non digit characters removed) + [NOTE: function does not verify the phone number is in any way correct + before sending to the endpoint.] + + Returns a success tuple: {:ok, }} or {:error, } + """ + + @spec generate_checkout_token(String.t()) :: {:error, any()} | {:ok, any()} + def generate_checkout_token(phone_number) do + # Can't use the default client, since we have a different URL + token_middleware = [ + {Tesla.Middleware.BaseUrl, get_token_url()}, + Tesla.Middleware.FormUrlencoded + ] + + with {:ok, %Tesla.Env{body: body, status: 201}} <- + Tesla.post(Tesla.client(token_middleware), "/create", %{phoneNumber: phone_number}), + {:ok, body} <- Jason.decode(body) do + case body do + # Only success case + %{"description" => "Success", "token" => token} -> + {:ok, token} + + %{"description" => "Failure", "token" => message} -> + {:error, "Failure - #{message}"} + + %{"description" => message, "token" => "None"} -> + {:error, "Failure - #{message}"} + + _ -> + {:error, "Failure - Unknown Error"} + end + else + {:ok, %Tesla.Env{status: status, body: body}} -> + {:error, "#{status} - #{body}"} + + {:error, reason} -> + {:error, reason} + end + end + + + + @doc """ + This function makes a post request to subscribe to premium sms content via the Africa's talking subscription endpoint, this + function accepts an map of parameters. + sent + + ## Parameters + attrs: - a map containing: + - `shortCode` - premium short code mapped to your account + - `keyword` - premium keyword under the above short code mapped to your account + - `phoneNumber` - phone number to be subscribed + + ## Example + iex> AtEx.Gateway.Sms.create_subscription(%{phoneNumber: "+2541231111"}) + {:ok, result} + """ + @spec create_subscription(map()) :: {:error, any()} | {:ok, any()} + def create_subscription(%{phoneNumber: phone_number} = attrs) do + username = Application.get_env(:at_ex, :username) + shortcode = Application.get_env(:at_ex, :short_code) + keyword = Application.get_env(:at_ex, :keyword) + + case generate_checkout_token(phone_number) do + {:ok, token} -> + params = + attrs + |> Map.put(:username, username) + |> Map.put(:shortCode, shortcode) + |> Map.put(:keyword, keyword) + |> Map.put(:checkoutToken, token) + + IO.inspect(params) + + + with {:ok, %{status: 201} = res} <- post("/subscription/create", params) do + {:ok, Jason.decode!(res.body)} + else + {:ok, val} -> + {:error, %{status: val.status, message: val.body}} + + {:error, message} -> + {:error, message} + end + + {:error, reason} -> + {:error, reason} + end + end + + @doc """ + This function makes a GET request to fetch premium sms subscriptions via the Africa's talking subscription endpoint, this + function accepts an map of parameters. + sent + + ## Parameters + attrs: - a map containing: + - `shortCode` - premium short code mapped to your account + - `keyword` - premium keyword under the above short code mapped to your account + - `lastReceivedId` - (optional) ID of the subscription you believe to be your last. Set it to 0 to for the first time. + + ## Example + iex> AtEx.Gateway.Sms.create_subscription(%{ + ...> shortCode: "1234", + ...> keyword: "keyword", + ...> }) + {:ok, result} + """ + @spec fetch_subscriptions() :: {:error, any()} | {:ok, any()} + def fetch_subscriptions() do + username = Application.get_env(:at_ex, :username) + shortcode = Application.get_env(:at_ex, :short_code) + keyword = Application.get_env(:at_ex, :keyword) + + params = + %{} + |> Map.put(:username, username) + |> Map.put(:shortCode, shortcode) + |> Map.put(:keyword, keyword) + + + with {:ok, %{status: 200} = res} <- get("/subscription", query: params) do + {:ok, Jason.decode!(res.body)} + else + {:ok, val} -> + {:error, %{status: val.status, message: val.body}} + + {:error, message} -> + {:error, message} + end + end + + @doc """ + This function makes a POST request to delete sms subscriptions via the Africa's talking subscription endpoint, this + function accepts an map of parameters: + + ## Parameters + attrs: - a map containing: + - `shortCode` - premium short code mapped to your account + - `keyword` - premium keyword under the above short code mapped to your account + - `phoneNumber` - phone number to be unsubscribed + + ## Example + iex> AtEx.Gateway.Sms.delete_subscription(%{ phoneNumber: "+2541231111"}) + {:ok, %{"description" => "Succeeded", "status" => "Success"}} + """ + @spec delete_subscription(map()) :: {:error, any()} | {:ok, any()} + def delete_subscription(attrs) do + username = Application.get_env(:at_ex, :username) + shortcode = Application.get_env(:at_ex, :short_code) + keyword = Application.get_env(:at_ex, :keyword) + + params = + attrs + |> Map.put(:username, username) + |> Map.put(:shortCode, shortcode) + |> Map.put(:keyword, keyword) + + + with {:ok, %{status: 201} = res} <- post("/subscription/delete", params) do + {:ok, Jason.decode!(res.body)} + else + {:ok, val} -> + {:error, %{status: val.status, message: val.body}} + + {:error, message} -> + {:error, message} + end + end + +end + diff --git a/lib/at_ex/gateway/base_http.ex b/lib/at_ex/gateway/base_http.ex index 1c2e6ee..90034d3 100644 --- a/lib/at_ex/gateway/base_http.ex +++ b/lib/at_ex/gateway/base_http.ex @@ -56,6 +56,7 @@ defmodule AtEx.Gateway.Base do {:error, val} end end + end end end diff --git a/lib/at_ex/gateway/sms.ex b/lib/at_ex/gateway/sms.ex index f3b63f7..bb20b70 100644 --- a/lib/at_ex/gateway/sms.ex +++ b/lib/at_ex/gateway/sms.ex @@ -33,213 +33,7 @@ defmodule AtEx.Gateway.Sms do end end - @doc """ - This function makes a get request to fetch an SMS via the Africa's talking SMS endpoint, this - function accepts an map of parameters that optionally accepts `lastReceivedId` of the message. - sent - - ## Parameters - attrs: - an empty map or a map containing optionally `lastReceivedId` of the message to be fetched, see the docs at https://build.at-labs.io/docs/sms%2Ffetch_messages for how to use these keys - """ - - @spec fetch_sms(map()) :: {:error, any()} | {:ok, any()} - def fetch_sms(attrs) do - username = Application.get_env(:at_ex, :username) - - params = - attrs - |> Map.put(:username, username) - |> Map.to_list() - - with {:ok, %{status: 200} = res} <- get("/messaging", query: params) do - {:ok, Jason.decode!(res.body)} - else - {:ok, val} -> - {:error, %{status: val.status, message: val.body}} - - {:error, message} -> - {:error, message} - end - end - - # Checkout token endpoints - @live_token_url "https://api.africastalking.com/checkout/token" - @sandbox_token_url "https://api.sandbox.africastalking.com/checkout/token" - - # Using this system for delivery of which URL to use (sandbox or live) - # determined by whether we ar in production or development or test - # Selection of the live URL can be forced by setting an environment - # variable FORCE_TOKEN_LIVE=YES - defp get_token_url do - cond do - Mix.env() == :prod -> @live_token_url - System.get_env("FORCE_TOKEN_LIVE") == "YES" -> @live_token_url - true -> @sandbox_token_url - end - end - - @doc """ - This function fetches the checkout token from the checkout token endpoint - THIS IS DIFFERENT THAN THE SMS ENDPOINT!! - - phone_number: - a string representing a valid phone number in EL64 - (+ with all non digit characters removed) - [NOTE: function does not verify the phone number is in any way correct - before sending to the endpoint.] - - Returns a success tuple: {:ok, }} or {:error, } - """ - - @spec generate_checkout_token(String.t()) :: {:error, any()} | {:ok, any()} - def generate_checkout_token(phone_number) do - # Can't use the default client, since we have a different URL - token_middleware = [ - {Tesla.Middleware.BaseUrl, get_token_url()}, - Tesla.Middleware.FormUrlencoded - ] - - with {:ok, %Tesla.Env{body: body, status: 201}} <- - Tesla.post(Tesla.client(token_middleware), "/create", %{phoneNumber: phone_number}), - {:ok, body} <- Jason.decode(body) do - case body do - # Only success case - %{"description" => "Success", "token" => token} -> - {:ok, token} - - %{"description" => "Failure", "token" => message} -> - {:error, "Failure - #{message}"} - - %{"description" => message, "token" => "None"} -> - {:error, "Failure - #{message}"} - - _ -> - {:error, "Failure - Unknown Error"} - end - else - {:ok, %Tesla.Env{status: status, body: body}} -> - {:error, "#{status} - #{body}"} - - {:error, reason} -> - {:error, reason} - end - end - - @doc """ - This function makes a post request to subscribe to premium sms content via the Africa's talking subscription endpoint, this - function accepts an map of parameters. - sent - - ## Parameters - attrs: - a map containing: - - `shortCode` - premium short code mapped to your account - - `keyword` - premium keyword under the above short code mapped to your account - - `phoneNumber` - phone number to be subscribed - - ## Example - iex> AtEx.Gateway.Sms.create_subscription(%{ - ...> shortCode: "1234", - ...> keyword: "keyword", - ...> phoneNumber: "+2541231111" - ...> }) - {:ok, result} - """ - @spec create_subscription(map()) :: {:error, any()} | {:ok, any()} - def create_subscription(%{phoneNumber: phone_number} = attrs) do - username = Application.get_env(:at_ex, :username) + - case generate_checkout_token(phone_number) do - {:ok, token} -> - params = - attrs - |> Map.put(:username, username) - |> Map.put(:checkoutToken, token) - with {:ok, %{status: 201} = res} <- post("/subscription/create", params) do - {:ok, Jason.decode!(res.body)} - else - {:ok, val} -> - {:error, %{status: val.status, message: val.body}} - - {:error, message} -> - {:error, message} - end - - {:error, reason} -> - {:error, reason} - end - end - - @doc """ - This function makes a GET request to fetch premium sms subscriptions via the Africa's talking subscription endpoint, this - function accepts an map of parameters. - sent - - ## Parameters - attrs: - a map containing: - - `shortCode` - premium short code mapped to your account - - `keyword` - premium keyword under the above short code mapped to your account - - `lastReceivedId` - (optional) ID of the subscription you believe to be your last. Set it to 0 to for the first time. - - ## Example - iex> AtEx.Gateway.Sms.create_subscription(%{ - ...> shortCode: "1234", - ...> keyword: "keyword", - ...> }) - {:ok, result} - """ - @spec fetch_subscriptions(map()) :: {:error, any()} | {:ok, any()} - def fetch_subscriptions(attrs) do - username = Application.get_env(:at_ex, :username) - - params = - attrs - |> Map.put(:username, username) - - with {:ok, %{status: 200} = res} <- get("/subscription", query: params) do - {:ok, Jason.decode!(res.body)} - else - {:ok, val} -> - {:error, %{status: val.status, message: val.body}} - - {:error, message} -> - {:error, message} - end - end - - @doc """ - This function makes a POST request to delete sms subscriptions via the Africa's talking subscription endpoint, this - function accepts an map of parameters: - - ## Parameters - attrs: - a map containing: - - `shortCode` - premium short code mapped to your account - - `keyword` - premium keyword under the above short code mapped to your account - - `phoneNumber` - phone number to be unsubscribed - - ## Example - iex> AtEx.Gateway.Sms.delete_subscription(%{ - ...> shortCode: "1234", - ...> keyword: "keyword", - ...> phoneNumber: "+2541231111" - ...> }) - {:ok, %{"description" => "Succeeded", "status" => "Success"}} - """ - @spec delete_subscription(map()) :: {:error, any()} | {:ok, any()} - def delete_subscription(attrs) do - username = Application.get_env(:at_ex, :username) - - params = - attrs - |> Map.put(:username, username) - - with {:ok, %{status: 201} = res} <- post("/subscription/delete", params) do - {:ok, Jason.decode!(res.body)} - else - {:ok, val} -> - {:error, %{status: val.status, message: val.body}} - - {:error, message} -> - {:error, message} - end - end end diff --git a/lib/at_ex/gateway/voice.ex b/lib/at_ex/gateway/voice.ex new file mode 100644 index 0000000..298584d --- /dev/null +++ b/lib/at_ex/gateway/voice.ex @@ -0,0 +1,3 @@ +defmodule AtEx.Gateway.Voice do + +end diff --git a/test/at_ex/gateway/Sms/bulk_test.exs b/test/at_ex/gateway/Sms/bulk_test.exs new file mode 100644 index 0000000..1b354f0 --- /dev/null +++ b/test/at_ex/gateway/Sms/bulk_test.exs @@ -0,0 +1,101 @@ +defmodule AtEx.Gateway.Sms.BulkTest do + @moduledoc """ + This module holds unit tests for the functions in the SMS gateway + """ + use ExUnit.Case + + alias AtEx.Gateway.Sms.Bulk + + @attr "username=" + + + + setup do + Tesla.Mock.mock(fn + %{method: :post, body: @attr} -> + %Tesla.Env{ + status: 400, + body: "Request is missing required form field 'to'" + } + + %{method: :post} -> + %Tesla.Env{ + status: 201, + body: + Jason.encode!(%{ + "SMSMessageData" => %{ + "Message" => "Sent to 1/1 Total Cost: ZAR 0.1124", + "Recipients" => [ + %{ + "cost" => "KES 0.8000", + "messageId" => "ATXid_a584c3fd712a00b7bce3c4b7b552ac56", + "number" => "+254728833181", + "status" => "Success", + "statusCode" => 101 + } + ] + } + }) + } + + %{method: :get} -> + %Tesla.Env{ + status: 200, + body: + Jason.encode!(%{ + "SMSMessageData" => %{ + "Messages" => [ + %{ + linkId: "SampleLinkId123", + text: "Hello", + to: "28901", + id: 15071, + date: "2018-03-19T08:34:18.445Z", + from: "+254711XXXYYY" + } + ] + } + }) + } + end) + + :ok + end + + describe "Sms Gateway" do + test "sends_sms/1 should send sms with required parameters" do + # make message details + send_details = %{to: "+254728833181", message: "new music"} + + # run details through our code + {:ok, result} = Bulk.send_sms(send_details) + + # assert our code gives us a single element list of messages + [msg] = result["SMSMessageData"]["Recipients"] + + # assert that message details correspond to details of set up message + assert msg["number"] == send_details.to + end + + test "sends_sms/1 should error out without phone number parameter" do + # run details through our code + {:error, result} = Bulk.send_sms(%{}) + + "Request is missing required form field 'to'" = result.message + + 400 = result.status + end + + test "fetches sms collects data with correct params" do + send_details = %{username: "sandbox"} + + # run details through our code + {:ok, result} = Bulk.fetch_sms(send_details) + # assert our code gives us a single element list of messages + [msg] = result["SMSMessageData"]["Messages"] + + # assert that message details correspond to details of set up message + assert msg["text"] == "Hello" + end + end +end diff --git a/test/at_ex/gateway/sms_test.exs b/test/at_ex/gateway/Sms/premium_test.exs similarity index 79% rename from test/at_ex/gateway/sms_test.exs rename to test/at_ex/gateway/Sms/premium_test.exs index 003f95f..41f81ea 100644 --- a/test/at_ex/gateway/sms_test.exs +++ b/test/at_ex/gateway/Sms/premium_test.exs @@ -4,11 +4,11 @@ defmodule AtEx.Gateway.SmsTest do """ use ExUnit.Case - alias AtEx.Gateway.Sms + alias AtEx.Gateway.Sms.PremiumSubscriptions @attr "username=" - # Endpoint for getting the checkout token + # Endpoint for getting the checkout token # Unless overridden, we will always use the sandbox URL during test # If overridden, all the checkout token tests will fail. @checkout_token_url "https://api.sandbox.africastalking.com/checkout/token/create" @@ -72,43 +72,13 @@ defmodule AtEx.Gateway.SmsTest do :ok end - describe "Sms Gateway" do - test "sends_sms/1 should send sms with required parameters" do - # make message details - send_details = %{to: "+254728833181", message: "new music"} + describe "Sms Gateway/Premium" do - # run details through our code - {:ok, result} = Sms.send_sms(send_details) - # assert our code gives us a single element list of messages - [msg] = result["SMSMessageData"]["Recipients"] - # assert that message details correspond to details of set up message - assert msg["number"] == send_details.to - end - - test "sends_sms/1 should error out without phone number parameter" do - # run details through our code - {:error, result} = Sms.send_sms(%{}) - "Request is missing required form field 'to'" = result.message - - 400 = result.status - end - test "fetches sms collects data with correct params" do - send_details = %{username: "sandbox"} - - # run details through our code - {:ok, result} = Sms.fetch_sms(send_details) - # assert our code gives us a single element list of messages - [msg] = result["SMSMessageData"]["Messages"] - - # assert that message details correspond to details of set up message - assert msg["text"] == "Hello" - end - - # Checkout token tests need their own mock calls, or we would need + # Checkout token tests need their own mock calls, or we would need # separate phone numbers for each test. This way values can be # reused. @@ -125,7 +95,7 @@ defmodule AtEx.Gateway.SmsTest do } end) - assert {:ok, token} = Sms.generate_checkout_token(@checkout_token_phonenumber) + assert {:ok, token} = PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) assert token == @checkout_token end @@ -142,7 +112,7 @@ defmodule AtEx.Gateway.SmsTest do } end) - assert {:error, message} = Sms.generate_checkout_token(@checkout_token_phonenumber) + assert {:error, message} = PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) assert message == "Failure - Error Message" end @@ -156,7 +126,7 @@ defmodule AtEx.Gateway.SmsTest do } end) - assert {:error, message} = Sms.generate_checkout_token(@checkout_token_phonenumber) + assert {:error, message} = PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) assert message = "500 - Error Message" end @@ -173,7 +143,7 @@ defmodule AtEx.Gateway.SmsTest do } end) - assert {:error, message} = Sms.generate_checkout_token(@checkout_token_phonenumber) + assert {:error, message} = PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) assert message == "Failure - Potential Error Message" end @@ -202,7 +172,7 @@ defmodule AtEx.Gateway.SmsTest do end) assert {:ok, response} = - Sms.create_subscription(%{ + PremiumSubscriptions.create_subscription(%{ phoneNumber: @checkout_token_phonenumber, shortCode: "12345", keyword: "music" @@ -235,7 +205,7 @@ defmodule AtEx.Gateway.SmsTest do end) assert {:ok, response} = - Sms.create_subscription(%{ + PremiumSubscriptions.create_subscription(%{ phoneNumber: @checkout_token_phonenumber, shortCode: "99999", keyword: "music" @@ -255,7 +225,9 @@ defmodule AtEx.Gateway.SmsTest do %{ "date" => "2019-10-05T18:57:08.000", "id" => 2_007_800, - "phoneNumber" => "+254711123123" + "phoneNumber" => "+254711123123", + "shortCode" => "12345", + "keyword" => "music" } ] }) @@ -263,10 +235,7 @@ defmodule AtEx.Gateway.SmsTest do end) assert {:ok, response} = - Sms.fetch_subscriptions(%{ - shortCode: "12345", - keyword: "music" - }) + PremiumSubscriptions.fetch_subscriptions() assert Enum.count(response["responses"]) == 1 end @@ -281,9 +250,7 @@ defmodule AtEx.Gateway.SmsTest do end) assert {:error, response} = - Sms.fetch_subscriptions(%{ - keyword: "music" - }) + PremiumSubscriptions.fetch_subscriptions() assert response.message == "Request is missing required query parameter 'shortCode'" end @@ -302,7 +269,7 @@ defmodule AtEx.Gateway.SmsTest do end) assert {:ok, response} = - Sms.delete_subscription(%{ + PremiumSubscriptions.delete_subscription(%{ phoneNumber: @checkout_token_phonenumber, shortCode: "12345", keyword: "music" @@ -326,7 +293,7 @@ defmodule AtEx.Gateway.SmsTest do end) assert {:ok, response} = - Sms.delete_subscription(%{ + PremiumSubscriptions.delete_subscription(%{ phoneNumber: @checkout_token_phonenumber, shortCode: "99900", keyword: "music" From 12edccb5bd897faf70baa55b47492d5eca52d01f Mon Sep 17 00:00:00 2001 From: kamalogudah Date: Wed, 10 Jun 2020 14:16:33 +0300 Subject: [PATCH 2/6] Refactor SMS functionality --- lib/at_ex.ex | 64 ++++++++++++------------- lib/at_ex/gateway/Sms/premium.ex | 23 +++------ lib/at_ex/gateway/base_http.ex | 1 - lib/at_ex/gateway/sms.ex | 4 -- lib/at_ex/gateway/voice.ex | 1 - lib/at_ex/sms/sms.ex | 1 - test/at_ex/gateway/Sms/bulk_test.exs | 2 - test/at_ex/gateway/Sms/premium_test.exs | 26 +++++----- 8 files changed, 51 insertions(+), 71 deletions(-) delete mode 100644 lib/at_ex/sms/sms.ex diff --git a/lib/at_ex.ex b/lib/at_ex.ex index 5067f53..0568fa5 100644 --- a/lib/at_ex.ex +++ b/lib/at_ex.ex @@ -36,21 +36,21 @@ defmodule AtEx do ## Examples - iex> AtEx.send_airtime(%{recipients: [%{phone_number: "+254721978097", amount: "KES 50"}]}) - {:ok, - %{ - "errorMessage" => "None", - "numSent" => 1, - "responses" => [ - %{ - "amount" => "KES 50.0000", - "discount" => "KES 2.0000", - "errorMessage" => "None", - "phoneNumber" => "+254721978097", - "requestId" => "ATQid_630557e624c70f2b0d2c5e90ebc282bb", - "status" => "Sent" - } - ], + iex> AtEx.send_airtime(%{recipients: [%{phone_number: "+254721978097", amount: "KES 50"}]}) + {:ok, + %{ + "errorMessage" => "None", + "numSent" => 1, + "responses" => [ + %{ + "amount" => "KES 50.0000", + "discount" => "KES 2.0000", + "errorMessage" => "None", + "phoneNumber" => "+254721978097", + "requestId" => "ATQid_630557e624c70f2b0d2c5e90ebc282bb", + "status" => "Sent" + } + ], "totalAmount" => "ZAR 7.0277", "totalDiscount" => "ZAR 0.2811" }} @@ -76,26 +76,26 @@ defmodule AtEx do sent ## Parameters - - map: a map containing a `to` and `message` key optionally it may also contain `from`, bulk_sms, enqueue, key_word - link_id and retry_hours keys, see the docs at https://build.at-labs.io/docs/sms%2Fsending for how to use these keys + - map: a map containing a `to` and `message` key optionally it may also contain `from`, bulk_sms, enqueue, key_word + link_id and retry_hours keys, see the docs at https://build.at-labs.io/docs/sms%2Fsending for how to use these keys ## Examples - iex> AtEx.send_sms(%{to: "+254721978097", message: "Howdy"}) - {:ok, + iex> AtEx.send_sms(%{to: "+254721978097", message: "Howdy"}) + {:ok, + %{ + "SMSMessageData" => %{ + "Message" => "Sent to 1/1 Total Cost: ZAR 0.1124", + "Recipients" => [ %{ - "SMSMessageData" => %{ - "Message" => "Sent to 1/1 Total Cost: ZAR 0.1124", - "Recipients" => [ - %{ - "cost" => "KES 0.8000", - "messageId" => "ATXid_96e52a761a82c1bad58e885109224aad", - "number" => "+254721978097", - "status" => "Success", - "statusCode" => 101 - } - ] + "cost" => "KES 0.8000", + "messageId" => "ATXid_96e52a761a82c1bad58e885109224aad", + "number" => "+254721978097", + "status" => "Success", + "statusCode" => 101 } - }} + ] + } + }} """ defdelegate send_sms(map), to: Sms.Bulk @@ -120,5 +120,5 @@ defmodule AtEx do """ - defdelegate fetch_sms(map), to: Sms + defdelegate fetch_sms(map), to: Sms.Bulk end diff --git a/lib/at_ex/gateway/Sms/premium.ex b/lib/at_ex/gateway/Sms/premium.ex index 800e984..f91a6c5 100644 --- a/lib/at_ex/gateway/Sms/premium.ex +++ b/lib/at_ex/gateway/Sms/premium.ex @@ -1,6 +1,6 @@ defmodule AtEx.Gateway.Sms.PremiumSubscriptions do use AtEx.Gateway.Base, url: "https://api.sandbox.africastalking.com/version1" - # Checkout token endpoints + # Checkout token endpoints @live_token_url "https://api.africastalking.com/checkout/token" @sandbox_token_url "https://api.sandbox.africastalking.com/checkout/token" @@ -62,8 +62,6 @@ defmodule AtEx.Gateway.Sms.PremiumSubscriptions do end end - - @doc """ This function makes a post request to subscribe to premium sms content via the Africa's talking subscription endpoint, this function accepts an map of parameters. @@ -76,7 +74,7 @@ defmodule AtEx.Gateway.Sms.PremiumSubscriptions do - `phoneNumber` - phone number to be subscribed ## Example - iex> AtEx.Gateway.Sms.create_subscription(%{phoneNumber: "+2541231111"}) + iex> AtEx.Gateway.Sms.PremiumSubscriptions.create_subscription(%{phoneNumber: "+2541231111"}) {:ok, result} """ @spec create_subscription(map()) :: {:error, any()} | {:ok, any()} @@ -94,9 +92,6 @@ defmodule AtEx.Gateway.Sms.PremiumSubscriptions do |> Map.put(:keyword, keyword) |> Map.put(:checkoutToken, token) - IO.inspect(params) - - with {:ok, %{status: 201} = res} <- post("/subscription/create", params) do {:ok, Jason.decode!(res.body)} else @@ -124,11 +119,11 @@ defmodule AtEx.Gateway.Sms.PremiumSubscriptions do - `lastReceivedId` - (optional) ID of the subscription you believe to be your last. Set it to 0 to for the first time. ## Example - iex> AtEx.Gateway.Sms.create_subscription(%{ - ...> shortCode: "1234", - ...> keyword: "keyword", - ...> }) - {:ok, result} + iex> AtEx.Gateway.Sms.create_subscription(%{ + ...> shortCode: "1234", + ...> keyword: "keyword", + ...> }) + {:ok, result} """ @spec fetch_subscriptions() :: {:error, any()} | {:ok, any()} def fetch_subscriptions() do @@ -142,7 +137,6 @@ defmodule AtEx.Gateway.Sms.PremiumSubscriptions do |> Map.put(:shortCode, shortcode) |> Map.put(:keyword, keyword) - with {:ok, %{status: 200} = res} <- get("/subscription", query: params) do {:ok, Jason.decode!(res.body)} else @@ -180,7 +174,6 @@ defmodule AtEx.Gateway.Sms.PremiumSubscriptions do |> Map.put(:shortCode, shortcode) |> Map.put(:keyword, keyword) - with {:ok, %{status: 201} = res} <- post("/subscription/delete", params) do {:ok, Jason.decode!(res.body)} else @@ -191,6 +184,4 @@ defmodule AtEx.Gateway.Sms.PremiumSubscriptions do {:error, message} end end - end - diff --git a/lib/at_ex/gateway/base_http.ex b/lib/at_ex/gateway/base_http.ex index 90034d3..1c2e6ee 100644 --- a/lib/at_ex/gateway/base_http.ex +++ b/lib/at_ex/gateway/base_http.ex @@ -56,7 +56,6 @@ defmodule AtEx.Gateway.Base do {:error, val} end end - end end end diff --git a/lib/at_ex/gateway/sms.ex b/lib/at_ex/gateway/sms.ex index bb20b70..e12960b 100644 --- a/lib/at_ex/gateway/sms.ex +++ b/lib/at_ex/gateway/sms.ex @@ -32,8 +32,4 @@ defmodule AtEx.Gateway.Sms do {:error, message} end end - - - - end diff --git a/lib/at_ex/gateway/voice.ex b/lib/at_ex/gateway/voice.ex index 298584d..e1c292c 100644 --- a/lib/at_ex/gateway/voice.ex +++ b/lib/at_ex/gateway/voice.ex @@ -1,3 +1,2 @@ defmodule AtEx.Gateway.Voice do - end diff --git a/lib/at_ex/sms/sms.ex b/lib/at_ex/sms/sms.ex deleted file mode 100644 index 8b13789..0000000 --- a/lib/at_ex/sms/sms.ex +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/at_ex/gateway/Sms/bulk_test.exs b/test/at_ex/gateway/Sms/bulk_test.exs index 1b354f0..fc213de 100644 --- a/test/at_ex/gateway/Sms/bulk_test.exs +++ b/test/at_ex/gateway/Sms/bulk_test.exs @@ -8,8 +8,6 @@ defmodule AtEx.Gateway.Sms.BulkTest do @attr "username=" - - setup do Tesla.Mock.mock(fn %{method: :post, body: @attr} -> diff --git a/test/at_ex/gateway/Sms/premium_test.exs b/test/at_ex/gateway/Sms/premium_test.exs index 41f81ea..17edf21 100644 --- a/test/at_ex/gateway/Sms/premium_test.exs +++ b/test/at_ex/gateway/Sms/premium_test.exs @@ -73,15 +73,9 @@ defmodule AtEx.Gateway.SmsTest do end describe "Sms Gateway/Premium" do - - - - - # Checkout token tests need their own mock calls, or we would need # separate phone numbers for each test. This way values can be # reused. - test "fetch checkout token successfully" do Tesla.Mock.mock(fn %{method: :post, url: @checkout_token_url, body: @checkout_token_query} -> @@ -95,7 +89,9 @@ defmodule AtEx.Gateway.SmsTest do } end) - assert {:ok, token} = PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) + assert {:ok, token} = + PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) + assert token == @checkout_token end @@ -112,7 +108,8 @@ defmodule AtEx.Gateway.SmsTest do } end) - assert {:error, message} = PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) + assert {:error, message} = + PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) assert message == "Failure - Error Message" end @@ -126,7 +123,9 @@ defmodule AtEx.Gateway.SmsTest do } end) - assert {:error, message} = PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) + assert {:error, message} = + PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) + assert message = "500 - Error Message" end @@ -143,7 +142,8 @@ defmodule AtEx.Gateway.SmsTest do } end) - assert {:error, message} = PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) + assert {:error, message} = + PremiumSubscriptions.generate_checkout_token(@checkout_token_phonenumber) assert message == "Failure - Potential Error Message" end @@ -234,8 +234,7 @@ defmodule AtEx.Gateway.SmsTest do } end) - assert {:ok, response} = - PremiumSubscriptions.fetch_subscriptions() + assert {:ok, response} = PremiumSubscriptions.fetch_subscriptions() assert Enum.count(response["responses"]) == 1 end @@ -249,8 +248,7 @@ defmodule AtEx.Gateway.SmsTest do } end) - assert {:error, response} = - PremiumSubscriptions.fetch_subscriptions() + assert {:error, response} = PremiumSubscriptions.fetch_subscriptions() assert response.message == "Request is missing required query parameter 'shortCode'" end From ada5082d49fc12d63170b4453a76bcb96871d36a Mon Sep 17 00:00:00 2001 From: Tee22 Date: Sun, 14 Jun 2020 18:28:29 +0300 Subject: [PATCH 3/6] deleted (sms.ex) has similar existing functionalities with that in bulk.ex --- lib/at_ex/gateway/sms.ex | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 lib/at_ex/gateway/sms.ex diff --git a/lib/at_ex/gateway/sms.ex b/lib/at_ex/gateway/sms.ex deleted file mode 100644 index e12960b..0000000 --- a/lib/at_ex/gateway/sms.ex +++ /dev/null @@ -1,35 +0,0 @@ -defmodule AtEx.Gateway.Sms do - @moduledoc """ - This module holds the implementation for the HTTP Gateway that runs calls against the Africas Talking API - SMS endpoint, use it to POST and GET requests to the SMS endpoint - """ - use AtEx.Gateway.Base, url: "https://api.sandbox.africastalking.com/version1" - - @doc """ - This function builds and runs a post request to send an SMS via the Africa's talking SMS endpoint, this - function accepts a map of parameters that should always contain the `to` address and the `message` to be - sent - - ## Parameters - attrs: - a map containing a `to` and `message` key optionally it may also contain `from`, bulk_sms, enqueue, key_word - link_id and retry_hours keys, see the docs at https://build.at-labs.io/docs/sms%2Fsending for how to use these keys - """ - @spec send_sms(map()) :: {:ok, term()} | {:error, term()} - def send_sms(attrs) do - username = Application.get_env(:at_ex, :username) - - params = - attrs - |> Map.put(:username, username) - - with {:ok, %{status: 201} = res} <- post("/messaging", params) do - {:ok, Jason.decode!(res.body)} - else - {:ok, val} -> - {:error, %{status: val.status, message: val.body}} - - {:error, message} -> - {:error, message} - end - end -end From 8ffecefd73cbf9906816e7afe5adc6beecd2447c Mon Sep 17 00:00:00 2001 From: Tee22 Date: Sun, 14 Jun 2020 20:33:42 +0300 Subject: [PATCH 4/6] replace Jason.decode! with the existing in --- lib/at_ex/gateway/Sms/bulk.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/at_ex/gateway/Sms/bulk.ex b/lib/at_ex/gateway/Sms/bulk.ex index a676596..91f2ac4 100644 --- a/lib/at_ex/gateway/Sms/bulk.ex +++ b/lib/at_ex/gateway/Sms/bulk.ex @@ -23,7 +23,7 @@ defmodule AtEx.Gateway.Sms.Bulk do |> Map.put(:username, username) with {:ok, %{status: 201} = res} <- post("/messaging", params) do - {:ok, Jason.decode!(res.body)} + process_result(res.body) else {:ok, val} -> {:error, %{status: val.status, message: val.body}} @@ -35,7 +35,7 @@ defmodule AtEx.Gateway.Sms.Bulk do @doc """ This function makes a get request to fetch an SMS via the Africa's talking SMS endpoint, this - function accepts an map of parameters that optionally accepts `lastReceivedId` of the message. + function accepts a map of parameters that optionally accepts `lastReceivedId` of the message. sent ## Parameters From 57273673f1d4938146455120f95fbbd4f084bc25 Mon Sep 17 00:00:00 2001 From: Tee22 Date: Sat, 20 Jun 2020 15:36:18 +0300 Subject: [PATCH 5/6] refactoring process_result function --- lib/at_ex/gateway/Sms/bulk.ex | 12 +++--------- lib/at_ex/gateway/base_http.ex | 10 +++++++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/at_ex/gateway/Sms/bulk.ex b/lib/at_ex/gateway/Sms/bulk.ex index 91f2ac4..8c0cd8a 100644 --- a/lib/at_ex/gateway/Sms/bulk.ex +++ b/lib/at_ex/gateway/Sms/bulk.ex @@ -22,15 +22,9 @@ defmodule AtEx.Gateway.Sms.Bulk do attrs |> Map.put(:username, username) - with {:ok, %{status: 201} = res} <- post("/messaging", params) do - process_result(res.body) - else - {:ok, val} -> - {:error, %{status: val.status, message: val.body}} - - {:error, message} -> - {:error, message} - end + "/messaging" + |> post(params) + |> process_result() end @doc """ diff --git a/lib/at_ex/gateway/base_http.ex b/lib/at_ex/gateway/base_http.ex index 1c2e6ee..2578231 100644 --- a/lib/at_ex/gateway/base_http.ex +++ b/lib/at_ex/gateway/base_http.ex @@ -49,9 +49,13 @@ defmodule AtEx.Gateway.Base do Process results from calling the gateway """ def process_result(result) do - with {:ok, res} <- Jason.decode(result) do - {:ok, res} - else + case result do + {:ok, %{status: 201} = res} -> + Jason.decode(res.body) + + {:ok, val} -> + {:error, %{status: val.status, message: val.body}} + {:error, val} -> {:error, val} end From ffd4721932789c3e940b22e1e2de84809b3f03a8 Mon Sep 17 00:00:00 2001 From: Tee22 Date: Mon, 22 Jun 2020 14:38:59 +0300 Subject: [PATCH 6/6] pattern matching with 200 and 201 status --- lib/at_ex/gateway/base_http.ex | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/at_ex/gateway/base_http.ex b/lib/at_ex/gateway/base_http.ex index 2578231..6b5f73a 100644 --- a/lib/at_ex/gateway/base_http.ex +++ b/lib/at_ex/gateway/base_http.ex @@ -48,17 +48,21 @@ defmodule AtEx.Gateway.Base do @doc """ Process results from calling the gateway """ - def process_result(result) do - case result do - {:ok, %{status: 201} = res} -> - Jason.decode(res.body) - {:ok, val} -> - {:error, %{status: val.status, message: val.body}} + def process_result({:ok, %{status: 200} = res}) do + Jason.decode(res.body) + end - {:error, val} -> - {:error, val} - end + def process_result({:ok, %{status: 201} = res}) do + Jason.decode(res.body) + end + + def process_result({:ok, result}) do + {:error, %{status: result.status, message: result.body}} + end + + def process_result({:error, result}) do + {:error, result} end end end