diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..a677202 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,7 @@ +[ + inputs: [ + "{mix, .formatter, .credo}.exs", + "{config,lib,test}/**/*.{ex,exs}" + ], + line_length: 100 +] diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 0000000..2926074 --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,86 @@ +name: Elixir CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build_and_test: + name: Build and test + runs-on: ubuntu-latest + strategy: + matrix: + elixir: ["1.10.4"] + otp: [23] + cache: [1] + services: + db: + image: postgres:11 + ports: ['5432:5432'] + env: + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up Elixir + uses: actions/setup-elixir@v1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} + + - name: Restore dependencies cache + id: mix-deps-cache + uses: actions/cache@v2 + with: + path: deps + key: ${{ runner.os }}-mix-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.cache }}-mix + + - name: Restore build cache + uses: actions/cache@v1 + with: + path: _build + key: cache-${{ runner.os }}-dialyzer_build-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }} + restore-keys: cache-${{ runner.os }}-${{ matrix.cache }}-dialyzer_build- + + - name: Install Mix Dependencies + if: steps.mix-deps-cache.outputs.cache-hit != 'true' + run: | + mix local.rebar --force + mix local.hex --force + mix deps.get + + - name: Credo + run: mix credo --strict + + - name: Check formatting + run: mix format --check-formatted + + - name: Restore PLT Cache + uses: actions/cache@v1 + id: plt-cache + with: + path: priv/plts + key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plts-${{ hashFiles('**/mix.lock') }} + restore-keys: cache-${{ runner.os }}-${{ matrix.cache }}-dialyzer_build- + + - name: Create PLTs + if: steps.plt-cache.outputs.cache-hit != 'true' + run: | + mkdir -p plts + mix dialyzer --plt + + - name: Run dialyzer + run: mix dialyzer + + - name: Run tests + run: mix test + diff --git a/.gitignore b/.gitignore index b322b03..a0f49de 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ erl_crash.dump *.ez *.swp +/plts/*.plt +/plts/*.plt.hash diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..0eed8ce --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +elixir 1.10.4 diff --git a/examples/simplesend.exs b/examples/simplesend.exs index 966321c..ba6c6e2 100644 --- a/examples/simplesend.exs +++ b/examples/simplesend.exs @@ -3,7 +3,7 @@ from = "Elixir SparkPost " SparkPost.send( to: to, from: from, - subject: "My first Elixir email", + subject: "My first Elixir email", text: "This is the boring version of the email body", html: "This is the tasty rich version of the email body." ) diff --git a/lib/address.ex b/lib/address.ex index 4af05ce..d61a9ad 100644 --- a/lib/address.ex +++ b/lib/address.ex @@ -17,24 +17,27 @@ defmodule SparkPost.Address do - `%{name: ..., email: ...}` """ def to_address(email) when is_binary(email) do - parse_address(email) + parse_address(email) end - def to_address(%{name: name, email: email})do + def to_address(%{name: name, email: email}) do %__MODULE__{name: name, email: email} end - def to_address(%{email: email})do + def to_address(%{email: email}) do %__MODULE__{email: email} end defp parse_address(addr) when is_binary(addr) do case Regex.run(~r/\s*(.+)\s+<(.+@.+)>\s*$/, addr) do - [_, name, email] -> %__MODULE__{ name: name, email: email } - nil -> case Regex.run(~r/\s*(.+@.+)\s*$/, addr) do - [_, email] -> %__MODULE__{ email: email } - nil -> raise __MODULE__.FormatError, message: "Invalid email address: #{addr}" - end + [_, name, email] -> + %__MODULE__{name: name, email: email} + + nil -> + case Regex.run(~r/\s*(.+@.+)\s*$/, addr) do + [_, email] -> %__MODULE__{email: email} + nil -> raise __MODULE__.FormatError, message: "Invalid email address: #{addr}" + end end end end diff --git a/lib/content.ex b/lib/content.ex index e5efc9c..b35a8b4 100644 --- a/lib/content.ex +++ b/lib/content.ex @@ -64,12 +64,10 @@ defmodule SparkPost.Content do end def to_content(%SparkPost.Content.Inline{} = content) do - %{ content | - from: SparkPost.Address.to_address(content.from)} + %{content | from: SparkPost.Address.to_address(content.from)} end def to_content(content) when is_map(content) do - %{ struct(SparkPost.Content.Inline, content) | - from: SparkPost.Address.to_address(content.from)} + %{struct(SparkPost.Content.Inline, content) | from: SparkPost.Address.to_address(content.from)} end end diff --git a/lib/content/inline.ex b/lib/content/inline.ex index 8e803f2..9598ed6 100644 --- a/lib/content/inline.ex +++ b/lib/content/inline.ex @@ -18,18 +18,19 @@ defmodule SparkPost.Content.Inline do """ defstruct from: :required, - reply_to: nil, - headers: nil, - subject: :required, - text: nil, - html: nil, - attachments: nil, - inline_images: nil + reply_to: nil, + headers: nil, + subject: :required, + text: nil, + html: nil, + attachments: nil, + inline_images: nil @doc """ Convert a raw "from" field into a %SparkPost.Address{} object. """ def convert_from_field(%SparkPost.Endpoint.Error{} = content), do: content + def convert_from_field(%__MODULE__{} = content) do %{content | from: SparkPost.Address.to_address(content.from)} end diff --git a/lib/endpoint.ex b/lib/endpoint.ex index 728c7ba..538dd6c 100644 --- a/lib/endpoint.ex +++ b/lib/endpoint.ex @@ -15,7 +15,7 @@ defmodule SparkPost.Endpoint do - `:get` - `:head` - `:options` - - `:patch` + - `:patch` - `:post` - `:put` - `endpoint`: SparkPost API endpoint as string ("transmissions", "templates", ...) @@ -32,27 +32,29 @@ defmodule SparkPost.Endpoint do "id" => "102258558346809186", "name" => "102258558346809186", "state" => "Success"}, ...], status_code: 200} """ - def request(method, endpoint, body \\ %{}, headers \\ %{}, options \\ [], decode_results \\ true) do + def request(method, endpoint, body \\ %{}, headers \\ %{}, options \\ [], decode_results \\ true) do url = Application.get_env(:sparkpost, :api_endpoint, @default_endpoint) <> endpoint {:ok, request_body} = encode_request_body(body) - request_headers = if method in [:get, :delete] do + request_headers = + if method in [:get, :delete] do headers else Map.merge(headers, %{"Content-Type": "application/json"}) end |> Map.merge(base_request_headers()) - request_options = options - |> Keyword.put(:timeout, Application.get_env(:sparkpost, :http_timeout, 30000)) - |> Keyword.put(:recv_timeout, Application.get_env(:sparkpost, :http_recv_timeout, 8000)) + request_options = + options + |> Keyword.put(:timeout, Application.get_env(:sparkpost, :http_timeout, 30_000)) + |> Keyword.put(:recv_timeout, Application.get_env(:sparkpost, :http_recv_timeout, 8000)) HTTPoison.request(method, url, request_body, request_headers, request_options) |> handle_response(decode_results) end - def marshal_response(response, struct_type, subkey\\nil) + def marshal_response(response, struct_type, subkey \\ nil) @doc """ Extract a meaningful structure from a generic endpoint response: @@ -70,17 +72,21 @@ defmodule SparkPost.Endpoint do response end - defp handle_response({:ok, %HTTPoison.Response{status_code: code, body: body}}, decode_results) when code >= 200 and code < 300 do + defp handle_response({:ok, %HTTPoison.Response{status_code: code, body: body}}, decode_results) + when code >= 200 and code < 300 do decoded_body = decode_response_body(body) - if decode_results do + + if decode_results && Map.has_key?(decoded_body, :results) do %SparkPost.Endpoint.Response{status_code: code, results: decoded_body.results} else %SparkPost.Endpoint.Response{status_code: code, results: decoded_body} end end - defp handle_response({:ok, %HTTPoison.Response{status_code: code, body: body}}, _decode_results) when code >= 400 do + defp handle_response({:ok, %HTTPoison.Response{status_code: code, body: body}}, _decode_results) + when code >= 400 do decoded_body = decode_response_body(body) + if Map.has_key?(decoded_body, :errors) do %SparkPost.Endpoint.Error{status_code: code, errors: decoded_body.errors} end @@ -90,24 +96,25 @@ defmodule SparkPost.Endpoint do %SparkPost.Endpoint.Error{status_code: nil, errors: [reason]} end - defp base_request_headers() do + defp base_request_headers do {:ok, version} = :application.get_key(:sparkpost, :vsn) + %{ "User-Agent": "elixir-sparkpost/" <> to_string(version), - "Authorization": Application.get_env(:sparkpost, :api_key) + Authorization: Application.get_env(:sparkpost, :api_key) } end - # Do not try to remove nils from an empty map + # Do not try to remove nils from an empty map defp encode_request_body(body) when is_map(body) and map_size(body) == 0, do: {:ok, ""} + defp encode_request_body(body) do - body |> Washup.filter |> Poison.encode + body |> Washup.filter() |> Poison.encode() end defp decode_response_body(body) when byte_size(body) == 0, do: "" + defp decode_response_body(body) do - # TODO: [key: :atoms] is unsafe for open-ended structures such as - # metadata and substitution_data - body |> Poison.decode!([keys: :atoms]) + body |> Poison.decode!(keys: :atoms) end end diff --git a/lib/mockserver.ex b/lib/mockserver.ex index 6094ccd..dcbcb30 100644 --- a/lib/mockserver.ex +++ b/lib/mockserver.ex @@ -1,19 +1,19 @@ defmodule SparkPost.MockServer do @moduledoc false - def create_json(endpoint\\"transmission") do + def create_json(endpoint \\ "transmission") do File.read!("test/data/create#{endpoint}.json") end - def create_fail_json(endpoint\\"transmission") do + def create_fail_json(endpoint \\ "transmission") do File.read!("test/data/create#{endpoint}fail.json") end - def list_json(endpoint\\"transmission") do + def list_json(endpoint \\ "transmission") do File.read!("test/data/list#{endpoint}.json") end - def get_json(endpoint\\"transmission") do + def get_json(endpoint \\ "transmission") do File.read!("test/data/#{endpoint}.json") end @@ -34,10 +34,12 @@ defmodule SparkPost.MockServer do end def mk_http_resp(status_code, body) do - fn (_method, _url, _body, _headers, _opts) -> {:ok, %HTTPoison.Response{status_code: status_code, body: body}} end + fn _method, _url, _body, _headers, _opts -> + {:ok, %HTTPoison.Response{status_code: status_code, body: body}} + end end def mk_error(reason) do - fn (_method, _url, _body, _headers, _opts) -> {:error, %HTTPoison.Error{reason: reason}} end + fn _method, _url, _body, _headers, _opts -> {:error, %HTTPoison.Error{reason: reason}} end end end diff --git a/lib/recipient.ex b/lib/recipient.ex index c9f8f7f..b5b6b8b 100644 --- a/lib/recipient.ex +++ b/lib/recipient.ex @@ -12,12 +12,12 @@ defmodule SparkPost.Recipient do """ defstruct address: :required, - return_path: nil, - tags: nil, - metadata: nil, - substitution_data: nil + return_path: nil, + tags: nil, + metadata: nil, + substitution_data: nil - alias SparkPost.{Recipient, Address} + alias SparkPost.{Address, Recipient} @doc """ Convenience conversions to `[ %SparkPost.Recipient{} ]` from: @@ -29,12 +29,11 @@ defmodule SparkPost.Recipient do end def to_recipient_list(email_list) when is_list(email_list) do - Enum.map(email_list, fn (recip) -> to_recipient(recip) - end) + Enum.map(email_list, fn recip -> to_recipient(recip) end) end def to_recipient_list(email) when is_binary(email) do - [ to_recipient(email) ] + [to_recipient(email)] end @doc """ @@ -58,15 +57,16 @@ defmodule SparkPost.Recipient do def to_recipient(%{address: address} = struc) do struct(__MODULE__, %{ - struc | address: Address.to_address(address) + struc + | address: Address.to_address(address) }) end def to_recipient(%{name: _name, email: _email} = struc) do - %__MODULE__{ address: Address.to_address(struc) } + %__MODULE__{address: Address.to_address(struc)} end def to_recipient(%{email: _} = struc) do - %__MODULE__{ address: Address.to_address(struc) } + %__MODULE__{address: Address.to_address(struc)} end end diff --git a/lib/suppression_list.ex b/lib/suppression_list.ex index f341a6b..e0135d7 100644 --- a/lib/suppression_list.ex +++ b/lib/suppression_list.ex @@ -32,16 +32,21 @@ defmodule SparkPost.SuppressionList do - description (optional): optional description of this entry in the suppression list """ def upsert_one(recipient, type, description \\ nil) do - body = if description == nil do - %{type: type} - else - %{type: type, description: description} - end + body = + if description == nil do + %{type: type} + else + %{type: type, description: description} + end + response = Endpoint.request(:put, "suppression-list/#{recipient}", body) + case response do %SparkPost.Endpoint.Response{status_code: 200, results: results} -> {:ok, Map.get(results, :message, "")} - _ -> {:error, response} + + _ -> + {:error, response} end end @@ -56,10 +61,13 @@ defmodule SparkPost.SuppressionList do """ def delete(recipient) do response = Endpoint.request(:delete, "suppression-list/#{recipient}", %{}, %{}, [], false) + case response do %SparkPost.Endpoint.Response{status_code: 204} -> {:ok, ""} - _ -> {:error, response} + + _ -> + {:error, response} end end @@ -81,15 +89,20 @@ defmodule SparkPost.SuppressionList do """ def search(params \\ []) do response = Endpoint.request(:get, "suppression-list", %{}, %{}, [params: params], false) + case response do %SparkPost.Endpoint.Response{results: body} -> - mapped_results = Enum.map(body.results, fn res -> struct(SparkPost.SuppressionList.ListEntry, res) end) + mapped_results = + Enum.map(body.results, fn res -> struct(SparkPost.SuppressionList.ListEntry, res) end) + %SparkPost.SuppressionList.SearchResult{ results: mapped_results, links: body.links, total_count: body.total_count } - _ -> response + + _ -> + response end end end diff --git a/lib/suppression_list/list_entry.ex b/lib/suppression_list/list_entry.ex index 4b63e67..61f6a13 100644 --- a/lib/suppression_list/list_entry.ex +++ b/lib/suppression_list/list_entry.ex @@ -12,9 +12,9 @@ defmodule SparkPost.SuppressionList.ListEntry do """ defstruct recipient: :required, - type: :required, - source: nil, - description: nil, - transactional: nil, - non_transactional: nil + type: :required, + source: nil, + description: nil, + transactional: nil, + non_transactional: nil end diff --git a/lib/suppression_list/search_result.ex b/lib/suppression_list/search_result.ex index 16816e9..f374e84 100644 --- a/lib/suppression_list/search_result.ex +++ b/lib/suppression_list/search_result.ex @@ -10,6 +10,6 @@ defmodule SparkPost.SuppressionList.SearchResult do """ defstruct results: :required, - links: :required, - total_count: :required + links: :required, + total_count: :required end diff --git a/lib/template.ex b/lib/template.ex index 4c0b529..5e4162b 100644 --- a/lib/template.ex +++ b/lib/template.ex @@ -6,18 +6,30 @@ defmodule SparkPost.Template do Check out the documentation for each function or use the [SparkPost API reference](https://developers.sparkpost.com/api/templates.html) for details. - Returned by `SparkPost.template.preview/2`. - - from - - email - - name - - subject - - reply_to - - text - - html - - headers + ## Struct Fields + + - id: Template identifier, auto-generated if not provided upon create. + - name: Editable template display name, auto-generated if not provided. At minimum, `:name` or `:id` is required, but not both + - content: Content that will be used to construct a message. Can be a `%SparkPost.Content.Inline` or a `%SparkPost.Content.Raw{}` + - published: Boolean indicating the published/draft state of the template. Defaults to false + - description: Detailed description of the template + - options: A `%SparkPost.Transmission.Options{}` struzct, but only `:open_tracking`, `:click_tracking` and `:transactional` are accepted when working with a template. + - shared_with_subaccounts: boolean indicating if the template is accessible to subaccounts. Defaults to false. + - has_draft: Read-only. Indicates if template has a draft version. + - has_published: Read-only. Indicates if template has a published version. """ - alias SparkPost.Endpoint + alias SparkPost.{Content, Endpoint, Transmission} + + defstruct id: nil, + name: nil, + content: %Content.Inline{}, + published: false, + description: nil, + options: %Transmission.Options{}, + shared_with_subaccounts: false, + has_draft: nil, + has_published: nil @doc """ Generate a preview of an existing template. @@ -31,17 +43,96 @@ defmodule SparkPost.Template do - substitution_data: k,v map consisting of substituions. See the [SparkPost Substitutions Reference](https://developers.sparkpost.com/api/substitutions-reference.html) for more details. + + Response is a `%SparkPost.Content.Inline{}` consisting of + - from + - email + - name + - subject + - reply_to + - text + - html + - headers """ def preview(%SparkPost.Content.TemplateRef{} = template, substitution_data) do - qs = if is_nil(template.use_draft_template) do - "" - else - "?draft=#{template.use_draft_template}" - end + qs = + if is_nil(template.use_draft_template) do + "" + else + "?draft=#{template.use_draft_template}" + end + body = %{substitution_data: substitution_data} + :post |> Endpoint.request("templates/#{template.template_id}/preview#{qs}", body) |> Endpoint.marshal_response(SparkPost.Content.Inline) - |> SparkPost.Content.Inline.convert_from_field + |> Content.Inline.convert_from_field() + end + + @doc """ + Create a SparkPost Template + + ## Parameters + + - `%SparkPost.Template{}` + + ## Response + + - `%SparkPost.Template.Response{}` + """ + def create(%__MODULE__{} = template) do + :post + |> Endpoint.request("templates", template) + |> Endpoint.marshal_response(SparkPost.Template.Response) + end + + @doc """ + Update a SparkPost Template + + ## Parameters + + - `%SparkPost.Template{}` containing a valid `:id` as well as the updated content + - optional keyword list as a second argument, supporting the fields + - `:update_published` - defaults to false, specifies if the published version of the template should be directly updated, instead of storing the update as a draft + + ## Note on `:update_published` option, vs `:published` struct field + + Setting `published: true` on the struct itself performs the act of publishing a draft template. If the field is set to + `true`, the `:update_published` option is ingored completely. + """ + def update( + %__MODULE__{id: template_id, published: published} = template, + options \\ [update_published: false] + ) do + qs = + if published != true && Keyword.get(options, :update_published, false) == true do + "?update_published=true" + else + "" + end + + :put + |> Endpoint.request("templates/#{template_id}#{qs}", template) + |> Endpoint.marshal_response(SparkPost.Template.Response) + end + + @doc """ + Delete a SparkPost Template + + ## Parameters + + - a valid template id + + ## Response + + - `{:ok, %SparkPost.Endpoint.Response{}}` if successful + - `{:error, %SparkPost.Endpoint.Error{}}` if failure + """ + def delete(template_id) do + case Endpoint.request(:delete, "templates/#{template_id}") do + %SparkPost.Endpoint.Response{status_code: 200} = response -> {:ok, response} + other -> {:error, other} + end end end diff --git a/lib/template/options.ex b/lib/template/options.ex new file mode 100644 index 0000000..498280d --- /dev/null +++ b/lib/template/options.ex @@ -0,0 +1,16 @@ +defmodule SparkPost.Template.Options do + @moduledoc """ + Template options. + + Designed for use in `%SparkPost.Content.Template{options: ...}` + + ## Fields + - open_tracking: enable 'email open' tracking? + - click_tracking: enable 'link click' tracking? + - transactional: is this a transactional message? + """ + + defstruct open_tracking: true, + click_tracking: true, + transactional: nil +end diff --git a/lib/template/response.ex b/lib/template/response.ex new file mode 100644 index 0000000..9debc94 --- /dev/null +++ b/lib/template/response.ex @@ -0,0 +1,12 @@ +defmodule SparkPost.Template.Response do + @moduledoc """ + The response generated when SparkPost receives a Template request. + + Returned by `SparkPost.Template.create/1` + + ## Fields + - id: Unique id of the template, generated automatically or specified as part of the original request + """ + + defstruct id: nil +end diff --git a/lib/transmission.ex b/lib/transmission.ex index 1c94d16..0289014 100644 --- a/lib/transmission.ex +++ b/lib/transmission.ex @@ -42,24 +42,30 @@ defmodule SparkPost.Transmission do """ defstruct options: %SparkPost.Transmission.Options{}, - campaign_id: nil, - return_path: nil, - metadata: nil, - substitution_data: nil, - recipients: :required, - content: :required, - id: nil, # System generated fields from this point on - description: nil, - state: nil, - rcpt_list_chunk_size: nil, - rcp_list_total_chunks: nil, - num_rcpts: nil, - num_generated: nil, - num_failed_gen: nil, - generation_start_time: nil, - generation_end_time: nil - - alias SparkPost.{Transmission, Recipient, Endpoint, Content} + campaign_id: nil, + return_path: nil, + metadata: nil, + substitution_data: nil, + recipients: :required, + content: :required, + # System generated fields from this point on + id: nil, + description: nil, + state: nil, + rcpt_list_chunk_size: nil, + rcp_list_total_chunks: nil, + num_rcpts: nil, + num_generated: nil, + num_failed_gen: nil, + generation_start_time: nil, + generation_end_time: nil + + alias SparkPost.{ + Content, + Endpoint, + Recipient, + Transmission + } @doc """ Create a new transmission and send some email. @@ -81,7 +87,7 @@ defmodule SparkPost.Transmission do Transmission.send(%Transmission{ recipients: ["to@you.com"], content: %Content.Inline{ - from: "from@me.com", + from: "from@me.com", subject: subject, text: text, html: html @@ -119,10 +125,12 @@ defmodule SparkPost.Transmission do total_accepted_recipients: 1, total_rejected_recipients: 0} """ def send(%__MODULE__{} = body) do - body = %{body | - recipients: Recipient.to_recipient_list(body.recipients), - content: Content.to_content(body.content) + body = %{ + body + | recipients: Recipient.to_recipient_list(body.recipients), + content: Content.to_content(body.content) } + response = Endpoint.request(:post, "transmissions", body) Endpoint.marshal_response(response, Transmission.Response) end @@ -175,12 +183,15 @@ defmodule SparkPost.Transmission do rcp_list_total_chunks: nil, rcpt_list_chunk_size: nil, recipients: :required, return_path: :nil, state: "Success", substitution_data: nil}] """ - def list(filters\\[]) do - response = Endpoint.request(:get, "transmissions", %{}, %{}, [params: filters]) + def list(filters \\ []) do + response = Endpoint.request(:get, "transmissions", %{}, %{}, params: filters) + case response do %Endpoint.Response{} -> - Enum.map(response.results, fn (trans) -> struct(__MODULE__, trans) end) - _ -> response + Enum.map(response.results, fn trans -> struct(__MODULE__, trans) end) + + _ -> + response end end end diff --git a/lib/transmission/options.ex b/lib/transmission/options.ex index acf30dc..e05fc5c 100644 --- a/lib/transmission/options.ex +++ b/lib/transmission/options.ex @@ -16,11 +16,11 @@ defmodule SparkPost.Transmission.Options do """ defstruct start_time: nil, - open_tracking: true, - click_tracking: true, - transactional: nil, - sandbox: nil, - skip_suppression: nil, - ip_pool: nil, - inline_css: nil + open_tracking: true, + click_tracking: true, + transactional: nil, + sandbox: nil, + skip_suppression: nil, + ip_pool: nil, + inline_css: nil end diff --git a/lib/transmission/response.ex b/lib/transmission/response.ex index af3bd0e..9fdfae6 100644 --- a/lib/transmission/response.ex +++ b/lib/transmission/response.ex @@ -10,6 +10,6 @@ defmodule SparkPost.Transmission.Response do """ defstruct id: nil, - total_accepted_recipients: nil, - total_rejected_recipients: nil + total_accepted_recipients: nil, + total_rejected_recipients: nil end diff --git a/lib/washup.ex b/lib/washup.ex index 8606e5c..e7c8e68 100644 --- a/lib/washup.ex +++ b/lib/washup.ex @@ -16,7 +16,7 @@ defmodule Washup do def filter(it) do cond do is_map(it) and Map.has_key?(it, :__struct__) -> filter(Map.from_struct(it)) - is_map(it) -> for {k,v} <- it, not is_nil(v), into: %{}, do: {k, do_filter(v)} + is_map(it) -> for {k, v} <- it, not is_nil(v), into: %{}, do: {k, do_filter(v)} is_list(it) -> for x <- it, not is_nil(x), do: do_filter(x) true -> it end @@ -39,21 +39,24 @@ defmodule Washup do iex> Washup.verify(jenny) ** (Washup.RequiredError) pets->listidx->species required """ - def verify(it, path\\[]) do + def verify(it, path \\ []) do cond do is_map(it) and Map.has_key?(it, :__struct__) -> verify(Map.from_struct(it), path) - is_map(it) -> for {k,v} <- it, into: %{}, do: {k, verify(v, [k|path])} - is_list(it) -> for x <- it, do: verify(x, ["listidx"|path]) + is_map(it) -> for {k, v} <- it, into: %{}, do: {k, verify(v, [k | path])} + is_list(it) -> for x <- it, do: verify(x, ["listidx" | path]) true -> verify_val(it, path) end end defp verify_val(val, path) do case val do - :required -> raise Washup.RequiredError, - path: path, - message: Enum.join(Enum.reverse(path), "->") <> " required" - _ -> val + :required -> + raise Washup.RequiredError, + path: path, + message: Enum.join(Enum.reverse(path), "->") <> " required" + + _ -> + val end end end diff --git a/mix.exs b/mix.exs index f0412f9..b3723c5 100644 --- a/mix.exs +++ b/mix.exs @@ -2,21 +2,29 @@ defmodule SparkPost.Mixfile do use Mix.Project def project do - [app: :sparkpost, - version: "0.5.2", - elixir: "~> 1.2", - build_embedded: Mix.env == :prod, - start_permanent: Mix.env == :prod, - source_url: "https://github.com/SparkPost/elixir-sparkpost", - description: "The official Elixir package for the SparkPost API", - package: package(), - deps: deps(), - docs: [ - extras: ["README.md", "CONTRIBUTING.md", "CHANGELOG.md"] - ], - test_coverage: [tool: ExCoveralls], - preferred_cli_env: ["coveralls": :test, "coveralls.detail": :test, "coveralls.post": :test] - ] + [ + app: :sparkpost, + build_embedded: Mix.env() == :prod, + deps: deps(), + description: "The official Elixir package for the SparkPost API", + dialyzer: dialyzer(), + docs: [extras: ["README.md", "CONTRIBUTING.md", "CHANGELOG.md"]], + elixir: "~> 1.10", + package: package(), + preferred_cli_env: [coveralls: :test, "coveralls.detail": :test, "coveralls.post": :test], + source_url: "https://github.com/SparkPost/elixir-sparkpost", + start_permanent: Mix.env() == :prod, + test_coverage: [tool: ExCoveralls], + version: "0.5.2" + ] + end + + defp dialyzer do + [ + plt_add_apps: [:mix, :ex_unit, :poison], + plt_core_path: "plts", + plt_file: {:no_warn, "plts/dialyzer.plt"} + ] end def application do @@ -25,13 +33,14 @@ defmodule SparkPost.Mixfile do defp deps do [ - {:httpoison, "~> 1.0"}, - {:poison, "~> 2.0 or ~> 3.0"}, - {:mock, "~> 0.2.0", only: :test}, - {:excoveralls, "~> 0.5.7", only: :test}, - {:credo, "~> 0.5.1", only: [:dev, :test]}, - {:earmark, "~> 1.0.3", only: :dev}, - {:ex_doc, "~> 0.14.3", only: :dev} + {:dialyxir, "~> 1.0.0-rc.6", only: [:dev, :test], runtime: false}, + {:httpoison, "~> 1.7"}, + {:poison, "~> 4.0"}, + {:mock, "~> 0.3.5", only: :test}, + {:excoveralls, "~> 0.13", only: :test}, + {:credo, "~> 1.4", only: [:dev, :test], runtime: false}, + {:earmark, "~> 1.4", only: :dev}, + {:ex_doc, "~> 0.20", only: :dev, runtime: false} ] end @@ -47,4 +56,3 @@ defmodule SparkPost.Mixfile do ] end end - diff --git a/mix.lock b/mix.lock index 6c58af5..efc55a0 100644 --- a/mix.lock +++ b/mix.lock @@ -1,24 +1,31 @@ %{ - "bunt": {:hex, :bunt, "0.1.6", "5d95a6882f73f3b9969fdfd1953798046664e6f77ec4e486e6fafc7caad97c6f", [:mix], []}, - "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, - "credo": {:hex, :credo, "0.5.1", "2395862b94628cadf0f5c68975c1440393f425b955f1e70ce1aea267e00187a1", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, optional: false]}]}, - "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []}, - "ex_doc": {:hex, :ex_doc, "0.14.3", "e61cec6cf9731d7d23d254266ab06ac1decbb7651c3d1568402ec535d387b6f7", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]}, - "excoveralls": {:hex, :excoveralls, "0.5.7", "5d26e4a7cdf08294217594a1b0643636accc2ad30e984d62f1d166f70629ff50", [:mix], [{:exjsx, "~> 3.0", [hex: :exjsx, optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}]}, - "exjsx": {:hex, :exjsx, "3.2.1", "1bc5bf1e4fd249104178f0885030bcd75a4526f4d2a1e976f4b428d347614f0f", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, optional: false]}]}, - "hackney": {:hex, :hackney, "1.12.1", "8bf2d0e11e722e533903fe126e14d6e7e94d9b7983ced595b75f532e04b7fdc7", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "httpoison": {:hex, :httpoison, "1.1.1", "96ed7ab79f78a31081bb523eefec205fd2900a02cda6dbc2300e7a1226219566", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, + "credo": {:hex, :credo, "1.4.1", "16392f1edd2cdb1de9fe4004f5ab0ae612c92e230433968eab00aafd976282fc", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "155f8a2989ad77504de5d8291fa0d41320fdcaa6a1030472e9967f285f8c7692"}, + "dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"}, + "earmark": {:hex, :earmark, "1.4.10", "bddce5e8ea37712a5bfb01541be8ba57d3b171d3fa4f80a0be9bcf1db417bcaf", [:mix], [{:earmark_parser, ">= 1.4.10", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "12dbfa80810478e521d3ffb941ad9fbfcbbd7debe94e1341b4c4a1b2411c1c27"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "ex_doc": {:hex, :ex_doc, "0.22.6", "0fb1e09a3e8b69af0ae94c8b4e4df36995d8c88d5ec7dbd35617929144b62c00", [: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", "1e0aceda15faf71f1b0983165e6e7313be628a460e22a031e32913b98edbd638"}, + "excoveralls": {:hex, :excoveralls, "0.13.2", "5ca05099750c086f144fcf75842c363fc15d7d9c6faa7ad323d010294ced685e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1e7ed75c158808a5a8f019d3ad63a5efe482994f2f8336c0a8c77d2f0ab152ce"}, + "exjsx": {:hex, :exjsx, "3.2.1", "1bc5bf1e4fd249104178f0885030bcd75a4526f4d2a1e976f4b428d347614f0f", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "b55727b206dab96feb025267e5c122ddb448f55b6648f9156b8d481215d80290"}, + "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, + "httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"}, "httpotion": {:hex, :httpotion, "2.1.0", "3fe84fbd13d4560c2514da656d022b1191a079178ee4992d245fc3c33c01ee18", [:mix], []}, "ibrowse": {:git, "https://github.com/cmullaparthi/ibrowse.git", "ea3305d21f37eced4fac290f64b068e56df7de80", [tag: "v4.1.2"]}, - "idna": {:hex, :idna, "5.1.1", "cbc3b2fa1645113267cc59c760bafa64b2ea0334635ef06dbac8801e42f7279c", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, - "jsx": {:hex, :jsx, "2.8.0", "749bec6d205c694ae1786d62cea6cc45a390437e24835fd16d12d74f07097727", [:mix, :rebar], []}, - "meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:make, :rebar], []}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, - "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, - "mock": {:hex, :mock, "0.2.0", "5991877be6bb514b647dbd6f4869bc12bd7f2829df16e86c98d6108f966d34d7", [:mix], [{:meck, "~> 0.8.2", [hex: :meck, optional: false]}]}, - "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}, + "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, + "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "jsx": {:hex, :jsx, "2.8.0", "749bec6d205c694ae1786d62cea6cc45a390437e24835fd16d12d74f07097727", [:mix, :rebar], [], "hexpm", "a8ba15d5bac2c48b2be1224a0542ad794538d79e2cc16841a4e24ca75f0f8378"}, + "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.0", "98312c9f0d3730fde4049985a1105da5155bfe5c11e47bdc7406d88e01e4219b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "75ffa34ab1056b7e24844c90bfc62aaf6f3a37a15faa76b07bc5eba27e4a8b4a"}, + "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "6fae404799408300f863550392635d8f7e3da6b71abdd5c393faf41b131c8728"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, + "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5", "2e73e068cd6393526f9fa6d399353d7c9477d6886ba005f323b592d389fb47be", [:make], []}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"}, } diff --git a/test/data/createtemplate.json b/test/data/createtemplate.json new file mode 100644 index 0000000..9e91f98 --- /dev/null +++ b/test/data/createtemplate.json @@ -0,0 +1,5 @@ +{ + "results": { + "id": "TEMPLATE_ID" + } +} diff --git a/test/data/templatedelete_fail_404.json b/test/data/templatedelete_fail_404.json new file mode 100644 index 0000000..34cd884 --- /dev/null +++ b/test/data/templatedelete_fail_404.json @@ -0,0 +1,9 @@ +{ + "errors": [ + { + "message": "resource not found", + "code": "1600", + "description": "Template does not exist" + } + ] +} diff --git a/test/data/templatedelete_fail_409.json b/test/data/templatedelete_fail_409.json new file mode 100644 index 0000000..3a5e918 --- /dev/null +++ b/test/data/templatedelete_fail_409.json @@ -0,0 +1,9 @@ +{ + "errors": [ + { + "message": "resource conflict", + "code": "1602", + "description": "Template is in use by msg generation" + } + ] +} diff --git a/test/data/updatetemplate.json b/test/data/updatetemplate.json new file mode 100644 index 0000000..9e91f98 --- /dev/null +++ b/test/data/updatetemplate.json @@ -0,0 +1,5 @@ +{ + "results": { + "id": "TEMPLATE_ID" + } +} diff --git a/test/endpoint_test.exs b/test/endpoint_test.exs index 2d44d60..bb6a7fa 100644 --- a/test/endpoint_test.exs +++ b/test/endpoint_test.exs @@ -1,4 +1,5 @@ defmodule SparkPost.EndpointTest do + @moduledoc false use ExUnit.Case, async: false alias SparkPost.Endpoint @@ -7,96 +8,119 @@ defmodule SparkPost.EndpointTest do import Mock defmodule Headers do + @moduledoc false def for_method(method) do - cond do - method in [:post, :put] -> Map.merge(for_body_requests(), core()) - true -> core() + if method in [:post, :put] do + Map.merge(for_body_requests(), core()) + else + core() end end defp for_body_requests do - %{"Content-Type" => &(&1=="application/json")} + %{"Content-Type" => &(&1 == "application/json")} end def core do %{ "Authorization" => &(Application.get_env(:sparkpost, :api_key) == &1), - "User-Agent" => &(Regex.match?(~r/elixir-sparkpost\/\d+\.\d+\.\d+/, &1)) + "User-Agent" => &Regex.match?(~r/elixir-sparkpost\/\d+\.\d+\.\d+/, &1) } end end test "Endpoint.request succeeds with Endpoint.Response" do - with_mock HTTPoison, [request: fn(_, _, _, _, _) -> - r = MockServer.mk_resp - r.(nil, nil, nil, nil, nil) - end] do + with_mock HTTPoison, + request: fn _, _, _, _, _ -> + r = MockServer.mk_resp() + r.(nil, nil, nil, nil, nil) + end do Endpoint.request(:get, "transmissions", %{}) end end - test "Endpoint.request populates Endpoint.Response" do + test "Endpoint.request populates Endpoint.Response" do status_code = 200 - results = Poison.decode!(MockServer.create_json, [keys: :atoms]).results - with_mock HTTPoison, [request: MockServer.mk_resp] do + results = Poison.decode!(MockServer.create_json(), keys: :atoms).results + + with_mock HTTPoison, request: MockServer.mk_resp() do resp = %Endpoint.Response{} = Endpoint.request(:get, "transmissions", %{}, %{}, []) assert %Endpoint.Response{status_code: ^status_code, results: ^results} = resp end end test "Endpoint.request fails with Endpoint.Error" do - with_mock HTTPoison, [request: MockServer.mk_fail] do - %Endpoint.Error{} = Endpoint.request( - :get, "transmissions", []) + with_mock HTTPoison, request: MockServer.mk_fail() do + %Endpoint.Error{} = + Endpoint.request( + :get, + "transmissions", + [] + ) end end test "Endpoint.request populates Endpoint.Error" do status_code = 400 - errors = Poison.decode!(MockServer.create_fail_json, [keys: :atoms]).errors - with_mock HTTPoison, [request: MockServer.mk_fail] do - resp = %Endpoint.Error{} = Endpoint.request( - :get, "transmissions", []) + errors = Poison.decode!(MockServer.create_fail_json(), keys: :atoms).errors + + with_mock HTTPoison, request: MockServer.mk_fail() do + resp = + %Endpoint.Error{} = + Endpoint.request( + :get, + "transmissions", + [] + ) assert %Endpoint.Error{status_code: ^status_code, errors: ^errors} = resp end end test "Endpoint.request includes the core HTTP headers" do - respfn = MockServer.mk_resp - with_mock HTTPoison, [request: fn (method, url, body, headers, opts) -> - Enum.each(Headers.for_method(method), fn {header, tester} -> - header_atom = String.to_atom(header) - assert Map.has_key?(headers, header_atom), "#{header} header required for #{method} requests" - assert tester.(headers[header_atom]), "Malformed header: #{header}. See Headers module in #{__ENV__.file} for formatting rules." - end) - respfn.(method, url, body, headers, opts) - end - ] do + respfn = MockServer.mk_resp() + + with_mock HTTPoison, + request: fn method, url, body, headers, opts -> + Enum.each(Headers.for_method(method), fn {header, tester} -> + header_atom = String.to_atom(header) + + assert Map.has_key?(headers, header_atom), + "#{header} header required for #{method} requests" + + assert tester.(headers[header_atom]), + "Malformed header: #{header}. See Headers module in #{__ENV__.file} for formatting rules." + end) + + respfn.(method, url, body, headers, opts) + end do Enum.each([:get, :post, :put, :delete], fn method -> - Endpoint.request(method, "transmissions", %{}, %{}, []) end) + Endpoint.request(method, "transmissions", %{}, %{}, []) + end) end end test "Endpoint.request includes request bodies for appropriate methods" do - respfn = MockServer.mk_resp - with_mock HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert body == "" - respfn.(method, url, body, headers, opts) - end - ] do + respfn = MockServer.mk_resp() + + with_mock HTTPoison, + request: fn method, url, body, headers, opts -> + assert body == "" + respfn.(method, url, body, headers, opts) + end do Endpoint.request(:post, "transmissions", %{}, %{}, []) Endpoint.request(:put, "transmissions", %{}, %{}, []) end end test "Endpoint.request includes request timeout" do - respfn = MockServer.mk_resp - with_mock HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert Keyword.has_key?(opts, :timeout) - respfn.(method, url, body, headers, opts) - end - ] do + respfn = MockServer.mk_resp() + + with_mock HTTPoison, + request: fn method, url, body, headers, opts -> + assert Keyword.has_key?(opts, :timeout) + respfn.(method, url, body, headers, opts) + end do Endpoint.request(:post, "transmissions", %{}, %{}, []) Endpoint.request(:put, "transmissions", %{}, %{}, []) Endpoint.request(:get, "transmissions", %{}, %{}, []) @@ -104,12 +128,20 @@ defmodule SparkPost.EndpointTest do end test_with_mock "Endpoint request can handle httpoison timeouts", HTTPoison, - [request: fn (method, url, body, headers, opts) -> + request: fn method, url, body, headers, opts -> fun = MockServer.mk_error(:timeout) fun.(method, url, body, headers, opts) - end - ] do - assert %Endpoint.Error{errors: [:timeout], status_code: nil, results: nil} == - Endpoint.request(:post, "transmissions", %{}, %{}, []) + end do + assert %Endpoint.Error{errors: [:timeout], status_code: nil, results: nil} == + Endpoint.request(:post, "transmissions", %{}, %{}, []) + end + + test_with_mock "Endpoint request can handle blank map as response", HTTPoison, + request: fn method, url, body, headers, opts -> + fun = MockServer.mk_http_resp(200, "{}") + fun.(method, url, body, headers, opts) + end do + assert %Endpoint.Response{status_code: 200, results: %{}} == + Endpoint.request(:post, "transmissions", %{}, %{}, []) end end diff --git a/test/filter_test.exs b/test/filter_test.exs index 680328e..71bb39d 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -23,6 +23,6 @@ defmodule Washup.Test.Filter do test "map -> map" do assert Washup.filter(%{key1: %{a: 1, b: nil, c: 3}, key2: nil, key3: %{q: "q"}}) == - %{key1: %{a: 1, c: 3}, key3: %{q: "q"}} + %{key1: %{a: 1, c: 3}, key3: %{q: "q"}} end end diff --git a/test/sparkpost_test.exs b/test/sparkpost_test.exs index 91a5d00..854beb1 100644 --- a/test/sparkpost_test.exs +++ b/test/sparkpost_test.exs @@ -1,32 +1,37 @@ defmodule SparkPostTest do + @moduledoc false use ExUnit.Case - alias SparkPost.{Address, Content, Recipient, MockServer} + alias SparkPost.{Address, Content, MockServer, Recipient} import Mock test "send succeeds with a Transmission.Response" do - with_mock HTTPoison, [request: MockServer.mk_resp] do - resp = SparkPost.send( - to: "you@there.com", - from: "me@here.com", - subject: "Elixir and SparkPost...", - text: "Raw text email is boring", - html: "Rich text email is terrifying" - ) + with_mock HTTPoison, request: MockServer.mk_resp() do + resp = + SparkPost.send( + to: "you@there.com", + from: "me@here.com", + subject: "Elixir and SparkPost...", + text: "Raw text email is boring", + html: "Rich text email is terrifying" + ) + assert %SparkPost.Transmission.Response{} = resp end end test "send fails with a Endpoint.Error" do - with_mock HTTPoison, [request: MockServer.mk_fail] do - resp = SparkPost.send( - to: "you@there.com", - from: "me@here.com", - subject: "Elixir and SparkPost...", - text: nil, - html: nil - ) + with_mock HTTPoison, request: MockServer.mk_fail() do + resp = + SparkPost.send( + to: "you@there.com", + from: "me@here.com", + subject: "Elixir and SparkPost...", + text: nil, + html: nil + ) + assert %SparkPost.Endpoint.Error{} = resp end end @@ -37,17 +42,21 @@ defmodule SparkPostTest do subject = "Elixir and SparkPost..." text = "Raw text email is boring" html = "Rich text email is terrifying" - with_mock HTTPoison, [request: fn (method, url, body, headers, opts) -> - inreq = Poison.decode!(body, [keys: :atoms]) - assert Recipient.to_recipient_list(inreq.recipients) == Recipient.to_recipient_list(to) - assert Content.to_content(inreq.content) == %Content.Inline{ - from: Address.to_address(from), - subject: subject, - text: text, - html: html - } - MockServer.mk_resp.(method, url, body, headers, opts) - end] do + + with_mock HTTPoison, + request: fn method, url, body, headers, opts -> + inreq = Poison.decode!(body, keys: :atoms) + assert Recipient.to_recipient_list(inreq.recipients) == Recipient.to_recipient_list(to) + + assert Content.to_content(inreq.content) == %Content.Inline{ + from: Address.to_address(from), + subject: subject, + text: text, + html: html + } + + MockServer.mk_resp().(method, url, body, headers, opts) + end do SparkPost.send( to: to, from: from, diff --git a/test/suppression_list_test.exs b/test/suppression_list_test.exs index 175631f..9b848f0 100644 --- a/test/suppression_list_test.exs +++ b/test/suppression_list_test.exs @@ -4,26 +4,30 @@ defmodule SparkPost.SuppressionListTest do use ExUnit.Case, async: false alias SparkPost.{MockServer, SuppressionList} - alias SparkPost.SuppressionList.{SearchResult, ListEntry} + alias SparkPost.SuppressionList.{ListEntry, SearchResult} import Mock test_with_mock "SuppressionList.upsert_one succeeds with message", - HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert method == :put - fun = MockServer.mk_http_resp(200, MockServer.get_json("suppressionlistupdate")) - fun.(method, url, body, headers, opts) - end] do - {:ok, resp} = SuppressionList.upsert_one("test@marketing.com", "non_transactional", "test description") + HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :put + fun = MockServer.mk_http_resp(200, MockServer.get_json("suppressionlistupdate")) + fun.(method, url, body, headers, opts) + end do + {:ok, resp} = + SuppressionList.upsert_one("test@marketing.com", "non_transactional", "test description") + assert resp == "Test response message" end test_with_mock "SuppressionList.upsert_one fails with invalid type", - HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert method == :put - fun = MockServer.mk_http_resp(400, MockServer.get_json("suppressionupdate_fail")) - fun.(method, url, body, headers, opts) - end] do + HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :put + fun = MockServer.mk_http_resp(400, MockServer.get_json("suppressionupdate_fail")) + fun.(method, url, body, headers, opts) + end do {:error, resp} = SuppressionList.upsert_one("test@marketing.com", "bad_type") assert %SparkPost.Endpoint.Error{} = resp assert resp.status_code == 400 @@ -31,87 +35,92 @@ defmodule SparkPost.SuppressionListTest do end test_with_mock "SuppressionList.delete succeeds with empty body", - HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert method == :delete - fun = MockServer.mk_http_resp(204, "") - fun.(method, url, body, headers, opts) - end] do - {:ok, resp} = SuppressionList.delete("test@marketing.com") - assert resp == "" + HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :delete + fun = MockServer.mk_http_resp(204, "") + fun.(method, url, body, headers, opts) + end do + {:ok, resp} = SuppressionList.delete("test@marketing.com") + assert resp == "" end test_with_mock "SuppressionList.delete fails 404", - HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert method == :delete - fun = MockServer.mk_http_resp(404, MockServer.get_json("suppressiondelete_fail")) - fun.(method, url, body, headers, opts) - end] do - {:error, resp} = SuppressionList.delete("test@marketing.com") - assert %SparkPost.Endpoint.Error{} = resp - assert resp.status_code == 404 - assert resp.errors == [%{message: "Recipient could not be found"}] + HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :delete + fun = MockServer.mk_http_resp(404, MockServer.get_json("suppressiondelete_fail")) + fun.(method, url, body, headers, opts) + end do + {:error, resp} = SuppressionList.delete("test@marketing.com") + assert %SparkPost.Endpoint.Error{} = resp + assert resp.status_code == 404 + assert resp.errors == [%{message: "Recipient could not be found"}] end test_with_mock "SuppressionList.search succeeds with SuppressionList.SearchResult", - HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert method == :get - fun = MockServer.mk_http_resp(200, MockServer.get_json("suppressionsearch")) - fun.(method, url, body, headers, opts) - end] do - resp = SuppressionList.search() - assert %SearchResult{} = resp + HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :get + fun = MockServer.mk_http_resp(200, MockServer.get_json("suppressionsearch")) + fun.(method, url, body, headers, opts) + end do + resp = SuppressionList.search() + assert %SearchResult{} = resp end test_with_mock "SuppressionList.search fails with Endpoint.Error", HTTPoison, - [request: MockServer.mk_fail] do - resp = SuppressionList.search() - assert %SparkPost.Endpoint.Error{} = resp + request: MockServer.mk_fail() do + resp = SuppressionList.search() + assert %SparkPost.Endpoint.Error{} = resp end test_with_mock "SuppressionList.search creates ListEntry structs", HTTPoison, - [request: fn (method, url, body, headers, opts) -> + request: fn method, url, body, headers, opts -> assert method == :get fun = MockServer.mk_http_resp(200, MockServer.get_json("suppressionsearch")) fun.(method, url, body, headers, opts) - end] do - resp = SuppressionList.search() - assert %SearchResult{ - results: [ - %ListEntry{ - recipient: "test@marketing.com", - type: "non_transactional", - source: nil, - description: nil, - non_transactional: nil - } - ], - links: [], - total_count: 1 - } == resp + end do + resp = SuppressionList.search() + + assert %SearchResult{ + results: [ + %ListEntry{ + recipient: "test@marketing.com", + type: "non_transactional", + source: nil, + description: nil, + non_transactional: nil + } + ], + links: [], + total_count: 1 + } == resp end test_with_mock "SuppressionList.search parses out cursor info", HTTPoison, - [request: fn (method, url, body, headers, opts) -> + request: fn method, url, body, headers, opts -> assert method == :get fun = MockServer.mk_http_resp(200, MockServer.get_json("suppressionsearch_links")) fun.(method, url, body, headers, opts) - end] do - resp = SuppressionList.search() - assert %SearchResult{ - results: [ - %ListEntry{ - recipient: "test@marketing.com", - type: "non_transactional", - source: nil, - description: nil, - non_transactional: nil - } - ], - links: [ - %{href: "/currentlink", rel: "first"}, - %{href: "/linkwithcursor", rel: "next"} - ], - total_count: 1 - } == resp + end do + resp = SuppressionList.search() + + assert %SearchResult{ + results: [ + %ListEntry{ + recipient: "test@marketing.com", + type: "non_transactional", + source: nil, + description: nil, + non_transactional: nil + } + ], + links: [ + %{href: "/currentlink", rel: "first"}, + %{href: "/linkwithcursor", rel: "next"} + ], + total_count: 1 + } == resp end end diff --git a/test/template_test.exs b/test/template_test.exs index e2477ea..f84b848 100644 --- a/test/template_test.exs +++ b/test/template_test.exs @@ -1,13 +1,19 @@ defmodule SparkPost.TemplateTest do + @moduledoc false use ExUnit.Case, async: false - alias SparkPost.Content.{TemplateRef, Inline} alias SparkPost.{Endpoint, MockServer, Template} + alias SparkPost.Content.{Inline, TemplateRef} import Mock defmodule TestStruct do + @moduledoc false def basic_template do + %SparkPost.Template{id: "TEMPLATE_ID"} + end + + def basic_template_ref do %TemplateRef{template_id: "TEMPLATE_ID", use_draft_template: nil} end @@ -24,57 +30,179 @@ defmodule SparkPost.TemplateTest do end test_with_mock "Template.preview succeeds with Content.Inline", - HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert method == :post - # draft not set - assert String.ends_with?(url, "preview") - fun = MockServer.mk_http_resp(200, MockServer.get_json("previewtemplate")) - fun.(method, url, body, headers, opts) - end] do - resp = Template.preview(TestStruct.basic_template(), TestStruct.substitution_data()) - assert %Inline{} = resp + HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :post + # draft not set + assert String.ends_with?(url, "preview") + fun = MockServer.mk_http_resp(200, MockServer.get_json("previewtemplate")) + fun.(method, url, body, headers, opts) + end do + resp = Template.preview(TestStruct.basic_template_ref(), TestStruct.substitution_data()) + assert %Inline{} = resp end test_with_mock "Template.preview succeeds with Content.Inline and draft set", - HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert method == :post - assert String.ends_with?(url, "preview?draft=true") - fun = MockServer.mk_http_resp(200, MockServer.get_json("previewtemplate")) - fun.(method, url, body, headers, opts) - end] do - resp = Template.preview(TestStruct.template_with_draft(), TestStruct.substitution_data()) - assert %Inline{} = resp + HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :post + assert String.ends_with?(url, "preview?draft=true") + fun = MockServer.mk_http_resp(200, MockServer.get_json("previewtemplate")) + fun.(method, url, body, headers, opts) + end do + resp = Template.preview(TestStruct.template_with_draft(), TestStruct.substitution_data()) + assert %Inline{} = resp end test_with_mock "Template.preview fails with Endpoint.Error", HTTPoison, - [request: MockServer.mk_fail] do - resp = Template.preview(TestStruct.basic_template(), TestStruct.substitution_data()) - assert %Endpoint.Error{} = resp + request: MockServer.mk_fail() do + resp = Template.preview(TestStruct.basic_template_ref(), TestStruct.substitution_data()) + assert %Endpoint.Error{} = resp end test_with_mock "Template.preview unmarshals complex from field correctly", HTTPoison, - [request: fn (method, url, body, headers, opts) -> + request: fn method, url, body, headers, opts -> assert method == :post fun = MockServer.mk_http_resp(200, MockServer.get_json("previewtemplate")) fun.(method, url, body, headers, opts) - end] do - resp = Template.preview(TestStruct.basic_template(), TestStruct.substitution_data()) - assert %SparkPost.Address{ - name: "Example Company Marketing", - "email": "marketing@bounces.company.example" - } == resp.from + end do + resp = Template.preview(TestStruct.basic_template_ref(), TestStruct.substitution_data()) + + assert %SparkPost.Address{ + name: "Example Company Marketing", + email: "marketing@bounces.company.example" + } == resp.from end test_with_mock "Template.preview unmarshals simple from field correctly", HTTPoison, - [request: fn (method, url, body, headers, opts) -> + request: fn method, url, body, headers, opts -> assert method == :post fun = MockServer.mk_http_resp(200, MockServer.get_json("previewtemplate_simpleemail")) fun.(method, url, body, headers, opts) - end] do - resp = Template.preview(TestStruct.basic_template(), TestStruct.substitution_data()) - assert %SparkPost.Address{ - name: nil, - "email": "marketing@bounces.company.example" - } == resp.from + end do + resp = Template.preview(TestStruct.basic_template_ref(), TestStruct.substitution_data()) + + assert %SparkPost.Address{ + name: nil, + email: "marketing@bounces.company.example" + } == resp.from + end + + test_with_mock "Template.create succeeds with Template.Response", HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :post + assert url =~ "/templates" + fun = MockServer.mk_http_resp(200, MockServer.get_json("createtemplate")) + fun.(method, url, body, headers, opts) + end do + assert Template.create(TestStruct.basic_template()) == + %SparkPost.Template.Response{id: "TEMPLATE_ID"} + end + + test_with_mock "Template.create fails with Endpoint.Error", HTTPoison, + request: MockServer.mk_fail() do + resp = Template.create(TestStruct.basic_template()) + assert %Endpoint.Error{} = resp + end + + test_with_mock "Template.update succeeds with Template.Response", HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :put + assert url =~ "/templates/TEMPLATE_ID" + fun = MockServer.mk_http_resp(200, MockServer.get_json("updatetemplate")) + fun.(method, url, body, headers, opts) + end do + assert Template.update(TestStruct.basic_template()) == + %SparkPost.Template.Response{id: "TEMPLATE_ID"} + end + + test_with_mock "Template.update succeeds with update_published set", HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :put + assert url =~ "/templates/TEMPLATE_ID?update_published=true" + fun = MockServer.mk_http_resp(200, MockServer.get_json("updatetemplate")) + fun.(method, url, body, headers, opts) + end do + assert Template.update(TestStruct.basic_template(), update_published: true) == + %SparkPost.Template.Response{id: "TEMPLATE_ID"} + end + + test_with_mock "Template.update ignores update_published set if published field set", HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :put + refute url =~ "/templates/TEMPLATE_ID?update_published=true" + assert url =~ "/templates/TEMPLATE_ID" + fun = MockServer.mk_http_resp(200, MockServer.get_json("updatetemplate")) + fun.(method, url, body, headers, opts) + end do + template = %{TestStruct.basic_template() | published: true} + + assert Template.update(template, update_published: true) == %SparkPost.Template.Response{ + id: "TEMPLATE_ID" + } + end + + test_with_mock "Template.update fails with Endpoint.Error", HTTPoison, + request: MockServer.mk_fail() do + resp = Template.update(TestStruct.basic_template()) + assert %Endpoint.Error{} = resp + end + + test_with_mock "Template.delete succeeds with empty body", + HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :delete + assert url =~ "/templates/TEMPLATE_ID" + fun = MockServer.mk_http_resp(200, "{}") + fun.(method, url, body, headers, opts) + end do + assert Template.delete("TEMPLATE_ID") == + {:ok, %SparkPost.Endpoint.Response{results: %{}, status_code: 200}} + end + + test_with_mock "Template.delete fails with 404", + HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :delete + assert url =~ "/templates/TEMPLATE_ID" + + fun = + MockServer.mk_http_resp(404, MockServer.get_json("templatedelete_fail_404")) + + fun.(method, url, body, headers, opts) + end do + assert {:error, %Endpoint.Error{} = resp} = Template.delete("TEMPLATE_ID") + assert resp.status_code == 404 + + assert resp.errors == [ + %{ + code: "1600", + description: "Template does not exist", + message: "resource not found" + } + ] + end + + test_with_mock "Template.delete fails with 409", + HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :delete + assert url =~ "/templates/TEMPLATE_ID" + + fun = + MockServer.mk_http_resp(409, MockServer.get_json("templatedelete_fail_409")) + + fun.(method, url, body, headers, opts) + end do + assert {:error, %Endpoint.Error{} = resp} = Template.delete("TEMPLATE_ID") + assert resp.status_code == 409 + + assert resp.errors == [ + %{ + code: "1602", + description: "Template is in use by msg generation", + message: "resource conflict" + } + ] end end diff --git a/test/transmission_test.exs b/test/transmission_test.exs index 0e3864e..de3691e 100644 --- a/test/transmission_test.exs +++ b/test/transmission_test.exs @@ -1,11 +1,19 @@ defmodule SparkPost.TransmissionTest do + @moduledoc false use ExUnit.Case - alias SparkPost.{Transmission, Recipient, Address, Content, MockServer} + alias SparkPost.{ + Address, + Content, + MockServer, + Recipient, + Transmission + } import Mock defmodule TestStructs do + @moduledoc false def skeleton(options: options, recipients: recipients, content: content) do %Transmission{ options: options, @@ -22,21 +30,21 @@ defmodule SparkPost.TransmissionTest do end end - def full_addr_recipient(name\\"You There", email\\"you@there.com") do - %Recipient{ address: %Address{ name: name, email: email} } + def full_addr_recipient(name \\ "You There", email \\ "you@there.com") do + %Recipient{address: %Address{name: name, email: email}} end - def addr_spec_recipient(email\\"you@there.com") do - %Recipient{ address: %Address{ email: email } } + def addr_spec_recipient(email \\ "you@there.com") do + %Recipient{address: %Address{email: email}} end def inline_content do %Content.Inline{ - subject: "Subject line", - from: %Address{ email: "from@me.com" }, - text: "text content", - html: "html content" - } + subject: "Subject line", + from: %Address{email: "from@me.com"}, + text: "text content", + html: "html content" + } end def basic_transmission do @@ -49,25 +57,28 @@ defmodule SparkPost.TransmissionTest do end defmodule TestRequests do + @moduledoc false def test_send(req, test_fn) do - with_mock HTTPoison, [ - request: handle_send(test_fn) - ] do + with_mock HTTPoison, + request: handle_send(test_fn) do Transmission.send(req) end end defp handle_send(response_test_fn) do - fn (method, url, body, headers, opts) -> - req = Poison.decode!(body, [keys: :atoms]) - fullreq = struct(Transmission, %{ - req | - options: struct(Transmission.Options, req.options), - recipients: parse_recipients_field(req.recipients), - content: Content.to_content(req.content) - }) + fn method, url, body, headers, opts -> + req = Poison.decode!(body, keys: :atoms) + + fullreq = + struct(Transmission, %{ + req + | options: struct(Transmission.Options, req.options), + recipients: parse_recipients_field(req.recipients), + content: Content.to_content(req.content) + }) + response_test_fn.(fullreq) - MockServer.mk_resp.(method, url, body, headers, opts) + MockServer.mk_resp().(method, url, body, headers, opts) end end @@ -95,26 +106,27 @@ defmodule SparkPost.TransmissionTest do end test "Transmission.send succeeds with Transmission.Response" do - with_mock HTTPoison, [request: MockServer.mk_resp] do - resp = Transmission.send(TestStructs.basic_transmission) + with_mock HTTPoison, request: MockServer.mk_resp() do + resp = Transmission.send(TestStructs.basic_transmission()) assert %Transmission.Response{} = resp end end test "Transmission.send fails with Endpoint.Error" do - with_mock HTTPoison, [request: MockServer.mk_fail] do - req = TestStructs.basic_transmission + with_mock HTTPoison, request: MockServer.mk_fail() do + req = TestStructs.basic_transmission() resp = Transmission.send(req) assert %SparkPost.Endpoint.Error{} = resp end end test "Transmission.send emits a POST" do - with_mock HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert method == :post - MockServer.mk_resp.(method, url, body, headers, opts) - end] do - Transmission.send(TestStructs.basic_transmission) + with_mock HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :post + MockServer.mk_resp().(method, url, body, headers, opts) + end do + Transmission.send(TestStructs.basic_transmission()) end end @@ -126,9 +138,10 @@ defmodule SparkPost.TransmissionTest do sandbox: false, skip_suppression: false } + TestRequests.test_send( - %{TestStructs.basic_transmission | options: transopts}, - &(assert &1.options == transopts) + %{TestStructs.basic_transmission() | options: transopts}, + &assert(&1.options == transopts) ) end @@ -137,33 +150,40 @@ defmodule SparkPost.TransmissionTest do TestStructs.full_addr_recipient("You There", "you@there.com"), TestStructs.full_addr_recipient("Them There", "them@there.com") ] + TestRequests.test_send( - %{TestStructs.basic_transmission | recipients: recipients}, - &(assert &1.recipients == recipients) + %{TestStructs.basic_transmission() | recipients: recipients}, + &assert(&1.recipients == recipients) ) end test "Transmission.send accepts a list of long-form recipient email addresses" do # RFC2822 3.4: Address Specification recipients = ["You There ", "You Too There "] - expected = Enum.map recipients, fn recip -> - TestStructs.full_addr_recipient(recip) - end + + expected = + Enum.map(recipients, fn recip -> + TestStructs.full_addr_recipient(recip) + end) + TestRequests.test_send( - %{TestStructs.basic_transmission | recipients: recipients}, - &(assert &1.recipients == expected) + %{TestStructs.basic_transmission() | recipients: recipients}, + &assert(&1.recipients == expected) ) end test "Transmission.send accepts a list of short-form recipient email addresses" do # RFC2822 3.4.1: Addr-spec specification recipients = ["you@there.com", "youtoo@theretoo.com"] - expected = Enum.map recipients, fn recip -> - TestStructs.addr_spec_recipient(recip) - end + + expected = + Enum.map(recipients, fn recip -> + TestStructs.addr_spec_recipient(recip) + end) + TestRequests.test_send( - %{TestStructs.basic_transmission | recipients: recipients}, - &(assert &1.recipients == expected) + %{TestStructs.basic_transmission() | recipients: recipients}, + &assert(&1.recipients == expected) ) end @@ -174,6 +194,7 @@ defmodule SparkPost.TransmissionTest do recip3 = %{name: "And You", email: "and@you.com"} recip4 = %{email: "me@too.com"} recipients = [recip0, recip1, recip2, recip3, recip4] + expected = [ TestStructs.full_addr_recipient(recip0), TestStructs.addr_spec_recipient(recip1), @@ -181,33 +202,40 @@ defmodule SparkPost.TransmissionTest do %Recipient{address: %Address{name: recip3.name, email: recip3.email}}, %Recipient{address: %Address{email: recip4.email}} ] + TestRequests.test_send( - %{TestStructs.basic_transmission | recipients: recipients}, - &(assert &1.recipients == expected) + %{TestStructs.basic_transmission() | recipients: recipients}, + &assert(&1.recipients == expected) ) end test "Transmission.send requires correctly-formatted email addresses" do - assert_raise Address.FormatError, fn -> TestRequests.test_send( - %{TestStructs.basic_transmission | recipients: "paula and paul"}, &(&1)) + assert_raise Address.FormatError, fn -> + TestRequests.test_send( + %{TestStructs.basic_transmission() | recipients: "paula and paul"}, + & &1 + ) end end test "Transmission.send marshals recipient lists correctly" do recip_lst = %Recipient.ListRef{list_id: "list101"} + TestRequests.test_send( - %{TestStructs.basic_transmission | recipients: recip_lst}, - &(assert &1.recipients == recip_lst) + %{TestStructs.basic_transmission() | recipients: recip_lst}, + &assert(&1.recipients == recip_lst) ) end test "Transmission.send marshals inline raw content correctly" do content = %Content.Raw{ - email_rfc822: "Content-Type: text/plain\r\nTo: \"{{address.name}}\" <{{address.email}}>\r\n\r\n We are testing Elixir and SparkPost together\r\n" + email_rfc822: + "Content-Type: text/plain\r\nTo: \"{{address.name}}\" <{{address.email}}>\r\n\r\n We are testing Elixir and SparkPost together\r\n" } + TestRequests.test_send( - %{TestStructs.basic_transmission | content: content}, - &(assert &1.content == content) + %{TestStructs.basic_transmission() | content: content}, + &assert(&1.content == content) ) end @@ -217,14 +245,16 @@ defmodule SparkPost.TransmissionTest do subject: "Testing SparkPost and Elixir", text: "We all live in a transient theoretical construct" } + expected = %Content.Inline{ from: %Address{email: "me@here.com"}, subject: "Testing SparkPost and Elixir", text: "We all live in a transient theoretical construct" } + TestRequests.test_send( - %{TestStructs.basic_transmission | content: content}, - &(assert &1.content == expected) + %{TestStructs.basic_transmission() | content: content}, + &assert(&1.content == expected) ) end @@ -233,33 +263,35 @@ defmodule SparkPost.TransmissionTest do template_id: "template101", use_draft_template: true } + TestRequests.test_send( - %{TestStructs.basic_transmission | content: content}, - &(assert &1.content == content) + %{TestStructs.basic_transmission() | content: content}, + &assert(&1.content == content) ) end test "Transmission.list succeeds with a list of Transmission" do - with_mock HTTPoison, [request: MockServer.mk_list] do - resp = Transmission.list + with_mock HTTPoison, request: MockServer.mk_list() do + resp = Transmission.list() assert is_list(resp) Enum.each(resp, fn r -> assert %Transmission{} = r end) end end test "Transmission.list fails with Endpoint.Error" do - with_mock HTTPoison, [request: MockServer.mk_fail] do - resp = Transmission.list + with_mock HTTPoison, request: MockServer.mk_fail() do + resp = Transmission.list() assert %SparkPost.Endpoint.Error{} = resp end end test "Transmission.list emits a GET" do - with_mock HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert method == :get - MockServer.mk_list.(method, url, body, headers, opts) - end] do - Transmission.list + with_mock HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :get + MockServer.mk_list().(method, url, body, headers, opts) + end do + Transmission.list() end end @@ -267,10 +299,11 @@ defmodule SparkPost.TransmissionTest do end test "Transmission.get emits a GET" do - with_mock HTTPoison, [request: fn (method, url, body, headers, opts) -> - assert method == :get - MockServer.mk_get.(method, url, body, headers, opts) - end] do + with_mock HTTPoison, + request: fn method, url, body, headers, opts -> + assert method == :get + MockServer.mk_get().(method, url, body, headers, opts) + end do Transmission.get("TRANSMISSION_ID") end end