test: cover Excache.StoreOpsRegressionTest

This commit is contained in:
dualfroz committed 2026-09-13 21:14:19 +02:00
1 parent 5839570919
commit fb085d0d90
1 file changed
+158
+158
View File
@@ -0,0 +1,158 @@
defmodule Excache.StoreOpsRegressionTest do
use Excache.Test.ServerCase, async: false
alias Excache.Store
describe "SMOVE must not drop the member when the destination has the wrong type" do
test "the member stays in the source and the caller is told about the type clash" do
source = "regr:smove:src"
dest = "regr:smove:dst-string"
assert {:ok, 1} = Store.sadd(source, ["alpha"])
assert :ok = Store.set(dest, "not-a-set", [])
assert {:error, :wrongtype} = Store.smove(source, dest, "alpha")
assert {:ok, ["alpha"]} = Store.smembers(source)
assert {:ok, "not-a-set"} = Store.get(dest)
end
test "the destination type is checked before the source is touched" do
source = "regr:smove:absent-src"
dest = "regr:smove:dst-list"
assert {:ok, 1} = Store.rpush(dest, ["element"])
assert {:error, :wrongtype} = Store.smove(source, dest, "alpha")
assert {:ok, ["element"]} = Store.lrange(dest, 0, -1)
end
test "a wrong type on the source is reported and the destination is left alone" do
source = "regr:smove:src-string"
dest = "regr:smove:dst-set"
assert :ok = Store.set(source, "not-a-set", [])
assert {:ok, 1} = Store.sadd(dest, ["beta"])
assert {:error, :wrongtype} = Store.smove(source, dest, "alpha")
assert {:ok, ["beta"]} = Store.smembers(dest)
end
test "a healthy move across two shards still transfers the member exactly once" do
source = "regr:smove:move-src"
dest = keyInOtherShard("regr:smove:move-dst-", source)
assert {:ok, 2} = Store.sadd(source, ["alpha", "gamma"])
assert {:ok, 1} = Store.sadd(dest, ["beta"])
assert {:ok, 1} = Store.smove(source, dest, "alpha")
assert {:ok, ["gamma"]} = Store.smembers(source)
assert {:ok, members} = Store.smembers(dest)
assert Enum.sort(members) == ["alpha", "beta"]
end
test "moving a member the source does not hold reports zero and adds nothing" do
source = "regr:smove:miss-src"
dest = "regr:smove:miss-dst"
assert {:ok, 1} = Store.sadd(source, ["alpha"])
assert {:ok, 1} = Store.sadd(dest, ["beta"])
assert {:ok, 0} = Store.smove(source, dest, "nothing-like-this")
assert {:ok, ["alpha"]} = Store.smembers(source)
assert {:ok, ["beta"]} = Store.smembers(dest)
end
test "SMOVE over RESP reports WRONGTYPE and keeps the member", %{conn: conn} do
assert {1, conn} = Client.command(conn, ["SADD", "regr:smove:resp-src", "alpha"])
assert {{:simple, "OK"}, conn} = Client.command(conn, ["SET", "regr:smove:resp-dst", "s"])
assert {{:error, "WRONGTYPE" <> _}, conn} =
Client.command(conn, [
"SMOVE",
"regr:smove:resp-src",
"regr:smove:resp-dst",
"alpha"
])
assert {["alpha"], _conn} = Client.command(conn, ["SMEMBERS", "regr:smove:resp-src"])
end
end
describe "RPOPLPUSH must not drop the element when the destination has the wrong type" do
test "the element stays at the tail of the source and the caller sees the error" do
source = "regr:rpoplpush:src"
dest = "regr:rpoplpush:dst-string"
assert {:ok, 2} = Store.rpush(source, ["head", "tail"])
assert :ok = Store.set(dest, "not-a-list", [])
assert {:error, :wrongtype} = Store.rpoplpush(source, dest)
assert {:ok, ["head", "tail"]} = Store.lrange(source, 0, -1)
assert {:ok, "not-a-list"} = Store.get(dest)
end
test "the destination type is checked before the source is popped" do
source = "regr:rpoplpush:absent-src"
dest = "regr:rpoplpush:dst-set"
assert {:ok, 1} = Store.sadd(dest, ["beta"])
assert {:error, :wrongtype} = Store.rpoplpush(source, dest)
assert {:ok, ["beta"]} = Store.smembers(dest)
end
test "a healthy rotation across two shards moves the tail to the head" do
source = "regr:rpoplpush:rot-src"
dest = keyInOtherShard("regr:rpoplpush:rot-dst-", source)
assert {:ok, 2} = Store.rpush(source, ["head", "tail"])
assert {:ok, 1} = Store.rpush(dest, ["existing"])
assert {:ok, "tail"} = Store.rpoplpush(source, dest)
assert {:ok, ["head"]} = Store.lrange(source, 0, -1)
assert {:ok, ["tail", "existing"]} = Store.lrange(dest, 0, -1)
end
test "an empty source yields nil and never creates the destination" do
source = "regr:rpoplpush:empty-src"
dest = "regr:rpoplpush:empty-dst"
assert {:ok, nil} = Store.rpoplpush(source, dest)
assert 0 == Store.exists([dest])
end
test "RPOPLPUSH over RESP reports WRONGTYPE and keeps the element", %{conn: conn} do
assert {1, conn} = Client.command(conn, ["RPUSH", "regr:rpoplpush:resp-src", "tail"])
assert {{:simple, "OK"}, conn} =
Client.command(conn, ["SET", "regr:rpoplpush:resp-dst", "s"])
assert {{:error, "WRONGTYPE" <> _}, conn} =
Client.command(conn, [
"RPOPLPUSH",
"regr:rpoplpush:resp-src",
"regr:rpoplpush:resp-dst"
])
assert {["tail"], _conn} =
Client.command(conn, ["LRANGE", "regr:rpoplpush:resp-src", "0", "-1"])
end
end
describe "LPOS with a nil count must not crash the shard" do
test "a nil count means the first match and the shard survives the call" do
key = "regr:lpos:nil-count"
assert {:ok, 4} = Store.rpush(key, ["a", "b", "c", "b"])
shardBefore = shardPid(key)
assert {:ok, 1} = Store.lpos(key, "b", 1, nil, 0)
assert shardPid(key) == shardBefore
assert Process.alive?(shardBefore)
assert {:ok, ["a", "b", "c", "b"]} = Store.lrange(key, 0, -1)
end
test "a nil count must not take the shard down and wipe its other keys" do
key = "regr:lpos:blast-radius"
neighbour = keyInSameShard("regr:lpos:neighbour-", key)