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
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-03 11:56 +0000
1from datetime import UTC, date, datetime, timedelta, timezone
3from google.protobuf.timestamp_pb2 import Timestamp
4from sqlalchemy import select, update
5from sqlalchemy.sql import func
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
25def test_page_token_time_python():
26 now_ = now()
27 assert now_ == dt_from_page_token(dt_to_page_token(now_))
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)
34def test_page_token_time_db(db):
35 user, _ = generate_user()
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()))
41 with session_scope() as session:
42 # pull it back into python
43 joined = session.execute(select(User)).scalar_one().joined
45 # roundtrip page token
46 roundtrip = dt_from_page_token(dt_to_page_token(joined))
48 # make sure euqality is still equality
49 user = session.execute(select(User).where(User.joined == roundtrip)).scalar_one()
50 assert user.joined == roundtrip
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)
57 result = http_date(dt)
59 assert result == "Mon, 15 Jan 2024 10:30:45 GMT"
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))
66 assert http_date() == "Wed, 20 Mar 2024 14:25:30 GMT"
69def test_session_cookies_partitioned():
70 config.DEV = False
71 config.COOKIE_SAMESITE = "None"
73 config.COOKIE_PARTITIONED = False
74 for cookie in create_session_cookies("token", 1, now()):
75 assert "Partitioned" not in cookie
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
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 ]
169 for coords, coords_expected in test_coords:
170 coords_wrapped = wrap_coordinate(*coords)
171 assert coords_expected == coords_wrapped
174def test_to_timezone():
175 utc_plus_one = timezone(offset=timedelta(hours=1))
177 epoch_utc = to_timezone(Timestamp(seconds=0), UTC)
178 assert epoch_utc.isoformat() == "1970-01-01T00:00:00+00:00"
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"
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"