Skip to content

Commit 9e09202

Browse files
authored
Merge pull request #40 from asgoel/suppression_list_delete_insert
Suppression list, search, insert and delete
2 parents e20f6da + d8efa11 commit 9e09202

File tree

8 files changed

+138
-5
lines changed

8 files changed

+138
-5
lines changed

lib/endpoint.ex

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ defmodule SparkPost.Endpoint do
3636
url = Application.get_env(:sparkpost, :api_endpoint, @default_endpoint) <> endpoint
3737

3838
{:ok, request_body} = encode_request_body(body)
39-
39+
4040
request_headers = if method in [:get, :delete] do
41-
headers
41+
headers
4242
else
4343
Map.merge(headers, %{"Content-Type": "application/json"})
4444
end
@@ -73,9 +73,9 @@ defmodule SparkPost.Endpoint do
7373
defp handle_response({:ok, %HTTPoison.Response{status_code: code, body: body}}, decode_results) when code >= 200 and code < 300 do
7474
decoded_body = decode_response_body(body)
7575
if decode_results do
76-
%SparkPost.Endpoint.Response{status_code: 200, results: decoded_body.results}
76+
%SparkPost.Endpoint.Response{status_code: code, results: decoded_body.results}
7777
else
78-
%SparkPost.Endpoint.Response{status_code: 200, results: decoded_body}
78+
%SparkPost.Endpoint.Response{status_code: code, results: decoded_body}
7979
end
8080
end
8181

@@ -86,6 +86,10 @@ defmodule SparkPost.Endpoint do
8686
end
8787
end
8888

