test: use Mox for Valkey tests without real Redis (55%→88%)
- Create Microwaveprop.Valkey.Adapter behaviour with command/3 + pipeline/3 - Create Microwaveprop.Valkey.RedixAdapter as the production impl - Inject adapter via Application config; mock with Mox in tests - Cover all Valkey operations: get, mget, set, mset_with_ttl, zadd, zrevrange, zrangebyscore, zrem, del, scan_match (multi-cursor) - Full round-trip tests for encode/decode, error paths, edge cases Coverage: 78.93% → 79.07%
This commit is contained in:
parent
58f4a7714b
commit
89e8bb2267
5 changed files with 220 additions and 25 deletions
|
|
@ -209,10 +209,12 @@ defmodule Microwaveprop.Valkey do
|
|||
end
|
||||
end
|
||||
|
||||
defp adapter, do: Application.get_env(:microwaveprop, :valkey_adapter, Microwaveprop.Valkey.RedixAdapter)
|
||||
|
||||
defp command(args) do
|
||||
if configured?() do
|
||||
try do
|
||||
Redix.command(@conn, args, timeout: 1500)
|
||||
adapter().command(@conn, args, timeout: 1500)
|
||||
catch
|
||||
:exit, reason -> {:error, {:exit, reason}}
|
||||
end
|
||||
|
|
@ -224,7 +226,7 @@ defmodule Microwaveprop.Valkey do
|
|||
defp pipeline(cmds) do
|
||||
if configured?() do
|
||||
try do
|
||||
Redix.pipeline(@conn, cmds, timeout: 3000)
|
||||
adapter().pipeline(@conn, cmds, timeout: 3000)
|
||||
catch
|
||||
:exit, reason -> {:error, {:exit, reason}}
|
||||
end
|
||||
|
|
|
|||
11
lib/microwaveprop/valkey/adapter.ex
Normal file
11
lib/microwaveprop/valkey/adapter.ex
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Microwaveprop.Valkey.Adapter do
|
||||
@moduledoc """
|
||||
Behaviour for the Valkey/Redis command runner. The production
|
||||
implementation delegates to Redix; tests inject a Mox mock.
|
||||
"""
|
||||
@doc "Run a single Redis command."
|
||||
@callback command(Redix.connection(), [String.t()], keyword()) :: {:ok, term()} | {:error, term()}
|
||||
|
||||
@doc "Run a pipeline of Redis commands."
|
||||
@callback pipeline(Redix.connection(), [[String.t()]], keyword()) :: {:ok, [term()]} | {:error, term()}
|
||||
end
|
||||
14
lib/microwaveprop/valkey/redix_adapter.ex
Normal file
14
lib/microwaveprop/valkey/redix_adapter.ex
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
defmodule Microwaveprop.Valkey.RedixAdapter do
|
||||
@moduledoc false
|
||||
@behaviour Microwaveprop.Valkey.Adapter
|
||||
|
||||
@impl true
|
||||
def command(conn, args, opts) do
|
||||
Redix.command(conn, args, opts)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def pipeline(conn, cmds, opts) do
|
||||
Redix.pipeline(conn, cmds, opts)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +1,39 @@
|
|||
defmodule Microwaveprop.ValkeyTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
import Mox
|
||||
|
||||
alias Microwaveprop.Valkey
|
||||
|
||||
# Register Mox mock as the adapter and fake a Redix connection so
|
||||
# configured?() returns true, letting command/pipeline run through the mock.
|
||||
setup do
|
||||
prev_url = Application.get_env(:microwaveprop, :valkey_url)
|
||||
prev_adapter = Application.get_env(:microwaveprop, :valkey_adapter)
|
||||
|
||||
Application.put_env(:microwaveprop, :valkey_url, "redis://localhost:6379")
|
||||
Application.put_env(:microwaveprop, :valkey_adapter, Microwaveprop.Valkey.MockAdapter)
|
||||
|
||||
# Start a dummy process registered at the connection name.
|
||||
{:ok, pid} = Task.start(fn -> Process.sleep(:infinity) end)
|
||||
Process.register(pid, Microwaveprop.Valkey.Conn)
|
||||
|
||||
on_exit(fn ->
|
||||
Process.unregister(Microwaveprop.Valkey.Conn)
|
||||
Process.exit(pid, :kill)
|
||||
|
||||
if prev_url,
|
||||
do: Application.put_env(:microwaveprop, :valkey_url, prev_url),
|
||||
else: Application.delete_env(:microwaveprop, :valkey_url)
|
||||
|
||||
if prev_adapter,
|
||||
do: Application.put_env(:microwaveprop, :valkey_adapter, prev_adapter),
|
||||
else: Application.delete_env(:microwaveprop, :valkey_adapter)
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "child_spec/1" do
|
||||
test "returns a valid child spec map" do
|
||||
spec = Valkey.child_spec([])
|
||||
|
|
@ -14,14 +45,34 @@ defmodule Microwaveprop.ValkeyTest do
|
|||
end
|
||||
|
||||
describe "configured?/0" do
|
||||
test "returns false when Valkey is not configured" do
|
||||
assert Valkey.configured?() == false
|
||||
test "returns true when Redix process is alive" do
|
||||
assert Valkey.configured?() == true
|
||||
end
|
||||
end
|
||||
|
||||
describe "get/1" do
|
||||
test "returns error when not configured" do
|
||||
assert {:error, :not_configured} = Valkey.get("test_key")
|
||||
test "returns :miss when key is nil" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["GET", "missing"], _opts ->
|
||||
{:ok, nil}
|
||||
end)
|
||||
|
||||
assert Valkey.get("missing") == :miss
|
||||
end
|
||||
|
||||
test "returns decoded term on hit" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["GET", "key"], _opts ->
|
||||
{:ok, :erlang.term_to_binary(%{foo: "bar"})}
|
||||
end)
|
||||
|
||||
assert Valkey.get("key") == {:ok, %{foo: "bar"}}
|
||||
end
|
||||
|
||||
test "propagates error" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["GET", "key"], _opts ->
|
||||
{:error, :connection_lost}
|
||||
end)
|
||||
|
||||
assert Valkey.get("key") == {:error, :connection_lost}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -30,14 +81,46 @@ defmodule Microwaveprop.ValkeyTest do
|
|||
assert Valkey.mget([]) == {:ok, []}
|
||||
end
|
||||
|
||||
test "returns error when not configured" do
|
||||
assert {:error, :not_configured} = Valkey.mget(["key1", "key2"])
|
||||
test "returns decoded values preserving order" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["MGET", "a", "b"], _opts ->
|
||||
{:ok, [nil, :erlang.term_to_binary(42)]}
|
||||
end)
|
||||
|
||||
assert Valkey.mget(["a", "b"]) == {:ok, [nil, 42]}
|
||||
end
|
||||
|
||||
test "propagates error" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["MGET", "key"], _opts ->
|
||||
{:error, :connection_lost}
|
||||
end)
|
||||
|
||||
assert Valkey.mget(["key"]) == {:error, :connection_lost}
|
||||
end
|
||||
end
|
||||
|
||||
describe "set/3" do
|
||||
test "returns error when not configured" do
|
||||
assert {:error, :not_configured} = Valkey.set("key", "value", 3600)
|
||||
test "returns :ok on success" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["SET", "k", _, "EX", "3600"], _opts ->
|
||||
{:ok, "OK"}
|
||||
end)
|
||||
|
||||
assert Valkey.set("k", "value", 3600) == :ok
|
||||
end
|
||||
|
||||
test "propagates error" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["SET", "k", _, "EX", "3600"], _opts ->
|
||||
{:error, :connection_lost}
|
||||
end)
|
||||
|
||||
assert Valkey.set("k", "value", 3600) == {:error, :connection_lost}
|
||||
end
|
||||
|
||||
test "handles unexpected response" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["SET", "k", _, "EX", "3600"], _opts ->
|
||||
{:ok, "QUEUED"}
|
||||
end)
|
||||
|
||||
assert Valkey.set("k", "value", 3600) == {:error, {:ok, "QUEUED"}}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -46,27 +129,78 @@ defmodule Microwaveprop.ValkeyTest do
|
|||
assert Valkey.mset_with_ttl([], 3600) == :ok
|
||||
end
|
||||
|
||||
test "returns error when not configured" do
|
||||
pairs = [{"key1", "val1"}, {"key2", "val2"}]
|
||||
assert {:error, :not_configured} = Valkey.mset_with_ttl(pairs, 3600)
|
||||
test "returns :ok when all SETs succeed" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :pipeline, fn _conn, cmds, _opts ->
|
||||
assert length(cmds) == 2
|
||||
assert hd(cmds) |> List.first() == "SET"
|
||||
{:ok, ["OK", "OK"]}
|
||||
end)
|
||||
|
||||
assert Valkey.mset_with_ttl([{"a", 1}, {"b", 2}], 3600) == :ok
|
||||
end
|
||||
|
||||
test "returns {:error, {:partial, _}} when some SETs fail" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :pipeline, fn _conn, _cmds, _opts ->
|
||||
{:ok, ["OK", "ERROR"]}
|
||||
end)
|
||||
|
||||
assert {:error, {:partial, _}} = Valkey.mset_with_ttl([{"a", 1}, {"b", 2}], 3600)
|
||||
end
|
||||
|
||||
test "propagates pipeline error" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :pipeline, fn _conn, _cmds, _opts ->
|
||||
{:error, :connection_lost}
|
||||
end)
|
||||
|
||||
assert Valkey.mset_with_ttl([{"a", 1}], 3600) == {:error, :connection_lost}
|
||||
end
|
||||
end
|
||||
|
||||
describe "zadd/3" do
|
||||
test "returns error when not configured" do
|
||||
assert {:error, :not_configured} = Valkey.zadd("zset", 1.0, "member")
|
||||
test "returns :ok on success" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["ZADD", "zset", "1.5", "mem"], _opts ->
|
||||
{:ok, 1}
|
||||
end)
|
||||
|
||||
assert Valkey.zadd("zset", 1.5, "mem") == :ok
|
||||
end
|
||||
|
||||
test "propagates error" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["ZADD", "zset", "1.0", "mem"], _opts ->
|
||||
{:error, :connection_lost}
|
||||
end)
|
||||
|
||||
assert Valkey.zadd("zset", 1.0, "mem") == {:error, :connection_lost}
|
||||
end
|
||||
end
|
||||
|
||||
describe "zrevrange/3" do
|
||||
test "returns error when not configured" do
|
||||
assert {:error, :not_configured} = Valkey.zrevrange("zset", 0, 10)
|
||||
test "returns members list" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["ZREVRANGE", "zset", "0", "10"], _opts ->
|
||||
{:ok, ["a", "b"]}
|
||||
end)
|
||||
|
||||
assert Valkey.zrevrange("zset", 0, 10) == {:ok, ["a", "b"]}
|
||||
end
|
||||
|
||||
test "propagates error" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, _, _opts ->
|
||||
{:error, :connection_lost}
|
||||
end)
|
||||
|
||||
assert Valkey.zrevrange("zset", 0, 10) == {:error, :connection_lost}
|
||||
end
|
||||
end
|
||||
|
||||
describe "zrangebyscore/3" do
|
||||
test "returns error when not configured" do
|
||||
assert {:error, :not_configured} = Valkey.zrangebyscore("zset", "0", "100")
|
||||
test "returns members list" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn,
|
||||
["ZRANGEBYSCORE", "zset", "0", "100"],
|
||||
_opts ->
|
||||
{:ok, ["a", "b"]}
|
||||
end)
|
||||
|
||||
assert Valkey.zrangebyscore("zset", "0", "100") == {:ok, ["a", "b"]}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -75,8 +209,12 @@ defmodule Microwaveprop.ValkeyTest do
|
|||
assert Valkey.zrem("zset", []) == :ok
|
||||
end
|
||||
|
||||
test "returns error when not configured" do
|
||||
assert {:error, :not_configured} = Valkey.zrem("zset", ["member1"])
|
||||
test "removes members" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["ZREM", "zset", "a", "b"], _opts ->
|
||||
{:ok, 2}
|
||||
end)
|
||||
|
||||
assert Valkey.zrem("zset", ["a", "b"]) == :ok
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -85,14 +223,42 @@ defmodule Microwaveprop.ValkeyTest do
|
|||
assert Valkey.del([]) == :ok
|
||||
end
|
||||
|
||||
test "returns error when not configured" do
|
||||
assert {:error, :not_configured} = Valkey.del(["key1"])
|
||||
test "deletes keys" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["DEL", "k1", "k2"], _opts ->
|
||||
{:ok, 2}
|
||||
end)
|
||||
|
||||
assert Valkey.del(["k1", "k2"]) == :ok
|
||||
end
|
||||
end
|
||||
|
||||
describe "scan_match/1" do
|
||||
test "returns error when not configured" do
|
||||
assert {:error, :not_configured} = Valkey.scan_match("pattern:*")
|
||||
test "returns all matching keys from single pass" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, ["SCAN", "0", "MATCH", "pfx:*", "COUNT", "500"], _opts ->
|
||||
{:ok, ["0", ["pfx:a", "pfx:b"]]}
|
||||
end)
|
||||
|
||||
assert Valkey.scan_match("pfx:*") == {:ok, ["pfx:a", "pfx:b"]}
|
||||
end
|
||||
|
||||
test "follows cursor across multiple passes" do
|
||||
Microwaveprop.Valkey.MockAdapter
|
||||
|> expect(:command, fn _conn, ["SCAN", "0", "MATCH", "pfx:*", "COUNT", "500"], _opts ->
|
||||
{:ok, ["3", ["pfx:a"]]}
|
||||
end)
|
||||
|> expect(:command, fn _conn, ["SCAN", "3", "MATCH", "pfx:*", "COUNT", "500"], _opts ->
|
||||
{:ok, ["0", ["pfx:b"]]}
|
||||
end)
|
||||
|
||||
assert Valkey.scan_match("pfx:*") == {:ok, ["pfx:a", "pfx:b"]}
|
||||
end
|
||||
|
||||
test "propagates error" do
|
||||
expect(Microwaveprop.Valkey.MockAdapter, :command, fn _conn, _, _opts ->
|
||||
{:error, :connection_lost}
|
||||
end)
|
||||
|
||||
assert Valkey.scan_match("pfx:*") == {:error, :connection_lost}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,3 +3,5 @@ alias Ecto.Adapters.SQL.Sandbox
|
|||
ExUnit.start(exclude: [:slow], capture_log: true)
|
||||
Sandbox.mode(Microwaveprop.Repo, :manual)
|
||||
Sandbox.mode(Microwaveprop.AprsRepo, :manual)
|
||||
|
||||
Mox.defmock(Microwaveprop.Valkey.MockAdapter, for: Microwaveprop.Valkey.Adapter)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue