diff options
-rw-r--r-- | lib/polyjuice/client.ex | 184 | ||||
-rw-r--r-- | lib/polyjuice/client/endpoint.ex | 2 | ||||
-rw-r--r-- | lib/polyjuice/client/endpoint/post_login.ex | 3 | ||||
-rw-r--r-- | lib/polyjuice/client/endpoint/post_logout.ex | 3 | ||||
-rw-r--r-- | lib/polyjuice/client/endpoint/post_rooms_receipt.ex | 3 | ||||
-rw-r--r-- | lib/polyjuice/client/sync.ex | 33 | ||||
-rw-r--r-- | lib/polyjuice/client/user.ex | 102 | ||||
-rw-r--r-- | test/polyjuice/client/media_test.exs | 15 | ||||
-rw-r--r-- | test/polyjuice/client/sync_test.exs | 19 | ||||
-rw-r--r-- | test/polyjuice/client/user_test.exs | 105 | ||||
-rw-r--r-- | test/polyjuice/client_test.exs | 189 |
11 files changed, 370 insertions, 288 deletions
diff --git a/lib/polyjuice/client.ex b/lib/polyjuice/client.ex index 0d3bc75..84e44ca 100644 --- a/lib/polyjuice/client.ex +++ b/lib/polyjuice/client.ex @@ -16,13 +16,16 @@ defmodule Polyjuice.Client do @moduledoc """ Matrix client functions. + To create a client, use `start/2`, and to destroy it, use + `stop/1`. + The struct in this module, or any struct that implements the `Polyjuice.Client.API` protocol, can be used to connect to a Matrix server using the functions from submodules. - - `Polyjuice.Client.User`: user-related functions - `Polyjuice.Client.Filter`: build filters for use with sync - `Polyjuice.Client.Room`: interact with rooms, such as sending messages + - `Polyjuice.Client.Media`: use the media repository - `Polyjuice.Client.MsgBuilder`: build message contents To sync with the homeserver, start a process using the child spec returned by @@ -31,35 +34,82 @@ defmodule Polyjuice.Client do """ require Logger - @typedoc """ - Matrix client data. - - - `base_url` (string): Required. The base URL for the homeserver. - - `access_token` (string): Required to call endpoints that require an - authenticated user. The user's access token. - - `user_id` (string): Required for some endpoints and for sync. The user's - Matrix ID. - - `storage` (`Polyjuice.Client.Storage`): Required for some endpoints and for - sync. Storage for the client. - - """ + @typedoc false + # Matrix client data. + # - `base_url` (string): Required. The base URL for the homeserver. + # - `pid` (string): PID for the agent that contains the state. + # - `user_id` (string): Required for some endpoints and for sync. The user's + # Matrix ID. + # - `storage` (`Polyjuice.Client.Storage`): Required for some endpoints and for + # sync. Storage for the client. + # - `test` (boolean): if the client is used for a unit test (converts POST + # requests to PUT, to make mod_esi happy) @type t :: %__MODULE__{ base_url: String.t(), - access_token: String.t(), - user_id: String.t(), + pid: pid, storage: Polyjuice.Client.Storage.t(), test: boolean } - @enforce_keys [:base_url] + @enforce_keys [:base_url, :pid] defstruct [ :base_url, - :access_token, - :user_id, + :pid, :storage, test: false ] + @doc """ + Start a client. + """ + @spec start(base_url :: String.t(), opts :: Keyword.t()) :: t() + def start(base_url, opts \\ []) when is_binary(base_url) do + {:ok, pid} = + Agent.start(fn -> + %{ + access_token: Keyword.get(opts, :access_token), + user_id: Keyword.get(opts, :user_id), + device_id: Keyword.get(opts, :device_id) + } + end) + + %__MODULE__{ + base_url: + if(String.ends_with?(base_url, "/"), do: base_url, else: base_url <> "/") + |> URI.parse(), + pid: pid, + storage: Keyword.get(opts, :storage), + test: Keyword.get(opts, :test, false) + } + end + + @doc """ + Start a client, linking the client process to the calling process. + """ + @spec start_link(base_url :: String.t(), opts :: Keyword.t()) :: t() + def start_link(base_url, opts \\ []) when is_binary(base_url) do + {:ok, pid} = + Agent.start_link(fn -> + %{ + access_token: Keyword.get(opts, :access_token), + user_id: Keyword.get(opts, :user_id) + } + end) + + %__MODULE__{ + base_url: + if(String.ends_with?(base_url, "/"), do: base_url, else: base_url <> "/") + |> URI.parse(), + pid: pid, + storage: Keyword.get(opts, :storage), + test: Keyword.get(opts, :test, false) + } + end + + def stop(%__MODULE__{pid: pid}) do + Process.exit(pid, :normal) + end + @doc "The r0 client URL prefix" def prefix_r0, do: "_matrix/client/r0" @doc "The unstable client URL prefix" @@ -118,7 +168,7 @@ defmodule Polyjuice.Client do end defimpl Polyjuice.Client.API do - def call(%{base_url: base_url, access_token: access_token, test: test}, endpoint) do + def call(%{base_url: base_url, pid: pid, test: test}, endpoint) do %Polyjuice.Client.Endpoint.HttpSpec{ method: method, headers: headers, @@ -130,6 +180,13 @@ defmodule Polyjuice.Client do Logger.debug("calling #{method} #{url}") + access_token = + if auth_required do + Agent.get(pid, fn %{access_token: access_token} -> access_token end) + else + nil + end + if auth_required and access_token == nil do {:error, :auth_required} else @@ -193,8 +250,9 @@ defmodule Polyjuice.Client do @doc """ Synchronize messages from the server. - Normally, you should use `Polyjuice.Client.Sync` rather than calling this - function, but this function may be used where more control is needed. + Normally, you should create a sync process using + `Polyjuice.Client.API.sync_child_spec/3` rather than calling this function, but + this function may be used where more control is needed. `opts` is a keyword list of options: @@ -222,4 +280,88 @@ defmodule Polyjuice.Client do } ) end + + @doc """ + Log in with a password. + + `identifier` may be a single string (in which case it represents a username + -- either just the localpart or the full MXID), a tuple of the form + `{:email, "email@address"}`, a tuple of the form `{:phone, "country_code", + "phone_number"}`, or a map that is passed directly to the login endpoint. + + `opts` is a keyword list of options: + + - `device_id:` (string) the device ID to use + - `initial_device_display_name:` (string) the display name to use for the device + """ + @spec log_in_with_password( + client :: Polyjuice.Client.t(), + identifier :: String.t() | tuple() | map(), + password :: String.t(), + opts :: list() + ) :: {:ok, map()} | any + def log_in_with_password(client, identifier, password, opts \\ []) + when (is_binary(identifier) or is_tuple(identifier) or is_map(identifier)) and + is_binary(password) and is_list(opts) do + id = + case identifier do + x when is_binary(x) -> + %{ + "type" => "m.id.user", + "user" => identifier + } + + {:email, address} -> + %{ + "type" => "m.id.thirdparty", + "medium" => "email", + "address" => address + } + + {:phone, country, phone} -> + %{ + "type" => "m.id.phone", + "country" => country, + "phone" => phone + } + + x when is_map(x) -> + identifier + end + + ret = + {:ok, %{"access_token" => access_token, "user_id" => user_id, "device_id" => device_id}} = + Polyjuice.Client.API.call( + client, + %Polyjuice.Client.Endpoint.PostLogin{ + type: "m.login.password", + identifier: id, + password: password, + device_id: Keyword.get(opts, :device_id), + initial_device_display_name: Keyword.get(opts, :initial_device_display_name) + } + ) + + Agent.cast(client.pid, fn state -> + %{state | access_token: access_token, user_id: user_id, device_id: device_id} + end) + + ret + end + + @doc """ + Log out an existing session. + """ + @spec log_out(client :: Polyjuice.Client.t()) :: {:ok} | any + def log_out(client) do + {:ok} = + Polyjuice.Client.API.call( + client, + %Polyjuice.Client.Endpoint.PostLogout{} + ) + + Agent.cast(client.pid, fn state -> %{state | access_token: nil} end) + + {:ok} + end end diff --git a/lib/polyjuice/client/endpoint.ex b/lib/polyjuice/client/endpoint.ex index b344743..bc7589b 100644 --- a/lib/polyjuice/client/endpoint.ex +++ b/lib/polyjuice/client/endpoint.ex @@ -56,7 +56,7 @@ defmodule Polyjuice.Client.Endpoint do """ @spec http_spec( endpoint_args :: __MODULE__.t(), - base_url :: String.t() + base_url :: URI.t() ) :: Polyjuice.Client.Endpoint.HttpSpec.t() def http_spec(endpoint_args, base_url) diff --git a/lib/polyjuice/client/endpoint/post_login.ex b/lib/polyjuice/client/endpoint/post_login.ex index 0414cfc..808708a 100644 --- a/lib/polyjuice/client/endpoint/post_login.ex +++ b/lib/polyjuice/client/endpoint/post_login.ex @@ -50,8 +50,7 @@ defmodule Polyjuice.Client.Endpoint.PostLogin do initial_device_display_name: initial_device_display_name }, base_url - ) - when is_binary(base_url) do + ) do body = [ [{"type", type}, {"identifier", identifier}], diff --git a/lib/polyjuice/client/endpoint/post_logout.ex b/lib/polyjuice/client/endpoint/post_logout.ex index d0e4e1a..f16889c 100644 --- a/lib/polyjuice/client/endpoint/post_logout.ex +++ b/lib/polyjuice/client/endpoint/post_logout.ex @@ -27,8 +27,7 @@ defmodule Polyjuice.Client.Endpoint.PostLogout do def http_spec( %Polyjuice.Client.Endpoint.PostLogout{}, base_url - ) - when is_binary(base_url) do + ) do %Polyjuice.Client.Endpoint.HttpSpec{ method: :post, headers: [ diff --git a/lib/polyjuice/client/endpoint/post_rooms_receipt.ex b/lib/polyjuice/client/endpoint/post_rooms_receipt.ex index 5b92803..9b83891 100644 --- a/lib/polyjuice/client/endpoint/post_rooms_receipt.ex +++ b/lib/polyjuice/client/endpoint/post_rooms_receipt.ex @@ -41,8 +41,7 @@ defmodule Polyjuice.Client.Endpoint.PostRoomsReceipt do receipt_type: receipt_type }, base_url - ) - when is_binary(base_url) do + ) do e = &URI.encode_www_form/1 %Polyjuice.Client.Endpoint.HttpSpec{ diff --git a/lib/polyjuice/client/sync.ex b/lib/polyjuice/client/sync.ex index c898a6e..df1e13b 100644 --- a/lib/polyjuice/client/sync.ex +++ b/lib/polyjuice/client/sync.ex @@ -27,14 +27,13 @@ defmodule Polyjuice.Client.Sync do Task.start_link(__MODULE__, :sync, [client, listener, opts]) end - @enforce_keys [:send, :access_token, :homeserver_url, :uri, :user_id, :storage] + @enforce_keys [:send, :pid, :homeserver_url, :uri, :storage] defstruct [ :send, :conn_ref, - :access_token, + :pid, :homeserver_url, :uri, - :user_id, :storage, :since, query_params: "", @@ -51,19 +50,15 @@ defmodule Polyjuice.Client.Sync do @doc false def sync( %Polyjuice.Client{ - access_token: access_token, + pid: pid, base_url: homeserver_url, - user_id: user_id, storage: storage, test: test }, listener, opts ) do - # Make sure the URL ends with a slash, so that URI.merge doesn't clobber - # the last path component. (URI.merge is smart enough to drop the double - # "/" if it already ends with a slash.) - homeserver_url = homeserver_url <> "/" + homeserver_url = homeserver_url # Figure out how to handle the filter (if any): can we pass it in straight # to the query, or do we need to get its ID. And if we get its ID, do we @@ -103,9 +98,8 @@ defmodule Polyjuice.Client.Sync do connect(%__MODULE__{ send: if(is_function(listener), do: listener, else: &send(listener, &1)), - access_token: access_token, + pid: pid, homeserver_url: homeserver_url, - user_id: user_id, uri: uri, query_params: query_params, storage: storage, @@ -151,16 +145,21 @@ defmodule Polyjuice.Client.Sync do e = &URI.encode_www_form/1 + {access_token, user_id} = + Agent.get(state.pid, fn %{access_token: access_token, user_id: user_id} -> + {access_token, user_id} + end) + path = URI.merge( state.homeserver_url, - "#{Polyjuice.Client.prefix_r0()}/user/#{e.(state.user_id)}/filter" + "#{Polyjuice.Client.prefix_r0()}/user/#{e.(user_id)}/filter" ).path headers = [ {"Accept", "application/json"}, {"Content-Type", "application/json"}, - {"Authorization", "Bearer #{state.access_token}"} + {"Authorization", "Bearer #{access_token}"} ] case :hackney.send_request( @@ -224,9 +223,11 @@ defmodule Polyjuice.Client.Sync do defp do_sync(state) do if state.backoff, do: :timer.sleep(state.backoff * 1000) + access_token = Agent.get(state.pid, fn %{access_token: access_token} -> access_token end) + headers = [ {"Accept", "application/json"}, - {"Authorization", "Bearer #{state.access_token}"} + {"Authorization", "Bearer #{access_token}"} ] path = @@ -372,10 +373,12 @@ defmodule Polyjuice.Client.Sync do end ) + user_id = Agent.get(state.pid, fn %{user_id: user_id} -> user_id end) + inviter = invite_state |> Map.get("m.room.member", %{}) - |> Map.get(state.user_id, %{}) + |> Map.get(user_id, %{}) |> Map.get("sender") if inviter do diff --git a/lib/polyjuice/client/user.ex b/lib/polyjuice/client/user.ex deleted file mode 100644 index dd0f921..0000000 --- a/lib/polyjuice/client/user.ex +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright 2019 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.User do - @moduledoc """ - User-related functions. - """ - - @doc """ - Log in with a password. - - `client_or_url` may be either something that implements `Polyjuice.Client.API` - or a homeserver base URL. - - `identifier` may be a single string (in which case it represents a username - -- either just the localpart or the full MXID), a tuple of the form - `{:email, "email@address"}`, a tuple of the form `{:phone, "country_code", - "phone_number"}`, or a map that is passed directly to the login endpoint. - - `opts` is a keyword list of options: - - - `device_id:` (string) the device ID to use - - `initial_device_display_name:` (string) the display name to use for the device - """ - @spec log_in_with_password( - client_or_url :: Polyjuice.Client.API.t() | String.t(), - identifier :: String.t() | tuple() | map(), - password :: String.t(), - opts :: list() - ) :: {:ok, map()} | any - def log_in_with_password(client_or_url, identifier, password, opts \\ []) - when (is_binary(identifier) or is_tuple(identifier) or is_map(identifier)) and - is_binary(password) and is_list(opts) do - client_api = - if is_binary(client_or_url) do - %Polyjuice.Client{ - base_url: client_or_url - } - else - client_or_url - end - - id = - case identifier do - x when is_binary(x) -> - %{ - "type" => "m.id.user", - "user" => identifier - } - - {:email, address} -> - %{ - "type" => "m.id.thirdparty", - "medium" => "email", - "address" => address - } - - {:phone, country, phone} -> - %{ - "type" => "m.id.phone", - "country" => country, - "phone" => phone - } - - x when is_map(x) -> - identifier - end - - Polyjuice.Client.API.call( - client_api, - %Polyjuice.Client.Endpoint.PostLogin{ - type: "m.login.password", - identifier: id, - password: password, - device_id: Keyword.get(opts, :device_id), - initial_device_display_name: Keyword.get(opts, :initial_device_display_name) - } - ) - end - - @doc """ - Log out an existing session. - """ - @spec log_out(client_api :: Polyjuice.Client.API.t()) :: {:ok} | any - def log_out(client_api) do - Polyjuice.Client.API.call( - client_api, - %Polyjuice.Client.Endpoint.PostLogout{} - ) - end -end diff --git a/test/polyjuice/client/media_test.exs b/test/polyjuice/client/media_test.exs index c9d8df9..b4899ca 100644 --- a/test/polyjuice/client/media_test.exs +++ b/test/polyjuice/client/media_test.exs @@ -86,12 +86,13 @@ defmodule Polyjuice.Client.MediaTest do 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 - } + client = + Polyjuice.Client.start( + "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"}) @@ -104,6 +105,8 @@ defmodule Polyjuice.Client.MediaTest do assert content_type == "text/plain" assert Enum.join(body) == "foo" + Polyjuice.Client.stop(client) + :inets.stop(:httpd, httpd_pid) after File.rm_rf(tmpdir) diff --git a/test/polyjuice/client/sync_test.exs b/test/polyjuice/client/sync_test.exs index b1f2752..c1b4f64 100644 --- a/test/polyjuice/client/sync_test.exs +++ b/test/polyjuice/client/sync_test.exs @@ -17,7 +17,7 @@ defmodule Polyjuice.Client.SyncTest do defmodule Httpd do def _matrix(session_id, env, input) do - # FIXME: check authorization header + assert Keyword.get(env, :http_authorization) == 'Bearer an_access_token' # FIXME: check method [path | _] = Keyword.get(env, :path_info) @@ -213,13 +213,14 @@ defmodule Polyjuice.Client.SyncTest do port = :httpd.info(httpd_pid) |> Keyword.fetch!(:port) - client = %Polyjuice.Client{ - base_url: "http://127.0.0.1:#{port}/Elixir.Polyjuice.Client.SyncTest.Httpd", - access_token: "an_access_token", - user_id: "@alice:example.org", - storage: storage, - test: true - } + client = + Polyjuice.Client.start( + "http://127.0.0.1:#{port}/Elixir.Polyjuice.Client.SyncTest.Httpd", + access_token: "an_access_token", + user_id: "@alice:example.org", + storage: storage, + test: true + ) {:ok, sync_pid} = Polyjuice.Client.API.sync_child_spec(client, self(), filter: %{}) @@ -307,6 +308,8 @@ defmodule Polyjuice.Client.SyncTest do Process.unlink(sync_pid) Process.exit(sync_pid, :kill) + Polyjuice.Client.stop(client) + :inets.stop(:httpd, httpd_pid) after Polyjuice.Client.Storage.close(storage) diff --git a/test/polyjuice/client/user_test.exs b/test/polyjuice/client/user_test.exs deleted file mode 100644 index 06b95bd..0000000 --- a/test/polyjuice/client/user_test.exs +++ /dev/null @@ -1,105 +0,0 @@ -# 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.UserTest do - use ExUnit.Case - - test "log in" do - with client = %DummyClient{ - response: { - %Polyjuice.Client.Endpoint.PostLogin{ - type: "m.login.password", - identifier: %{ - "type" => "m.id.user", - "user" => "user" - }, - password: "password" - }, - {:ok, %{}} - } - } do - {:ok, %{}} = Polyjuice.Client.User.log_in_with_password(client, "user", "password") - end - - with client = %DummyClient{ - response: { - %Polyjuice.Client.Endpoint.PostLogin{ - type: "m.login.password", - identifier: %{ - "type" => "m.id.thirdparty", - "medium" => "email", - "address" => "user@example.com" - }, - password: "password", - device_id: "device_id", - initial_device_display_name: "Device name" - }, - {:ok, %{}} - } - } do - {:ok, %{}} = - Polyjuice.Client.User.log_in_with_password( - client, - {:email, "user@example.com"}, - "password", - device_id: "device_id", - initial_device_display_name: "Device name" - ) - end - - with client = %DummyClient{ - response: { - %Polyjuice.Client.Endpoint.PostLogin{ - type: "m.login.password", - identifier: %{ - "type" => "m.id.phone", - "country" => "CA", - "phone" => "1234567890" - }, - password: "password" - }, - {:ok, %{}} - } - } do - {:ok, %{}} = - Polyjuice.Client.User.log_in_with_password( - client, - {:phone, "CA", "1234567890"}, - "password" - ) - - {:ok, %{}} = - Polyjuice.Client.User.log_in_with_password( - client, - %{ - "type" => "m.id.phone", - "country" => "CA", - "phone" => "1234567890" - }, - "password" - ) - end - end - - test "log out" do - with client = %DummyClient{ - response: { - %Polyjuice.Client.Endpoint.PostLogout{}, - {:ok} - } - } do - {:ok} = Polyjuice.Client.User.log_out(client) - end - end -end diff --git a/test/polyjuice/client_test.exs b/test/polyjuice/client_test.exs index 8b5b415..0c880c1 100644 --- a/test/polyjuice/client_test.exs +++ b/test/polyjuice/client_test.exs @@ -15,9 +15,67 @@ defmodule Polyjuice.ClientTest do use ExUnit.Case + defmodule Httpd do + # for testing basic calls + 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 + + def _matrix(session_id, env, input) do + [path | _] = + Keyword.get(env, :path_info) + |> to_string() + |> String.split("?", parts: 2) + + case path do + "client/r0/login" -> + handle_login(session_id, env, input) + + "client/r0/logout" -> + handle_logout(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_login(session_id, _env, {_, input}) do + identifier_type = + to_string(input) |> Jason.decode!() |> Map.get("identifier") |> Map.get("type") + + :mod_esi.deliver( + session_id, + 'Content-Type: application/json\r\n\r\n{"user_id":"@alice:example.org","access_token":"#{ + identifier_type + }_login","device_id":"foo"}' + ) + end + + defp handle_logout(session_id, _env, _input) do + :mod_esi.deliver( + session_id, + 'Content-Type: application/json\r\n\r\n{}' + ) + end + end + test "transaction_id is unique" do client = %Polyjuice.Client{ - base_url: "http://localhost:8008" + base_url: "http://localhost:8008", + pid: nil } # the best that we can do is test that two calls to transaction_id return @@ -28,7 +86,8 @@ defmodule Polyjuice.ClientTest do test "sync child spec" do client = %Polyjuice.Client{ - base_url: "http://localhost:8008" + base_url: "http://localhost:8008", + pid: nil } %{id: Polyjuice.Client.Sync, restart: :permanent, start: start} = @@ -71,22 +130,6 @@ 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-") @@ -109,12 +152,13 @@ defmodule Polyjuice.ClientTest do 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 - } + client = + Polyjuice.Client.start( + "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( @@ -144,6 +188,103 @@ defmodule Polyjuice.ClientTest do } ) == {:ok, %{"foo" => "bar"}} + Polyjuice.Client.stop(client) + + :inets.stop(:httpd, httpd_pid) + after + File.rm_rf(tmpdir) + end + end + + test "login" 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.start( + "http://127.0.0.1:#{port}/Elixir.Polyjuice.ClientTest.Httpd/", + access_token: nil, + test: true + ) + + Polyjuice.Client.log_in_with_password(client, "@alice:example.org", "password") + + assert Agent.get(client.pid, fn %{ + access_token: access_token, + user_id: user_id, + device_id: device_id + } -> + {access_token, user_id, device_id} + end) == {"m.id.user_login", "@alice:example.org", "foo"} + + Polyjuice.Client.log_out(client) + + assert Agent.get(client.pid, fn %{access_token: access_token} -> access_token end) == nil + + Polyjuice.Client.log_in_with_password( + client, + {:email, "user@example.com"}, + "password" + ) + + assert Agent.get(client.pid, fn %{ + access_token: access_token, + user_id: user_id, + device_id: device_id + } -> + {access_token, user_id, device_id} + end) == {"m.id.thirdparty_login", "@alice:example.org", "foo"} + + Polyjuice.Client.log_in_with_password( + client, + {:phone, "CA", "1234567890"}, + "password" + ) + + assert Agent.get(client.pid, fn %{ + access_token: access_token, + user_id: user_id, + device_id: device_id + } -> + {access_token, user_id, device_id} + end) == {"m.id.phone_login", "@alice:example.org", "foo"} + + Polyjuice.Client.log_in_with_password( + client, + %{ + "type" => "ca.uhoreg.foo" + }, + "password" + ) + + assert Agent.get(client.pid, fn %{ + access_token: access_token, + user_id: user_id, + device_id: device_id + } -> + {access_token, user_id, device_id} + end) == {"ca.uhoreg.foo_login", "@alice:example.org", "foo"} + + Polyjuice.Client.stop(client) + :inets.stop(:httpd, httpd_pid) after File.rm_rf(tmpdir) |