您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

test_uuid_utils.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. import struct
  2. import time
  3. import uuid
  4. from unittest import mock
  5. import pytest
  6. from hypothesis import given
  7. from hypothesis import strategies as st
  8. from libs.uuid_utils import _create_uuidv7_bytes, uuidv7, uuidv7_boundary, uuidv7_timestamp
  9. # Tests for private helper function _create_uuidv7_bytes
  10. def test_create_uuidv7_bytes_basic_structure():
  11. """Test basic byte structure creation."""
  12. timestamp_ms = 1609459200000 # 2021-01-01 00:00:00 UTC in milliseconds
  13. random_bytes = b"\x12\x34\x56\x78\x9a\xbc\xde\xf0\x11\x22"
  14. result = _create_uuidv7_bytes(timestamp_ms, random_bytes)
  15. # Should be exactly 16 bytes
  16. assert len(result) == 16
  17. assert isinstance(result, bytes)
  18. # Create UUID from bytes to verify it's valid
  19. uuid_obj = uuid.UUID(bytes=result)
  20. assert uuid_obj.version == 7
  21. def test_create_uuidv7_bytes_timestamp_encoding():
  22. """Test timestamp is correctly encoded in first 48 bits."""
  23. timestamp_ms = 1609459200000 # 2021-01-01 00:00:00 UTC in milliseconds
  24. random_bytes = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
  25. result = _create_uuidv7_bytes(timestamp_ms, random_bytes)
  26. # Extract timestamp from first 6 bytes
  27. timestamp_bytes = b"\x00\x00" + result[0:6]
  28. extracted_timestamp = struct.unpack(">Q", timestamp_bytes)[0]
  29. assert extracted_timestamp == timestamp_ms
  30. def test_create_uuidv7_bytes_version_bits():
  31. """Test version bits are set to 7."""
  32. timestamp_ms = 1609459200000
  33. random_bytes = b"\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00" # Set first 2 bytes to all 1s
  34. result = _create_uuidv7_bytes(timestamp_ms, random_bytes)
  35. # Extract version from bytes 6-7
  36. version_and_rand_a = struct.unpack(">H", result[6:8])[0]
  37. version = (version_and_rand_a >> 12) & 0x0F
  38. assert version == 7
  39. def test_create_uuidv7_bytes_variant_bits():
  40. """Test variant bits are set correctly."""
  41. timestamp_ms = 1609459200000
  42. random_bytes = b"\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00" # Set byte 8 to all 1s
  43. result = _create_uuidv7_bytes(timestamp_ms, random_bytes)
  44. # Check variant bits in byte 8 (should be 10xxxxxx)
  45. variant_byte = result[8]
  46. variant_bits = (variant_byte >> 6) & 0b11
  47. assert variant_bits == 0b10 # Should be binary 10
  48. def test_create_uuidv7_bytes_random_data():
  49. """Test random bytes are placed correctly."""
  50. timestamp_ms = 1609459200000
  51. random_bytes = b"\x12\x34\x56\x78\x9a\xbc\xde\xf0\x11\x22"
  52. result = _create_uuidv7_bytes(timestamp_ms, random_bytes)
  53. # Check random data A (12 bits from bytes 6-7, excluding version)
  54. version_and_rand_a = struct.unpack(">H", result[6:8])[0]
  55. rand_a = version_and_rand_a & 0x0FFF
  56. expected_rand_a = struct.unpack(">H", random_bytes[0:2])[0] & 0x0FFF
  57. assert rand_a == expected_rand_a
  58. # Check random data B (bytes 8-15, with variant bits preserved)
  59. # Byte 8 should have variant bits set but preserve lower 6 bits
  60. expected_byte_8 = (random_bytes[2] & 0x3F) | 0x80
  61. assert result[8] == expected_byte_8
  62. # Bytes 9-15 should match random_bytes[3:10]
  63. assert result[9:16] == random_bytes[3:10]
  64. def test_create_uuidv7_bytes_zero_random():
  65. """Test with zero random bytes (boundary case)."""
  66. timestamp_ms = 1609459200000
  67. zero_random_bytes = b"\x00" * 10
  68. result = _create_uuidv7_bytes(timestamp_ms, zero_random_bytes)
  69. # Should still be valid UUIDv7
  70. uuid_obj = uuid.UUID(bytes=result)
  71. assert uuid_obj.version == 7
  72. # Version bits should be 0x7000
  73. version_and_rand_a = struct.unpack(">H", result[6:8])[0]
  74. assert version_and_rand_a == 0x7000
  75. # Variant byte should be 0x80 (variant bits + zero random bits)
  76. assert result[8] == 0x80
  77. # Remaining bytes should be zero
  78. assert result[9:16] == b"\x00" * 7
  79. def test_uuidv7_basic_generation():
  80. """Test basic UUID generation produces valid UUIDv7."""
  81. result = uuidv7()
  82. # Should be a UUID object
  83. assert isinstance(result, uuid.UUID)
  84. # Should be version 7
  85. assert result.version == 7
  86. # Should have correct variant (RFC 4122 variant)
  87. # Variant bits should be 10xxxxxx (0x80-0xBF range)
  88. variant_byte = result.bytes[8]
  89. assert (variant_byte >> 6) == 0b10
  90. def test_uuidv7_with_custom_timestamp():
  91. """Test UUID generation with custom timestamp."""
  92. custom_timestamp = 1609459200000 # 2021-01-01 00:00:00 UTC in milliseconds
  93. result = uuidv7(custom_timestamp)
  94. assert isinstance(result, uuid.UUID)
  95. assert result.version == 7
  96. # Extract and verify timestamp
  97. extracted_timestamp = uuidv7_timestamp(result)
  98. assert isinstance(extracted_timestamp, int)
  99. assert extracted_timestamp == custom_timestamp # Exact match for integer milliseconds
  100. def test_uuidv7_with_none_timestamp(monkeypatch):
  101. """Test UUID generation with None timestamp uses current time."""
  102. mock_time = 1609459200
  103. mock_time_func = mock.Mock(return_value=mock_time)
  104. monkeypatch.setattr("time.time", mock_time_func)
  105. result = uuidv7(None)
  106. assert isinstance(result, uuid.UUID)
  107. assert result.version == 7
  108. # Should use the mocked current time (converted to milliseconds)
  109. assert mock_time_func.called
  110. extracted_timestamp = uuidv7_timestamp(result)
  111. assert extracted_timestamp == mock_time * 1000 # 1609459200.0 * 1000
  112. def test_uuidv7_time_ordering():
  113. """Test that sequential UUIDs have increasing timestamps."""
  114. # Generate UUIDs with incrementing timestamps (in milliseconds)
  115. timestamp1 = 1609459200000 # 2021-01-01 00:00:00 UTC
  116. timestamp2 = 1609459201000 # 2021-01-01 00:00:01 UTC
  117. timestamp3 = 1609459202000 # 2021-01-01 00:00:02 UTC
  118. uuid1 = uuidv7(timestamp1)
  119. uuid2 = uuidv7(timestamp2)
  120. uuid3 = uuidv7(timestamp3)
  121. # Extract timestamps
  122. ts1 = uuidv7_timestamp(uuid1)
  123. ts2 = uuidv7_timestamp(uuid2)
  124. ts3 = uuidv7_timestamp(uuid3)
  125. # Should be in ascending order
  126. assert ts1 < ts2 < ts3
  127. # UUIDs should be lexicographically ordered by their string representation
  128. # due to time-ordering property of UUIDv7
  129. uuid_strings = [str(uuid1), str(uuid2), str(uuid3)]
  130. assert uuid_strings == sorted(uuid_strings)
  131. def test_uuidv7_uniqueness():
  132. """Test that multiple calls generate different UUIDs."""
  133. # Generate multiple UUIDs with the same timestamp (in milliseconds)
  134. timestamp = 1609459200000
  135. uuids = [uuidv7(timestamp) for _ in range(100)]
  136. # All should be unique despite same timestamp (due to random bits)
  137. assert len(set(uuids)) == 100
  138. # All should have the same extracted timestamp
  139. for uuid_obj in uuids:
  140. extracted_ts = uuidv7_timestamp(uuid_obj)
  141. assert extracted_ts == timestamp
  142. def test_uuidv7_timestamp_error_handling_wrong_version():
  143. """Test error handling for non-UUIDv7 inputs."""
  144. uuid_v4 = uuid.uuid4()
  145. with pytest.raises(ValueError) as exc_ctx:
  146. uuidv7_timestamp(uuid_v4)
  147. assert "Expected UUIDv7 (version 7)" in str(exc_ctx.value)
  148. assert f"got version {uuid_v4.version}" in str(exc_ctx.value)
  149. @given(st.integers(max_value=2**48 - 1, min_value=0))
  150. def test_uuidv7_timestamp_round_trip(timestamp_ms):
  151. # Generate UUID with timestamp
  152. uuid_obj = uuidv7(timestamp_ms)
  153. # Extract timestamp back
  154. extracted_timestamp = uuidv7_timestamp(uuid_obj)
  155. # Should match exactly for integer millisecond timestamps
  156. assert extracted_timestamp == timestamp_ms
  157. def test_uuidv7_timestamp_edge_cases():
  158. """Test timestamp extraction with edge case values."""
  159. # Test with very small timestamp
  160. small_timestamp = 1 # 1ms after epoch
  161. uuid_small = uuidv7(small_timestamp)
  162. extracted_small = uuidv7_timestamp(uuid_small)
  163. assert extracted_small == small_timestamp
  164. # Test with large timestamp (year 2038+)
  165. large_timestamp = 2147483647000 # 2038-01-19 03:14:07 UTC in milliseconds
  166. uuid_large = uuidv7(large_timestamp)
  167. extracted_large = uuidv7_timestamp(uuid_large)
  168. assert extracted_large == large_timestamp
  169. def test_uuidv7_boundary_basic_generation():
  170. """Test basic boundary UUID generation with a known timestamp."""
  171. timestamp = 1609459200000 # 2021-01-01 00:00:00 UTC in milliseconds
  172. result = uuidv7_boundary(timestamp)
  173. # Should be a UUID object
  174. assert isinstance(result, uuid.UUID)
  175. # Should be version 7
  176. assert result.version == 7
  177. # Should have correct variant (RFC 4122 variant)
  178. # Variant bits should be 10xxxxxx (0x80-0xBF range)
  179. variant_byte = result.bytes[8]
  180. assert (variant_byte >> 6) == 0b10
  181. def test_uuidv7_boundary_timestamp_extraction():
  182. """Test that boundary UUID timestamp can be extracted correctly."""
  183. timestamp = 1609459200000 # 2021-01-01 00:00:00 UTC in milliseconds
  184. boundary_uuid = uuidv7_boundary(timestamp)
  185. # Extract timestamp using existing function
  186. extracted_timestamp = uuidv7_timestamp(boundary_uuid)
  187. # Should match exactly
  188. assert extracted_timestamp == timestamp
  189. def test_uuidv7_boundary_deterministic():
  190. """Test that boundary UUIDs are deterministic for same timestamp."""
  191. timestamp = 1609459200000 # 2021-01-01 00:00:00 UTC in milliseconds
  192. # Generate multiple boundary UUIDs with same timestamp
  193. uuid1 = uuidv7_boundary(timestamp)
  194. uuid2 = uuidv7_boundary(timestamp)
  195. uuid3 = uuidv7_boundary(timestamp)
  196. # Should all be identical
  197. assert uuid1 == uuid2 == uuid3
  198. assert str(uuid1) == str(uuid2) == str(uuid3)
  199. def test_uuidv7_boundary_is_minimum():
  200. """Test that boundary UUID is lexicographically smaller than regular UUIDs."""
  201. timestamp = 1609459200000 # 2021-01-01 00:00:00 UTC in milliseconds
  202. # Generate boundary UUID
  203. boundary_uuid = uuidv7_boundary(timestamp)
  204. # Generate multiple regular UUIDs with same timestamp
  205. regular_uuids = [uuidv7(timestamp) for _ in range(50)]
  206. # Boundary UUID should be lexicographically smaller than all regular UUIDs
  207. boundary_str = str(boundary_uuid)
  208. for regular_uuid in regular_uuids:
  209. regular_str = str(regular_uuid)
  210. assert boundary_str < regular_str, f"Boundary {boundary_str} should be < regular {regular_str}"
  211. # Also test with bytes comparison
  212. boundary_bytes = boundary_uuid.bytes
  213. for regular_uuid in regular_uuids:
  214. regular_bytes = regular_uuid.bytes
  215. assert boundary_bytes < regular_bytes
  216. def test_uuidv7_boundary_different_timestamps():
  217. """Test that boundary UUIDs with different timestamps are ordered correctly."""
  218. timestamp1 = 1609459200000 # 2021-01-01 00:00:00 UTC
  219. timestamp2 = 1609459201000 # 2021-01-01 00:00:01 UTC
  220. timestamp3 = 1609459202000 # 2021-01-01 00:00:02 UTC
  221. uuid1 = uuidv7_boundary(timestamp1)
  222. uuid2 = uuidv7_boundary(timestamp2)
  223. uuid3 = uuidv7_boundary(timestamp3)
  224. # Extract timestamps to verify
  225. ts1 = uuidv7_timestamp(uuid1)
  226. ts2 = uuidv7_timestamp(uuid2)
  227. ts3 = uuidv7_timestamp(uuid3)
  228. # Should be in ascending order
  229. assert ts1 < ts2 < ts3
  230. # UUIDs should be lexicographically ordered
  231. uuid_strings = [str(uuid1), str(uuid2), str(uuid3)]
  232. assert uuid_strings == sorted(uuid_strings)
  233. # Bytes should also be ordered
  234. assert uuid1.bytes < uuid2.bytes < uuid3.bytes
  235. def test_uuidv7_boundary_edge_cases():
  236. """Test boundary UUID generation with edge case timestamp values."""
  237. # Test with timestamp 0 (Unix epoch)
  238. epoch_uuid = uuidv7_boundary(0)
  239. assert isinstance(epoch_uuid, uuid.UUID)
  240. assert epoch_uuid.version == 7
  241. assert uuidv7_timestamp(epoch_uuid) == 0
  242. # Test with very large timestamp values
  243. large_timestamp = 2147483647000 # 2038-01-19 03:14:07 UTC in milliseconds
  244. large_uuid = uuidv7_boundary(large_timestamp)
  245. assert isinstance(large_uuid, uuid.UUID)
  246. assert large_uuid.version == 7
  247. assert uuidv7_timestamp(large_uuid) == large_timestamp
  248. # Test with current time
  249. current_time = int(time.time() * 1000)
  250. current_uuid = uuidv7_boundary(current_time)
  251. assert isinstance(current_uuid, uuid.UUID)
  252. assert current_uuid.version == 7
  253. assert uuidv7_timestamp(current_uuid) == current_time