diff --git a/config/config.exs b/config/config.exs index 408948a..ed1c192 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,3 +1,7 @@ import Config +if Mix.env() == :test do + config :logger, level: :info +end + # import_config "#{Mix.env}.exs" diff --git a/lib/bamboo/adapters/message/content.ex b/lib/bamboo/adapters/message/content.ex index d76f142..f8dce24 100644 --- a/lib/bamboo/adapters/message/content.ex +++ b/lib/bamboo/adapters/message/content.ex @@ -4,6 +4,8 @@ defmodule BambooSes.Message.Content do Depending on email it can generate simple, raw or template content. """ + alias BambooSes.Encoding + @type t :: %__MODULE__{ Template: %{ @@ -78,8 +80,8 @@ defmodule BambooSes.Message.Content do when is_map(template_params), do: %__MODULE__{Template: template_params} - defp build_content(_email, _template_params, subject, text, html, [], []), - do: build_simple_content(subject, text, html) + defp build_content(_email, _template_params, subject, text, html, headers, []), + do: build_simple_content(subject, text, html, headers) defp build_content(email, _template_params, _subject, _text, _html, _headers, _attachments) do raw_data = @@ -94,18 +96,26 @@ defmodule BambooSes.Message.Content do } end - defp build_simple_content(subject, text, html) do + defp build_simple_content(subject, text, html, headers) do %__MODULE__{ Simple: %{ Subject: %{ Charset: "UTF-8", Data: subject }, - Body: build_simple_body(text, html) + Body: build_simple_body(text, html), + Headers: build_headers(headers) } } end + defp build_headers(headers) do + Enum.map( + headers, + fn {name, value} -> %{"Name" => name, "Value" => Encoding.maybe_rfc1342_encode(value)} end + ) + end + defp build_simple_body(text, html) do %{} |> put_text(text) diff --git a/lib/bamboo/adapters/render/LICENSE.txt b/lib/bamboo/adapters/render/LICENSE.txt deleted file mode 100644 index 9438d7d..0000000 --- a/lib/bamboo/adapters/render/LICENSE.txt +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2012 Kamil Ciemniewski -Copyright (c) 2023 Kalys Osmonov - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/bamboo/adapters/render/raw.ex b/lib/bamboo/adapters/render/raw.ex index 79baa6d..f785ffe 100644 --- a/lib/bamboo/adapters/render/raw.ex +++ b/lib/bamboo/adapters/render/raw.ex @@ -10,79 +10,181 @@ defmodule BambooSes.Render.Raw do alias BambooSes.Encoding def render(email, extra_headers \\ []) do - email - # Returns a list of tuples - |> compile_parts() - # Nests the tuples and attaches necessary metadata - |> nest_parts(email, extra_headers) + has_text = !is_nil(email.text_body) && String.length(email.text_body) > 0 + has_html = !is_nil(email.html_body) && String.length(email.html_body) > 0 + has_attachments = length(filter_regular_attachments(email)) > 0 + has_inline_attachments = length(filter_inline_attachments(email)) > 0 + + headers = headers_for(email) ++ extra_headers + + build_parts( + has_text, + has_html, + has_attachments, + has_inline_attachments, + email, + headers + ) |> :mimemail.encode() end - defp nest_parts(parts, email, extra_headers) do - {top_mime_type, top_mime_sub_type, _, _, top_content_part} = nested_content_part_tuples(parts) + defp build_parts(false, false, _, _, email, headers) do + { + "multipart", + "mixed", + headers, + %{}, + prepare_attachments(email.attachments) + } + end + + defp build_parts(false, true, false, false, email, headers) do + { + "text", + "html", + headers, + parameters_for(nil), + email.html_body + } + end + defp build_parts(false, true, false, true, email, headers) do { - top_mime_type, - top_mime_sub_type, - headers_for(email) ++ extra_headers, + "multipart", + "related", + headers, %{}, - top_content_part + [ + # generates html + build_parts(false, true, false, false, email, []) + ] ++ prepare_attachments(filter_inline_attachments(email)) } end - defp nested_content_part_tuples(parts) do - plain_part_tuple = body_part_tuple(parts, :plain) - html_part_tuple = body_part_tuple(parts, :html) - # attachment_part_tuples(parts) - inline_attachment_part_tuples = [] - attached_attachment_part_tuples = attachment_part_tuples(parts) - - related_or_html_part_tuple = - if Enum.empty?(inline_attachment_part_tuples) do - html_part_tuple - else - if is_nil(html_part_tuple), - do: nil, - else: - {"multipart", "related", [], %{}, [html_part_tuple | inline_attachment_part_tuples]} - end - - alternative_or_plain_tuple = - if is_nil(related_or_html_part_tuple) do - plain_part_tuple - else - {"multipart", "alternative", [], %{}, [plain_part_tuple, related_or_html_part_tuple]} - end - - mixed_or_alternative_tuple = - if Enum.empty?(attached_attachment_part_tuples) do - alternative_or_plain_tuple - else - if is_nil(alternative_or_plain_tuple), - do: nil, - else: - {"multipart", "mixed", [], %{}, - [alternative_or_plain_tuple | attached_attachment_part_tuples]} - end - - mixed_or_alternative_tuple - end - - @spec body_part_tuple([tuple()], atom()) :: nil | tuple() - defp body_part_tuple(parts, type) do - part = Enum.find(parts, &(elem(&1, 0) == type)) - - if is_nil(part) do - nil - else - { - mime_type_for(part), - mime_subtype_for(part), - headers_for(part), - parameters_for(part), - elem(part, 1) - } - end + defp build_parts(false, true, true, false, email, headers) do + { + "multipart", + "mixed", + headers, + %{}, + [ + # generates html + build_parts(false, true, false, false, email, []) + ] ++ prepare_attachments(email.attachments) + } + end + + defp build_parts(false, true, true, true, email, headers) do + { + "multipart", + "mixed", + headers, + %{}, + [ + # generates html + build_parts(false, true, false, true, email, []) + ] ++ prepare_attachments(filter_regular_attachments(email)) + } + end + + defp build_parts(true, false, false, false, email, headers) do + { + "text", + "plain", + headers, + parameters_for(nil), + email.text_body + } + end + + defp build_parts(true, false, _, _, email, headers) do + { + "multipart", + "mixed", + headers, + %{}, + [ + # generates text + build_parts(true, false, false, false, email, []) + ] ++ prepare_attachments(email.attachments) + } + end + + defp build_parts(true, true, false, false, email, headers) do + { + "multipart", + "alternative", + headers, + %{}, + [ + # generates text + build_parts(true, false, false, false, email, []), + # generates html + build_parts(false, true, false, false, email, []) + ] + } + end + + defp build_parts(true, true, false, true, email, headers) do + { + "multipart", + "related", + headers, + %{}, + [ + # generates alternative + build_parts(true, true, false, false, email, []) + ] ++ prepare_attachments(filter_inline_attachments(email)) + } + end + + defp build_parts(true, true, true, false, email, headers) do + { + "multipart", + "mixed", + headers, + %{}, + [ + # generates alternative + build_parts(true, true, false, false, email, []) + ] ++ prepare_attachments(email.attachments) + } + end + + defp build_parts(true, true, true, true, email, headers) do + { + "multipart", + "mixed", + headers, + %{}, + [ + # generates related with alternative + build_parts(true, true, false, true, email, []) + ] ++ prepare_attachments(filter_regular_attachments(email)) + } + end + + defp prepare_attachments(attachments) do + attachments + |> Enum.map(fn attachment -> {:attachment, attachment.data, attachment} end) + |> attachment_part_tuples() + end + + def filter_inline_attachments(email) do + Enum.filter(email.attachments, fn + attachment -> + !is_nil(attachment) && + !is_nil(attachment.content_id) && + String.length(attachment.content_id) > 0 + end) + end + + def filter_regular_attachments(email) do + Enum.filter(email.attachments, fn + attachment -> + !is_nil(attachment) && + (is_nil(attachment.content_id) || String.length(attachment.content_id) == 0) + end) end @spec attachment_part_tuples([tuple()]) :: list(tuple()) @@ -178,21 +280,4 @@ defmodule BambooSes.Render.Raw do end defp preprocess_header({key, value}), do: {key, value} - - defp compile_parts(email) do - [ - {:plain, email.text_body}, - {:html, email.html_body}, - Enum.map(email.attachments, fn attachment -> - {:attachment, attachment.data, attachment} - end) - ] - |> List.flatten() - |> Enum.filter(¬_empty_tuple_value(&1)) - end - - defp not_empty_tuple_value(tuple) when is_tuple(tuple) do - value = elem(tuple, 1) - value != nil && value != [] && value != "" - end end diff --git a/mix.lock b/mix.lock index 3f417ec..468d7b0 100644 --- a/mix.lock +++ b/mix.lock @@ -1,19 +1,19 @@ %{ "bamboo": {:hex, :bamboo, "2.2.0", "f10a406d2b7f5123eb1f02edfa043c259db04b47ab956041f279eaac776ef5ce", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8c3b14ba7d2f40cb4be04128ed1e2aff06d91d9413d38bafb4afccffa3ade4fc"}, - "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, - "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, + "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, "eiconv": {:hex, :eiconv, "1.0.0", "ee1e47ee37799a05beff7a68d61f63cccc93101833c4fb94b454c23b12a21629", [:rebar3], [], "hexpm", "8c80851decf72fc4571a70278d7932e9a87437770322077ecf797533fbb792cd"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_aws": {:hex, :ex_aws, "2.5.3", "9c2d05ba0c057395b12c7b5ca6267d14cdaec1d8e65bdf6481fe1fd245accfb4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "67115f1d399d7ec4d191812ee565c6106cb4b1bbf19a9d4db06f265fd87da97e"}, "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, diff --git a/test/lib/bamboo/adapters/content_raw_parts_text.exs b/test/lib/bamboo/adapters/content_raw_parts_text.exs new file mode 100644 index 0000000..a440ef4 --- /dev/null +++ b/test/lib/bamboo/adapters/content_raw_parts_text.exs @@ -0,0 +1,354 @@ +defmodule BambooSes.ContentRawPartsTest do + use ExUnit.Case + alias BambooSes.Message.Content + alias BambooSes.{EmailParser, TestHelpers} + alias Bamboo.Email + + @moduledoc """ + + TEXT | HTML | ATTACHMENTS | INLINE ATTACHMENTS | RESULT + f | f | f | f | NOT VALID + f | f | f | t | mixed(attachments[]) + f | f | t | f | mixed(attachments[]) + f | f | t | t | mixed(attachments[]) + f | t | f | f | text/html + f | t | f | t | related(html,inline_attachments[]) + f | t | t | f | mixed(html,attachments[]) + f | t | t | t | mixed(related(html,inline_attachments[]),attachments[]) + t | f | f | f | text/plain + t | f | f | t | mixed(text,attachments[]) + t | f | t | f | mixed(text,attachments[]) + t | f | t | t | mixed(text,attachments[]) + t | t | f | f | alternative(text,html) + t | t | f | t | related(alternative(text,html),inline_attachments[]) + t | t | t | f | mixed(alternative(text,html),attachments[]) + t | t | t | t | mixed(related(alternative(text,html),inline_attachments[]),attachments[]) + + """ + + @doc "f f f t" + test "generates multipart/mixed when only inline attachments are provided; no text, no html" do + content = + TestHelpers.new_email() + |> Email.text_body("") + |> Email.html_body("") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/pole.png"), + content_id: "img-1" + ) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "mixed", _, _, [png]} = EmailParser.parse(raw_data) + assert {"image", "png", _, _, _} = png + end + + @doc "f f t f" + test "generates multipart/mixed when only regular attachments are provided; no text, no html" do + content = + TestHelpers.new_email() + |> Email.text_body("") + |> Email.html_body("") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "mixed", _, _, [pdf]} = EmailParser.parse(raw_data) + assert {"application", "pdf", _, _, _} = pdf + end + + @doc "f f t t" + test "generates multipart/mixed when inline and regular attachments are provided; no text, no html" do + content = + TestHelpers.new_email() + |> Email.text_body("") + |> Email.html_body("") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/pole.png"), + content_id: "img-1" + ) + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "mixed", _, _, [pdf, png]} = EmailParser.parse(raw_data) + assert {"application", "pdf", _, _, _} = pdf + assert {"image", "png", _, _, _} = png + end + + @doc "f t f f" + test "generates text/html when only html is provided" do + content = + TestHelpers.new_email() + |> Email.text_body("") + |> Email.html_body("Email body") + |> Email.put_header("X-Custom-Header", "custom-value") + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"text", "html", _, _, "Email body"} = EmailParser.parse(raw_data) + end + + @doc "f t f t" + test "generates multipart/related when html and attachments with content_id are provided" do + content = + TestHelpers.new_email() + |> Email.text_body("") + |> Email.html_body("Email body") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/pole.png"), + content_id: "img-1" + ) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "related", _, _, [html, png]} = EmailParser.parse(raw_data) + assert {"text", "html", _, _, "Email body"} = html + assert {"image", "png", _, _, _} = png + end + + @doc "f t t f" + test "generates multipart/mixed when html and attachments are provided" do + content = + TestHelpers.new_email() + |> Email.text_body("") + |> Email.html_body("Email body") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "mixed", _, _, [html, pdf]} = EmailParser.parse(raw_data) + assert {"text", "html", _, _, "Email body"} = html + assert {"application", "pdf", _, _, _} = pdf + end + + @doc "f t t t" + test "generates multipart/mixed with multipart/related" do + content = + TestHelpers.new_email() + |> Email.text_body("") + |> Email.html_body("Email body") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/pole.png"), + content_id: "img-1" + ) + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "mixed", _, _, [related, pdf]} = EmailParser.parse(raw_data) + assert {"multipart", "related", _, _, [html, png]} = related + assert {"application", "pdf", _, _, _} = pdf + assert {"text", "html", _, _, "Email body"} = html + assert {"image", "png", _, _, _} = png + end + + @doc "t f f f" + test "generates text/plain when only text is provided" do + content = + TestHelpers.new_email() + |> Email.text_body("Email text body") + |> Email.html_body("") + |> Email.put_header("X-Custom-Header", "custom-value") + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"text", "plain", _, _, "Email text body"} = EmailParser.parse(raw_data) + end + + @doc "t f f t" + test "generates multipart/mixed when text and inline attathments are provided" do + content = + TestHelpers.new_email() + |> Email.text_body("Email text body") + |> Email.html_body("") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/pole.png"), + content_id: "img-1" + ) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "mixed", _, _, [text, png]} = EmailParser.parse(raw_data) + assert {"text", "plain", _, _, "Email text body"} = text + assert {"image", "png", _, _, _} = png + end + + @doc "t f t f" + test "generates multipart/mixed when text and attachments are provided" do + content = + TestHelpers.new_email() + |> Email.text_body("Email text body") + |> Email.html_body("") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "mixed", _, _, [text, pdf]} = EmailParser.parse(raw_data) + assert {"text", "plain", _, _, "Email text body"} = text + assert {"application", "pdf", _, _, _} = pdf + end + + @doc "t f t t" + test "generates multipart/mixed when text and both inline and regular attachments are provided" do + content = + TestHelpers.new_email() + |> Email.text_body("Email text body") + |> Email.html_body("") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/pole.png"), + content_id: "img-1" + ) + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "mixed", _, _, [text, pdf, png]} = EmailParser.parse(raw_data) + assert {"text", "plain", _, _, "Email text body"} = text + assert {"image", "png", _, _, _} = png + assert {"application", "pdf", _, _, _} = pdf + end + + @doc "t t f f" + test "generates multipart/alternative when text and html are provided" do + content = + TestHelpers.new_email() + |> Email.text_body("Email text body") + |> Email.html_body("Email html body") + |> Email.put_header("X-Custom-Header", "custom-value") + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "alternative", _, _, [text, html]} = EmailParser.parse(raw_data) + assert {"text", "plain", _, _, "Email text body"} = text + assert {"text", "html", _, _, "Email html body"} = html + end + + @doc "t t f t" + test "generates multipart/related when text, html and inline attachments are provided" do + content = + TestHelpers.new_email() + |> Email.text_body("Email text body") + |> Email.html_body("Email html body") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/pole.png"), + content_id: "img-1" + ) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "related", _, _, [alternative, png]} = EmailParser.parse(raw_data) + assert {"multipart", "alternative", _, _, [text, html]} = alternative + assert {"image", "png", _, _, _} = png + assert {"text", "plain", _, _, "Email text body"} = text + assert {"text", "html", _, _, "Email html body"} = html + end + + @doc "t t t f" + test "generates multipart/mixed when text, html and regular attachements are provided" do + content = + TestHelpers.new_email() + |> Email.text_body("Email text body") + |> Email.html_body("Email html body") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "mixed", _, _, [alternative, pdf]} = EmailParser.parse(raw_data) + assert {"multipart", "alternative", _, _, [text, html]} = alternative + assert {"text", "plain", _, _, "Email text body"} = text + assert {"text", "plain", _, _, "Email text body"} = text + assert {"text", "html", _, _, "Email html body"} = html + assert {"application", "pdf", _, _, _} = pdf + end + + @doc "t t t t" + test "generates multipart/mixed when text, html and both inline and regular attachements are provided" do + content = + TestHelpers.new_email() + |> Email.text_body("Email text body") + |> Email.html_body("Email html body") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/pole.png"), + content_id: "img-1" + ) + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) + |> Content.build_from_bamboo_email() + + %Content{ + Raw: %{ + Data: raw_data + } + } = content + + assert {"multipart", "mixed", _, _, [related, pdf]} = EmailParser.parse(raw_data) + assert {"multipart", "related", _, _, [alternative, png]} = related + assert {"multipart", "alternative", _, _, [text, html]} = alternative + assert {"image", "png", _, _, _} = png + assert {"text", "plain", _, _, "Email text body"} = text + assert {"application", "pdf", _, _, _} = pdf + assert {"text", "html", _, _, "Email html body"} = html + end +end diff --git a/test/lib/bamboo/adapters/content_raw_test.exs b/test/lib/bamboo/adapters/content_raw_test.exs index ed2cae9..bc1aaae 100644 --- a/test/lib/bamboo/adapters/content_raw_test.exs +++ b/test/lib/bamboo/adapters/content_raw_test.exs @@ -7,6 +7,7 @@ defmodule BambooSes.ContentRawTest do test "generates raw content when there is a header" do content = TestHelpers.new_email() + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) |> Email.put_header("X-Custom-Header", "custom-header-value") |> Content.build_from_bamboo_email() @@ -18,12 +19,7 @@ defmodule BambooSes.ContentRawTest do parsed_content = EmailParser.parse(raw_data) - raw_data - |> EmailParser.parse() - assert EmailParser.subject(parsed_content) == "Welcome to the app." - assert EmailParser.text(parsed_content) == "Thanks for joining!" - assert EmailParser.html(parsed_content) == "Thanks for joining!" assert header = EmailParser.header(parsed_content, "X-Custom-Header") assert header == "custom-header-value" end @@ -100,6 +96,7 @@ defmodule BambooSes.ContentRawTest do content = TestHelpers.new_email() + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) |> Email.put_header("X-Custom-Header", custom_header) |> Content.build_from_bamboo_email() diff --git a/test/lib/bamboo/adapters/content_simple_test.exs b/test/lib/bamboo/adapters/content_simple_test.exs index f7abe85..61810ff 100644 --- a/test/lib/bamboo/adapters/content_simple_test.exs +++ b/test/lib/bamboo/adapters/content_simple_test.exs @@ -15,7 +15,36 @@ defmodule BambooSes.ContentSimpleTest do Html: %{Charset: "UTF-8", Data: "Thanks for joining!"}, Text: %{Charset: "UTF-8", Data: "Thanks for joining!"} }, - Subject: %{Charset: "UTF-8", Data: "Welcome to the app."} + Subject: %{Charset: "UTF-8", Data: "Welcome to the app."}, + Headers: [] + } + } + end + + test "generates simple content with headers" do + content = + new_email() + |> Email.put_header("X-Custom-Header", "custom-value") + |> Email.put_header("X-Custom-Non-Ascii-Header", "𐰴𐰀𐰽𐱄𐰆𐰢") + |> Content.build_from_bamboo_email() + + assert content == %Content{ + Simple: %{ + Body: %{ + Html: %{Charset: "UTF-8", Data: "Thanks for joining!"}, + Text: %{Charset: "UTF-8", Data: "Thanks for joining!"} + }, + Subject: %{Charset: "UTF-8", Data: "Welcome to the app."}, + Headers: [ + %{ + "Name" => "X-Custom-Header", + "Value" => "custom-value" + }, + %{ + "Name" => "X-Custom-Non-Ascii-Header", + "Value" => "=?utf-8?B?8JCwtPCQsIDwkLC98JCxhPCQsIbwkLCi?=" + } + ] } } end @@ -32,7 +61,8 @@ defmodule BambooSes.ContentSimpleTest do Html: %{Charset: "UTF-8", Data: "Thanks for joining!"}, Text: %{Charset: "UTF-8", Data: "Thanks for joining!"} }, - Subject: %{Charset: "UTF-8", Data: "Welcome to the app."} + Subject: %{Charset: "UTF-8", Data: "Welcome to the app."}, + Headers: [] } } end diff --git a/test/lib/bamboo/adapters/ses_adapter_test.exs b/test/lib/bamboo/adapters/ses_adapter_test.exs index 1d84719..ffe8962 100644 --- a/test/lib/bamboo/adapters/ses_adapter_test.exs +++ b/test/lib/bamboo/adapters/ses_adapter_test.exs @@ -323,6 +323,7 @@ defmodule Bamboo.SesAdapterTest do TestHelpers.new_email() |> Email.put_header("X-Custom-Header", "header-value; another-value") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) |> SesAdapter.deliver(%{}) end @@ -351,6 +352,7 @@ defmodule Bamboo.SesAdapterTest do TestHelpers.new_email() |> Email.put_header("X-Custom-Header", "header-value") |> Email.from({"John [Schmidt]", "from@example.com"}) + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) |> SesAdapter.deliver(%{}) end @@ -377,7 +379,7 @@ defmodule Bamboo.SesAdapterTest do expect(HttpMock, :request, expected_request_fn) TestHelpers.new_email() - |> Email.put_header("X-Custom-Header", "header-value") + |> Email.put_attachment(Path.join(__DIR__, "../../../support/invoice.pdf")) |> Email.put_header("Reply-To", {"John Schmidt", "reply-to@example.com"}) |> SesAdapter.deliver(%{}) end diff --git a/test/support/pole.png b/test/support/pole.png new file mode 100644 index 0000000..3fda0cc Binary files /dev/null and b/test/support/pole.png differ