89+
defp handle_response({:error, %HTTPoison.Error{reason: reason}}, _decode_results) do
90+
%SparkPost.Endpoint.Error{status_code: nil, errors: [reason]}
91+
end
92+
8993
defp base_request_headers() do
9094
{:ok, version} = :application.get_key(:sparkpost, :vsn)
9195
%{
@@ -100,6 +104,7 @@ defmodule SparkPost.Endpoint do
100104
body |> Washup.filter |> Poison.encode
101105
end
102106

107+
defp decode_response_body(body) when byte_size(body) == 0, do: ""
103108
defp decode_response_body(body) do
104109
# TODO: [key: :atoms] is unsafe for open-ended structures such as
105110
# metadata and substitution_data

lib/mockserver.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@ defmodule SparkPost.MockServer do
3636
def mk_http_resp(status_code, body) do
3737
fn (_method, _url, _body, _headers, _opts) -> {:ok, %HTTPoison.Response{status_code: status_code, body: body}} end
3838
end
39+
40+
def mk_error(reason) do
41+
fn (_method, _url, _body, _headers, _opts) -> {:error, %HTTPoison.Error{reason: reason}} end
42+
end
3943
end

lib/suppression_list.ex

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,68 @@
11
defmodule SparkPost.SuppressionList do
22
@moduledoc """
33
The SparkPost Suppression List API for working with suppression lists.
4-
Use `SparkPost.SuppressionList.search/1` to search through your account's suppression list.
4+
Use `SparkPost.SuppressionList.delete/1` to delete a single entry from a list,
5+
`SparkPost.SuppressionList.upsert_one/3` to insert or update a single list entry,
6+
or `SparkPost.SuppressionList.search/1` to search through your account's suppression list.
57
68
Check out the documentation for each function
79
or use the [SparkPost API reference](https://developers.sparkpost.com/api/suppression_list.html) for details.
810
11+
Returned by `SparkPost.SuppressionList.delete/1`:
12+
- {:ok, ""}
13+
14+
Returned by `SparkPost.SuppressionList.upsert_one/3`:
15+
- {:ok, message} (A success message string)
16+
917
Returned by `SparkPost.SuppressionList.search/1`.
1018
- %SparkPost.SuppressionList.SearchResult{}
1119
"""
1220

1321
alias SparkPost.Endpoint
1422

23+
@doc """
24+
Insert or update a single entry in the suppression list.
25+
Returns a single string with the success message if the entry
26+
was updated or inserted. Returns a %SparkPost.Endpoint.Error{} with a 400
27+
if there was an issue with the request format.
28+
29+
Parameters:
30+
- recipient: the email to insert or update in the suppression list
31+
- type: one of "transactional" or "non_transactional"
32+
- description (optional): optional description of this entry in the suppression list
33+
"""
34+
def upsert_one(recipient, type, description \\ nil) do
35+
body = if description == nil do
36+
%{type: type}
37+
else
38+
%{type: type, description: description}
39+
end
40+
response = Endpoint.request(:put, "suppression-list/#{recipient}", body)
41+
case response do
42+
%SparkPost.Endpoint.Response{status_code: 200, results: results} ->
43+
{:ok, Map.get(results, :message, "")}
44+
_ -> {:error, response}
45+
end
46+
end
47+
48+
@doc """
49+
Deletes a specific entry from the list. Returns an empty string if
50+
the deletion was successful. Returns a %SparkPost.Endpoint.Error{} with a 404
51+
if the specified entry is not in the list. Returns a %SparkPost.Endpoint.Error{}
52+
with a 403 if the entry could not be removed for any reason (such as Compliance).
53+
54+
Parameters:
55+
recipient: the entry to delete from the suppression list.
56+
"""
57+
def delete(recipient) do
58+
response = Endpoint.request(:delete, "suppression-list/#{recipient}", %{}, %{}, [], false)
59+
case response do
60+
%SparkPost.Endpoint.Response{status_code: 204} ->
61+
{:ok, ""}
62+
_ -> {:error, response}
63+
end
64+
end
65+
1566
@doc """
1667
Execute a search of the suppression list based on the provided
1768
parameters.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"errors": [
3+
{
4+
"message": "Recipient could not be found"
5+
}
6+
]
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"results": {
3+
"message": "Test response message"
4+
}
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"errors": [
3+
{
4+
"message": "Type must be one of: 'transactional', 'non_transactional'"
5+
}
6+
]
7+
}

test/endpoint_test.exs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,14 @@ defmodule SparkPost.EndpointTest do
102102
Endpoint.request(:get, "transmissions", %{}, %{}, [])
103103
end
104104
end
105+
106+
test_with_mock "Endpoint request can handle httpoison timeouts", HTTPoison,
107+
[request: fn (method, url, body, headers, opts) ->
108+
fun = MockServer.mk_error(:timeout)
109+
fun.(method, url, body, headers, opts)
110+
end
111+
] do
112+
assert %Endpoint.Error{errors: [:timeout], status_code: nil, results: nil} ==
113+
Endpoint.request(:post, "transmissions", %{}, %{}, [])
114+
end
105115
end

test/suppression_list_test.exs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,50 @@ defmodule SparkPost.SuppressionListTest do
88

99
import Mock
1010

11+
test_with_mock "SuppressionList.upsert_one succeeds with message",
12+
HTTPoison, [request: fn (method, url, body, headers, opts) ->
13+
assert method == :put
14+
fun = MockServer.mk_http_resp(200, MockServer.get_json("suppressionlistupdate"))
15+
fun.(method, url, body, headers, opts)
16+
end] do
17+
{:ok, resp} = SuppressionList.upsert_one("test@marketing.com", "non_transactional", "test description")
18+
assert resp == "Test response message"
19+
end
20+
21+
test_with_mock "SuppressionList.upsert_one fails with invalid type",
22+
HTTPoison, [request: fn (method, url, body, headers, opts) ->
23+
assert method == :put
24+
fun = MockServer.mk_http_resp(400, MockServer.get_json("suppressionupdate_fail"))
25+
fun.(method, url, body, headers, opts)
26+
end] do
27+
{:error, resp} = SuppressionList.upsert_one("test@marketing.com", "bad_type")
28+
assert %SparkPost.Endpoint.Error{} = resp
29+
assert resp.status_code == 400
30+
assert resp.errors == [%{message: "Type must be one of: 'transactional', 'non_transactional'"}]
31+
end
32+
33+
test_with_mock "SuppressionList.delete succeeds with empty body",
34+
HTTPoison, [request: fn (method, url, body, headers, opts) ->
35+
assert method == :delete
36+
fun = MockServer.mk_http_resp(204, "")
37+
fun.(method, url, body, headers, opts)
38+
end] do
39+
{:ok, resp} = SuppressionList.delete("test@marketing.com")
40+
assert resp == ""
41+
end
42+
43+
test_with_mock "SuppressionList.delete fails 404",
44+
HTTPoison, [request: fn (method, url, body, headers, opts) ->
45+
assert method == :delete
46+
fun = MockServer.mk_http_resp(404, MockServer.get_json("suppressiondelete_fail"))
47+
fun.(method, url, body, headers, opts)
48+
end] do
49+
{:error, resp} = SuppressionList.delete("test@marketing.com")
50+
assert %SparkPost.Endpoint.Error{} = resp
51+
assert resp.status_code == 404
52+
assert resp.errors == [%{message: "Recipient could not be found"}]
53+
end
54+
1155
test_with_mock "SuppressionList.search succeeds with SuppressionList.SearchResult",
1256
HTTPoison, [request: fn (method, url, body, headers, opts) ->
1357
assert method == :get

0 commit comments

Comments
 (0)