Skip to content

Commit fbeb1a0

Browse files
authored
test(timestamp): add unit tests for timestamp (#1216)
Signed-off-by: Sarthak Tyagi <[email protected]> Signed-off-by: Sarthak Tyagi <[email protected]>
1 parent ca24c84 commit fbeb1a0

File tree

2 files changed

+219
-1
lines changed

2 files changed

+219
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
9494
- Moved helpful references to Additional Context section and added clickable links.
9595
- Transformed `examples\tokens\custom_royalty_fee.py` to be an end-to-end example, that interacts with the Hedera network, rather than a static object demo.
9696
- Refactored `examples/tokens/custom_royalty_fee.py` by splitting monolithic function custom_royalty_fee_example() into modular functions create_royalty_fee_object(), create_token_with_fee(), verify_token_fee(), and main() to improve readability, cleaned up setup_client() (#1169)
97-
97+
- Added comprehensive unit tests for Timestamp class (#1158)
9898

9999

100100

tests/unit/timestamp_test.py

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
"""
2+
Unit tests for the Timestamp class.
3+
4+
These tests validate correctness, edge cases, precision handling,
5+
serialization, arithmetic, comparison, and time-based behavior to
6+
ensure robust coverage of timestamp functionality.
7+
"""
8+
9+
import time
10+
import pytest
11+
from datetime import datetime, timezone
12+
13+
from hiero_sdk_python.timestamp import Timestamp
14+
from hiero_sdk_python.hapi.services.timestamp_pb2 import Timestamp as TimestampProto
15+
16+
pytestmark = pytest.mark.unit
17+
18+
19+
# Constructor and basic tests
20+
21+
22+
def test_init_and_attributes():
23+
"""Verify that Timestamp initializes correctly with seconds and nanoseconds."""
24+
ts = Timestamp(10, 500)
25+
assert ts.seconds == 10
26+
assert ts.nanos == 500
27+
28+
29+
def test_eq_and_hash():
30+
"""Ensure equality and hashing behave correctly for Timestamp instances."""
31+
ts1 = Timestamp(10, 500)
32+
ts2 = Timestamp(10, 500)
33+
ts3 = Timestamp(11, 0)
34+
35+
assert ts1 == ts2
36+
assert ts1 != ts3
37+
assert hash(ts1) == hash(ts2)
38+
39+
40+
def test_str_representation_zero_padded():
41+
"""Ensure string representation is zero-padded to 9 nanoseconds digits."""
42+
ts = Timestamp(10, 5)
43+
assert str(ts) == "10.000000005"
44+
45+
46+
# from_date() tests
47+
48+
49+
@pytest.mark.parametrize(
50+
"value",
51+
[
52+
datetime(1970, 1, 1, tzinfo=timezone.utc),
53+
int(time.time()),
54+
"1970-01-01T00:00:00+00:00",
55+
],
56+
)
57+
def test_from_date_valid_inputs(value):
58+
"""Test from_date with valid inputs: datetime, int, and ISO-8601 string."""
59+
ts = Timestamp.from_date(value)
60+
assert isinstance(ts, Timestamp)
61+
62+
63+
def test_from_date_unix_epoch():
64+
"""Test from_date with the Unix epoch (0 seconds)."""
65+
dt = datetime(1970, 1, 1, tzinfo=timezone.utc)
66+
ts = Timestamp.from_date(dt)
67+
assert ts.seconds == 0
68+
assert ts.nanos == 0
69+
70+
71+
def test_from_date_max_microseconds():
72+
"""Test from_date with maximum microseconds to ensure nanos calculation is correct."""
73+
dt = datetime(2020, 1, 1, 0, 0, 0, 999999, tzinfo=timezone.utc)
74+
ts = Timestamp.from_date(dt)
75+
76+
expected = 999_999_000
77+
assert abs(ts.nanos - expected) < 1_000
78+
79+
@pytest.mark.parametrize("bad_input", [None, [], {}, 3.14])
80+
def test_from_date_invalid_type(bad_input):
81+
"""Ensure from_date raises ValueError for invalid input types."""
82+
with pytest.raises(ValueError, match="Invalid type for 'date'"):
83+
Timestamp.from_date(bad_input)
84+
85+
86+
# to_date() tests
87+
88+
89+
def test_to_date_returns_utc_datetime():
90+
"""Verify that to_date returns a UTC datetime with correct seconds and microseconds."""
91+
ts = Timestamp(10, 500_000_000)
92+
dt = ts.to_date()
93+
94+
assert isinstance(dt, datetime)
95+
assert dt.tzinfo == timezone.utc
96+
assert dt.second == 10
97+
assert dt.microsecond == 500_000
98+
99+
100+
def test_to_date_truncates_nanoseconds():
101+
"""Ensure that nanoseconds are truncated (not rounded) when converting to datetime."""
102+
ts = Timestamp(0, 123_456_789)
103+
dt = ts.to_date()
104+
assert dt.microsecond == 123_456
105+
106+
107+
def test_datetime_round_trip_preserves_microseconds():
108+
"""Verify that datetime -> Timestamp -> datetime preserves microsecond precision."""
109+
original = datetime.now(timezone.utc).replace(microsecond=654321)
110+
ts = Timestamp.from_date(original)
111+
result = ts.to_date()
112+
assert original.replace(microsecond=result.microsecond) == result
113+
114+
115+
# plus_nanos() tests
116+
117+
118+
def test_plus_nanos_simple_add():
119+
"""Test simple addition of nanoseconds without overflow."""
120+
ts = Timestamp(1, 100)
121+
new_ts = ts.plus_nanos(200)
122+
assert new_ts.seconds == 1
123+
assert new_ts.nanos == 300
124+
125+
126+
def test_plus_nanos_carry_over():
127+
"""Test addition of nanoseconds causing a carry-over into seconds."""
128+
ts = Timestamp(1, 900_000_000)
129+
new_ts = ts.plus_nanos(200_000_000)
130+
assert new_ts.seconds == 2
131+
assert new_ts.nanos == 100_000_000
132+
133+
134+
def test_plus_nanos_multiple_seconds():
135+
"""Test addition of nanoseconds resulting in multiple seconds overflow."""
136+
ts = Timestamp(1, 0)
137+
new_ts = ts.plus_nanos(3_000_000_000)
138+
assert new_ts.seconds == 4
139+
assert new_ts.nanos == 0
140+
141+
142+
def test_plus_nanos_zero():
143+
"""Test adding zero nanoseconds returns the same Timestamp instance."""
144+
ts = Timestamp(5, 123)
145+
new_ts = ts.plus_nanos(0)
146+
assert new_ts == ts
147+
148+
149+
# compare() tests
150+
151+
152+
def test_compare_equal():
153+
"""Verify compare returns 0 for equal Timestamps."""
154+
ts1 = Timestamp(10, 0)
155+
ts2 = Timestamp(10, 0)
156+
assert ts1.compare(ts2) == 0
157+
158+
159+
def test_compare_less_than():
160+
"""Verify compare returns -1 when first Timestamp is earlier."""
161+
ts1 = Timestamp(9, 0)
162+
ts2 = Timestamp(10, 0)
163+
assert ts1.compare(ts2) == -1
164+
165+
166+
def test_compare_greater_than():
167+
"""Verify compare returns 1 when first Timestamp is later."""
168+
ts1 = Timestamp(10, 1)
169+
ts2 = Timestamp(10, 0)
170+
assert ts1.compare(ts2) == 1
171+
172+
173+
# generate() tests
174+
175+
176+
def test_generate_without_jitter():
177+
"""Ensure generate without jitter produces a timestamp close to current time."""
178+
ts = Timestamp.generate(has_jitter=False)
179+
delta = abs(ts.to_date().timestamp() - time.time())
180+
181+
assert delta < 0.1
182+
183+
184+
185+
def test_generate_with_jitter():
186+
"""Verify that generated timestamps with jitter remain close to the system time within a safe tolerance."""
187+
ts = Timestamp.generate(has_jitter=True)
188+
delta = time.time() - ts.to_date().timestamp()
189+
190+
# Jitter is explicitly 3-8 seconds backward
191+
assert 3.0 <= delta <= 9.0
192+
193+
# Protobuf serialization tests
194+
195+
196+
def test_to_protobuf():
197+
"""Ensure Timestamp converts correctly to protobuf representation."""
198+
ts = Timestamp(10, 500)
199+
proto = ts._to_protobuf()
200+
assert isinstance(proto, TimestampProto)
201+
assert proto.seconds == 10
202+
assert proto.nanos == 500
203+
204+
205+
def test_from_protobuf():
206+
"""Ensure Timestamp can be created from a protobuf object."""
207+
proto = TimestampProto(seconds=10, nanos=500)
208+
ts = Timestamp._from_protobuf(proto)
209+
assert ts.seconds == 10
210+
assert ts.nanos == 500
211+
212+
213+
def test_protobuf_round_trip():
214+
"""Verify that protobuf serialization and deserialization preserves the original Timestamp."""
215+
original = Timestamp(123, 456)
216+
proto = original._to_protobuf()
217+
restored = Timestamp._from_protobuf(proto)
218+
assert original == restored

0 commit comments

Comments
 (0)