Skip to content

Commit b1bddb7

Browse files
Copilots3rius
andauthored
Expand test coverage and fix incorrect stub type annotations (#49)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: s3rius <18153319+s3rius@users.noreply.github.com>
1 parent 1e67c68 commit b1bddb7

9 files changed

Lines changed: 337 additions & 6 deletions

File tree

python/natsrpy/_natsrpy_rs/js/kv.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,10 @@ class KVConfig:
122122
"""
123123

124124
bucket: str
125-
description: str
125+
description: str | None
126126
max_value_size: int | None
127127
history: int | None
128-
max_age: float | None
128+
max_age: timedelta | None
129129
max_bytes: int | None
130130
storage: StorageType | None
131131
num_replicas: int | None
@@ -135,7 +135,7 @@ class KVConfig:
135135
mirror_direct: bool | None
136136
compression: bool | None
137137
placement: Placement | None
138-
limit_markers: float | None
138+
limit_markers: timedelta | None
139139

140140
def __new__(
141141
cls,

python/natsrpy/_natsrpy_rs/js/stream.pyi

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,6 @@ class SubjectTransform:
132132
source: str
133133
destination: str
134134

135-
def __new__(cls, source: str, destination: str) -> Self: ...
136-
137135
@final
138136
class Source:
139137
"""Configuration for a stream source or mirror origin.
@@ -454,7 +452,7 @@ class StreamInfo:
454452
"""
455453

456454
config: StreamConfig
457-
created: float
455+
created: int
458456
state: StreamState
459457
cluster: ClusterInfo | None
460458
mirror: SourceInfo | None

python/tests/test_consumers.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,43 @@ async def test_push_consumer_config_properties() -> None:
304304
assert config.description == "push test"
305305
assert config.ack_policy == AckPolicy.EXPLICIT
306306
assert config.deliver_policy == DeliverPolicy.NEW
307+
308+
309+
async def test_pull_consumer_consume_context_manager(js: JetStream) -> None:
310+
stream_name = f"test-pullctx-{uuid.uuid4().hex[:8]}"
311+
subj = f"{stream_name}.data"
312+
config = StreamConfig(name=stream_name, subjects=[f"{stream_name}.>"])
313+
stream = await js.streams.create(config)
314+
try:
315+
await js.publish(subj, b"consume-msg", wait=True)
316+
consumer = await stream.consumers.create(
317+
PullConsumerConfig(name=f"consumer-{uuid.uuid4().hex[:8]}"),
318+
)
319+
async with consumer.consume() as fetcher:
320+
msg = await anext(fetcher)
321+
assert msg.payload == b"consume-msg"
322+
await msg.ack()
323+
finally:
324+
await js.streams.delete(stream_name)
325+
326+
327+
async def test_push_consumer_consume_context_manager(js: JetStream) -> None:
328+
stream_name = f"test-pushctx-{uuid.uuid4().hex[:8]}"
329+
subj = f"{stream_name}.data"
330+
config = StreamConfig(name=stream_name, subjects=[f"{stream_name}.>"])
331+
stream = await js.streams.create(config)
332+
try:
333+
await js.publish(subj, b"push-consume-msg", wait=True)
334+
deliver_subj = uuid.uuid4().hex
335+
consumer = await stream.consumers.create(
336+
PushConsumerConfig(
337+
deliver_subject=deliver_subj,
338+
name=f"push-{uuid.uuid4().hex[:8]}",
339+
),
340+
)
341+
async with consumer.consume() as msgs:
342+
msg = await anext(msgs)
343+
assert msg.payload == b"push-consume-msg"
344+
await msg.ack()
345+
finally:
346+
await js.streams.delete(stream_name)

python/tests/test_jetstream.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@ async def test_jetstream_publish_with_headers(js: JetStream) -> None:
4848
await js.publish(subj, b"with-headers", headers={"x-test": "value"}, wait=True)
4949
finally:
5050
await js.streams.delete(stream_name)
51+
52+
53+
async def test_jetstream_has_counters_manager(js: JetStream) -> None:
54+
assert js.counters is not None

python/tests/test_js_message_acks.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import uuid
2+
from datetime import datetime
23

34
from natsrpy.js import (
45
AckPolicy,
@@ -166,3 +167,107 @@ async def test_message_headers_empty(js: JetStream) -> None:
166167
assert isinstance(messages[0].headers, dict)
167168
finally:
168169
await js.streams.delete(stream_name)
170+
171+
172+
async def test_message_stream_sequence_and_consumer_sequence(js: JetStream) -> None:
173+
stream_name = f"test-seqs-{uuid.uuid4().hex[:8]}"
174+
subj = f"{stream_name}.data"
175+
config = StreamConfig(name=stream_name, subjects=[f"{stream_name}.>"])
176+
stream = await js.streams.create(config)
177+
try:
178+
await js.publish(subj, b"seq-msg", wait=True)
179+
consumer = await stream.consumers.create(
180+
PullConsumerConfig(name=f"consumer-{uuid.uuid4().hex[:8]}"),
181+
)
182+
messages = await consumer.fetch(max_messages=1, timeout=5.0)
183+
assert len(messages) == 1
184+
msg = messages[0]
185+
assert isinstance(msg.stream_sequence, int)
186+
assert msg.stream_sequence >= 1
187+
assert isinstance(msg.consumer_sequence, int)
188+
assert msg.consumer_sequence >= 1
189+
finally:
190+
await js.streams.delete(stream_name)
191+
192+
193+
async def test_message_consumer_and_stream_names(js: JetStream) -> None:
194+
stream_name = f"test-names-{uuid.uuid4().hex[:8]}"
195+
subj = f"{stream_name}.data"
196+
config = StreamConfig(name=stream_name, subjects=[f"{stream_name}.>"])
197+
stream = await js.streams.create(config)
198+
try:
199+
await js.publish(subj, b"names-msg", wait=True)
200+
consumer_name = f"consumer-{uuid.uuid4().hex[:8]}"
201+
consumer = await stream.consumers.create(
202+
PullConsumerConfig(name=consumer_name),
203+
)
204+
messages = await consumer.fetch(max_messages=1, timeout=5.0)
205+
assert len(messages) == 1
206+
msg = messages[0]
207+
assert msg.stream == stream_name
208+
assert msg.consumer == consumer_name
209+
finally:
210+
await js.streams.delete(stream_name)
211+
212+
213+
async def test_message_delivered_and_pending(js: JetStream) -> None:
214+
stream_name = f"test-delpend-{uuid.uuid4().hex[:8]}"
215+
subj = f"{stream_name}.data"
216+
config = StreamConfig(name=stream_name, subjects=[f"{stream_name}.>"])
217+
stream = await js.streams.create(config)
218+
try:
219+
await js.publish(subj, b"msg-1", wait=True)
220+
await js.publish(subj, b"msg-2", wait=True)
221+
consumer = await stream.consumers.create(
222+
PullConsumerConfig(name=f"consumer-{uuid.uuid4().hex[:8]}"),
223+
)
224+
messages = await consumer.fetch(max_messages=1, timeout=5.0)
225+
assert len(messages) == 1
226+
msg = messages[0]
227+
assert isinstance(msg.delivered, int)
228+
assert msg.delivered >= 1
229+
assert isinstance(msg.pending, int)
230+
assert msg.pending >= 0
231+
await msg.ack()
232+
finally:
233+
await js.streams.delete(stream_name)
234+
235+
236+
async def test_message_published_timestamp(js: JetStream) -> None:
237+
stream_name = f"test-pub-ts-{uuid.uuid4().hex[:8]}"
238+
subj = f"{stream_name}.data"
239+
config = StreamConfig(name=stream_name, subjects=[f"{stream_name}.>"])
240+
stream = await js.streams.create(config)
241+
try:
242+
await js.publish(subj, b"ts-msg", wait=True)
243+
consumer = await stream.consumers.create(
244+
PullConsumerConfig(name=f"consumer-{uuid.uuid4().hex[:8]}"),
245+
)
246+
messages = await consumer.fetch(max_messages=1, timeout=5.0)
247+
assert len(messages) == 1
248+
msg = messages[0]
249+
assert isinstance(msg.published, datetime)
250+
finally:
251+
await js.streams.delete(stream_name)
252+
253+
254+
async def test_message_length_and_dunder_len(js: JetStream) -> None:
255+
stream_name = f"test-msglen-{uuid.uuid4().hex[:8]}"
256+
subj = f"{stream_name}.data"
257+
config = StreamConfig(name=stream_name, subjects=[f"{stream_name}.>"])
258+
stream = await js.streams.create(config)
259+
try:
260+
payload = b"length-test-payload"
261+
await js.publish(subj, payload, wait=True)
262+
consumer = await stream.consumers.create(
263+
PullConsumerConfig(name=f"consumer-{uuid.uuid4().hex[:8]}"),
264+
)
265+
messages = await consumer.fetch(max_messages=1, timeout=5.0)
266+
assert len(messages) == 1
267+
msg = messages[0]
268+
assert isinstance(msg.length, int)
269+
assert msg.length >= len(payload)
270+
assert len(msg) == msg.length
271+
await msg.ack()
272+
finally:
273+
await js.streams.delete(stream_name)

python/tests/test_kv.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,3 +831,41 @@ async def test_kv_operation_equality() -> None:
831831
assert KVOperation.Put != KVOperation.Delete
832832
assert KVOperation.Put != KVOperation.Purge
833833
assert KVOperation.Delete != KVOperation.Purge
834+
835+
836+
async def test_kv_entry_seen_current(js: JetStream) -> None:
837+
bucket = f"test-kv-seen-{uuid.uuid4().hex[:8]}"
838+
config = KVConfig(bucket=bucket)
839+
kv = await js.kv.create(config)
840+
try:
841+
# Create watcher on empty bucket, then put — the received entry is a live update
842+
watcher = await kv.watch_all()
843+
await kv.put("mykey", b"value1")
844+
entry = await asyncio.wait_for(anext(watcher), timeout=5.0)
845+
assert isinstance(entry.seen_current, bool)
846+
# First live update on an empty bucket is marked as seen_current
847+
assert entry.seen_current is True
848+
finally:
849+
await js.kv.delete(bucket)
850+
851+
852+
async def test_kv_config_max_age_timedelta(js: JetStream) -> None:
853+
bucket = f"test-kv-maxage-{uuid.uuid4().hex[:8]}"
854+
max_age = timedelta(hours=1)
855+
config = KVConfig(bucket=bucket, max_age=max_age)
856+
assert config.max_age == max_age
857+
kv = await js.kv.create(config)
858+
try:
859+
assert kv is not None
860+
finally:
861+
await js.kv.delete(bucket)
862+
863+
864+
async def test_kv_config_description_none() -> None:
865+
config = KVConfig(bucket="test-desc-none")
866+
assert config.description is None
867+
868+
869+
async def test_kv_config_description_set() -> None:
870+
config = KVConfig(bucket="test-desc-set", description="my description")
871+
assert config.description == "my description"

python/tests/test_nats_client.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,78 @@ async def test_nats_connection_failure() -> None:
9292
nats = Nats(addrs=["localhost:19999"])
9393
with pytest.raises(Exception):
9494
await nats.startup()
95+
96+
97+
async def test_nats_addr_property(nats_url: str) -> None:
98+
nats = Nats(addrs=[nats_url])
99+
assert nats.addr == [nats_url]
100+
101+
102+
async def test_nats_addr_default() -> None:
103+
nats = Nats()
104+
assert nats.addr == ["nats://localhost:4222"]
105+
106+
107+
async def test_nats_token_property() -> None:
108+
nats = Nats(token="secret-token") # noqa: S106
109+
assert nats.token == "secret-token" # noqa: S105
110+
111+
112+
async def test_nats_token_default() -> None:
113+
nats = Nats()
114+
assert nats.token is None
115+
116+
117+
async def test_nats_nkey_property() -> None:
118+
nats = Nats()
119+
assert nats.nkey is None
120+
121+
122+
async def test_nats_user_and_pass_property() -> None:
123+
nats = Nats(user_and_pass=("user", "pass"))
124+
assert nats.user_and_pass == ("user", "pass")
125+
126+
127+
async def test_nats_user_and_pass_default() -> None:
128+
nats = Nats()
129+
assert nats.user_and_pass is None
130+
131+
132+
async def test_nats_custom_inbox_prefix_property() -> None:
133+
nats = Nats(custom_inbox_prefix="_custom")
134+
assert nats.custom_inbox_prefix == "_custom"
135+
136+
137+
async def test_nats_custom_inbox_prefix_default() -> None:
138+
nats = Nats()
139+
assert nats.custom_inbox_prefix is None
140+
141+
142+
async def test_nats_read_buffer_capacity_property() -> None:
143+
nats = Nats(read_buffer_capacity=1024)
144+
assert nats.read_buffer_capacity == 1024
145+
146+
147+
async def test_nats_read_buffer_capacity_default() -> None:
148+
nats = Nats()
149+
assert nats.read_buffer_capacity == 65535
150+
151+
152+
async def test_nats_sender_capacity_property() -> None:
153+
nats = Nats(sender_capacity=64)
154+
assert nats.sender_capacity == 64
155+
156+
157+
async def test_nats_sender_capacity_default() -> None:
158+
nats = Nats()
159+
assert nats.sender_capacity == 128
160+
161+
162+
async def test_nats_max_reconnects_property() -> None:
163+
nats = Nats(max_reconnects=5)
164+
assert nats.max_reconnects == 5
165+
166+
167+
async def test_nats_max_reconnects_default() -> None:
168+
nats = Nats()
169+
assert nats.max_reconnects is None

python/tests/test_streams.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,63 @@ async def test_stream_state_after_publish(js: JetStream) -> None:
252252
assert info.state.bytes > 0
253253
finally:
254254
await js.streams.delete(name)
255+
256+
257+
async def test_stream_name_property(js: JetStream) -> None:
258+
name = f"test-sname-{uuid.uuid4().hex[:8]}"
259+
config = StreamConfig(name=name, subjects=[f"{name}.>"])
260+
stream = await js.streams.create(config)
261+
try:
262+
assert stream.name == name
263+
finally:
264+
await js.streams.delete(name)
265+
266+
267+
async def test_stream_info_created_field(js: JetStream) -> None:
268+
name = f"test-screated-{uuid.uuid4().hex[:8]}"
269+
config = StreamConfig(name=name, subjects=[f"{name}.>"])
270+
stream = await js.streams.create(config)
271+
try:
272+
info = await stream.get_info()
273+
assert isinstance(info.created, (int, float))
274+
assert info.created > 0
275+
finally:
276+
await js.streams.delete(name)
277+
278+
279+
async def test_stream_state_subjects_count(js: JetStream) -> None:
280+
name = f"test-ssubj-{uuid.uuid4().hex[:8]}"
281+
config = StreamConfig(name=name, subjects=[f"{name}.>"])
282+
stream = await js.streams.create(config)
283+
try:
284+
await js.publish(f"{name}.a", b"msg-a", wait=True)
285+
await js.publish(f"{name}.b", b"msg-b", wait=True)
286+
info = await stream.get_info()
287+
assert info.state.subjects_count == 2
288+
finally:
289+
await js.streams.delete(name)
290+
291+
292+
async def test_stream_state_timestamps(js: JetStream) -> None:
293+
name = f"test-sts-{uuid.uuid4().hex[:8]}"
294+
subj = f"{name}.data"
295+
config = StreamConfig(name=name, subjects=[f"{name}.>"])
296+
stream = await js.streams.create(config)
297+
try:
298+
await js.publish(subj, b"ts-msg", wait=True)
299+
info = await stream.get_info()
300+
assert info.state.first_timestamp >= 0
301+
assert info.state.last_timestamp >= 0
302+
finally:
303+
await js.streams.delete(name)
304+
305+
306+
async def test_stream_state_consumer_count(js: JetStream) -> None:
307+
name = f"test-scnt-{uuid.uuid4().hex[:8]}"
308+
config = StreamConfig(name=name, subjects=[f"{name}.>"])
309+
stream = await js.streams.create(config)
310+
try:
311+
info = await stream.get_info()
312+
assert info.state.consumer_count == 0
313+
finally:
314+
await js.streams.delete(name)

0 commit comments

Comments
 (0)