From 7671ca730f50d4b39813553d848e1c91636f8021 Mon Sep 17 00:00:00 2001 From: Luis Araujo Date: Mon, 1 Sep 2025 14:29:00 +0100 Subject: [PATCH] chore: add tenants context tests --- lib/supavisor/tenants.ex | 135 +++++++++---------- lib/supavisor/tenants/user.ex | 8 +- test/supavisor/tenants_test.exs | 151 ++++++++++++++++++++-- test/support/fixtures/tenants_fixtures.ex | 37 ++++++ 4 files changed, 254 insertions(+), 77 deletions(-) diff --git a/lib/supavisor/tenants.ex b/lib/supavisor/tenants.ex index a404ba0a..ce4a74d9 100644 --- a/lib/supavisor/tenants.ex +++ b/lib/supavisor/tenants.ex @@ -84,60 +84,6 @@ defmodule Supavisor.Tenants do def get_tenant(_, _), do: nil - @spec get_user_cache(:single | :cluster, String.t(), String.t() | nil, String.t() | nil) :: - {:ok, map()} | {:error, any()} - def get_user_cache(type, user, external_id, sni_hostname) do - cache_key = {:user_cache, type, user, external_id, sni_hostname} - - case Cachex.fetch(Supavisor.Cache, cache_key, fn _key -> - {:commit, {:cached, get_user(type, user, external_id, sni_hostname)}, - ttl: :timer.hours(24)} - end) do - {_, {:cached, value}} -> value - {_, {:cached, value}, _} -> value - end - end - - @spec get_user(atom(), String.t(), String.t() | nil, String.t() | nil) :: - {:ok, map()} | {:error, any()} - def get_user(_, _, nil, nil) do - {:error, "Either external_id or sni_hostname must be provided"} - end - - def get_user(:cluster, user, external_id, sni_hostname) do - query = - from(ct in ClusterTenants, - where: ct.cluster_alias == ^external_id and ct.active == true, - limit: 1 - ) - - case Repo.all(query) do - [%ClusterTenants{} = ct] -> - get_user(:single, user, ct.tenant_external_id, sni_hostname) - - [_ | _] -> - {:error, :multiple_results} - - _ -> - {:error, :not_found} - end - end - - def get_user(:single, user, external_id, sni_hostname) do - query = build_user_query(user, external_id, sni_hostname) - - case Repo.all(query) do - [{%User{}, %Tenant{}} = {user, tenant}] -> - {:ok, %{user: user, tenant: tenant}} - - [_ | _] -> - {:error, :multiple_results} - - _ -> - {:error, :not_found} - end - end - def get_pool_config(external_id, user) do query = from(a in User, @@ -306,6 +252,76 @@ defmodule Supavisor.Tenants 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) + + @spec get_user_cache(:single | :cluster, String.t(), String.t() | nil, String.t() | nil) :: + {:ok, map()} | {:error, any()} + def get_user_cache(type, user, external_id, sni_hostname) do + cache_key = {:user_cache, type, user, external_id, sni_hostname} + + case Cachex.fetch(Supavisor.Cache, cache_key, fn _key -> + {:commit, {:cached, get_user(type, user, external_id, sni_hostname)}, + ttl: :timer.hours(24)} + end) do + {_, {:cached, value}} -> value + {_, {:cached, value}, _} -> value + end + end + + @spec get_user(atom(), String.t(), String.t() | nil, String.t() | nil) :: + {:ok, map()} | {:error, any()} + def get_user(_, _, nil, nil) do + {:error, "Either external_id or sni_hostname must be provided"} + end + + def get_user(:cluster, user, external_id, sni_hostname) do + query = + from(ct in ClusterTenants, + where: ct.cluster_alias == ^external_id and ct.active == true, + limit: 1 + ) + + case Repo.all(query) do + [%ClusterTenants{} = ct] -> + get_user(:single, user, ct.tenant_external_id, sni_hostname) + + [_ | _] -> + {:error, :multiple_results} + + _ -> + {:error, :not_found} + end + end + + def get_user(:single, user, external_id, sni_hostname) do + query = build_user_query(user, external_id, sni_hostname) + + case Repo.all(query) do + [{%User{}, %Tenant{}} = {user, tenant}] -> + {:ok, %{user: user, tenant: tenant}} + + [_ | _] -> + {:error, :multiple_results} + + _ -> + {:error, :not_found} + end + end + @doc """ Creates a user. @@ -424,17 +440,6 @@ defmodule Supavisor.Tenants do """ def get_cluster!(id), do: Repo.get!(Cluster, id) - @spec get_cluster_with_rel(String.t()) :: {:ok, Cluster.t()} | {:error, any()} - def get_cluster_with_rel(id) do - case Repo.get(Cluster, id) do - nil -> - {:error, :not_found} - - cluster -> - {:ok, Repo.preload(cluster, :cluster_tenants)} - end - end - @doc """ Creates a cluster. diff --git a/lib/supavisor/tenants/user.ex b/lib/supavisor/tenants/user.ex index b208ff57..7ac4af64 100644 --- a/lib/supavisor/tenants/user.ex +++ b/lib/supavisor/tenants/user.ex @@ -24,10 +24,10 @@ defmodule Supavisor.Tenants.User do @doc false def changeset(user, attrs) do attrs = - if attrs["db_user_alias"] do - attrs - else - Map.put(attrs, "db_user_alias", attrs["db_user"]) + case attrs do + %{"db_user_alias" => _} -> attrs + %{"db_user" => db_user} -> Map.put(attrs, "db_user_alias", db_user) + _ -> attrs end user diff --git a/test/supavisor/tenants_test.exs b/test/supavisor/tenants_test.exs index f41bb860..e0124775 100644 --- a/test/supavisor/tenants_test.exs +++ b/test/supavisor/tenants_test.exs @@ -139,14 +139,6 @@ defmodule Supavisor.TenantsTest do assert %Ecto.Changeset{} = Tenants.change_tenant(tenant) end - test "get_user/4" do - _tenant = tenant_fixture() - assert {:error, :not_found} = Tenants.get_user(:single, "no_user", "no_tenant", "") - - assert {:ok, %{tenant: _, user: _}} = - Tenants.get_user(:single, "postgres", "dev_tenant", "") - end - test "update_tenant_ps/2 updates the tenant's default_parameter_status" do _tenant = tenant_fixture() default_parameter_status = %{"server_version" => "17.0"} @@ -174,6 +166,77 @@ defmodule Supavisor.TenantsTest do end end + describe "users" do + alias Supavisor.Tenants.User + + import Supavisor.TenantsFixtures + + @invalid_attrs %{ + "db_user" => nil, + "db_password" => nil, + "pool_size" => nil, + "mode_type" => nil + } + + test "list_users/0 returns all users" do + user = user_fixture() + assert user in Tenants.list_users() + end + + test "get_user!/1 returns the user with given id" do + user = user_fixture() + assert Tenants.get_user!(user.id) == user + end + + test "get_user/4" do + _tenant = tenant_fixture() + assert {:error, :not_found} = Tenants.get_user(:single, "no_user", "no_tenant", "") + + assert {:ok, %{tenant: _, user: _}} = + Tenants.get_user(:single, "postgres", "dev_tenant", "") + end + + test "create_user/1 with valid data creates a user" do + valid_attrs = %{ + "db_user" => "some_user", + "db_password" => "some_password", + "pool_size" => 3, + "mode_type" => "transaction" + } + + assert {:ok, %User{} = user} = Tenants.create_user(valid_attrs) + assert user.db_user_alias == "some_user" + assert user.db_user == "some_user" + assert user.db_password == "some_password" + assert user.pool_size == 3 + assert user.mode_type == :transaction + end + + test "create_user/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Tenants.create_user(@invalid_attrs) + end + + test "update_user/2 with valid data updates the user" do + user = user_fixture() + update_attrs = %{"db_password" => "some_updated_password", "pool_size" => 4} + + assert {:ok, %User{} = user} = Tenants.update_user(user, update_attrs) + assert user.db_password == "some_updated_password" + assert user.pool_size == 4 + end + + test "delete_user/1 deletes the user" do + user = user_fixture() + assert {:ok, %User{}} = Tenants.delete_user(user) + assert_raise Ecto.NoResultsError, fn -> Tenants.get_user!(user.id) end + end + + test "change_user/1 returns a user changeset" do + user = user_fixture() + assert %Ecto.Changeset{} = Tenants.change_user(user) + end + end + describe "clusters" do alias Supavisor.Tenants.Cluster @@ -225,4 +288,76 @@ defmodule Supavisor.TenantsTest do assert %Ecto.Changeset{} = Tenants.change_cluster(cluster) end end + + describe "cluster_tenants" do + alias Supavisor.Tenants.ClusterTenants + + import Supavisor.TenantsFixtures + + @invalid_attrs %{type: nil, cluster_alias: nil, tenant_external_id: nil, active: nil} + + test "list_cluster_tenants/0 returns all cluster_tenants" do + cluster_tenants = cluster_tenants_fixture() + assert cluster_tenants in Tenants.list_cluster_tenants() + end + + test "get_cluster_tenants!/1 returns the cluster_tenants with given id" do + cluster_tenants = cluster_tenants_fixture() + assert Tenants.get_cluster_tenants!(cluster_tenants.id) == cluster_tenants + end + + test "create_cluster_tenants/1 with valid data creates a cluster_tenants" do + tenant = tenant_fixture() + cluster = cluster_fixture() + + valid_attrs = %{ + type: "write", + cluster_alias: cluster.alias, + tenant_external_id: tenant.external_id, + active: true + } + + assert {:ok, %ClusterTenants{} = cluster_tenants} = + Tenants.create_cluster_tenants(valid_attrs) + + assert cluster_tenants.type == :write + assert cluster_tenants.cluster_alias == cluster.alias + assert cluster_tenants.tenant_external_id == tenant.external_id + assert cluster_tenants.active == true + end + + test "create_cluster_tenants/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Tenants.create_cluster_tenants(@invalid_attrs) + end + + test "update_cluster_tenants/2 with valid data updates the cluster_tenants" do + valid_attrs = %{type: "read"} + cluster_tenants = cluster_tenants_fixture() + + assert {:ok, %ClusterTenants{} = cluster_tenants} = + Tenants.update_cluster_tenants(cluster_tenants, valid_attrs) + + assert cluster_tenants.active == true + end + + test "update_cluster_tenants/2 with invalid data returns error changeset" do + cluster_tenants = cluster_tenants_fixture() + + assert {:error, %Ecto.Changeset{}} = + Tenants.update_cluster_tenants(cluster_tenants, @invalid_attrs) + + assert cluster_tenants == Tenants.get_cluster_tenants!(cluster_tenants.id) + end + + test "delete_cluster_tenants/1 deletes the cluster_tenants" do + cluster_tenants = cluster_tenants_fixture() + assert {:ok, %ClusterTenants{}} = Tenants.delete_cluster_tenants(cluster_tenants) + assert_raise Ecto.NoResultsError, fn -> Tenants.get_cluster_tenants!(cluster_tenants.id) end + end + + test "change_cluster_tenants/1 returns a cluster_tenants changeset" do + cluster_tenants = cluster_tenants_fixture() + assert %Ecto.Changeset{} = Tenants.change_cluster_tenants(cluster_tenants) + end + end end diff --git a/test/support/fixtures/tenants_fixtures.ex b/test/support/fixtures/tenants_fixtures.ex index 989a4dd7..b0063cda 100644 --- a/test/support/fixtures/tenants_fixtures.ex +++ b/test/support/fixtures/tenants_fixtures.ex @@ -31,6 +31,23 @@ defmodule Supavisor.TenantsFixtures do tenant end + @doc """ + Generate a user. + """ + def user_fixture(attrs \\ %{}) do + {:ok, user} = + attrs + |> Enum.into(%{ + "db_user" => "postgres", + "db_password" => "postgres", + "pool_size" => 3, + "mode_type" => "transaction" + }) + |> Supavisor.Tenants.create_user() + + user + end + # @doc """ # Generate a unique cluster tenant_external_id. # """ @@ -64,4 +81,24 @@ defmodule Supavisor.TenantsFixtures do cluster end + + @doc """ + Generate a cluster tenants. + """ + def cluster_tenants_fixture(attrs \\ %{}) do + tenant = tenant_fixture() + cluster = cluster_fixture(%{cluster_tenants: []}) + + {:ok, cluster_tenants} = + attrs + |> Enum.into(%{ + type: "write", + cluster_alias: cluster.alias, + tenant_external_id: tenant.external_id, + active: true + }) + |> Supavisor.Tenants.create_cluster_tenants() + + cluster_tenants + end end