From fca687004a66d7f824c88e671cca3b4c15bc5aa1 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Wed, 25 Jan 2023 22:57:34 -0500 Subject: [PATCH] Start skeleton for streams commands --- fakeredis/_fakesocket.py | 2 + fakeredis/commands_mixins/streams_mixin.py | 3 + test/test_json/test_json.py | 63 ++++++++++++ test/test_json/test_json_commands.py | 106 +-------------------- test/test_mixins/test_streams_commands.py | 101 ++++++++++++++++++++ 5 files changed, 170 insertions(+), 105 deletions(-) create mode 100644 fakeredis/commands_mixins/streams_mixin.py create mode 100644 test/test_mixins/test_streams_commands.py diff --git a/fakeredis/_fakesocket.py b/fakeredis/_fakesocket.py index dab8e252..c0104a14 100644 --- a/fakeredis/_fakesocket.py +++ b/fakeredis/_fakesocket.py @@ -10,6 +10,7 @@ from .commands_mixins.server_mixin import ServerCommandsMixin from .commands_mixins.set_mixin import SetCommandsMixin from .commands_mixins.sortedset_mixin import SortedSetCommandsMixin +from .commands_mixins.streams_mixin import StreamsCommandsMixin from .commands_mixins.string_mixin import StringCommandsMixin from .commands_mixins.transactions_mixin import TransactionsCommandsMixin @@ -28,6 +29,7 @@ class FakeSocket( SetCommandsMixin, BitmapCommandsMixin, SortedSetCommandsMixin, + StreamsCommandsMixin, JSONCommandsMixin, ): diff --git a/fakeredis/commands_mixins/streams_mixin.py b/fakeredis/commands_mixins/streams_mixin.py new file mode 100644 index 00000000..09886e94 --- /dev/null +++ b/fakeredis/commands_mixins/streams_mixin.py @@ -0,0 +1,3 @@ +class StreamsCommandsMixin: + # Streams commands + pass diff --git a/test/test_json/test_json.py b/test/test_json/test_json.py index 66b90d75..7fc2b936 100644 --- a/test/test_json/test_json.py +++ b/test/test_json/test_json.py @@ -322,3 +322,66 @@ def test_decode_null(r: redis.Redis): def test_decode_response_disabaled_null(r: redis.Redis): assert r.json().get("abc") is None + + +def test_json_get_jset(r: redis.Redis) -> None: + assert r.json().set("foo", Path.root_path(), "bar", ) == 1 + assert "bar" == r.json().get("foo") + assert r.json().get("baz") is None + assert 1 == r.json().delete("foo") + assert r.exists("foo") == 0 + + +def test_nonascii_setgetdelete(r: redis.Redis) -> None: + assert r.json().set("not-ascii", Path.root_path(), "hyvää-élève", ) + assert "hyvää-élève" == r.json().get("not-ascii", no_escape=True, ) + assert 1 == r.json().delete("not-ascii") + assert r.exists("not-ascii") == 0 + + +def test_json_setbinarykey(r: redis.Redis) -> None: + data = {"hello": "world", b"some": "value"} + + with pytest.raises(TypeError): + r.json().set("some-key", Path.root_path(), data) + + assert r.json().set("some-key", Path.root_path(), data, decode_keys=True) + + +def test_set_file(r: redis.Redis) -> None: + # Standard Library Imports + import json + import tempfile + + obj = {"hello": "world"} + jsonfile = tempfile.NamedTemporaryFile(suffix=".json") + with open(jsonfile.name, "w+") as fp: + fp.write(json.dumps(obj)) + + no_json_file = tempfile.NamedTemporaryFile() + no_json_file.write(b"Hello World") + + assert r.json().set_file("test", Path.root_path(), jsonfile.name) + assert r.json().get("test") == obj + with pytest.raises(json.JSONDecodeError): + r.json().set_file("test2", Path.root_path(), no_json_file.name) + + +def test_set_path(r: redis.Redis) -> None: + # Standard Library Imports + import json + import tempfile + + root = tempfile.mkdtemp() + sub = tempfile.mkdtemp(dir=root) + jsonfile = tempfile.mktemp(suffix=".json", dir=sub) + no_json_file = tempfile.mktemp(dir=root) + + with open(jsonfile, "w+") as fp: + fp.write(json.dumps({"hello": "world"})) + with open(no_json_file, "a+") as fp: + fp.write("hello") + + result = {jsonfile: True, no_json_file: False} + assert r.json().set_path(Path.root_path(), root) == result + assert r.json().get(jsonfile.rsplit(".")[0]) == {"hello": "world"} diff --git a/test/test_json/test_json_commands.py b/test/test_json/test_json_commands.py index 68ef7de6..1d5568a6 100644 --- a/test/test_json/test_json_commands.py +++ b/test/test_json/test_json_commands.py @@ -2,13 +2,11 @@ from __future__ import annotations -from typing import (Any, Dict, List, Tuple, ) - import pytest import redis from redis import exceptions -from redis.commands.json.decoders import decode_list, unstring from redis.commands.json.path import Path +from typing import (Any, Dict, List, Tuple, ) json_tests = pytest.importorskip("jsonpath_ng") @@ -64,37 +62,6 @@ def json_data() -> Dict[str, Any]: } -def test_json_setbinarykey(r: redis.Redis) -> None: - data = {"hello": "world", b"some": "value"} - - with pytest.raises(TypeError): - r.json().set("some-key", Path.root_path(), data) - - result = r.json().set( - "some-key", - Path.root_path(), - data, - decode_keys=True, - ) - - assert result - - -def test_json_get_jset(r: redis.Redis) -> None: - assert r.json().set("foo", Path.root_path(), "bar", ) == 1 - assert "bar" == r.json().get("foo") - assert r.json().get("baz") is None - assert 1 == r.json().delete("foo") - assert r.exists("foo") == 0 - - -def test_nonascii_setgetdelete(r: redis.Redis) -> None: - assert r.json().set("not-ascii", Path.root_path(), "hyvää-élève", ) - assert "hyvää-élève" == r.json().get("not-ascii", no_escape=True, ) - assert 1 == r.json().delete("not-ascii") - assert r.exists("not-ascii") == 0 - - @pytest.mark.xfail def test_type(r: redis.Redis) -> None: r.json().set("1", Path.root_path(), 1, ) @@ -1077,74 +1044,3 @@ def test_arrindex_dollar(r: redis.Redis) -> None: # Test index of None scalar in single value assert r.json().arrindex("test_None", ".[0].arr", "None") == 1 assert r.json().arrindex("test_None", "..nested2_not_found.arr", "None") == 0 - - -# @pytest.mark.xfail -def test_decoders_and_unstring(): - assert unstring("4") == 4 - assert unstring("45.55") == 45.55 - assert unstring("hello world") == "hello world" - - assert decode_list(b"45.55") == 45.55 - assert decode_list("45.55") == 45.55 - assert decode_list(["hello", b"world"]) == ["hello", "world"] - - -# noinspection PyUnresolvedReferences -@pytest.mark.xfail -def test_custom_decoder(r: redis.Redis) -> None: - # Standard Library Imports - import json - - # Third-Party Imports - import orjson - - cj = r.json(encoder=orjson, decoder=orjson) - assert cj.set("foo", Path.root_path(), "bar") - assert "bar" == cj.get("foo") - assert cj.get("baz") is None - assert 1 == cj.delete("foo") - assert r.exists("foo") == 0 - assert not isinstance(cj.__encoder__, json.JSONEncoder) - assert not isinstance(cj.__decoder__, json.JSONDecoder) - - -# @pytest.mark.xfail -def test_set_file(r: redis.Redis) -> None: - # Standard Library Imports - import json - import tempfile - - obj = {"hello": "world"} - jsonfile = tempfile.NamedTemporaryFile(suffix=".json") - with open(jsonfile.name, "w+") as fp: - fp.write(json.dumps(obj)) - - no_json_file = tempfile.NamedTemporaryFile() - no_json_file.write(b"Hello World") - - assert r.json().set_file("test", Path.root_path(), jsonfile.name) - assert r.json().get("test") == obj - with pytest.raises(json.JSONDecodeError): - r.json().set_file("test2", Path.root_path(), no_json_file.name) - - -# @pytest.mark.xfail -def test_set_path(r: redis.Redis) -> None: - # Standard Library Imports - import json - import tempfile - - root = tempfile.mkdtemp() - sub = tempfile.mkdtemp(dir=root) - jsonfile = tempfile.mktemp(suffix=".json", dir=sub) - no_json_file = tempfile.mktemp(dir=root) - - with open(jsonfile, "w+") as fp: - fp.write(json.dumps({"hello": "world"})) - with open(no_json_file, "a+") as fp: - fp.write("hello") - - result = {jsonfile: True, no_json_file: False} - assert r.json().set_path(Path.root_path(), root) == result - assert r.json().get(jsonfile.rsplit(".")[0]) == {"hello": "world"} diff --git a/test/test_mixins/test_streams_commands.py b/test/test_mixins/test_streams_commands.py new file mode 100644 index 00000000..edf61bec --- /dev/null +++ b/test/test_mixins/test_streams_commands.py @@ -0,0 +1,101 @@ +import pytest +import re + +import redis + + +@pytest.mark.xfail +def test_xrevrange(r: redis.Redis): + stream = "stream" + message_id = r.xadd(stream, {"foo": "bar"}) + assert re.match(rb"[0-9]+\-[0-9]+", message_id) + + # explicit message id + message_id = b"9999999999999999999-0" + assert message_id == r.xadd(stream, {"foo": "bar"}, id=message_id) + + # with maxlen, the list evicts the first message + r.xadd(stream, {"foo": "bar"}, maxlen=2, approximate=False) + assert r.xlen(stream) == 2 + + +@pytest.mark.xfail +def test_xadd_nomkstream(r: redis.Redis): + # nomkstream option + stream = "stream" + r.xadd(stream, {"foo": "bar"}) + r.xadd(stream, {"some": "other"}, nomkstream=False) + assert r.xlen(stream) == 2 + r.xadd(stream, {"some": "other"}, nomkstream=True) + assert r.xlen(stream) == 3 + + +@pytest.mark.xfail +def test_xadd_minlen_and_limit(r: redis.Redis): + stream = "stream" + + r.xadd(stream, {"foo": "bar"}) + r.xadd(stream, {"foo": "bar"}) + r.xadd(stream, {"foo": "bar"}) + r.xadd(stream, {"foo": "bar"}) + + # Future self: No limits without approximate, according to the api + with pytest.raises(redis.ResponseError): + assert r.xadd(stream, {"foo": "bar"}, maxlen=3, approximate=False, limit=2) + + # limit can not be provided without maxlen or minid + with pytest.raises(redis.ResponseError): + assert r.xadd(stream, {"foo": "bar"}, limit=2) + + # maxlen with a limit + assert r.xadd(stream, {"foo": "bar"}, maxlen=3, approximate=True, limit=2) + r.delete(stream) + + # maxlen and minid can not be provided together + with pytest.raises(redis.DataError): + assert r.xadd(stream, {"foo": "bar"}, maxlen=3, minid="sometestvalue") + + # minid with a limit + m1 = r.xadd(stream, {"foo": "bar"}) + r.xadd(stream, {"foo": "bar"}) + r.xadd(stream, {"foo": "bar"}) + r.xadd(stream, {"foo": "bar"}) + assert r.xadd(stream, {"foo": "bar"}, approximate=True, minid=m1, limit=3) + + # pure minid + r.xadd(stream, {"foo": "bar"}) + r.xadd(stream, {"foo": "bar"}) + r.xadd(stream, {"foo": "bar"}) + m4 = r.xadd(stream, {"foo": "bar"}) + assert r.xadd(stream, {"foo": "bar"}, approximate=False, minid=m4) + + # minid approximate + r.xadd(stream, {"foo": "bar"}) + r.xadd(stream, {"foo": "bar"}) + m3 = r.xadd(stream, {"foo": "bar"}) + r.xadd(stream, {"foo": "bar"}) + assert r.xadd(stream, {"foo": "bar"}, approximate=True, minid=m3) + + +@pytest.mark.xfail +def test_xrevrange(r: redis.Redis): + stream = "stream" + m1 = r.xadd(stream, {"foo": "bar"}) + m2 = r.xadd(stream, {"foo": "bar"}) + m3 = r.xadd(stream, {"foo": "bar"}) + m4 = r.xadd(stream, {"foo": "bar"}) + + def get_ids(results): + return [result[0] for result in results] + + results = r.xrevrange(stream, max=m4) + assert get_ids(results) == [m4, m3, m2, m1] + + results = r.xrevrange(stream, max=m3, min=m2) + assert get_ids(results) == [m3, m2] + + results = r.xrevrange(stream, min=m3) + assert get_ids(results) == [m4, m3] + + results = r.xrevrange(stream, min=m2, count=1) + assert get_ids(results) == [m4]