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

54 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-03 11:56 +0000

1from datetime import UTC, date, datetime, timedelta, timezone 

2 

3from google.protobuf.timestamp_pb2 import Timestamp 

4from sqlalchemy import select, update 

5from sqlalchemy.sql import func 

6 

7from couchers.config import config 

8from couchers.db import session_scope 

9from couchers.models import User 

10from couchers.utils import ( 

11 create_session_cookies, 

12 date_id_from_page_token, 

13 date_id_to_page_token, 

14 dt_from_page_token, 

15 dt_to_page_token, 

16 http_date, 

17 now, 

18 to_timezone, 

19 wrap_coordinate, 

20) 

21from tests.fixtures.db import generate_user 

22from tests.fixtures.timewarp import FrozenTimewarp 

23 

24 

25def test_page_token_time_python(): 

26 now_ = now() 

27 assert now_ == dt_from_page_token(dt_to_page_token(now_)) 

28 

29 

30def test_page_token_date_id(): 

31 assert date_id_from_page_token(date_id_to_page_token(date(2026, 6, 1), 123)) == (date(2026, 6, 1), 123) 

32 

33 

34def test_page_token_time_db(db): 

35 user, _ = generate_user() 

36 

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

38 with session_scope() as session: 

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

40 

41 with session_scope() as session: 

42 # pull it back into python 

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

44 

45 # roundtrip page token 

46 roundtrip = dt_from_page_token(dt_to_page_token(joined)) 

47 

48 # make sure euqality is still equality 

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

50 assert user.joined == roundtrip 

51 

52 

53def test_http_date_with_datetime(): 

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

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

56 

57 result = http_date(dt) 

58 

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

60 

61 

62def test_http_date_without_datetime(frozen_timewarp: FrozenTimewarp): 

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

64 frozen_timewarp.freeze_at(datetime(2024, 3, 20, 14, 25, 30, tzinfo=UTC)) 

65 

66 assert http_date() == "Wed, 20 Mar 2024 14:25:30 GMT" 

67 

68 

69def test_session_cookies_partitioned(): 

70 config.DEV = False 

71 config.COOKIE_SAMESITE = "None" 

72 

73 config.COOKIE_PARTITIONED = False 

74 for cookie in create_session_cookies("token", 1, now()): 

75 assert "Partitioned" not in cookie 

76 

77 config.COOKIE_PARTITIONED = True 

78 for cookie in create_session_cookies("token", 1, now()): 

79 assert "; Partitioned;" in cookie 

80 assert "; Secure" in cookie 

81 

82 

83def test_wrap_coordinate(): 

84 test_coords = [ 

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

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

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

88 ((90, -180), (90, -180)), 

89 ((20, 185), (20, -175)), 

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

91 ((-1000, 0), (80, 0)), 

92 ((1000, 0), (-80, 0)), 

93 ((-180, 0), (0, 0)), 

94 ((180, 0), (0, 0)), 

95 ((-200, 0), (20, 0)), 

96 ((200, 0), (-20, 0)), 

97 ((-450, 0), (-90, 0)), 

98 ((450, 0), (90, 0)), 

99 ((-540, 0), (0, 0)), 

100 ((540, 0), (0, 0)), 

101 ((-90, 0), (-90, 0)), 

102 ((90, 0), (90, 0)), 

103 ((-1000, -1000), (80, 80)), 

104 ((1000, 1000), (-80, -80)), 

105 ((-100, -100), (-80, -100)), 

106 ((100, 100), (80, 100)), 

107 ((1000, 10), (-80, 10)), 

108 ((-100.5, 10), (-79.5, 10)), 

109 ((100.5, 10), (79.5, 10)), 

110 ((-100, 10), (-80, 10)), 

111 ((100, 10), (80, 10)), 

112 ((-180, 10), (0, 10)), 

113 ((180, 10), (0, 10)), 

114 ((20, 10), (20, 10)), 

115 ((-270, 10), (90, 10)), 

116 ((270, 10), (-90, 10)), 

117 ((-360, 10), (0, 10)), 

118 ((360, 10), (0, 10)), 

119 ((-90.1, 10), (-89.9, 10)), 

120 ((90.1, 10), (89.9, 10)), 

121 ((-90, 10), (-90, 10)), 

122 ((90, 10), (90, 10)), 

123 ((-80, -170), (-80, -170)), 

124 ((80, 170), (80, 170)), 

125 ((0, -180), (0, -180)), 

126 ((0, 180), (0, 180)), 

127 ((100, -180), (80, 180)), 

128 ((100, 180), (80, 180)), 

129 ((20, -180.1), (20, 179.9)), 

130 ((20, 180.1), (20, -179.9)), 

131 ((20, -180), (20, -180)), 

132 ((20, 180), (20, 180)), 

133 ((-90, -180), (-90, -180)), 

134 ((90, 180), (90, 180)), 

135 ((30, -190), (30, 170)), 

136 ((30, 190), (30, -170)), 

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

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

139 ((0, -200), (0, 160)), 

140 ((0, 200), (0, -160)), 

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

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

143 ((-200, -200), (20, 160)), 

144 ((200, 200), (-20, -160)), 

145 ((20, -200), (20, 160)), 

146 ((20, 200), (20, -160)), 

147 ((-90, 200), (-90, -160)), 

148 ((90, 200), (90, -160)), 

149 ((0, -270), (0, 90)), 

150 ((0, 270), (0, -90)), 

151 ((-45, -270), (-45, 90)), 

152 ((45, 270), (45, -90)), 

153 ((0, -360), (0, 0)), 

154 ((0, 360), (0, 0)), 

155 ((-90, -360), (-90, 0)), 

156 ((90, 360), (90, 0)), 

157 ((-500, -500), (-40, -140)), 

158 ((500, 500), (40, 140)), 

159 ((50, -500), (50, -140)), 

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

161 ((-500, 50), (-40, 50)), 

162 ((500, 50), (40, 50)), 

163 ((0, -540), (0, 180)), 

164 ((0, 540), (0, 180)), 

165 ((-45, -90), (-45, -90)), 

166 ((45, 90), (45, 90)), 

167 ] 

168 

169 for coords, coords_expected in test_coords: 

170 coords_wrapped = wrap_coordinate(*coords) 

171 assert coords_expected == coords_wrapped 

172 

173 

174def test_to_timezone(): 

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

176 

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

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

179 

180 # With Timestamp 

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

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

183 

184 # With datetime 

185 epoch_utc_plus_one = to_timezone(epoch_utc, utc_plus_one) 

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