diff --git a/config/dev.exs b/config/dev.exs index 03489b3..903eb6b 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -8,7 +8,7 @@ config :matrix_server, MatrixServer.Repo, username: "matrix_server", password: "matrix_server", database: "matrix_server_dev", - hostname: "localhost", + hostname: hostname, show_sensitive_data_on_connection_error: true, pool_size: 10 diff --git a/config/test.exs b/config/test.exs index a196afe..5a80575 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,5 +1,8 @@ use Mix.Config +hostname = "localhost" +port = System.get_env("PORT") || 4000 + # Configure your database # # The MIX_TEST_PARTITION environment variable can be used @@ -9,16 +12,17 @@ config :matrix_server, MatrixServer.Repo, username: "matrix_server", password: "matrix_server", database: "matrix_server_test#{System.get_env("MIX_TEST_PARTITION")}", - hostname: "localhost", + hostname: hostname, pool: Ecto.Adapters.SQL.Sandbox # We don't run a server during test. If one is required, # you can enable the server option below. config :matrix_server, MatrixServerWeb.Endpoint, - http: [port: 4002], + http: [port: port], server: false # Print only warnings and errors during test config :logger, level: :warn -config :matrix_server, :server_name, "localhost" +config :matrix_server, server_name: "#{hostname}:#{port}" +config :matrix_server, private_key_file: "keys/id_ed25519" diff --git a/lib/matrix_server/quick_check.ex b/lib/matrix_server/quick_check.ex index 7de8a2d..065f83c 100644 --- a/lib/matrix_server/quick_check.ex +++ b/lib/matrix_server/quick_check.ex @@ -2,7 +2,7 @@ defmodule MatrixServer.QuickCheck do import Ecto.Query alias MatrixServer.{Repo, Room, Account, RoomServer} - alias MatrixServerWeb.API.CreateRoom + alias MatrixServerWeb.Request.CreateRoom def create_room(name \\ nil, topic \\ nil) do account = Repo.one!(from a in Account, limit: 1) diff --git a/lib/matrix_server/room_server.ex b/lib/matrix_server/room_server.ex index e0465c6..97c5bc4 100644 --- a/lib/matrix_server/room_server.ex +++ b/lib/matrix_server/room_server.ex @@ -5,7 +5,7 @@ defmodule MatrixServer.RoomServer do import Ecto.Changeset alias MatrixServer.{Repo, Room, Event, StateResolution} - alias MatrixServerWeb.API.CreateRoom + alias MatrixServerWeb.Request.CreateRoom alias MatrixServer.StateResolution.Authorization @registry MatrixServer.RoomServer.Registry diff --git a/lib/matrix_server/account.ex b/lib/matrix_server/schema/account.ex similarity index 98% rename from lib/matrix_server/account.ex rename to lib/matrix_server/schema/account.ex index 0d8bc4b..89d6f1d 100644 --- a/lib/matrix_server/account.ex +++ b/lib/matrix_server/schema/account.ex @@ -4,7 +4,7 @@ defmodule MatrixServer.Account do import Ecto.{Changeset, Query} alias MatrixServer.{Repo, Account, Device} - alias MatrixServerWeb.API.{Register, Login} + alias MatrixServerWeb.Request.{Register, Login} alias Ecto.Multi @max_mxid_length 255 diff --git a/lib/matrix_server/alias.ex b/lib/matrix_server/schema/alias.ex similarity index 100% rename from lib/matrix_server/alias.ex rename to lib/matrix_server/schema/alias.ex diff --git a/lib/matrix_server/device.ex b/lib/matrix_server/schema/device.ex similarity index 98% rename from lib/matrix_server/device.ex rename to lib/matrix_server/schema/device.ex index b78b549..2a09b0a 100644 --- a/lib/matrix_server/device.ex +++ b/lib/matrix_server/schema/device.ex @@ -4,7 +4,7 @@ defmodule MatrixServer.Device do import Ecto.{Changeset, Query} alias MatrixServer.{Account, Device, Repo} - alias MatrixServerWeb.API.Login + alias MatrixServerWeb.Request.Login @primary_key false schema "devices" do diff --git a/lib/matrix_server/event.ex b/lib/matrix_server/schema/event.ex similarity index 100% rename from lib/matrix_server/event.ex rename to lib/matrix_server/schema/event.ex diff --git a/lib/matrix_server/room.ex b/lib/matrix_server/schema/room.ex similarity index 97% rename from lib/matrix_server/room.ex rename to lib/matrix_server/schema/room.ex index 883cbdb..9b89775 100644 --- a/lib/matrix_server/room.ex +++ b/lib/matrix_server/schema/room.ex @@ -5,7 +5,7 @@ defmodule MatrixServer.Room do import Ecto.Query alias MatrixServer.{Repo, Room, Event, Alias, RoomServer} - alias MatrixServerWeb.API.CreateRoom + alias MatrixServerWeb.Request.CreateRoom @primary_key {:id, :string, []} schema "rooms" do diff --git a/lib/matrix_server_web/controllers/account_controller.ex b/lib/matrix_server_web/client/account_controller.ex similarity index 95% rename from lib/matrix_server_web/controllers/account_controller.ex rename to lib/matrix_server_web/client/account_controller.ex index 5525593..cd30831 100644 --- a/lib/matrix_server_web/controllers/account_controller.ex +++ b/lib/matrix_server_web/client/account_controller.ex @@ -1,4 +1,4 @@ -defmodule MatrixServerWeb.AccountController do +defmodule MatrixServerWeb.Client.AccountController do use MatrixServerWeb, :controller import MatrixServer diff --git a/lib/matrix_server_web/controllers/aliases_controller.ex b/lib/matrix_server_web/client/aliases_controller.ex similarity index 89% rename from lib/matrix_server_web/controllers/aliases_controller.ex rename to lib/matrix_server_web/client/aliases_controller.ex index 6c06641..4b54a13 100644 --- a/lib/matrix_server_web/controllers/aliases_controller.ex +++ b/lib/matrix_server_web/client/aliases_controller.ex @@ -1,4 +1,4 @@ -defmodule MatrixServerWeb.AliasesController do +defmodule MatrixServerWeb.Client.AliasesController do use MatrixServerWeb, :controller import MatrixServerWeb.Plug.Error diff --git a/lib/matrix_server_web/controllers/info_controller.ex b/lib/matrix_server_web/client/info_controller.ex similarity index 86% rename from lib/matrix_server_web/controllers/info_controller.ex rename to lib/matrix_server_web/client/info_controller.ex index 4384a73..1f596e1 100644 --- a/lib/matrix_server_web/controllers/info_controller.ex +++ b/lib/matrix_server_web/client/info_controller.ex @@ -1,4 +1,4 @@ -defmodule MatrixServerWeb.InfoController do +defmodule MatrixServerWeb.Client.InfoController do use MatrixServerWeb, :controller import MatrixServerWeb.Plug.Error diff --git a/lib/matrix_server_web/client/login_controller.ex b/lib/matrix_server_web/client/login_controller.ex new file mode 100644 index 0000000..9ba9b99 --- /dev/null +++ b/lib/matrix_server_web/client/login_controller.ex @@ -0,0 +1,57 @@ +defmodule MatrixServerWeb.Client.LoginController do + use MatrixServerWeb, :controller + + import MatrixServerWeb.Plug.Error + import Ecto.Changeset + + alias MatrixServer.{Repo, Account} + alias MatrixServerWeb.Request.Login + alias Ecto.Changeset + + @login_type "m.login.password" + + def login_types(conn, _params) do + data = %{flows: [%{type: @login_type}]} + + conn + |> put_status(200) + |> json(data) + end + + def login( + conn, + %{"type" => @login_type, "identifier" => %{"type" => "m.id.user"}} = params + ) do + case Login.changeset(params) do + %Changeset{valid?: true} = cs -> + input = apply_changes(cs) + + case Account.login(input) |> Repo.transaction() do + {:ok, device} -> + data = %{ + user_id: MatrixServer.get_mxid(device.localpart), + access_token: device.access_token, + device_id: device.device_id + } + + conn + |> put_status(200) + |> json(data) + + {:error, error} when is_atom(error) -> + put_error(conn, error) + + {:error, _} -> + put_error(conn, :unknown) + end + + _ -> + put_error(conn, :bad_json) + end + end + + def login(conn, _params) do + # Other login types and identifiers are unsupported for now. + put_error(conn, :unrecognized, "Only m.login.password is supported currently.") + end +end diff --git a/lib/matrix_server_web/controllers/auth_controller.ex b/lib/matrix_server_web/client/register_controller.ex similarity index 53% rename from lib/matrix_server_web/controllers/auth_controller.ex rename to lib/matrix_server_web/client/register_controller.ex index 2723aee..90ea7e4 100644 --- a/lib/matrix_server_web/controllers/auth_controller.ex +++ b/lib/matrix_server_web/client/register_controller.ex @@ -1,15 +1,14 @@ -defmodule MatrixServerWeb.AuthController do +defmodule MatrixServerWeb.Client.RegisterController do use MatrixServerWeb, :controller import MatrixServerWeb.Plug.Error import Ecto.Changeset alias MatrixServer.{Repo, Account} - alias MatrixServerWeb.API.{Register, Login} + alias MatrixServerWeb.Request.Register alias Ecto.Changeset @register_type "m.login.dummy" - @login_type "m.login.password" def register(conn, %{"auth" => %{"type" => @register_type}} = params) do case Register.changeset(params) do @@ -58,49 +57,4 @@ defmodule MatrixServerWeb.AuthController do |> put_status(401) |> json(data) end - - def login_types(conn, _params) do - data = %{flows: [%{type: @login_type}]} - - conn - |> put_status(200) - |> json(data) - end - - def login( - conn, - %{"type" => @login_type, "identifier" => %{"type" => "m.id.user"}} = params - ) do - case Login.changeset(params) do - %Changeset{valid?: true} = cs -> - input = apply_changes(cs) - - case Account.login(input) |> Repo.transaction() do - {:ok, device} -> - data = %{ - user_id: MatrixServer.get_mxid(device.localpart), - access_token: device.access_token, - device_id: device.device_id - } - - conn - |> put_status(200) - |> json(data) - - {:error, error} when is_atom(error) -> - put_error(conn, error) - - {:error, _} -> - put_error(conn, :unknown) - end - - _ -> - put_error(conn, :bad_json) - end - end - - def login(conn, _params) do - # Other login types and identifiers are unsupported for now. - put_error(conn, :unrecognized, "Only m.login.password is supported currently.") - end end diff --git a/lib/matrix_server_web/controllers/room_controller.ex b/lib/matrix_server_web/client/room_controller.ex similarity index 88% rename from lib/matrix_server_web/controllers/room_controller.ex rename to lib/matrix_server_web/client/room_controller.ex index 1b874f7..b82351e 100644 --- a/lib/matrix_server_web/controllers/room_controller.ex +++ b/lib/matrix_server_web/client/room_controller.ex @@ -1,11 +1,11 @@ -defmodule MatrixServerWeb.RoomController do +defmodule MatrixServerWeb.Client.RoomController do use MatrixServerWeb, :controller import MatrixServerWeb.Plug.Error import Ecto.Changeset alias MatrixServer.Room - alias MatrixServerWeb.API.{CreateRoom} + alias MatrixServerWeb.Request.{CreateRoom} alias Ecto.Changeset alias Plug.Conn diff --git a/lib/matrix_server_web/controllers/key_controller.ex b/lib/matrix_server_web/federation/key_controller.ex similarity index 93% rename from lib/matrix_server_web/controllers/key_controller.ex rename to lib/matrix_server_web/federation/key_controller.ex index 4c3c9fa..d0de415 100644 --- a/lib/matrix_server_web/controllers/key_controller.ex +++ b/lib/matrix_server_web/federation/key_controller.ex @@ -1,4 +1,4 @@ -defmodule MatrixServerWeb.KeyController do +defmodule MatrixServerWeb.Federation.KeyController do use MatrixServerWeb, :controller import MatrixServerWeb.Plug.Error diff --git a/lib/matrix_server_web/api/create_room.ex b/lib/matrix_server_web/request/create_room.ex similarity index 94% rename from lib/matrix_server_web/api/create_room.ex rename to lib/matrix_server_web/request/create_room.ex index fa019a9..c441aef 100644 --- a/lib/matrix_server_web/api/create_room.ex +++ b/lib/matrix_server_web/request/create_room.ex @@ -1,4 +1,4 @@ -defmodule MatrixServerWeb.API.CreateRoom do +defmodule MatrixServerWeb.Request.CreateRoom do use Ecto.Schema import Ecto.Changeset diff --git a/lib/matrix_server_web/api/login.ex b/lib/matrix_server_web/request/login.ex similarity index 94% rename from lib/matrix_server_web/api/login.ex rename to lib/matrix_server_web/request/login.ex index 447c4e9..ea3059e 100644 --- a/lib/matrix_server_web/api/login.ex +++ b/lib/matrix_server_web/request/login.ex @@ -1,4 +1,4 @@ -defmodule MatrixServerWeb.API.Login do +defmodule MatrixServerWeb.Request.Login do use Ecto.Schema import Ecto.Changeset diff --git a/lib/matrix_server_web/api/register.ex b/lib/matrix_server_web/request/register.ex similarity index 94% rename from lib/matrix_server_web/api/register.ex rename to lib/matrix_server_web/request/register.ex index 00d3345..3f09730 100644 --- a/lib/matrix_server_web/api/register.ex +++ b/lib/matrix_server_web/request/register.ex @@ -1,4 +1,4 @@ -defmodule MatrixServerWeb.API.Register do +defmodule MatrixServerWeb.Request.Register do use Ecto.Schema import Ecto.Changeset diff --git a/lib/matrix_server_web/router.ex b/lib/matrix_server_web/router.ex index 95993fd..a1e72d5 100644 --- a/lib/matrix_server_web/router.ex +++ b/lib/matrix_server_web/router.ex @@ -7,30 +7,37 @@ defmodule MatrixServerWeb.Router do plug :accepts, ["json"] end - pipeline :authenticated do + pipeline :authenticate_client do plug :accepts, ["json"] plug Authenticate end + pipeline :authenticate_server do + plug :accepts, ["json"] + # TODO: Add plug to verify peer. + end + scope "/_matrix", MatrixServerWeb do pipe_through :public - scope "/client/r0" do - post "/register", AuthController, :register - get "/register/available", AccountController, :available - get "/login", AuthController, :login_types - post "/login", AuthController, :login + scope "/client", Client do + scope "/r0" do + post "/register", RegisterController, :register + get "/register/available", AccountController, :available + get "/login", LoginController, :login_types + post "/login", LoginController, :login + end + + get "/versions", InfoController, :versions end - scope "/key/v2" do + scope "/key/v2", Federation do get "/server", KeyController, :get_signing_keys end - - get "/client/versions", InfoController, :versions end - scope "/_matrix", MatrixServerWeb do - pipe_through :authenticated + scope "/_matrix", MatrixServerWeb.Client do + pipe_through :authenticate_client scope "/client/r0" do get "/account/whoami", AccountController, :whoami @@ -44,7 +51,11 @@ defmodule MatrixServerWeb.Router do end end - scope "/", MatrixServerWeb do + scope "/_matrix", MatrixServerWeb.Federation do + + end + + scope "/", MatrixServerWeb.Client do match :*, "/*path", InfoController, :unrecognized end end diff --git a/test/controllers/auth_controller_test.exs b/test/controllers/auth_controller_test.exs deleted file mode 100644 index 0ff9965..0000000 --- a/test/controllers/auth_controller_test.exs +++ /dev/null @@ -1,135 +0,0 @@ -defmodule MatrixServerWeb.AuthControllerTest do - use MatrixServerWeb.ConnCase - - import Ecto.Query - - alias MatrixServer.{Repo, Device, Factory} - alias MatrixServerWeb.Endpoint - - @basic_params %{ - "username" => "user", - "password" => "lemmein", - "auth" => %{"type" => "m.login.dummy"} - } - - 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 - conn = post_json(conn, Routes.auth_path(Endpoint, :register), @basic_params) - user_id = MatrixServer.get_mxid("user") - - assert %{"access_token" => _, "device_id" => _, "user_id" => ^user_id} = - json_response(conn, 200) - end - - test "registers and sets device id", %{conn: conn} do - params = Map.put(@basic_params, :device_id, "android") - conn = post_json(conn, Routes.auth_path(Endpoint, :register), params) - - assert %{"device_id" => "android"} = json_response(conn, 200) - end - - test "registers and sets display name", %{conn: conn} do - params = Map.put(@basic_params, :initial_device_display_name, "My Android") - conn = post_json(conn, Routes.auth_path(Endpoint, :register), params) - - assert json_response(conn, 200) - assert Repo.one!(from d in Device, select: d.display_name) == "My Android" - end - - test "rejects account if localpart is already in use", %{conn: conn} do - Factory.insert(:account, localpart: "sneed") - - conn = - post_json(conn, Routes.auth_path(Endpoint, :register), %{ - @basic_params - | "username" => "sneed" - }) - - assert %{"errcode" => "M_USER_IN_USE"} = json_response(conn, 400) - end - - test "obeys inhibit_login parameter", %{conn: conn} do - params = Map.put(@basic_params, :inhibit_login, "true") - conn = post_json(conn, Routes.auth_path(Endpoint, :register), params) - - assert response = json_response(conn, 200) - refute Map.has_key?(response, "access_token") - refute Map.has_key?(response, "device_id") - end - - test "generates localpart if omitted", %{conn: conn} do - params = Map.delete(@basic_params, "username") - conn = post_json(conn, Routes.auth_path(Endpoint, :register), params) - - assert %{"user_id" => _} = json_response(conn, 200) - end - - test "rejects invalid usernames", %{conn: conn} do - conn = - post_json(conn, Routes.auth_path(Endpoint, :register), %{ - @basic_params - | "username" => "User1" - }) - - assert %{"errcode" => "M_INVALID_USERNAME"} = json_response(conn, 400) - end - end - - @basic_params %{ - "type" => "m.login.password", - "identifier" => %{ - "type" => "m.id.user", - "user" => "sneed" - }, - "password" => "lemmein" - } - - describe "login endpoint" do - test "renders the list of login types", %{conn: conn} do - conn = get(conn, Routes.auth_path(Endpoint, :login)) - - assert %{"flows" => flows} = json_response(conn, 200) - assert is_list(flows) - end - - test "logs a user in with password and matrix user id", %{conn: conn} do - Factory.insert(:account, localpart: "sneed", password_hash: Bcrypt.hash_pwd_salt("lemmein")) - conn = post_json(conn, Routes.auth_path(Endpoint, :login), @basic_params) - - assert %{"user_id" => _, "access_token" => _, "device_id" => _} = json_response(conn, 200) - - conn = - recycle(conn) - |> post_json(Routes.auth_path(Endpoint, :login), %{ - @basic_params - | "identifier" => %{"type" => "m.id.user", "user" => MatrixServer.get_mxid("sneed")} - }) - - assert %{"user_id" => _, "access_token" => _, "device_id" => _} = json_response(conn, 200) - end - - test "handles unknown matrix user id", %{conn: conn} do - conn = post_json(conn, Routes.auth_path(Endpoint, :login), @basic_params) - - assert %{"errcode" => "M_FORBIDDEN"} = json_response(conn, 400) - end - - test "handles wrong password", %{conn: conn} do - Factory.insert(:account, localpart: "sneed", password_hash: Bcrypt.hash_pwd_salt("surprise")) - - conn = post_json(conn, Routes.auth_path(Endpoint, :login), @basic_params) - - assert %{"errcode" => "M_FORBIDDEN"} = json_response(conn, 400) - end - - # TODO: Test display name - # TODO: Test device recycling - end -end diff --git a/test/controllers/login_controller_test.exs b/test/controllers/login_controller_test.exs new file mode 100644 index 0000000..c8db075 --- /dev/null +++ b/test/controllers/login_controller_test.exs @@ -0,0 +1,57 @@ +defmodule MatrixServerWeb.LoginControllerTest do + use MatrixServerWeb.ConnCase + + alias MatrixServer.Factory + alias MatrixServerWeb.Endpoint + + @basic_params %{ + "type" => "m.login.password", + "identifier" => %{ + "type" => "m.id.user", + "user" => "sneed" + }, + "password" => "lemmein" + } + + describe "login endpoint" do + test "renders the list of login types", %{conn: conn} do + conn = get(conn, Routes.login_path(Endpoint, :login)) + + assert %{"flows" => flows} = json_response(conn, 200) + assert is_list(flows) + end + + test "logs a user in with password and matrix user id", %{conn: conn} do + Factory.insert(:account, localpart: "sneed", password_hash: Bcrypt.hash_pwd_salt("lemmein")) + conn = post_json(conn, Routes.login_path(Endpoint, :login), @basic_params) + + assert %{"user_id" => _, "access_token" => _, "device_id" => _} = json_response(conn, 200) + + conn = + recycle(conn) + |> post_json(Routes.login_path(Endpoint, :login), %{ + @basic_params + | "identifier" => %{"type" => "m.id.user", "user" => MatrixServer.get_mxid("sneed")} + }) + + assert %{"user_id" => _, "access_token" => _, "device_id" => _} = json_response(conn, 200) + end + + test "handles unknown matrix user id", %{conn: conn} do + conn = post_json(conn, Routes.login_path(Endpoint, :login), @basic_params) + + assert %{"errcode" => "M_FORBIDDEN"} = json_response(conn, 400) + end + + test "handles wrong password", %{conn: conn} do + Factory.insert(:account, localpart: "sneed", password_hash: Bcrypt.hash_pwd_salt("surprise")) + + conn = post_json(conn, Routes.login_path(Endpoint, :login), @basic_params) + + assert %{"errcode" => "M_FORBIDDEN"} = json_response(conn, 400) + end + + # TODO: Test display name + # TODO: Test device recycling + end +end diff --git a/test/controllers/register_controller_test.exs b/test/controllers/register_controller_test.exs new file mode 100644 index 0000000..01c8bf2 --- /dev/null +++ b/test/controllers/register_controller_test.exs @@ -0,0 +1,84 @@ +defmodule MatrixServerWeb.RegisterControllerTest do + use MatrixServerWeb.ConnCase + + import Ecto.Query + + alias MatrixServer.{Repo, Device, Factory} + alias MatrixServerWeb.Endpoint + + @basic_params %{ + "username" => "user", + "password" => "lemmein", + "auth" => %{"type" => "m.login.dummy"} + } + + describe "register endpoint" do + test "renders the auth flow when no auth parameter is given", %{conn: conn} do + conn = post(conn, Routes.register_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 + conn = post_json(conn, Routes.register_path(Endpoint, :register), @basic_params) + user_id = MatrixServer.get_mxid("user") + + assert %{"access_token" => _, "device_id" => _, "user_id" => ^user_id} = + json_response(conn, 200) + end + + test "registers and sets device id", %{conn: conn} do + params = Map.put(@basic_params, :device_id, "android") + conn = post_json(conn, Routes.register_path(Endpoint, :register), params) + + assert %{"device_id" => "android"} = json_response(conn, 200) + end + + test "registers and sets display name", %{conn: conn} do + params = Map.put(@basic_params, :initial_device_display_name, "My Android") + conn = post_json(conn, Routes.register_path(Endpoint, :register), params) + + assert json_response(conn, 200) + assert Repo.one!(from d in Device, select: d.display_name) == "My Android" + end + + test "rejects account if localpart is already in use", %{conn: conn} do + Factory.insert(:account, localpart: "sneed") + + conn = + post_json(conn, Routes.register_path(Endpoint, :register), %{ + @basic_params + | "username" => "sneed" + }) + + assert %{"errcode" => "M_USER_IN_USE"} = json_response(conn, 400) + end + + test "obeys inhibit_login parameter", %{conn: conn} do + params = Map.put(@basic_params, :inhibit_login, "true") + conn = post_json(conn, Routes.register_path(Endpoint, :register), params) + + assert response = json_response(conn, 200) + refute Map.has_key?(response, "access_token") + refute Map.has_key?(response, "device_id") + end + + test "generates localpart if omitted", %{conn: conn} do + params = Map.delete(@basic_params, "username") + conn = post_json(conn, Routes.register_path(Endpoint, :register), params) + + assert %{"user_id" => _} = json_response(conn, 200) + end + + test "rejects invalid usernames", %{conn: conn} do + conn = + post_json(conn, Routes.register_path(Endpoint, :register), %{ + @basic_params + | "username" => "User1" + }) + + assert %{"errcode" => "M_INVALID_USERNAME"} = json_response(conn, 400) + end + end +end