From 00c212554e24ea707499859b30f75374d4d3508a Mon Sep 17 00:00:00 2001 From: Pierre de Lacroix Date: Wed, 9 Dec 2020 11:03:21 +0100 Subject: add bridge context --- config/config.exs | 3 + config/test.exs | 12 ++ lib/matrix_app_service/application.ex | 3 +- lib/matrix_app_service/bridge.ex | 3 + lib/matrix_app_service/bridge/room.ex | 21 ++ lib/matrix_app_service/bridge/user.ex | 21 ++ lib/matrix_app_service/bridge_config.ex | 221 +++++++++++++++++++++ lib/matrix_app_service/migrations.ex | 26 +++ lib/matrix_app_service/repo.ex | 5 + mix.exs | 4 +- mix.lock | 2 + .../migrations/20201209095612_migrate_itself.exs | 7 + test/matrix_app_service/bridge_test.exs | 131 ++++++++++++ test/support/data_case.ex | 53 +++++ 14 files changed, 510 insertions(+), 2 deletions(-) create mode 100644 lib/matrix_app_service/bridge.ex create mode 100644 lib/matrix_app_service/bridge/room.ex create mode 100644 lib/matrix_app_service/bridge/user.ex create mode 100644 lib/matrix_app_service/bridge_config.ex create mode 100644 lib/matrix_app_service/migrations.ex create mode 100644 lib/matrix_app_service/repo.ex create mode 100644 priv/repo/migrations/20201209095612_migrate_itself.exs create mode 100644 test/matrix_app_service/bridge_test.exs create mode 100644 test/support/data_case.ex diff --git a/config/config.exs b/config/config.exs index 8d782dc..692048a 100644 --- a/config/config.exs +++ b/config/config.exs @@ -7,6 +7,9 @@ # General application configuration use Mix.Config +config :matrix_app_service, + ecto_repos: [MatrixAppService.Repo] + # Configures the endpoint config :matrix_app_service, MatrixAppServiceWeb.Endpoint, url: [host: "localhost"], diff --git a/config/test.exs b/config/test.exs index ed14857..1402cf0 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,5 +1,17 @@ use Mix.Config +# Configure your database +# +# The MIX_TEST_PARTITION environment variable can be used +# to provide built-in test partitioning in CI environment. +# Run `mix help test` for more information. +config :matrix_app_service, MatrixAppService.Repo, + username: "postgres", + password: "postgres", + database: "matrix_app_service_test#{System.get_env("MIX_TEST_PARTITION")}", + hostname: "localhost", + 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_app_service, MatrixAppServiceWeb.Endpoint, diff --git a/lib/matrix_app_service/application.ex b/lib/matrix_app_service/application.ex index 383c0f9..ae935eb 100644 --- a/lib/matrix_app_service/application.ex +++ b/lib/matrix_app_service/application.ex @@ -21,7 +21,8 @@ defmodule MatrixAppService.Application do if start_endpoint?() do [ # MatrixAppServiceWeb.Endpoint - {MatrixAppServiceWeb.Endpoint, endpoint_config()} + {MatrixAppServiceWeb.Endpoint, endpoint_config()}, + MatrixAppService.Repo | children ] else diff --git a/lib/matrix_app_service/bridge.ex b/lib/matrix_app_service/bridge.ex new file mode 100644 index 0000000..e0e0fee --- /dev/null +++ b/lib/matrix_app_service/bridge.ex @@ -0,0 +1,3 @@ +defmodule MatrixAppService.Bridge do + use MatrixAppService.BridgeConfig, repo: MatrixAppService.Repo +end diff --git a/lib/matrix_app_service/bridge/room.ex b/lib/matrix_app_service/bridge/room.ex new file mode 100644 index 0000000..c40e0ed --- /dev/null +++ b/lib/matrix_app_service/bridge/room.ex @@ -0,0 +1,21 @@ +defmodule MatrixAppService.Bridge.Room do + use Ecto.Schema + import Ecto.Changeset + + schema "rooms" do + field :data, :map + field :local_id, :string + field :remote_id, :string + + timestamps() + end + + @doc false + def changeset(room, attrs) do + room + |> cast(attrs, [:local_id, :remote_id, :data]) + # |> validate_required([:local_id, :remote_id, :data]) + |> unique_constraint(:local_id) + |> unique_constraint(:remote_id) + end +end diff --git a/lib/matrix_app_service/bridge/user.ex b/lib/matrix_app_service/bridge/user.ex new file mode 100644 index 0000000..aaa3c46 --- /dev/null +++ b/lib/matrix_app_service/bridge/user.ex @@ -0,0 +1,21 @@ +defmodule MatrixAppService.Bridge.User do + use Ecto.Schema + import Ecto.Changeset + + schema "users" do + field :data, :map + field :local_id, :string + field :remote_id, :string + + timestamps() + end + + @doc false + def changeset(user, attrs) do + user + |> cast(attrs, [:local_id, :remote_id, :data]) + # |> validate_required([:local_id, :remote_id, :data]) + |> unique_constraint(:local_id) + |> unique_constraint(:remote_id) + end +end diff --git a/lib/matrix_app_service/bridge_config.ex b/lib/matrix_app_service/bridge_config.ex new file mode 100644 index 0000000..5356caf --- /dev/null +++ b/lib/matrix_app_service/bridge_config.ex @@ -0,0 +1,221 @@ +defmodule MatrixAppService.BridgeConfig do + @moduledoc """ + The Bridge config. + """ + + defmacro __using__([repo: repo]) do + quote bind_quoted: [repo: repo] do + @repo repo + + import Ecto.Query, warn: false + # alias MatrixAppService.Repo + + alias MatrixAppService.Bridge.User + + @doc """ + Returns the list of users. + + ## Examples + + iex> list_users() + [%User{}, ...] + + """ + def list_users do + @repo.all(User) + end + + @doc """ + Gets a single user. + + Raises `Ecto.NoResultsError` if the User does not exist. + + ## Examples + + iex> get_user!(123) + %User{} + + iex> get_user!(456) + ** (Ecto.NoResultsError) + + """ + def get_user!(id), do: @repo.get!(User, id) + + def get_user_by_local_id(local_id), do: @repo.get_by(User, local_id: local_id) || %User{local_id: local_id} + def get_user_by_remote_id(remote_id), do: @repo.get_by(User, remote_id: remote_id) || %User{remote_id: remote_id} + + @doc """ + Creates a user. + + ## Examples + + iex> create_user(%{field: value}) + {:ok, %User{}} + + iex> create_user(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_user(attrs \\ %{}) do + %User{} + |> User.changeset(attrs) + |> @repo.insert() + end + + def upsert_user(attrs, selectors) do + with %User{} = user <- @repo.get_by(User, selectors) do + update_user(user, attrs) + else + _ -> + create_user(Enum.into(selectors, attrs, fn {k, v} -> {to_string(k), v} end)) + end + end + + @doc """ + Updates a user. + + ## Examples + + iex> update_user(user, %{field: new_value}) + {:ok, %User{}} + + iex> update_user(user, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_user(%User{} = user, attrs) do + user + |> User.changeset(attrs) + |> @repo.update() + end + + @doc """ + Deletes a user. + + ## Examples + + iex> delete_user(user) + {:ok, %User{}} + + iex> delete_user(user) + {:error, %Ecto.Changeset{}} + + """ + def delete_user(%User{} = user) do + @repo.delete(user) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking user changes. + + ## Examples + + iex> change_user(user) + %Ecto.Changeset{data: %User{}} + + """ + def change_user(%User{} = user, attrs \\ %{}) do + User.changeset(user, attrs) + end + + alias MatrixAppService.Bridge.Room + + @doc """ + Returns the list of rooms. + + ## Examples + + iex> list_rooms() + [%Room{}, ...] + + """ + def list_rooms do + @repo.all(Room) + end + + @doc """ + Gets a single room. + + Raises `Ecto.NoResultsError` if the Room does not exist. + + ## Examples + + iex> get_room!(123) + %Room{} + + iex> get_room!(456) + ** (Ecto.NoResultsError) + + """ + def get_room!(id), do: @repo.get!(Room, id) + + def get_room_by_local_id(local_id), do: @repo.get_by(Room, local_id: local_id) || %Room{local_id: local_id} + def get_room_by_remote_id(remote_id), do: @repo.get_by(Room, remote_id: remote_id) || %Room{remote_id: remote_id} + + @doc """ + Creates a room. + + ## Examples + + iex> create_room(%{field: value}) + {:ok, %Room{}} + + iex> create_room(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_room(attrs \\ %{}) do + %Room{} + |> Room.changeset(attrs) + |> @repo.insert() + end + + @doc """ + Updates a room. + + ## Examples + + iex> update_room(room, %{field: new_value}) + {:ok, %Room{}} + + iex> update_room(room, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_room(%Room{} = room, attrs) do + room + |> Room.changeset(attrs) + |> @repo.update() + end + + @doc """ + Deletes a room. + + ## Examples + + iex> delete_room(room) + {:ok, %Room{}} + + iex> delete_room(room) + {:error, %Ecto.Changeset{}} + + """ + def delete_room(%Room{} = room) do + @repo.delete(room) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking room changes. + + ## Examples + + iex> change_room(room) + %Ecto.Changeset{data: %Room{}} + + """ + def change_room(%Room{} = room, attrs \\ %{}) do + Room.changeset(room, attrs) + end + end + end +end diff --git a/lib/matrix_app_service/migrations.ex b/lib/matrix_app_service/migrations.ex new file mode 100644 index 0000000..e6ce469 --- /dev/null +++ b/lib/matrix_app_service/migrations.ex @@ -0,0 +1,26 @@ +defmodule MatrixAppService.Migrations do + use Ecto.Migration + + def change do + create table(:users) do + add :local_id, :string + add :remote_id, :string + add :data, :map + + timestamps() + end + create unique_index(:users, [:local_id]) + create unique_index(:users, [:remote_id]) + + + create table(:rooms) do + add :local_id, :string + add :remote_id, :string + add :data, :map + + timestamps() + end + create unique_index(:rooms, [:local_id]) + create unique_index(:rooms, [:remote_id]) + end +end diff --git a/lib/matrix_app_service/repo.ex b/lib/matrix_app_service/repo.ex new file mode 100644 index 0000000..c52348a --- /dev/null +++ b/lib/matrix_app_service/repo.ex @@ -0,0 +1,5 @@ +defmodule MatrixAppService.Repo do + use Ecto.Repo, + otp_app: :matrix_app_service, + adapter: Ecto.Adapters.Postgres +end diff --git a/mix.exs b/mix.exs index bb0fe80..e0cffe4 100644 --- a/mix.exs +++ b/mix.exs @@ -59,11 +59,13 @@ defmodule MatrixAppService.MixProject do {:telemetry_poller, "~> 0.4"}, {:jason, "~> 1.0"}, {:plug_cowboy, "~> 2.0"}, + {:phoenix_ecto, "~> 4.1"}, {:ecto_sql, "~> 3.4"}, {:polyjuice_client, "~> 0.3.1"}, {:ex_doc, "~> 0.22", only: :dev, runtime: false}, {:junit_formatter, "~> 3.1", only: :test}, - {:cobertura_cover, "~> 0.9.0", only: :test} + {:cobertura_cover, "~> 0.9.0", only: :test}, + {:postgrex, ">= 0.0.0", only: :test} ] end diff --git a/mix.lock b/mix.lock index 951e330..9782076 100644 --- a/mix.lock +++ b/mix.lock @@ -24,6 +24,7 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "phoenix": {:hex, :phoenix, "1.5.7", "2923bb3af924f184459fe4fa4b100bd25fa6468e69b2803dfae82698269aa5e0", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [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", "774cd64417c5a3788414fdbb2be2eb9bcd0c048d9e6ad11a0c1fd67b7c0d0978"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, "plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"}, "plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"}, @@ -32,6 +33,7 @@ "polyjuice_client": {:hex, :polyjuice_client, "0.3.1", "cc312602a8da0d4d61b0a890bb72d9c6662f17cd0fc5782d8f7358274e3e6e4b", [:mix], [{:hackney, "~> 1.12", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:mutex, "~> 1.1.3", [hex: :mutex, repo: "hexpm", optional: false]}, {:polyjuice_util, "~> 0.1.0", [hex: :polyjuice_util, repo: "hexpm", optional: false]}], "hexpm", "e9585faab7562cac3e91a64d328905cefabc74219fafa9e77e09f4e5e4e82957"}, "polyjuice_util": {:hex, :polyjuice_util, "0.1.0", "69901959c143245b47829c8302d0605dff6c0e1c3b116730c162982e0f512ee0", [:mix], [], "hexpm", "af5d1f614f52ce1da59a1f5a7c49249a2dbfda279d99d52d1b4e83e84c19a8d5"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, + "postgrex": {:hex, :postgrex, "0.15.7", "724410acd48abac529d0faa6c2a379fb8ae2088e31247687b16cacc0e0883372", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "88310c010ff047cecd73d5ceca1d99205e4b1ab1b9abfdab7e00f5c9d20ef8f9"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, diff --git a/priv/repo/migrations/20201209095612_migrate_itself.exs b/priv/repo/migrations/20201209095612_migrate_itself.exs new file mode 100644 index 0000000..04df688 --- /dev/null +++ b/priv/repo/migrations/20201209095612_migrate_itself.exs @@ -0,0 +1,7 @@ +defmodule MatrixAppService.Repo.Migrations.MigrateItself do + use Ecto.Migration + + def change do + MatrixAppService.Migrations.change() + end +end diff --git a/test/matrix_app_service/bridge_test.exs b/test/matrix_app_service/bridge_test.exs new file mode 100644 index 0000000..46acffb --- /dev/null +++ b/test/matrix_app_service/bridge_test.exs @@ -0,0 +1,131 @@ +defmodule MatrixAppService.BridgeTest do + use MatrixAppService.DataCase + + alias MatrixAppService.Bridge + + describe "users" do + alias MatrixAppService.Bridge.User + + @valid_attrs %{data: %{}, local_id: "some local_id", remote_id: "some remote_id"} + @update_attrs %{data: %{}, local_id: "some updated local_id", remote_id: "some updated remote_id"} + # @invalid_attrs %{data: nil, local_id: nil, remote_id: nil} + + def user_fixture(attrs \\ %{}) do + {:ok, user} = + attrs + |> Enum.into(@valid_attrs) + |> Bridge.create_user() + + user + end + + test "list_users/0 returns all users" do + user = user_fixture() + assert Bridge.list_users() == [user] + end + + test "get_user!/1 returns the user with given id" do + user = user_fixture() + assert Bridge.get_user!(user.id) == user + end + + test "create_user/1 with valid data creates a user" do + assert {:ok, %User{} = user} = Bridge.create_user(@valid_attrs) + assert user.data == %{} + assert user.local_id == "some local_id" + assert user.remote_id == "some remote_id" + end + +# test "create_user/1 with invalid data returns error changeset" do +# assert {:error, %Ecto.Changeset{}} = Bridge.create_user(@invalid_attrs) +# end + + test "update_user/2 with valid data updates the user" do + user = user_fixture() + assert {:ok, %User{} = user} = Bridge.update_user(user, @update_attrs) + assert user.data == %{} + assert user.local_id == "some updated local_id" + assert user.remote_id == "some updated remote_id" + end + +# test "update_user/2 with invalid data returns error changeset" do +# user = user_fixture() +# assert {:error, %Ecto.Changeset{}} = Bridge.update_user(user, @invalid_attrs) +# assert user == Bridge.get_user!(user.id) +# end + + test "delete_user/1 deletes the user" do + user = user_fixture() + assert {:ok, %User{}} = Bridge.delete_user(user) + assert_raise Ecto.NoResultsError, fn -> Bridge.get_user!(user.id) end + end + + test "change_user/1 returns a user changeset" do + user = user_fixture() + assert %Ecto.Changeset{} = Bridge.change_user(user) + end + end + + describe "rooms" do + alias MatrixAppService.Bridge.Room + + @valid_attrs %{data: %{}, local_id: "some local_id", remote_id: "some remote_id"} + @update_attrs %{data: %{}, local_id: "some updated local_id", remote_id: "some updated remote_id"} + # @invalid_attrs %{data: nil, local_id: nil, remote_id: nil} + + def room_fixture(attrs \\ %{}) do + {:ok, room} = + attrs + |> Enum.into(@valid_attrs) + |> Bridge.create_room() + + room + end + + test "list_rooms/0 returns all rooms" do + room = room_fixture() + assert Bridge.list_rooms() == [room] + end + + test "get_room!/1 returns the room with given id" do + room = room_fixture() + assert Bridge.get_room!(room.id) == room + end + + test "create_room/1 with valid data creates a room" do + assert {:ok, %Room{} = room} = Bridge.create_room(@valid_attrs) + assert room.data == %{} + assert room.local_id == "some local_id" + assert room.remote_id == "some remote_id" + end + +# test "create_room/1 with invalid data returns error changeset" do +# assert {:error, %Ecto.Changeset{}} = Bridge.create_room(@invalid_attrs) +# end + + test "update_room/2 with valid data updates the room" do + room = room_fixture() + assert {:ok, %Room{} = room} = Bridge.update_room(room, @update_attrs) + assert room.data == %{} + assert room.local_id == "some updated local_id" + assert room.remote_id == "some updated remote_id" + end + +# test "update_room/2 with invalid data returns error changeset" do +# room = room_fixture() +# assert {:error, %Ecto.Changeset{}} = Bridge.update_room(room, @invalid_attrs) +# assert room == Bridge.get_room!(room.id) +# end + + test "delete_room/1 deletes the room" do + room = room_fixture() + assert {:ok, %Room{}} = Bridge.delete_room(room) + assert_raise Ecto.NoResultsError, fn -> Bridge.get_room!(room.id) end + end + + test "change_room/1 returns a room changeset" do + room = room_fixture() + assert %Ecto.Changeset{} = Bridge.change_room(room) + end + end +end diff --git a/test/support/data_case.ex b/test/support/data_case.ex new file mode 100644 index 0000000..475f748 --- /dev/null +++ b/test/support/data_case.ex @@ -0,0 +1,53 @@ +defmodule MatrixAppService.DataCase do + @moduledoc """ + This module defines the setup for tests requiring + access to the application's data layer. + You may define functions here to be used as helpers in + your tests. + Finally, if the test case interacts with the database, + it cannot be async. For this reason, every test runs + inside a transaction which is reset at the beginning + of the test unless the test case is marked as async. + """ + + use ExUnit.CaseTemplate + + alias Ecto.Adapters.SQL.Sandbox + alias Ecto.Changeset + alias MatrixAppService.Repo + + using do + quote do + alias MatrixAppService.Repo + + import Ecto + import Ecto.Changeset + import Ecto.Query + import MatrixAppService.DataCase + end + end + + setup tags do + :ok = Sandbox.checkout(Repo) + + unless tags[:async] do + Sandbox.mode(Repo, {:shared, self()}) + end + + :ok + end + + @doc """ + A helper that transform changeset errors to a map of messages. + assert {:error, changeset} = Accounts.create_user(%{password: "short"}) + assert "password is too short" in errors_on(changeset).password + assert %{password: ["password is too short"]} = errors_on(changeset) + """ + def errors_on(changeset) do + Changeset.traverse_errors(changeset, fn {message, opts} -> + Enum.reduce(opts, message, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", to_string(value)) + end) + end) + end +end -- cgit v1.2.3