1- """Tests for utils.path_helpers.normalize_file_path.
2-
3- Covers the shared implementation that was previously duplicated in
4- scripts/export.py (closes #46). All call-sites in both the web app and the
5- CLI export script now use this single copy.
6-
7- Edge-case matrix:
8- - file:/// and file:// URI schemes
9- - Percent-encoded characters: spaces (%20), colons (%3A), hashes (%23)
10- - Windows-style drive paths (backslash and forward-slash) on all platforms
11- - Drive-letter lowercasing on win32
12- - Plain POSIX paths pass through unchanged
13- - Empty / None-like input
1+ """Tests for utils.path_helpers path/timestamp helpers (closes #46).
2+
3+ Covers ``normalize_file_path`` and ``to_epoch_ms``, both previously duplicated
4+ in scripts/export.py. All call-sites in the web app and CLI export script now
5+ use the shared implementations in utils.path_helpers.
6+
7+ Test inventory (this module only): 21 cases — 12 ``normalize_file_path``,
8+ 9 ``to_epoch_ms``. On win32, 2 cases skip (POSIX passthrough in
9+ ``TestNormalizeFilePathPosixPassthrough`` only). A full-suite run may report
10+ more skips (e.g. ``skipped=4``) from other test modules, not this file.
1411"""
1512
1613import sys
1714import unittest
15+ from datetime import datetime , timezone
1816
19- from utils .path_helpers import normalize_file_path
17+ from utils .path_helpers import normalize_file_path , to_epoch_ms
2018
2119
2220class TestNormalizeFilePathUriStripping (unittest .TestCase ):
@@ -48,9 +46,14 @@ def test_hash_decoded(self) -> None:
4846 def test_percent_encoded_colon_in_uri_prefix (self ) -> None :
4947 """URI-style /d%3A/... path: %3A is decoded to ':'.
5048
51- On win32 the backslash branch is entered (leading slash removed
52- and path lowercased). On other platforms the leading slash prevents
53- the Windows-drive branch, so the path is returned as decoded only.
49+ Only test that exercises the leading-``/`` + drive-letter shape end-to-end
50+ (Cursor sometimes stores ``/d%3A/...`` URIs). Other drive-path tests use
51+ ``D:/...`` or ``D:\\ ...`` without a leading slash.
52+
53+ On win32 the win32 branch strips the leading slash, lowercases, and
54+ normalises to backslashes. On other platforms the leading ``/`` prevents
55+ the ``^[a-zA-Z]:[/\\ ]`` cross-platform branch in ``path_helpers``, so the
56+ path is returned as percent-decoded only (no slash flip / lowercasing).
5457 """
5558 out = normalize_file_path ("/d%3A/_Work/project" )
5659 self .assertNotIn ("%3A" , out )
@@ -79,9 +82,8 @@ def test_forward_slash_drive_path_converted(self) -> None:
7982
8083 def test_file_uri_with_windows_drive (self ) -> None :
8184 out = normalize_file_path ("file:///C:/Users/Dev/project" )
82- self .assertIn ("users" , out )
83- self .assertIn ("dev" , out )
84- self .assertTrue (out .startswith ("c:" ))
85+ # file:/// stripped, then same drive-letter branch as D:/ and D:\ inputs.
86+ self .assertEqual (out , r"c:\users\dev\project" )
8587
8688 def test_mixed_case_drive_lowercased (self ) -> None :
8789 out = normalize_file_path (r"E:\Mixed\Case\Path" )
@@ -103,5 +105,37 @@ def test_path_without_scheme_unchanged(self) -> None:
103105 self .assertEqual (out , "relative/path/file.py" )
104106
105107
108+ class TestToEpochMs (unittest .TestCase ):
109+ def test_none_returns_zero (self ) -> None :
110+ self .assertEqual (to_epoch_ms (None ), 0 )
111+
112+ def test_ms_int_passthrough (self ) -> None :
113+ self .assertEqual (to_epoch_ms (1_700_000_000_000 ), 1_700_000_000_000 )
114+
115+ def test_seconds_int_converted_to_ms (self ) -> None :
116+ self .assertEqual (to_epoch_ms (1_700_000_000 ), 1_700_000_000_000 )
117+
118+ def test_seconds_float_converted_to_ms (self ) -> None :
119+ self .assertEqual (to_epoch_ms (1_700_000_000.5 ), 1_700_000_000_500 )
120+
121+ def test_zero_returns_zero (self ) -> None :
122+ self .assertEqual (to_epoch_ms (0 ), 0 )
123+
124+ def test_iso8601_zulu (self ) -> None :
125+ expected = int (
126+ datetime (2026 , 2 , 3 , 20 , 39 , 54 , 17_000 , tzinfo = timezone .utc ).timestamp () * 1000
127+ )
128+ self .assertEqual (to_epoch_ms ("2026-02-03T20:39:54.017Z" ), expected )
129+
130+ def test_numeric_string_already_ms (self ) -> None :
131+ self .assertEqual (to_epoch_ms ("1700000000000" ), 1_700_000_000_000 )
132+
133+ def test_numeric_string_seconds (self ) -> None :
134+ self .assertEqual (to_epoch_ms ("1700000000" ), 1_700_000_000_000 )
135+
136+ def test_unrecognised_string_returns_zero (self ) -> None :
137+ self .assertEqual (to_epoch_ms ("not-a-timestamp" ), 0 )
138+
139+
106140if __name__ == "__main__" :
107141 unittest .main ()
0 commit comments