From 096c99df926b2b474a70ac4f4cfece8dee94be1f Mon Sep 17 00:00:00 2001 From: Pim Kunis Date: Tue, 13 Jul 2021 17:08:07 +0200 Subject: [PATCH] Add start of controller testing --- config/test.exs | 6 ++- lib/matrix_server/account.ex | 8 +++- lib/matrix_server/device.ex | 1 + lib/matrix_server_web/api/login.ex | 37 ++++++----------- .../controllers/auth_controller.ex | 1 + lib/matrix_server_web/router.ex | 4 +- mix.exs | 3 +- mix.lock | 1 + test/controllers/auth_controller_test.exs | 29 ++++++++++++++ test/controllers/info_controller_test.exs | 16 ++++++++ .../views/error_view_test.exs | 15 ------- test/support/channel_case.ex | 40 ------------------- test/support/conn_case.ex | 8 ++++ test/support/factory.ex | 24 +++++++++++ 14 files changed, 106 insertions(+), 87 deletions(-) create mode 100644 test/controllers/auth_controller_test.exs create mode 100644 test/controllers/info_controller_test.exs delete mode 100644 test/matrix_server_web/views/error_view_test.exs delete mode 100644 test/support/channel_case.ex create mode 100644 test/support/factory.ex diff --git a/config/test.exs b/config/test.exs index a6fa81b..a196afe 100644 --- a/config/test.exs +++ b/config/test.exs @@ -6,8 +6,8 @@ use Mix.Config # to provide built-in test partitioning in CI environment. # Run `mix help test` for more information. config :matrix_server, MatrixServer.Repo, - username: "postgres", - password: "postgres", + username: "matrix_server", + password: "matrix_server", database: "matrix_server_test#{System.get_env("MIX_TEST_PARTITION")}", hostname: "localhost", pool: Ecto.Adapters.SQL.Sandbox @@ -20,3 +20,5 @@ config :matrix_server, MatrixServerWeb.Endpoint, # Print only warnings and errors during test config :logger, level: :warn + +config :matrix_server, :server_name, "localhost" diff --git a/lib/matrix_server/account.ex b/lib/matrix_server/account.ex index 804fbcb..835c60f 100644 --- a/lib/matrix_server/account.ex +++ b/lib/matrix_server/account.ex @@ -38,9 +38,13 @@ defmodule MatrixServer.Account do |> Multi.insert(:device, fn %{account: account} -> device_id = Device.generate_device_id(account.localpart) - # TODO: fix device_id with UUID + params = + Map.update(params, :device_id, device_id, fn + nil -> device_id + x -> x + end) + Ecto.build_assoc(account, :devices) - |> Map.put(:device_id, device_id) |> Device.changeset(params) end) |> Multi.run(:device_with_access_token, &Device.insert_new_access_token/2) diff --git a/lib/matrix_server/device.ex b/lib/matrix_server/device.ex index 1381ed4..aa7a079 100644 --- a/lib/matrix_server/device.ex +++ b/lib/matrix_server/device.ex @@ -40,6 +40,7 @@ defmodule MatrixServer.Device do end def generate_device_id(localpart) do + # TODO: use random string instead time_string = DateTime.utc_now() |> DateTime.to_unix() diff --git a/lib/matrix_server_web/api/login.ex b/lib/matrix_server_web/api/login.ex index 81b74d3..447c4e9 100644 --- a/lib/matrix_server_web/api/login.ex +++ b/lib/matrix_server_web/api/login.ex @@ -1,44 +1,31 @@ -# https://gist.github.com/char0n/6fca76e886a2cfbd3aaa05526f287728 defmodule MatrixServerWeb.API.Login do use Ecto.Schema import Ecto.Changeset - # TODO: Maybe use inline embedded schema here - # https://hexdocs.pm/ecto/Ecto.Schema.html#embeds_one/3 - defmodule MatrixServerWeb.API.Login.Identifier do - use Ecto.Schema - - import Ecto.Changeset - - @primary_key false - embedded_schema do - field :type, :string - field :user, :string - end - - def changeset(identifier, params) do - identifier - |> cast(params, [:type, :user]) - |> validate_required([:type, :user]) - end - end - - alias MatrixServerWeb.API.Login.Identifier - @primary_key false embedded_schema do field :type, :string field :password, :string field :device_id, :string field :initial_device_display_name, :string - embeds_one :identifier, Identifier + + embeds_one :identifier, Identifier, primary_key: false do + field :type, :string + field :user, :string + end end def changeset(params) do %__MODULE__{} |> cast(params, [:type, :password, :device_id, :initial_device_display_name]) - |> cast_embed(:identifier, with: &Identifier.changeset/2, required: true) + |> cast_embed(:identifier, with: &identifier_changeset/2, required: true) |> validate_required([:type, :password]) end + + def identifier_changeset(identifier, params) do + identifier + |> cast(params, [:type, :user]) + |> validate_required([:type, :user]) + end end diff --git a/lib/matrix_server_web/controllers/auth_controller.ex b/lib/matrix_server_web/controllers/auth_controller.ex index 110ebb4..b100580 100644 --- a/lib/matrix_server_web/controllers/auth_controller.ex +++ b/lib/matrix_server_web/controllers/auth_controller.ex @@ -14,6 +14,7 @@ defmodule MatrixServerWeb.AuthController do def register(conn, %{"auth" => %{"type" => @register_type}} = params) do case Register.changeset(params) do %Changeset{valid?: true} = cs -> + # TODO: refactor this input = apply_changes(cs) |> Map.from_struct() diff --git a/lib/matrix_server_web/router.ex b/lib/matrix_server_web/router.ex index 09ec6c7..382169d 100644 --- a/lib/matrix_server_web/router.ex +++ b/lib/matrix_server_web/router.ex @@ -15,7 +15,7 @@ defmodule MatrixServerWeb.Router do scope "/_matrix", MatrixServerWeb do pipe_through :public - scope "/client/r0", as: :client do + scope "/client/r0" do post "/register", AuthController, :register get "/register/available", AccountController, :available get "/login", AuthController, :login_types @@ -28,7 +28,7 @@ defmodule MatrixServerWeb.Router do scope "/_matrix", MatrixServerWeb do pipe_through :authenticated - scope "/client/r0", as: :client do + scope "/client/r0" do get "/account/whoami", AccountController, :whoami post "/logout", AccountController, :logout post "/logout/all", AccountController, :logout_all diff --git a/mix.exs b/mix.exs index c8fa1c3..9ff7d67 100644 --- a/mix.exs +++ b/mix.exs @@ -42,7 +42,8 @@ defmodule MatrixServer.MixProject do {:jason, "~> 1.0"}, {:plug_cowboy, "~> 2.0"}, {:bcrypt_elixir, "~> 2.3"}, - {:cors_plug, "~> 2.0"} + {:cors_plug, "~> 2.0"}, + {:ex_machina, "~> 2.7", only: :test} ] end diff --git a/mix.lock b/mix.lock index b25f923..68135a5 100644 --- a/mix.lock +++ b/mix.lock @@ -11,6 +11,7 @@ "ecto": {:hex, :ecto, "3.6.2", "efdf52acfc4ce29249bab5417415bd50abd62db7b0603b8bab0d7b996548c2bc", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efad6dfb04e6f986b8a3047822b0f826d9affe8e4ebdd2aeedbfcb14fd48884e"}, "ecto_sql": {:hex, :ecto_sql, "3.6.2", "9526b5f691701a5181427634c30655ac33d11e17e4069eff3ae1176c764e0ba3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.6.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5ec9d7e6f742ea39b63aceaea9ac1d1773d574ea40df5a53ef8afbd9242fdb6b"}, "elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"}, + "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "phoenix": {:hex, :phoenix, "1.5.9", "a6368d36cfd59d917b37c44386e01315bc89f7609a10a45a22f47c007edf2597", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7e4bce20a67c012f1fbb0af90e5da49fa7bf0d34e3a067795703b74aef75427d"}, diff --git a/test/controllers/auth_controller_test.exs b/test/controllers/auth_controller_test.exs new file mode 100644 index 0000000..4e2abd8 --- /dev/null +++ b/test/controllers/auth_controller_test.exs @@ -0,0 +1,29 @@ +defmodule MatrixServerWeb.AuthControllerTest do + use MatrixServerWeb.ConnCase + + alias MatrixServerWeb.Endpoint + + describe "register endpoint" do + test "renders the auth flow when no auth parameter is given", %{conn: conn} do + conn = post(conn, Routes.auth_path(conn, :register)) + + assert %{"flows" => flows, "params" => _} = json_response(conn, 401) + assert is_list(flows) + end + + test "registers account with minimal information", %{conn: conn} do + params = %{ + "username" => "user", + "password" => "lemmein", + "auth" => %{"type" => "m.login.dummy"} + } + + conn = post_json(conn, Routes.auth_path(Endpoint, :register), params) + + user_id = MatrixServer.get_mxid("user") + + assert %{"access_token" => _, "device_id" => _, "user_id" => ^user_id} = + json_response(conn, 200) + end + end +end diff --git a/test/controllers/info_controller_test.exs b/test/controllers/info_controller_test.exs new file mode 100644 index 0000000..cf08c0b --- /dev/null +++ b/test/controllers/info_controller_test.exs @@ -0,0 +1,16 @@ +defmodule MatrixServerWeb.InfoControllerTest do + use MatrixServerWeb.ConnCase + + test "versions endpoint returns a list of supported Matrix spec versions", %{conn: conn} do + conn = get(conn, Routes.info_path(conn, :versions)) + + assert %{"versions" => versions} = json_response(conn, 200) + assert is_list(versions) + end + + test "unrecognized route renders M_UNRECOGNIZED error", %{conn: conn} do + conn = get(conn, MatrixServerWeb.Endpoint.url() <> "/sneed") + + assert %{"errcode" => "M_UNRECOGNIZED"} = json_response(conn, 400) + end +end diff --git a/test/matrix_server_web/views/error_view_test.exs b/test/matrix_server_web/views/error_view_test.exs deleted file mode 100644 index 357d931..0000000 --- a/test/matrix_server_web/views/error_view_test.exs +++ /dev/null @@ -1,15 +0,0 @@ -defmodule MatrixServerWeb.ErrorViewTest do - use MatrixServerWeb.ConnCase, async: true - - # Bring render/3 and render_to_string/3 for testing custom views - import Phoenix.View - - test "renders 404.json" do - assert render(MatrixServerWeb.ErrorView, "404.json", []) == %{errors: %{detail: "Not Found"}} - end - - test "renders 500.json" do - assert render(MatrixServerWeb.ErrorView, "500.json", []) == - %{errors: %{detail: "Internal Server Error"}} - end -end diff --git a/test/support/channel_case.ex b/test/support/channel_case.ex deleted file mode 100644 index 82c2c9b..0000000 --- a/test/support/channel_case.ex +++ /dev/null @@ -1,40 +0,0 @@ -defmodule MatrixServerWeb.ChannelCase do - @moduledoc """ - This module defines the test case to be used by - channel tests. - - Such tests rely on `Phoenix.ChannelTest` and also - import other functionality to make it easier - to build common data structures and query the data layer. - - Finally, if the test case interacts with the database, - we enable the SQL sandbox, so changes done to the database - are reverted at the end of every test. If you are using - PostgreSQL, you can even run database tests asynchronously - by setting `use MatrixServerWeb.ChannelCase, async: true`, although - this option is not recommended for other databases. - """ - - use ExUnit.CaseTemplate - - using do - quote do - # Import conveniences for testing with channels - import Phoenix.ChannelTest - import MatrixServerWeb.ChannelCase - - # The default endpoint for testing - @endpoint MatrixServerWeb.Endpoint - end - end - - setup tags do - :ok = Ecto.Adapters.SQL.Sandbox.checkout(MatrixServer.Repo) - - unless tags[:async] do - Ecto.Adapters.SQL.Sandbox.mode(MatrixServer.Repo, {:shared, self()}) - end - - :ok - end -end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 2b449e9..b989cdc 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -40,4 +40,12 @@ defmodule MatrixServerWeb.ConnCase do {:ok, conn: Phoenix.ConnTest.build_conn()} end + + defmacro post_json(conn, path, params) do + quote do + unquote(conn) + |> put_req_header("content-type", "application/json") + |> post(unquote(path), Jason.encode!(unquote(params))) + end + end end diff --git a/test/support/factory.ex b/test/support/factory.ex new file mode 100644 index 0000000..7c5e0d1 --- /dev/null +++ b/test/support/factory.ex @@ -0,0 +1,24 @@ +defmodule MatrixServer.Factory do + use ExMachina.Ecto, repo: MatrixServer.Repo + + alias MatrixServer.{Account, Device} + + def account_factory do + %Account{ + localpart: sequence(:localpart, &"account#{&1}"), + password_hash: Bcrypt.hash_pwd_salt("lemmein") + } + end + + def device_factory do + %Account{localpart: localpart} = account = build(:account) + device_id = sequence(:device_id, &"device#{&1}") + + %Device{ + device_id: device_id, + access_token: Device.generate_access_token(localpart, device_id), + display_name: sequence(:display_name, &"Device #{&1}"), + account: account + } + end +end