diff --git a/test/schema_cache/adapters/redis_adapter_set_ops_test.exs b/test/schema_cache/adapters/redis_adapter_set_ops_test.exs new file mode 100644 index 0000000..f94e429 --- /dev/null +++ b/test/schema_cache/adapters/redis_adapter_set_ops_test.exs @@ -0,0 +1,77 @@ +defmodule SchemaCache.Adapters.RedisAdapterSetOpsTest do + @moduledoc false + + use SchemaCache.Test.RedisCase, async: false + + alias SchemaCache.Test.RedisAdapter + + describe "sadd/2" do + test "adds a member to a set" do + assert :ok = RedisAdapter.sadd("my_set", 1) + + assert {:ok, members} = RedisAdapter.smembers("my_set") + assert 1 in members + end + + test "with duplicate member is idempotent" do + RedisAdapter.sadd("my_set", 1) + RedisAdapter.sadd("my_set", 1) + + assert {:ok, members} = RedisAdapter.smembers("my_set") + assert members == [1] + end + end + + describe "srem/2" do + test "removes a specific member" do + RedisAdapter.sadd("my_set", 1) + RedisAdapter.sadd("my_set", 2) + + assert :ok = RedisAdapter.srem("my_set", 1) + + assert {:ok, members} = RedisAdapter.smembers("my_set") + assert 2 in members + refute 1 in members + end + + test "on non-existent member returns :ok" do + RedisAdapter.sadd("my_set", 1) + + assert :ok = RedisAdapter.srem("my_set", 999) + + assert {:ok, members} = RedisAdapter.smembers("my_set") + assert 1 in members + end + end + + describe "smembers/1" do + test "returns {:ok, members} for populated set" do + RedisAdapter.sadd("my_set", 1) + RedisAdapter.sadd("my_set", 2) + RedisAdapter.sadd("my_set", 3) + + assert {:ok, members} = RedisAdapter.smembers("my_set") + assert is_list(members) + assert length(members) == 3 + assert Enum.sort(members) == [1, 2, 3] + end + + test "returns {:ok, nil} for empty/non-existent set" do + assert {:ok, nil} = RedisAdapter.smembers("nonexistent_set") + end + end + + describe "mget/1" do + test "returns values for existing keys and nil for missing keys" do + RedisAdapter.put("key_1", "value_1", []) + RedisAdapter.put("key_2", "value_2", []) + + assert {:ok, results} = RedisAdapter.mget(["key_1", "missing_key", "key_2"]) + assert results == ["value_1", nil, "value_2"] + end + + test "with empty list returns {:ok, []}" do + assert {:ok, []} = RedisAdapter.mget([]) + end + end +end diff --git a/test/schema_cache/adapters/redis_adapter_test.exs b/test/schema_cache/adapters/redis_adapter_test.exs new file mode 100644 index 0000000..d043eb2 --- /dev/null +++ b/test/schema_cache/adapters/redis_adapter_test.exs @@ -0,0 +1,54 @@ +defmodule SchemaCache.Adapters.RedisAdapterTest do + @moduledoc false + + use SchemaCache.Test.RedisCase, async: false + + alias SchemaCache.Test.RedisAdapter + + describe "get/1" do + test "returns {:ok, nil} for missing keys" do + assert {:ok, nil} = RedisAdapter.get("nonexistent") + end + + test "returns {:ok, value} for existing keys" do + RedisAdapter.put("key", "value", []) + assert {:ok, "value"} = RedisAdapter.get("key") + end + + test "round-trips complex Elixir terms" do + value = %{nested: [1, :atom, {"tuple"}], binary: <<0, 255>>} + RedisAdapter.put("complex", value, []) + assert {:ok, ^value} = RedisAdapter.get("complex") + end + end + + describe "put/3" do + test "stores and retrieves values" do + assert :ok = RedisAdapter.put("key", %{data: true}, []) + assert {:ok, %{data: true}} = RedisAdapter.get("key") + end + + test "overwrites existing values" do + RedisAdapter.put("key", "first", []) + RedisAdapter.put("key", "second", []) + assert {:ok, "second"} = RedisAdapter.get("key") + end + + test "supports TTL option" do + assert :ok = RedisAdapter.put("ttl_key", "value", ttl: 60_000) + assert {:ok, "value"} = RedisAdapter.get("ttl_key") + end + end + + describe "delete/1" do + test "removes a key" do + RedisAdapter.put("key", "value", []) + assert :ok = RedisAdapter.delete("key") + assert {:ok, nil} = RedisAdapter.get("key") + end + + test "returns :ok for nonexistent keys" do + assert :ok = RedisAdapter.delete("nonexistent") + end + end +end diff --git a/test/support/redis_adapter.ex b/test/support/redis_adapter.ex index c5c4869..c33298f 100644 --- a/test/support/redis_adapter.ex +++ b/test/support/redis_adapter.ex @@ -69,6 +69,8 @@ defmodule SchemaCache.Test.RedisAdapter do end @impl true + def mget([]), do: {:ok, []} + def mget(keys) do case Redix.command(redis_conn(), ["MGET" | keys]) do {:ok, values} -> diff --git a/test/support/redis_case.ex b/test/support/redis_case.ex index 0d4c7d4..96d5aef 100644 --- a/test/support/redis_case.ex +++ b/test/support/redis_case.ex @@ -3,33 +3,47 @@ defmodule SchemaCache.Test.RedisCase do use ExUnit.CaseTemplate - using do - quote do - alias SchemaCache.Test.User - end - end + alias SchemaCache.Adapter + + @ets_tables [ + :schema_cache_ets, + :schema_cache_ets_sets, + :schema_cache_key_to_id, + :schema_cache_id_to_key + ] setup do redis_url = Application.get_env(:schema_cache, :redis_url, "redis://localhost:6379") - {:ok, conn} = Redix.start_link(redis_url) - # Flush Redis before each test - {:ok, "OK"} = Redix.command(conn, ["FLUSHDB"]) + case Redix.start_link(redis_url) do + {:ok, conn} -> + {:ok, "OK"} = Redix.command(conn, ["FLUSHDB"]) + + for table <- @ets_tables do + if :ets.whereis(table) != :undefined do + :ets.delete_all_objects(table) + end + end - # Make connection available to the adapter - Process.put(:schema_cache_redis_conn, conn) + Process.put(:schema_cache_redis_conn, conn) - # Set the adapter to Redis for this test - Application.put_env(:schema_cache, :adapter, SchemaCache.Test.RedisAdapter) + original_adapter = :persistent_term.get(:schema_cache_adapter) + original_caps = :persistent_term.get(:schema_cache_adapter_caps) - on_exit(fn -> - # Reset adapter back to ETS - Application.put_env(:schema_cache, :adapter, SchemaCache.Adapters.ETS) + :persistent_term.put(:schema_cache_adapter, SchemaCache.Test.RedisAdapter) + Adapter.resolve_capabilities(SchemaCache.Test.RedisAdapter) - # Stop the connection - if Process.alive?(conn), do: GenServer.stop(conn) - end) + on_exit(fn -> + :persistent_term.put(:schema_cache_adapter, original_adapter) + :persistent_term.put(:schema_cache_adapter_caps, original_caps) - %{redis_conn: conn} + if Process.alive?(conn), do: GenServer.stop(conn) + end) + + %{redis_conn: conn} + + {:error, _reason} -> + :skip + end end end