summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHubert Chathi <hubert@uhoreg.ca>2020-08-15 04:19:50 -0400
committerHubert Chathi <hubert@uhoreg.ca>2020-08-15 04:28:16 -0400
commit988bf8af92c6f253ae729a044178a19c76648a71 (patch)
tree04df4a99c2a26a66fd7927a42a25163a6b685171
parents/Any/any/ in typespecs (diff)
allow body to be iolist or a filename, and add media upload/download
-rw-r--r--lib/polyjuice/client.ex31
-rw-r--r--lib/polyjuice/client/endpoint.ex13
-rw-r--r--lib/polyjuice/client/endpoint/get_media_download.ex133
-rw-r--r--lib/polyjuice/client/endpoint/post_join.ex2
-rw-r--r--lib/polyjuice/client/endpoint/post_login.ex2
-rw-r--r--lib/polyjuice/client/endpoint/post_media_upload.ex75
-rw-r--r--lib/polyjuice/client/endpoint/post_user_filter.ex2
-rw-r--r--lib/polyjuice/client/endpoint/put_rooms_send.ex2
-rw-r--r--lib/polyjuice/client/endpoint/put_rooms_state.ex2
-rw-r--r--lib/polyjuice/client/media.ex99
-rw-r--r--lib/polyjuice/client/sync.ex3
-rw-r--r--test/polyjuice/client/endpoint/post_user_filter_test.exs2
-rw-r--r--test/polyjuice/client/endpoint/put_rooms_send_test.exs2
-rw-r--r--test/polyjuice/client/endpoint/put_rooms_state_test.exs2
-rw-r--r--test/polyjuice/client/media_test.exs112
-rw-r--r--test/polyjuice/client/sync_test.exs2
-rw-r--r--test/polyjuice/client_test.exs79
-rw-r--r--test/support/dummy_endpoint.ex30
-rw-r--r--test/support/test_util.ex4
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