Coverage for app/backend/src/tests/test_utils.py: 100%

47 statements  

« prev     ^ index     » next       coverage.py v7.14.2, created at 2026-06-21 09:29 +0000

1from datetime import UTC, datetime, timedelta, timezone 

2from unittest.mock import patch 

3 

4import pytest 

5from google.protobuf.timestamp_pb2 import Timestamp 

6from sqlalchemy import select, update 

7from sqlalchemy.sql import func 

8 

9from couchers.db import session_scope 

10from couchers.models import User 

11from couchers.utils import dt_from_page_token, dt_to_page_token, http_date, now, to_timezone, wrap_coordinate 

12from tests.fixtures.db import generate_user 

13 

14 

15@pytest.fixture(autouse=True) 

16def _(testconfig): 

17 pass 

18 

19 

20def test_page_token_time_python(): 

21 now_ = now() 

22 assert now_ == dt_from_page_token(dt_to_page_token(now_)) 

23 

24 

25def test_page_token_time_db(db): 

26 user, _ = generate_user() 

27 

28 # generate a timestamp in postgres (note use of `func`) 

29 with session_scope() as session: 

30 session.execute(update(User).where(User.id == user.id).values(joined=func.now())) 

31 

32 with session_scope() as session: 

33 # pull it back into python 

34 joined = session.execute(select(User)).scalar_one().joined 

35 

36 # roundtrip page token 

37 roundtrip = dt_from_page_token(dt_to_page_token(joined)) 

38 

39 # make sure euqality is still equality 

40 user = session.execute(select(User).where(User.joined == roundtrip)).scalar_one() 

41 assert user.joined == roundtrip 

42 

43 

44def test_http_date_with_datetime(): 

45 """Test http_date with a specific datetime to verify usegmt=True is used""" 

46 dt = datetime(2024, 1, 15, 10, 30, 45, tzinfo=UTC) 

47 

48 result = http_date(dt) 

49 

50 assert result == "Mon, 15 Jan 2024 10:30:45 GMT" 

51 

52 

53def test_http_date_without_datetime(): 

54 """Test http_date with dt=None to verify it uses now() and usegmt=True""" 

55 mock_now = datetime(2024, 3, 20, 14, 25, 30, tzinfo=UTC) 

56 

57 with patch("couchers.utils.now", return_value=mock_now): 

58 result = http_date() 

59 

60 assert result == "Wed, 20 Mar 2024 14:25:30 GMT" 

61 

62 

63def test_wrap_coordinate(): 

64 test_coords = [ 

65 ((-95, -185), (-85, 175)), 

66 ((95, -180), (85, 180)), # Weird interaction in PostGIS where lng 

67 # flips at -180 only when there is latitude overflow 

68 ((90, -180), (90, -180)), 

69 ((20, 185), (20, -175)), 

70 ((0, 0), (0, 0)), 

71 ((-1000, 0), (80, 0)), 

72 ((1000, 0), (-80, 0)), 

73 ((-180, 0), (0, 0)), 

74 ((180, 0), (0, 0)), 

75 ((-200, 0), (20, 0)), 

76 ((200, 0), (-20, 0)), 

77 ((-450, 0), (-90, 0)), 

78 ((450, 0), (90, 0)), 

79 ((-540, 0), (0, 0)), 

80 ((540, 0), (0, 0)), 

81 ((-90, 0), (-90, 0)), 

82 ((90, 0), (90, 0)), 

83 ((-1000, -1000), (80, 80)), 

84 ((1000, 1000), (-80, -80)), 

85 ((-100, -100), (-80, -100)), 

86 ((100, 100), (80, 100)), 

87 ((1000, 10), (-80, 10)), 

88 ((-100.5, 10), (-79.5, 10)), 

89 ((100.5, 10), (79.5, 10)), 

90 ((-100, 10), (-80, 10)), 

91 ((100, 10), (80, 10)), 

92 ((-180, 10), (0, 10)), 

93 ((180, 10), (0, 10)), 

94 ((20, 10), (20, 10)), 

95 ((-270, 10), (90, 10)), 

96 ((270, 10), (-90, 10)), 

97 ((-360, 10), (0, 10)), 

98 ((360, 10), (0, 10)), 

99 ((-90.1, 10), (-89.9, 10)), 

100 ((90.1, 10), (89.9, 10)), 

101 ((-90, 10), (-90, 10)), 

102 ((90, 10), (90, 10)), 

103 ((-80, -170), (-80, -170)), 

104 ((80, 170), (80, 170)), 

105 ((0, -180), (0, -180)), 

106 ((0, 180), (0, 180)), 

107 ((100, -180), (80, 180)), 

108 ((100, 180), (80, 180)), 

109 ((20, -180.1), (20, 179.9)), 

110 ((20, 180.1), (20, -179.9)), 

111 ((20, -180), (20, -180)), 

112 ((20, 180), (20, 180)), 

113 ((-90, -180), (-90, -180)), 

114 ((90, 180), (90, 180)), 

115 ((30, -190), (30, 170)), 

116 ((30, 190), (30, -170)), 

117 ((-95, -190), (-85, 170)), 

118 ((95, 190), (85, -170)), 

119 ((0, -200), (0, 160)), 

120 ((0, 200), (0, -160)), 

121 ((-100, -200), (-80, 160)), 

122 ((100, 200), (80, -160)), 

123 ((-200, -200), (20, 160)), 

124 ((200, 200), (-20, -160)), 

125 ((20, -200), (20, 160)), 

126 ((20, 200), (20, -160)), 

127 ((-90, 200), (-90, -160)), 

128 ((90, 200), (90, -160)), 

129 ((0, -270), (0, 90)), 

130 ((0, 270), (0, -90)), 

131 ((-45, -270), (-45, 90)), 

132 ((45, 270), (45, -90)), 

133 ((0, -360), (0, 0)), 

134 ((0, 360), (0, 0)), 

135 ((-90, -360), (-90, 0)), 

136 ((90, 360), (90, 0)), 

137 ((-500, -500), (-40, -140)), 

138 ((500, 500), (40, 140)), 

139 ((50, -500), (50, -140)), 

140 ((50, 500), (50, 140)), 

141 ((-500, 50), (-40, 50)), 

142 ((500, 50), (40, 50)), 

143 ((0, -540), (0, 180)), 

144 ((0, 540), (0, 180)), 

145 ((-45, -90), (-45, -90)), 

146 ((45, 90), (45, 90)), 

147 ] 

148 

149 for coords, coords_expected in test_coords: 

150 coords_wrapped = wrap_coordinate(*coords) 

151 assert coords_expected == coords_wrapped 

152 

153 

154def test_to_timezone(): 

155 utc_plus_one = timezone(offset=timedelta(hours=1)) 

156 

157 epoch_utc = to_timezone(Timestamp(seconds=0), UTC) 

158 assert epoch_utc.isoformat() == "1970-01-01T00:00:00+00:00" 

159 

160 # With Timestamp 

161 epoch_utc_plus_one = to_timezone(Timestamp(seconds=0), utc_plus_one) 

162 assert epoch_utc_plus_one.isoformat() == "1970-01-01T01:00:00+01:00" 

163 

164 # With datetime 

165 epoch_utc_plus_one = to_timezone(epoch_utc, utc_plus_one) 

166 assert epoch_utc_plus_one.isoformat() == "1970-01-01T01:00:00+01:00"