ravecat

ravecat

Broken test for multi-tenancy and Triplex

Implement complex logic for multi-tenancy solution: create team → create schema → run migration on new schema. Found a thread and a mention in Triplex documentation and moved the migrations from the transaction. Method look like

  def create_team(attrs \\ %{}) do
    changeset =
      %Team{}
      |> change(%{uid: generate_token(type: :schema)})
      |> Team.changeset(attrs)

    Multi.new()
    |> Multi.insert(:team, changeset)
    |> Multi.run(:schema, fn repo, %{team: team} ->
      Triplex.create_schema(team.uid, repo)
    end)
    |> Repo.transaction()
    |> case do
      {:ok, %{team: team}} ->
        {:ok, _} = Triplex.migrate(team.uid)

        {:ok, team}

      {:error, _, reason, _} ->
        {:error, reason}
    end
  end

It work flawless inside iex

iex(97)> Runa.Teams.create_team(%{title: “test”})
[debug] QUERY OK db=0.4ms idle=1327.3ms
begin
↳ Runa.Teams.create_team/1, at: lib/domains/teams/teams.ex:82
[debug] QUERY OK source=“teams” db=0.5ms
INSERT INTO “teams” (“title”,“uid”,“inserted_at”,“updated_at”) VALUES ($1,$2,$3,$4) RETURNING “id” [“test”, “nm8qnu26poykd2y02e0dup5wnf168qz2”, ~U[2024-05-20 17:55:36Z], ~U[2024-05-20 17:55:36Z]]
↳ Runa.Teams.create_team/1, at: lib/domains/teams/teams.ex:82
[debug] QUERY OK db=0.1ms
CREATE SCHEMA “nm8qnu26poykd2y02e0dup5wnf168qz2”
[debug] QUERY OK db=14.5ms
commit
↳ Runa.Teams.create_team/1, at: lib/domains/teams/teams.ex:82
[info] == Running 20240420161100 Runa.Repo.Migrations.CreateContributors.up/0 forward
[info] execute “CREATE TABLE nm8qnu26poykd2y02e0dup5wnf168qz2.contributors (\n id serial PRIMARY KEY,\n user_id integer REFERENCES public.users(id) ON DELETE CASCADE NOT NULL,\n team_id integer REFERENCES public.teams(id) ON DELETE CASCADE NOT NULL,\n role_id integer REFERENCES public.roles(id) ON DELETE CASCADE NOT NULL,\n inserted_at timestamp NOT NULL,\n updated_at timestamp NOT NULL\n);\n”
[info] execute “CREATE UNIQUE INDEX contributors_user_id_team_id_role_id_index\nON nm8qnu26poykd2y02e0dup5wnf168qz2.contributors (user_id, team_id, role_id);\n”
[info] == Migrated 20240420161100 in 0.0s
[info] == Running 20240520093137 Runa.Repo.Migrations.CreateProjectsTable.up/0 forward
[info] create table nm8qnu26poykd2y02e0dup5wnf168qz2.projects
[info] == Migrated 20240520093137 in 0.0s
{:ok,
%Runa.Teams.Team{
meta: ecto.Schema.Metadata<:loaded, “teams”>,
id: 53,
title: “test”,
uid: “nm8qnu26poykd2y02e0dup5wnf168qz2”,
team_roles: ecto.Association.NotLoaded,
users: ecto.Association.NotLoaded,
inserted_at: ~U[2024-05-20 17:55:36Z],
updated_at: ~U[2024-05-20 17:55:36Z]
}}

But inside test in returns error, I use create_project inside the fixture and create a entity, schema and migrations before each test

as I see it migrations cannot be performed in a test environment, but Triplex.exists?(team.uid) return true, Triplex.migrate("public") return {:ok, _}

  1. test projects returns all projects (Runa.ProjectsTest)
    test/domains/projects_test.exs:16
    ** (MatchError) no match of right hand side value: {:error, “could not checkout the connection owned by #PID<0.610.0>. When using the sandbox, connections are shared, so this may imply another process is using a connection. Reason: connection not available and request was dropped from queue after 822ms. You can configure how long requests wait in the queue using :queue_target and :queue_interval. See DBConnection.start_link/2 for more information”}
    stacktrace:
    (runa 0.1.0) lib/domains/teams/teams.ex:71: Runa.Teams.create_team/1
    (runa 0.1.0) test/support/fixtures/teams_fixtures.ex:25: Runa.TeamsFixtures.create_aux_team/1
    Runa.ProjectsTest.__ex_unit_describe_0/1

Is this a Triplex problem or a test environment problem?

First Post!

ashrafhasson

ashrafhasson

Hi @ravecat I’ve stumbled upon this too but not the testing part. I didn’t think this would work in iex (or in a transaction) but then it seems it’s only Triplex.create that cannot run inside of a transaction since Ecto 3 :thinking:

However, running the Triplex.create_schema this way inside a multi transaction means it may leave unclean teams’ schemas should the Triplex.migrate fail later on.
If you use the Triplex.create_schema func argument to wrap the transaction instead, you might be able to avoid that side effect.

Regarding the failure in your test, I wonder whether your fixture is spawning a process that’s not dropping the connection before it’s checked out again by the test process? It would be useful to share your fixture and test code here. Also a total speculation is whether you have multiple of these tests in different modules with async turned on …

Where Next?

Popular in Questions Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New

We're in Beta

About us Mission Statement