diff options
author | Hubert Chathi <hubert@uhoreg.ca> | 2020-08-15 04:19:50 -0400 |
---|---|---|
committer | Hubert Chathi <hubert@uhoreg.ca> | 2020-08-15 04:28:16 -0400 |
commit | 988bf8af92c6f253ae729a044178a19c76648a71 (patch) | |
tree | 04df4a99c2a26a66fd7927a42a25163a6b685171 | |
parent | s/Any/any/ in typespecs (diff) |
allow body to be iolist or a filename, and add media upload/download
19 files changed, 578 insertions, 19 deletions
diff --git a/lib/polyjuice/client.ex b/lib/polyjuice/client.ex index dfe8775..0d3bc75 100644 --- a/lib/polyjuice/client.ex +++ b/lib/polyjuice/client.ex @@ -64,6 +64,8 @@ defmodule Polyjuice.Client do def prefix_r0, do: "_matrix/client/r0" @doc "The unstable client URL prefix" def prefix_unstable, do: "_matrix/client/unstable" + @doc "The r0 media URL prefix" + def prefix_media_r0, do: "_matrix/media/r0" defprotocol API do @moduledoc """ @@ -122,10 +124,11 @@ defmodule Polyjuice.Client do headers: headers, url: url, body: body, - auth_required: auth_required + auth_required: auth_required, + stream_response: stream_response } = Polyjuice.Client.Endpoint.Proto.http_spec(endpoint, base_url) - Logger.debug("calling #{url}") + Logger.debug("calling #{method} #{url}") if auth_required and access_token == nil do {:error, :auth_required} @@ -141,16 +144,21 @@ defmodule Polyjuice.Client do headers end, body, - [:with_body] + [] ) do - {:ok, status_code, resp_headers, body} -> + {:ok, status_code, resp_headers, client_ref} -> Logger.debug("status code #{status_code}") Polyjuice.Client.Endpoint.Proto.transform_http_result( endpoint, status_code, resp_headers, - body + if stream_response do + Polyjuice.Client.hackney_response_stream(client_ref) + else + {:ok, body} = :hackney.body(client_ref) + body + end ) err -> @@ -169,6 +177,19 @@ defmodule Polyjuice.Client do end end + @doc false + def hackney_response_stream(client_ref) do + Stream.unfold( + client_ref, + fn client_ref -> + case :hackney.stream_body(client_ref) do + {:ok, data} -> {data, client_ref} + _ -> nil + end + end + ) + end + @doc """ Synchronize messages from the server. diff --git a/lib/polyjuice/client/endpoint.ex b/lib/polyjuice/client/endpoint.ex index 41c0a86..b344743 100644 --- a/lib/polyjuice/client/endpoint.ex +++ b/lib/polyjuice/client/endpoint.ex @@ -22,16 +22,18 @@ defmodule Polyjuice.Client.Endpoint do - `method` is the HTTP verb - `headers` is a list of the HTTP headers - `url` is the URL to call - - `body` is the HTTP body (if any) - - `transform` is a function to transform the result (status code, headers, content) to a return value + - `body` is the HTTP body (if any) as a binary, or `{:file, filename}` + - `transform` is a function to transform the result (status code, headers, + content) to a return value - `auth_required` indicates whether the end point requires authentication """ @type t :: %__MODULE__{ method: atom, headers: [{String.t(), String.t()}], url: String.t(), - body: String.t(), - auth_required: true | false + body: iodata() | {:file, String.t()} | {Enumerable.t(), integer}, + auth_required: boolean, + stream_response: boolean } @enforce_keys [:method, :headers, :url] defstruct [ @@ -39,7 +41,8 @@ defmodule Polyjuice.Client.Endpoint do :headers, :url, body: "", - auth_required: true + auth_required: true, + stream_response: false ] end diff --git a/lib/polyjuice/client/endpoint/get_media_download.ex b/lib/polyjuice/client/endpoint/get_media_download.ex new file mode 100644 index 0000000..25250f9 --- /dev/null +++ b/lib/polyjuice/client/endpoint/get_media_download.ex @@ -0,0 +1,133 @@ +# Copyright 2020 Hubert Chathi <hubert@uhoreg.ca> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule Polyjuice.Client.Endpoint.GetMediaDownload do + @moduledoc """ + Download from the media repository. + + https://matrix.org/docs/spec/client_server/latest#get-matrix-media-r0-download-servername-mediaid + https://matrix.org/docs/spec/client_server/latest#get-matrix-media-r0-download-servername-mediaid-filename + """ + + @type t :: %__MODULE__{ + url: String.t() | URI.t() | {String.t(), String.t()}, + allow_remote: boolean, + filename: String.t() | nil + } + + @enforce_keys [:url] + defstruct [ + :url, + :filename, + allow_remote: true + ] + + defimpl Polyjuice.Client.Endpoint.Proto do + def http_spec( + %{ + url: mxc_url, + allow_remote: allow_remote, + filename: filename + }, + base_url + ) do + e = &URI.encode_www_form/1 + + {server_name, media_id} = + case mxc_url do + _ when is_binary(mxc_url) -> + %{ + scheme: "mxc", + host: host, + path: path + } = URI.parse(mxc_url) + + {host, String.trim_leading(path, "/")} + + %URI{ + scheme: "mxc", + host: host, + path: path + } -> + {host, String.trim_leading(path, "/")} + + {_, _} -> + mxc_url + end + + filename_part = if is_binary(filename), do: "/" <> filename, else: "" + + url = %{ + URI.merge( + base_url, + "#{Polyjuice.Client.prefix_media_r0()}/download/#{e.(server_name)}/#{e.(media_id)}#{ + filename_part + }" + ) + | query: if(allow_remote, do: nil, else: "allow_remote=false") + } + + %Polyjuice.Client.Endpoint.HttpSpec{ + method: :get, + headers: [ + {"Accept", "*/*"} + ], + url: to_string(url), + stream_response: true + } + end + + def transform_http_result(req, status_code, resp_headers, body) do + if status_code == 200 do + {_, content_type} = + Enum.find( + resp_headers, + {nil, "application/octet-stream"}, + fn {name, _} -> String.downcase(name, :ascii) == "content-type" end + ) + + filename = + Enum.find_value( + resp_headers, + fn {name, value} -> + if String.downcase(name, :ascii) == "content-disposition" do + Regex.split(~r";\s*", value) + |> Enum.find_value(fn directive -> + # FIXME: also handle "filename*" + with [d_name, quoted_name] <- String.split(directive, "=", parts: 2), + "filename" <- String.downcase(d_name) do + case Regex.run(~r/"(.*)"/, quoted_name) do + [_, name] -> Regex.replace(~r/\\(.)/, name, "\\1") + nil -> quoted_name + end + # do some basic sanitizing + |> (&Regex.replace(~r/\0/, &1, "")).() + |> Path.basename() + |> (&if(&1 == "", do: nil, else: &1)).() + else + _ -> + nil + end + end) + end + end + ) + + {:ok, filename, content_type, body} + else + Polyjuice.Client.Endpoint.parse_response(req, status_code, resp_headers, Enum.join(body)) + end + end + end +end diff --git a/lib/polyjuice/client/endpoint/post_join.ex b/lib/polyjuice/client/endpoint/post_join.ex index 00d3669..1ddc922 100644 --- a/lib/polyjuice/client/endpoint/post_join.ex +++ b/lib/polyjuice/client/endpoint/post_join.ex @@ -44,7 +44,7 @@ defmodule Polyjuice.Client.Endpoint.PostJoin do e = &URI.encode_www_form/1 body = - Jason.encode!( + Jason.encode_to_iodata!( if third_party_signed do %{"third_party_signed" => third_party_signed} else diff --git a/lib/polyjuice/client/endpoint/post_login.ex b/lib/polyjuice/client/endpoint/post_login.ex index ce093f0..0414cfc 100644 --- a/lib/polyjuice/client/endpoint/post_login.ex +++ b/lib/polyjuice/client/endpoint/post_login.ex @@ -65,7 +65,7 @@ defmodule Polyjuice.Client.Endpoint.PostLogin do ] |> Enum.concat() |> Map.new() - |> Jason.encode!() + |> Jason.encode_to_iodata!() %Polyjuice.Client.Endpoint.HttpSpec{ method: :post, diff --git a/lib/polyjuice/client/endpoint/post_media_upload.ex b/lib/polyjuice/client/endpoint/post_media_upload.ex new file mode 100644 index 0000000..27fca1a --- /dev/null +++ b/lib/polyjuice/client/endpoint/post_media_upload.ex @@ -0,0 +1,75 @@ +# Copyright 2020 Hubert Chathi <hubert@uhoreg.ca> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule Polyjuice.Client.Endpoint.PostMediaUpload do + @moduledoc """ + Upload to the media repository. + + https://matrix.org/docs/spec/client_server/latest#post-matrix-media-r0-upload + """ + + @type t :: %__MODULE__{ + filename: String.t(), + data: binary | {:file, String.t()}, + mimetype: String.t() + } + + @enforce_keys [:filename, :data, :mimetype] + defstruct [ + :filename, + :data, + mimetype: "application/octet-stream" + ] + + defimpl Polyjuice.Client.Endpoint.Proto do + def http_spec( + %{ + filename: filename, + data: data, + mimetype: mimetype + }, + base_url + ) do + e = &URI.encode_www_form/1 + + url = %{ + URI.merge( + base_url, + "#{Polyjuice.Client.prefix_media_r0()}/upload" + ) + | query: "filename=#{e.(filename)}" + } + + %Polyjuice.Client.Endpoint.HttpSpec{ + method: :post, + headers: [ + {"Accept", "application/json"}, + {"Content-Type", mimetype} + ], + url: to_string(url), + body: data + } + end + + def transform_http_result(req, status_code, resp_headers, body) do + Polyjuice.Client.Endpoint.parse_response(req, status_code, resp_headers, body) + end + end + + defimpl Polyjuice.Client.Endpoint.BodyParser do + def parse(_req, parsed) do + {:ok, Map.get(parsed, "content_uri")} + end + end +end diff --git a/lib/polyjuice/client/endpoint/post_user_filter.ex b/lib/polyjuice/client/endpoint/post_user_filter.ex index ceab001..22605aa 100644 --- a/lib/polyjuice/client/endpoint/post_user_filter.ex +++ b/lib/polyjuice/client/endpoint/post_user_filter.ex @@ -39,7 +39,7 @@ defmodule Polyjuice.Client.Endpoint.PostUserFilter do base_url ) do e = &URI.encode_www_form/1 - body = Jason.encode!(filter) + body = Jason.encode_to_iodata!(filter) %Polyjuice.Client.Endpoint.HttpSpec{ method: :post, diff --git a/lib/polyjuice/client/endpoint/put_rooms_send.ex b/lib/polyjuice/client/endpoint/put_rooms_send.ex index 855f346..1bb2ee8 100644 --- a/lib/polyjuice/client/endpoint/put_rooms_send.ex +++ b/lib/polyjuice/client/endpoint/put_rooms_send.ex @@ -45,7 +45,7 @@ defmodule Polyjuice.Client.Endpoint.PutRoomsSend do base_url ) do e = &URI.encode_www_form/1 - body = Jason.encode!(message) + body = Jason.encode_to_iodata!(message) %Polyjuice.Client.Endpoint.HttpSpec{ method: :put, diff --git a/lib/polyjuice/client/endpoint/put_rooms_state.ex b/lib/polyjuice/client/endpoint/put_rooms_state.ex index 74407bf..ca87c42 100644 --- a/lib/polyjuice/client/endpoint/put_rooms_state.ex +++ b/lib/polyjuice/client/endpoint/put_rooms_state.ex @@ -45,7 +45,7 @@ defmodule Polyjuice.Client.Endpoint.PutRoomsState do base_url ) do e = &URI.encode_www_form/1 - body = Jason.encode!(content) + body = Jason.encode_to_iodata!(content) %Polyjuice.Client.Endpoint.HttpSpec{ method: :put, diff --git a/lib/polyjuice/client/media.ex b/lib/polyjuice/client/media.ex new file mode 100644 index 0000000..2a37025 --- /dev/null +++ b/lib/polyjuice/client/media.ex @@ -0,0 +1,99 @@ +# Copyright 2020 Hubert Chathi <hubert@uhoreg.ca> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule Polyjuice.Client.Media do + @moduledoc """ + Media-related functions. + """ + require Logger + + @doc """ + Upload a file to the media repository. + + `data` may be either a binary, indicating the file contents, or a tuple + `{:file, path}`, where `path` is a path to the file to be uploaded. + + `opts` is a keyword list of options. Recognized options are: + - `filename:` the filename to use for the uploaded file. This is required + when `data` is a binary. If not specified, and `data` is of the form + `{:file, path}`, then the filename defaults to the basename of the path. + - `mimetype:` the mimetype to use for the uploaded file. Defaults to + `application/octet-stream`. + """ + @spec upload( + client_api :: Polyjuice.Client.API.t(), + data :: binary | {:file, String.t()}, + opts :: Keyword.t() + ) :: {:ok, String.t()} | any + def upload(client_api, data, opts \\ []) do + filename = + Keyword.get_lazy(opts, :filename, fn -> + case data do + {:file, name} -> + Path.basename(name) + end + end) + + mimetype = Keyword.get(opts, :mimetype, "application/octet-stream") + + Polyjuice.Client.API.call( + client_api, + %Polyjuice.Client.Endpoint.PostMediaUpload{ + filename: filename, + data: data, + mimetype: mimetype + } + ) + end + + @doc """ + Download a file from the media repository. + + `url` may be either a binary or a `URI` (giving an `mxc://` URI to download), + or a `{server_name, media_id}` tuple. `filename` is an (optional) filename to + request that the server use, and `allow_remote` indicates whether the server + should fetch media from remote servers if necessary (defaults to true). + + If successful, returns a tuple of the form `{:ok, filename, content_type, body}`, + where `body` is a `Stream` such that `Enum.join(body)` is the file contents. + """ + @spec download( + client_api :: Polyjuice.Client.API.t(), + url :: String.t() | URI.t() | {String.t(), String.t()}, + filename :: String.t(), + allow_remote :: boolean + ) :: {:ok, String.t(), String.t(), Stream.t()} | any + def download(client_api, url, filename \\ nil, allow_remote \\ true) do + Polyjuice.Client.API.call( + client_api, + %Polyjuice.Client.Endpoint.GetMediaDownload{ + url: url, + allow_remote: + cond do + is_boolean(filename) -> filename + is_boolean(allow_remote) -> allow_remote + end, + filename: + cond do + is_boolean(filename) -> nil + true -> filename + end + } + ) + end + + # 13.8.2.4 GET /_matrix/media/r0/thumbnail/{serverName}/{mediaId} + # 13.8.2.5 GET /_matrix/media/r0/preview_url + # 13.8.2.6 GET /_matrix/media/r0/config +end diff --git a/lib/polyjuice/client/sync.ex b/lib/polyjuice/client/sync.ex index d56099b..c898a6e 100644 --- a/lib/polyjuice/client/sync.ex +++ b/lib/polyjuice/client/sync.ex @@ -165,7 +165,8 @@ defmodule Polyjuice.Client.Sync do case :hackney.send_request( state.conn_ref, - {if(state.test, do: :put, else: :post), path, headers, Jason.encode!(state.set_filter)} + {if(state.test, do: :put, else: :post), path, headers, + Jason.encode_to_iodata!(state.set_filter)} ) do {:ok, status_code, _resp_headers, client_ref} -> case status_code do diff --git a/test/polyjuice/client/endpoint/post_user_filter_test.exs b/test/polyjuice/client/endpoint/post_user_filter_test.exs index 605834c..947474c 100644 --- a/test/polyjuice/client/endpoint/post_user_filter_test.exs +++ b/test/polyjuice/client/endpoint/post_user_filter_test.exs @@ -27,7 +27,7 @@ defmodule Polyjuice.Client.Endpoint.PostUserfilterTest do http_spec = Polyjuice.Client.Endpoint.Proto.http_spec(endpoint, "https://example.com") - assert http_spec == %Polyjuice.Client.Endpoint.HttpSpec{ + assert TestUtil.http_spec_body_to_binary(http_spec) == %Polyjuice.Client.Endpoint.HttpSpec{ auth_required: true, body: ~s({"presence":{"types":[]}}), headers: [ diff --git a/test/polyjuice/client/endpoint/put_rooms_send_test.exs b/test/polyjuice/client/endpoint/put_rooms_send_test.exs index 5c3f2ef..eeee870 100644 --- a/test/polyjuice/client/endpoint/put_rooms_send_test.exs +++ b/test/polyjuice/client/endpoint/put_rooms_send_test.exs @@ -27,7 +27,7 @@ defmodule Polyjuice.Client.Endpoint.PutRoomsSendTest do http_spec = Polyjuice.Client.Endpoint.Proto.http_spec(endpoint, "https://example.com") - assert http_spec == %Polyjuice.Client.Endpoint.HttpSpec{ + assert TestUtil.http_spec_body_to_binary(http_spec) == %Polyjuice.Client.Endpoint.HttpSpec{ auth_required: true, body: ~s({"body":"Hello World!"}), headers: [ diff --git a/test/polyjuice/client/endpoint/put_rooms_state_test.exs b/test/polyjuice/client/endpoint/put_rooms_state_test.exs index 04fa59e..690c885 100644 --- a/test/polyjuice/client/endpoint/put_rooms_state_test.exs +++ b/test/polyjuice/client/endpoint/put_rooms_state_test.exs @@ -27,7 +27,7 @@ defmodule Polyjuice.Client.Endpoint.PutRoomsStateTest do http_spec = Polyjuice.Client.Endpoint.Proto.http_spec(endpoint, "https://example.com") - assert http_spec == %Polyjuice.Client.Endpoint.HttpSpec{ + assert TestUtil.http_spec_body_to_binary(http_spec) == %Polyjuice.Client.Endpoint.HttpSpec{ auth_required: true, body: ~s({"name":"foo"}), headers: [ diff --git a/test/polyjuice/client/media_test.exs b/test/polyjuice/client/media_test.exs new file mode 100644 index 0000000..c9d8df9 --- /dev/null +++ b/test/polyjuice/client/media_test.exs @@ -0,0 +1,112 @@ +# Copyright 2019-2020 Hubert Chathi <hubert@uhoreg.ca> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule Polyjuice.Client.MediaTest do + use ExUnit.Case + doctest Polyjuice.Client.Room + + defmodule Httpd do + def _matrix(session_id, env, input) do + # FIXME: check authorization header + # FIXME: check method + [path | _] = + Keyword.get(env, :path_info) + |> to_string() + |> String.split("?", parts: 2) + + case path do + "media/r0/upload" -> + handle_upload(session_id, env, input) + + "media/r0/download/example.org/foo" -> + handle_download(session_id, env, input) + + _ -> + :mod_esi.deliver( + session_id, + 'Status: 404 Not Found\r\nContent-Type: application/json\r\n\r\n{"errcode":"M_NOT_FOUND","error":"Not found"}' + ) + end + end + + defp handle_upload(session_id, _env, {_, input}) do + {:ok, file} = File.read("mix.exs") + + if to_string(input) == file do + :mod_esi.deliver( + session_id, + 'Content-Type: application/json\r\n\r\n{"content_uri":"mxc://example.org/abcdefg"}' + ) + else + :mod_esi.deliver( + session_id, + 'Status: 400 Bad Request\r\nContent-Type: application/json\r\n\r\n{"errcode":"M_UNKNOWN","error":"Wrong contents"}' + ) + end + end + + defp handle_download(session_id, _env, _input) do + :mod_esi.deliver( + session_id, + 'Content-Type: text/plain\r\nContent-Disposition: attachment; filename="foo.txt"\r\n\r\nfoo' + ) + end + end + + test "upload" do + {:ok, tmpdir} = TestUtil.mktmpdir("sync-") + + try do + tmpdir_charlist = to_charlist(tmpdir) + + :inets.start() + + {:ok, httpd_pid} = + :inets.start( + :httpd, + port: 0, + server_name: 'sync.test', + server_root: tmpdir_charlist, + document_root: tmpdir_charlist, + bind_address: {127, 0, 0, 1}, + modules: [:mod_esi], + erl_script_alias: {'', [Polyjuice.Client.MediaTest.Httpd]} + ) + + port = :httpd.info(httpd_pid) |> Keyword.fetch!(:port) + + client = %Polyjuice.Client{ + base_url: "http://127.0.0.1:#{port}/Elixir.Polyjuice.Client.MediaTest.Httpd/", + access_token: "an_access_token", + user_id: "@alice:example.org", + test: true + } + + {:ok, url} = Polyjuice.Client.Media.upload(client, {:file, "mix.exs"}) + + assert url == "mxc://example.org/abcdefg" + + {:ok, filename, content_type, body} = + Polyjuice.Client.Media.download(client, "mxc://example.org/foo") + + assert filename == "foo.txt" + assert content_type == "text/plain" + assert Enum.join(body) == "foo" + + :inets.stop(:httpd, httpd_pid) + after + File.rm_rf(tmpdir) + end + end +end diff --git a/test/polyjuice/client/sync_test.exs b/test/polyjuice/client/sync_test.exs index f42e135..b1f2752 100644 --- a/test/polyjuice/client/sync_test.exs +++ b/test/polyjuice/client/sync_test.exs @@ -306,6 +306,8 @@ defmodule Polyjuice.Client.SyncTest do Process.unlink(sync_pid) Process.exit(sync_pid, :kill) + + :inets.stop(:httpd, httpd_pid) after Polyjuice.Client.Storage.close(storage) File.rm_rf(tmpdir) diff --git a/test/polyjuice/client_test.exs b/test/polyjuice/client_test.exs index ecb539a..8b5b415 100644 --- a/test/polyjuice/client_test.exs +++ b/test/polyjuice/client_test.exs @@ -70,4 +70,83 @@ defmodule Polyjuice.ClientTest do ) end end + + defmodule Httpd do + def foo(session_id, _env, {_, input}) do + if input == 'foobar' do + :mod_esi.deliver( + session_id, + 'Content-Type: application/json\r\n\r\n{"foo":"bar"}' + ) + else + :mod_esi.deliver( + session_id, + 'Status: 400 Bad Request\r\nContent-Type: application/json\r\n\r\n{"errcode":"M_UNKNOWN","error":"Wrong contents"}' + ) + end + end + end + + test "call" do + {:ok, tmpdir} = TestUtil.mktmpdir("sync-") + + try do + tmpdir_charlist = to_charlist(tmpdir) + + :inets.start() + + {:ok, httpd_pid} = + :inets.start( + :httpd, + port: 0, + server_name: 'sync.test', + server_root: tmpdir_charlist, + document_root: tmpdir_charlist, + bind_address: {127, 0, 0, 1}, + modules: [:mod_esi], + erl_script_alias: {'', [Polyjuice.ClientTest.Httpd]} + ) + + port = :httpd.info(httpd_pid) |> Keyword.fetch!(:port) + + client = %Polyjuice.Client{ + base_url: "http://127.0.0.1:#{port}/Elixir.Polyjuice.ClientTest.Httpd/", + access_token: "an_access_token", + user_id: "@alice:example.org", + test: true + } + + # binary body + assert Polyjuice.Client.API.call( + client, + %DummyEndpoint{ + http_spec: %Polyjuice.Client.Endpoint.HttpSpec{ + method: :put, + url: "foo", + headers: [], + body: "foobar", + auth_required: false + } + } + ) == {:ok, %{"foo" => "bar"}} + + # iolist body + assert Polyjuice.Client.API.call( + client, + %DummyEndpoint{ + http_spec: %Polyjuice.Client.Endpoint.HttpSpec{ + method: :put, + url: "foo", + headers: [], + body: [?f, ["oo"], ["b", [?a | "r"]]], + auth_required: false + } + } + ) == {:ok, %{"foo" => "bar"}} + + :inets.stop(:httpd, httpd_pid) + after + File.rm_rf(tmpdir) + end + end end diff --git a/test/support/dummy_endpoint.ex b/test/support/dummy_endpoint.ex new file mode 100644 index 0000000..9b0ea32 --- /dev/null +++ b/test/support/dummy_endpoint.ex @@ -0,0 +1,30 @@ +# Copyright 2020 Hubert Chathi <hubert@uhoreg.ca> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule DummyEndpoint do + @enforce_keys [:http_spec] + defstruct [ + :http_spec + ] + + defimpl Polyjuice.Client.Endpoint.Proto do + def http_spec(%{http_spec: http_spec}, base_url) do + %{http_spec | url: URI.merge(base_url, http_spec.url) |> to_string()} + end + + def transform_http_result(req, status_code, resp_headers, body) do + Polyjuice.Client.Endpoint.parse_response(req, status_code, resp_headers, body) + end + end +end diff --git a/test/support/test_util.ex b/test/support/test_util.ex index 2724678..80a1d7f 100644 --- a/test/support/test_util.ex +++ b/test/support/test_util.ex @@ -37,4 +37,8 @@ defmodule TestUtil do end ) end + + def http_spec_body_to_binary(http_spec) do + Map.update!(http_spec, :body, &IO.iodata_to_binary/1) + end end |