@@ -3932,6 +3932,58 @@ async def test_xinfo_stream_full(self, r: redis.Redis):
39323932 consumer = info ["groups" ][0 ]["consumers" ][0 ]
39333933 assert isinstance (consumer , dict )
39343934
3935+ @skip_if_server_version_lt ("8.5.0" )
3936+ async def test_xinfo_stream_idempotent_fields (self , r : redis .Redis ):
3937+ stream = "stream"
3938+
3939+ # Create stream with regular entry
3940+ await r .xadd (stream , {"foo" : "bar" })
3941+ info = await r .xinfo_stream (stream )
3942+
3943+ # Verify new idempotent producer fields are present with default values
3944+ assert "idmp-duration" in info
3945+ assert "idmp-maxsize" in info
3946+ assert "pids-tracked" in info
3947+ assert "iids-tracked" in info
3948+ assert "iids-added" in info
3949+ assert "iids-duplicates" in info
3950+
3951+ # Default values (before any idempotent entries)
3952+ assert info ["pids-tracked" ] == 0
3953+ assert info ["iids-tracked" ] == 0
3954+ assert info ["iids-added" ] == 0
3955+ assert info ["iids-duplicates" ] == 0
3956+
3957+ # Add idempotent entry
3958+ await r .xadd (stream , {"field1" : "value1" }, idmpauto = "producer1" )
3959+ info = await r .xinfo_stream (stream )
3960+
3961+ # After adding idempotent entry
3962+ assert info ["pids-tracked" ] == 1 # One producer tracked
3963+ assert info ["iids-tracked" ] == 1 # One iid tracked
3964+ assert info ["iids-added" ] == 1 # One idempotent entry added
3965+ assert info ["iids-duplicates" ] == 0 # No duplicates yet
3966+
3967+ # Add duplicate entry
3968+ await r .xadd (stream , {"field1" : "value1" }, idmpauto = "producer1" )
3969+ info = await r .xinfo_stream (stream )
3970+
3971+ # After duplicate
3972+ assert info ["pids-tracked" ] == 1 # Still one producer
3973+ assert info ["iids-tracked" ] == 1 # Still one iid (duplicate doesn't add new)
3974+ assert info ["iids-added" ] == 1 # Still one unique entry
3975+ assert info ["iids-duplicates" ] == 1 # One duplicate detected
3976+
3977+ # Add entry from different producer
3978+ await r .xadd (stream , {"field2" : "value2" }, idmpauto = "producer2" )
3979+ info = await r .xinfo_stream (stream )
3980+
3981+ # After second producer
3982+ assert info ["pids-tracked" ] == 2 # Two producers tracked
3983+ assert info ["iids-tracked" ] == 2 # Two iids tracked
3984+ assert info ["iids-added" ] == 2 # Two unique entries
3985+ assert info ["iids-duplicates" ] == 1 # Still one duplicate
3986+
39353987 @skip_if_server_version_lt ("5.0.0" )
39363988 async def test_xlen (self , r : redis .Redis ):
39373989 stream = "stream"
@@ -4660,6 +4712,175 @@ async def test_xadd_with_options(self, r: redis.Redis):
46604712 with pytest .raises (redis .DataError ):
46614713 await r .xadd (stream , {"foo" : "bar" }, ref_policy = "INVALID" )
46624714
4715+ @skip_if_server_version_lt ("8.5.0" )
4716+ async def test_xadd_idmpauto (self , r : redis .Redis ):
4717+ stream = "stream"
4718+
4719+ # XADD with IDMPAUTO - first write
4720+ message_id1 = await r .xadd (stream , {"field1" : "value1" }, idmpauto = "producer1" )
4721+
4722+ # Test XADD with IDMPAUTO - duplicate write returns same ID
4723+ message_id2 = await r .xadd (stream , {"field1" : "value1" }, idmpauto = "producer1" )
4724+ assert message_id1 == message_id2
4725+
4726+ # Test XADD with IDMPAUTO - different content creates new entry
4727+ message_id3 = await r .xadd (stream , {"field1" : "value2" }, idmpauto = "producer1" )
4728+ assert message_id3 != message_id1
4729+
4730+ # Test XADD with IDMPAUTO - different producer creates new entry
4731+ message_id4 = await r .xadd (stream , {"field1" : "value1" }, idmpauto = "producer2" )
4732+ assert message_id4 != message_id1
4733+
4734+ # Verify stream has 3 entries (2 unique from producer1, 1 from producer2)
4735+ assert await r .xlen (stream ) == 3
4736+
4737+ @skip_if_server_version_lt ("8.5.0" )
4738+ async def test_xadd_idmp (self , r : redis .Redis ):
4739+ stream = "stream"
4740+
4741+ # Test XADD with IDMP - first write
4742+ message_id1 = await r .xadd (
4743+ stream , {"field1" : "value1" }, idmp = ("producer1" , b"msg1" )
4744+ )
4745+
4746+ # Test XADD with IDMP - duplicate write returns same ID
4747+ message_id2 = await r .xadd (
4748+ stream , {"field1" : "value1" }, idmp = ("producer1" , b"msg1" )
4749+ )
4750+ assert message_id1 == message_id2
4751+
4752+ # Test XADD with IDMP - different iid creates new entry
4753+ message_id3 = await r .xadd (
4754+ stream , {"field1" : "value1" }, idmp = ("producer1" , b"msg2" )
4755+ )
4756+ assert message_id3 != message_id1
4757+
4758+ # Test XADD with IDMP - different producer creates new entry
4759+ message_id4 = await r .xadd (
4760+ stream , {"field1" : "value1" }, idmp = ("producer2" , b"msg1" )
4761+ )
4762+ assert message_id4 != message_id1
4763+
4764+ # Test XADD with IDMP - shorter binary iid
4765+ await r .xadd (stream , {"field1" : "value1" }, idmp = ("producer1" , b"\x01 " ))
4766+
4767+ # Verify stream has 4 entries
4768+ assert await r .xlen (stream ) == 4
4769+
4770+ @skip_if_server_version_lt ("8.5.0" )
4771+ async def test_xadd_idmp_validation (self , r : redis .Redis ):
4772+ stream = "stream"
4773+
4774+ # Test error: both idmpauto and idmp specified
4775+ with pytest .raises (redis .DataError ):
4776+ await r .xadd (
4777+ stream ,
4778+ {"foo" : "bar" },
4779+ idmpauto = "producer1" ,
4780+ idmp = ("producer1" , b"msg1" ),
4781+ )
4782+
4783+ # Test error: idmpauto with explicit id
4784+ with pytest .raises (redis .DataError ):
4785+ await r .xadd (
4786+ stream , {"foo" : "bar" }, id = "1234567890-0" , idmpauto = "producer1"
4787+ )
4788+
4789+ # Test error: idmp with explicit id
4790+ with pytest .raises (redis .DataError ):
4791+ await r .xadd (
4792+ stream , {"foo" : "bar" }, id = "1234567890-0" , idmp = ("producer1" , b"msg1" )
4793+ )
4794+
4795+ # Test error: idmp not a tuple
4796+ with pytest .raises (redis .DataError ):
4797+ await r .xadd (stream , {"foo" : "bar" }, idmp = "invalid" )
4798+
4799+ # Test error: idmp tuple with wrong number of elements
4800+ with pytest .raises (redis .DataError ):
4801+ await r .xadd (stream , {"foo" : "bar" }, idmp = ("producer1" ,))
4802+
4803+ # Test error: idmp tuple with wrong number of elements
4804+ with pytest .raises (redis .DataError ):
4805+ await r .xadd (stream , {"foo" : "bar" }, idmp = ("producer1" , b"msg1" , "extra" ))
4806+
4807+ @skip_if_server_version_lt ("8.5.0" )
4808+ async def test_xcfgset_idmp_duration (self , r : redis .Redis ):
4809+ stream = "stream"
4810+
4811+ # Create stream first
4812+ await r .xadd (stream , {"foo" : "bar" })
4813+
4814+ # Test XCFGSET with IDMP-DURATION only
4815+ assert await r .xcfgset (stream , idmp_duration = 120 ) == b"OK"
4816+
4817+ # Test with minimum value
4818+ assert await r .xcfgset (stream , idmp_duration = 1 ) == b"OK"
4819+
4820+ # Test with maximum value
4821+ assert await r .xcfgset (stream , idmp_duration = 300 ) == b"OK"
4822+
4823+ @skip_if_server_version_lt ("8.5.0" )
4824+ async def test_xcfgset_idmp_maxsize (self , r : redis .Redis ):
4825+ stream = "stream"
4826+
4827+ # Create stream first
4828+ await r .xadd (stream , {"foo" : "bar" })
4829+
4830+ # Test XCFGSET with IDMP-MAXSIZE only
4831+ assert await r .xcfgset (stream , idmp_maxsize = 5000 ) == b"OK"
4832+
4833+ # Test with minimum value
4834+ assert await r .xcfgset (stream , idmp_maxsize = 1 ) == b"OK"
4835+
4836+ # Test with maximum value
4837+ assert await r .xcfgset (stream , idmp_maxsize = 10000 ) == b"OK"
4838+
4839+ @skip_if_server_version_lt ("8.5.0" )
4840+ async def test_xcfgset_both_parameters (self , r : redis .Redis ):
4841+ stream = "stream"
4842+
4843+ # Create stream first
4844+ await r .xadd (stream , {"foo" : "bar" })
4845+
4846+ # Test XCFGSET with both IDMP-DURATION and IDMP-MAXSIZE
4847+ assert await r .xcfgset (stream , idmp_duration = 120 , idmp_maxsize = 5000 ) == b"OK"
4848+
4849+ # Test with different values
4850+ assert await r .xcfgset (stream , idmp_duration = 60 , idmp_maxsize = 10000 ) == b"OK"
4851+
4852+ @skip_if_server_version_lt ("8.5.0" )
4853+ async def test_xcfgset_validation (self , r : redis .Redis ):
4854+ stream = "stream"
4855+
4856+ # Test error: no parameters provided
4857+ with pytest .raises (redis .DataError ):
4858+ await r .xcfgset (stream )
4859+
4860+ # Test error: idmp_duration too small
4861+ with pytest .raises (redis .DataError ):
4862+ await r .xcfgset (stream , idmp_duration = 0 )
4863+
4864+ # Test error: idmp_duration too large
4865+ with pytest .raises (redis .DataError ):
4866+ await r .xcfgset (stream , idmp_duration = 301 )
4867+
4868+ # Test error: idmp_duration not an integer
4869+ with pytest .raises (redis .DataError ):
4870+ await r .xcfgset (stream , idmp_duration = "invalid" )
4871+
4872+ # Test error: idmp_maxsize too small
4873+ with pytest .raises (redis .DataError ):
4874+ await r .xcfgset (stream , idmp_maxsize = 0 )
4875+
4876+ # Test error: idmp_maxsize too large
4877+ with pytest .raises (redis .DataError ):
4878+ await r .xcfgset (stream , idmp_maxsize = 1000001 )
4879+
4880+ # Test error: idmp_maxsize not an integer
4881+ with pytest .raises (redis .DataError ):
4882+ await r .xcfgset (stream , idmp_maxsize = "invalid" )
4883+
46634884 @pytest .mark .onlynoncluster
46644885 async def test_bitfield_operations (self , r : redis .Redis ):
46654886 # comments show affected bits
0 commit comments