Add start of controller testing

This commit is contained in:
Pim Kunis 2021-07-13 17:08:07 +02:00
parent 598af7a884
commit 096c99df92
14 changed files with 106 additions and 87 deletions

View file

@ -6,8 +6,8 @@ use Mix.Config
# to provide built-in test partitioning in CI environment. # to provide built-in test partitioning in CI environment.
# Run `mix help test` for more information. # Run `mix help test` for more information.
config :matrix_server, MatrixServer.Repo, config :matrix_server, MatrixServer.Repo,
username: "postgres", username: "matrix_server",
password: "postgres", password: "matrix_server",
database: "matrix_server_test#{System.get_env("MIX_TEST_PARTITION")}", database: "matrix_server_test#{System.get_env("MIX_TEST_PARTITION")}",
hostname: "localhost", hostname: "localhost",
pool: Ecto.Adapters.SQL.Sandbox pool: Ecto.Adapters.SQL.Sandbox
@ -20,3 +20,5 @@ config :matrix_server, MatrixServerWeb.Endpoint,
# Print only warnings and errors during test # Print only warnings and errors during test
config :logger, level: :warn config :logger, level: :warn
config :matrix_server, :server_name, "localhost"

View file

@ -38,9 +38,13 @@ defmodule MatrixServer.Account do
|> Multi.insert(:device, fn %{account: account} -> |> Multi.insert(:device, fn %{account: account} ->
device_id = Device.generate_device_id(account.localpart) 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) Ecto.build_assoc(account, :devices)
|> Map.put(:device_id, device_id)
|> Device.changeset(params) |> Device.changeset(params)
end) end)
|> Multi.run(:device_with_access_token, &Device.insert_new_access_token/2) |> Multi.run(:device_with_access_token, &Device.insert_new_access_token/2)

View file

@ -40,6 +40,7 @@ defmodule MatrixServer.Device do
end end
def generate_device_id(localpart) do def generate_device_id(localpart) do
# TODO: use random string instead
time_string = time_string =
DateTime.utc_now() DateTime.utc_now()
|> DateTime.to_unix() |> DateTime.to_unix()

View file

@ -1,44 +1,31 @@
# https://gist.github.com/char0n/6fca76e886a2cfbd3aaa05526f287728
defmodule MatrixServerWeb.API.Login do defmodule MatrixServerWeb.API.Login do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset 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 @primary_key false
embedded_schema do embedded_schema do
field :type, :string field :type, :string
field :password, :string field :password, :string
field :device_id, :string field :device_id, :string
field :initial_device_display_name, :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 end
def changeset(params) do def changeset(params) do
%__MODULE__{} %__MODULE__{}
|> cast(params, [:type, :password, :device_id, :initial_device_display_name]) |> 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]) |> validate_required([:type, :password])
end end
def identifier_changeset(identifier, params) do
identifier
|> cast(params, [:type, :user])
|> validate_required([:type, :user])
end
end end

View file

@ -14,6 +14,7 @@ defmodule MatrixServerWeb.AuthController do
def register(conn, %{"auth" => %{"type" => @register_type}} = params) do def register(conn, %{"auth" => %{"type" => @register_type}} = params) do
case Register.changeset(params) do case Register.changeset(params) do
%Changeset{valid?: true} = cs -> %Changeset{valid?: true} = cs ->
# TODO: refactor this
input = input =
apply_changes(cs) apply_changes(cs)
|> Map.from_struct() |> Map.from_struct()

View file

@ -15,7 +15,7 @@ defmodule MatrixServerWeb.Router do
scope "/_matrix", MatrixServerWeb do scope "/_matrix", MatrixServerWeb do
pipe_through :public pipe_through :public
scope "/client/r0", as: :client do scope "/client/r0" do
post "/register", AuthController, :register post "/register", AuthController, :register
get "/register/available", AccountController, :available get "/register/available", AccountController, :available
get "/login", AuthController, :login_types get "/login", AuthController, :login_types
@ -28,7 +28,7 @@ defmodule MatrixServerWeb.Router do
scope "/_matrix", MatrixServerWeb do scope "/_matrix", MatrixServerWeb do
pipe_through :authenticated pipe_through :authenticated
scope "/client/r0", as: :client do scope "/client/r0" do
get "/account/whoami", AccountController, :whoami get "/account/whoami", AccountController, :whoami
post "/logout", AccountController, :logout post "/logout", AccountController, :logout
post "/logout/all", AccountController, :logout_all post "/logout/all", AccountController, :logout_all

View file

@ -42,7 +42,8 @@ defmodule MatrixServer.MixProject do
{:jason, "~> 1.0"}, {:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"}, {:plug_cowboy, "~> 2.0"},
{:bcrypt_elixir, "~> 2.3"}, {:bcrypt_elixir, "~> 2.3"},
{:cors_plug, "~> 2.0"} {:cors_plug, "~> 2.0"},
{:ex_machina, "~> 2.7", only: :test}
] ]
end end

View file

@ -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": {: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"}, "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"}, "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"}, "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"}, "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"}, "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"},

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -40,4 +40,12 @@ defmodule MatrixServerWeb.ConnCase do
{:ok, conn: Phoenix.ConnTest.build_conn()} {:ok, conn: Phoenix.ConnTest.build_conn()}
end 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 end

24
test/support/factory.ex Normal file
View file

@ -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