@@ -192,87 +192,145 @@ def test_unsafe_decode_token():
192192 assert result ["name" ] == "John Doe"
193193
194194
195- def test_timestamp_conversion_with_object ():
196- """
197- Testing timestamp_conversion works with objects that have seconds and nanoseconds attributes.
198- """
199-
200- class Timestamp :
201- def __init__ (self , seconds , nanoseconds ):
202- self .seconds = seconds
203- self .nanoseconds = nanoseconds
204-
205- test_cases = [
206- (1672578896 , 123456789 ),
207- (1672578896 , 0 ),
208- (1672578896 , 1_500_000_000 ),
209- ]
210-
211- for seconds , nanoseconds in test_cases :
212- timestamp_obj = Timestamp (seconds = seconds , nanoseconds = nanoseconds )
213- result = timestamp_conversion (timestamp_obj )
214- expected = _dt .datetime .fromtimestamp (
215- seconds + nanoseconds / 1_000_000_000 , tz = _dt .timezone .utc
216- )
217- assert result == expected
218- assert result .tzinfo == _dt .timezone .utc
219-
220-
221- def test_timestamp_conversion_with_dict ():
222- """
223- Testing timestamp_conversion works with dict objects containing seconds and nanoseconds keys.
224- """
225- test_cases = [
226- (1687256122 , 396358000 ),
195+ # Helper class for timestamp conversion tests
196+ class _Timestamp :
197+ """Helper class to simulate Firebase Timestamp objects."""
198+
199+ def __init__ (self , seconds : int , nanoseconds : int ):
200+ self .seconds = seconds
201+ self .nanoseconds = nanoseconds
202+
203+
204+ def _assert_utc_datetime (dt : _dt .datetime ) -> None :
205+ """Helper to assert datetime is UTC timezone-aware."""
206+ assert dt .tzinfo == _dt .timezone .utc
207+
208+
209+ @pytest .mark .parametrize (
210+ "seconds,nanoseconds,expected_str" ,
211+ [
212+ (0 , 0 , "1970-01-01T00:00:00.000000+00:00" ), # The epoch
213+ (1 , 0 , "1970-01-01T00:00:01.000000+00:00" ), # 1 second after epoch
214+ (0 , 1 , "1970-01-01T00:00:00.000000+00:00" ), # 1 nanosecond (truncated)
215+ (0 , 999_999 , "1970-01-01T00:00:00.000999+00:00" ), # < 1 microsecond
216+ (0 , 1_000 , "1970-01-01T00:00:00.000001+00:00" ), # 1 microsecond
217+ (0 , 999_999_999 , "1970-01-01T00:00:00.999999+00:00" ), # almost 1 second
218+ (0 , 1_000_000_000 , "1970-01-01T00:00:01.000000+00:00" ), # exactly 1 second (carries)
219+ (123456 , 1_500_000_000 , "1970-01-02T10:17:37.500000+00:00" ), # overflow with remainder
220+ (1672578896 , 123456789 , "2023-01-01T13:14:56.123456+00:00" ), # real-world example
221+ (- 1 , 0 , "1969-12-31T23:59:59.000000+00:00" ), # 1 second before epoch
222+ (- 1 , 500_000_000 , "1969-12-31T23:59:59.500000+00:00" ), # negative seconds, positive nsec
223+ ],
224+ )
225+ def test_timestamp_conversion_object_known_cases (seconds : int , nanoseconds : int , expected_str : str ):
226+ """Test timestamp_conversion with objects using known correct expected values."""
227+ timestamp_obj = _Timestamp (seconds = seconds , nanoseconds = nanoseconds )
228+ result = timestamp_conversion (timestamp_obj )
229+ expected = _dt .datetime .fromisoformat (expected_str )
230+ assert result == expected
231+ _assert_utc_datetime (result )
232+
233+
234+ @pytest .mark .parametrize (
235+ "seconds,nanoseconds" ,
236+ [
237+ (123456 , - 500_000_000 ), # negative nanoseconds
238+ (123456 , 2_999_999_999 ), # large nanoseconds, multiple second carry
239+ (2_147_483_647 , 0 ), # max 32-bit int
240+ (- 2 , 2_000_000_000 ), # negative seconds, nanoseconds w/ carry
241+ (0 , - 1 ), # negative nanoseconds underflow
242+ (0 , - 1_000_000_000 ), # underflow full second
243+ (0 , - 1_500_000_000 ), # underflow more than one second
244+ (1687256122 , 396358000 ), # nominal case
227245 (1687256122 , 0 ),
228- ]
246+ (0 , 0 ),
247+ (0 , 1 ),
248+ (0 , 999_999_999 ),
249+ (1687256122 , 2_000_000_000 ),
250+ (1687256122 , - 500_000_000 ),
251+ (- 1 , 999_999_999 ),
252+ (- 1 , 500_000_000 ),
253+ (- 2 , 2_000_000_000 ),
254+ (2_147_483_647 , 999_999_999 ),
255+ (- 2_147_483_648 , 0 ),
256+ (0 , - 2_000_000_000 ),
257+ (0 , 2_000_000_000 ),
258+ ],
259+ )
260+ def test_timestamp_conversion_object_dict_consistency (seconds : int , nanoseconds : int ):
261+ """Test that object and dict branches produce identical results."""
262+ timestamp_obj = _Timestamp (seconds = seconds , nanoseconds = nanoseconds )
263+ timestamp_dict = {"seconds" : seconds , "nanoseconds" : nanoseconds }
264+
265+ result_obj = timestamp_conversion (timestamp_obj )
266+ result_dict = timestamp_conversion (timestamp_dict )
267+
268+ assert result_obj == result_dict
269+ _assert_utc_datetime (result_obj )
270+ _assert_utc_datetime (result_dict )
271+
272+
273+ @pytest .mark .parametrize (
274+ "seconds,nanoseconds" ,
275+ [
276+ (1672576496 , 123456000 ), # nanoseconds already in microsecond precision
277+ (1672576496 , 0 ),
278+ (1672576496 , 999999000 ),
279+ ],
280+ )
281+ def test_timestamp_conversion_string_cross_validation (seconds : int , nanoseconds : int ):
282+ """Test cross-validation with string path for microsecond-precision nanoseconds."""
283+ dt_from_obj = timestamp_conversion (_Timestamp (seconds = seconds , nanoseconds = nanoseconds ))
284+ iso_str = dt_from_obj .strftime ("%Y-%m-%dT%H:%M:%S.%fZ" )
285+ dt_from_string = timestamp_conversion (iso_str )
229286
230- for seconds , nanoseconds in test_cases :
231- timestamp_dict = {"seconds" : seconds , "nanoseconds" : nanoseconds }
232- result = timestamp_conversion (timestamp_dict )
233- expected = _dt .datetime .fromtimestamp (
234- seconds + nanoseconds / 1_000_000_000 , tz = _dt .timezone .utc
235- )
236- assert result == expected
237- assert result .tzinfo == _dt .timezone .utc
287+ assert dt_from_obj == dt_from_string
238288
239289
240- def test_timestamp_conversion_with_string ():
241- """
242- Testing timestamp_conversion works with string inputs.
243- """
244- test_cases = [
290+ @pytest .mark .parametrize (
291+ "timestamp_str,conversion_func" ,
292+ [
245293 ("2023-01-01T12:34:56.123456789Z" , nanoseconds_timestamp_conversion ),
246294 ("2023-06-20T10:15:22.396358Z" , microsecond_timestamp_conversion ),
247295 ("2023-01-01T12:34:56Z" , second_timestamp_conversion ),
248- ]
249-
250- for timestamp_str , conversion_func in test_cases :
251- result = timestamp_conversion (timestamp_str )
252- expected = conversion_func (timestamp_str )
253- assert result == expected
296+ ],
297+ )
298+ def test_timestamp_conversion_with_string (timestamp_str : str , conversion_func ):
299+ """Test timestamp_conversion works with string inputs."""
300+ result = timestamp_conversion (timestamp_str )
301+ expected = conversion_func (timestamp_str )
302+ assert result == expected
303+ _assert_utc_datetime (result )
304+
305+
306+ @pytest .mark .parametrize (
307+ "invalid_input,expected_error_msg" ,
308+ [
309+ (12345 , "timestamp_conversion expects a string or a Timestamp-like object" ),
310+ ("invalid_timestamp" , None ), # Error message varies, just check ValueError
311+ (None , None ),
312+ ],
313+ )
314+ def test_timestamp_conversion_errors (invalid_input , expected_error_msg ):
315+ """Test timestamp_conversion raises appropriate errors for invalid inputs."""
316+ with pytest .raises (ValueError ) as exc_info :
317+ timestamp_conversion (invalid_input )
318+ if expected_error_msg :
319+ assert expected_error_msg in str (exc_info .value )
254320
255321
256- def test_timestamp_conversion_errors ():
257- """
258- Testing timestamp_conversion raises appropriate errors for invalid inputs.
259- """
322+ def test_timestamp_conversion_error_missing_seconds ():
323+ """Test timestamp_conversion raises error when seconds attribute is missing."""
260324
261325 class IncompleteTimestamp :
262- def __init__ (self , nanoseconds ):
326+ def __init__ (self , nanoseconds : int ):
263327 self .nanoseconds = nanoseconds
264328
265329 with pytest .raises (ValueError ):
266330 timestamp_conversion (IncompleteTimestamp (nanoseconds = 123456789 ))
267331
268- with pytest .raises (ValueError ) as context :
269- timestamp_conversion (12345 )
270- assert "timestamp_conversion expects a string or a Timestamp-like object" in str (context .value )
271- with pytest .raises (ValueError ):
272- timestamp_conversion ({"nanoseconds" : 123456789 })
273332
333+ def test_timestamp_conversion_error_missing_nanoseconds ():
334+ """Test timestamp_conversion raises error when nanoseconds key is missing in dict."""
274335 with pytest .raises (ValueError ):
275- timestamp_conversion ("invalid_timestamp" )
276-
277- with pytest .raises (ValueError ):
278- timestamp_conversion (None )
336+ timestamp_conversion ({"nanoseconds" : 123456789 })
0 commit comments