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.
# 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"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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_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"},

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()}
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

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