• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / 64ecce1f-d19c-4282-bcd0-3ab968fc2876

18 Nov 2025 11:27PM UTC coverage: 88.772% (+0.01%) from 88.762%
64ecce1f-d19c-4282-bcd0-3ab968fc2876

push

circleci

web-flow
Merge pull request #6052 from mozilla/MPP-4437/fix-scope-enforcement

fix(auth): enforce relay scope on bearer token auth

2908 of 3927 branches covered (74.05%)

Branch coverage included in aggregate %.

31 of 32 new or added lines in 4 files covered. (96.88%)

18154 of 19799 relevant lines covered (91.69%)

11.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.16
/api/tests/authentication_tests.py
1
from datetime import datetime
1✔
2
from typing import NotRequired, TypedDict
1✔
3

4
from django.core.cache import cache
1✔
5
from django.test import RequestFactory, TestCase
1✔
6

7
import responses
1✔
8
from allauth.socialaccount.models import SocialAccount
1✔
9
from model_bakery import baker
1✔
10
from rest_framework.exceptions import APIException, AuthenticationFailed, NotFound
1✔
11
from rest_framework.test import APIClient
1✔
12

13
from ..authentication import (
1✔
14
    INTROSPECT_TOKEN_URL,
15
    FxaTokenAuthentication,
16
    get_cache_key,
17
    get_fxa_uid_from_oauth_token,
18
    introspect_token,
19
)
20

21
MOCK_BASE = "api.authentication"
1✔
22

23

24
# TODO MPP-3527 - Many tests mock FxA responses. This one should specify that it is
25
# mocking the introspection URL. It could also be refactored to a pytest fixture, or a
26
# nullable.
27

28

29
class FxaResponse(TypedDict, total=False):
1✔
30
    active: bool
1✔
31
    sub: str
1✔
32
    exp: int
1✔
33
    error: str
1✔
34
    scope: str
1✔
35

36

37
class CachedFxaResponse(TypedDict):
1✔
38
    status_code: int
1✔
39
    json: NotRequired[FxaResponse | str]
1✔
40

41

42
def _setup_fxa_response(
1✔
43
    status_code: int, json: FxaResponse | str | None = None
44
) -> CachedFxaResponse:
45
    responses.add(
1✔
46
        responses.POST,
47
        INTROSPECT_TOKEN_URL,
48
        status=status_code,
49
        json=json,
50
    )
51
    if json is None:
1✔
52
        return {
1✔
53
            "status_code": status_code,
54
            "json": {"scope": "https://identity.mozilla.com/apps/relay"},
55
        }
56
    return {"status_code": status_code, "json": json}
1✔
57

58

59
class AuthenticationMiscellaneous(TestCase):
1✔
60
    def setUp(self):
1✔
61
        self.auth = FxaTokenAuthentication
1✔
62
        self.factory = RequestFactory()
1✔
63
        self.path = "/api/v1/relayaddresses"
1✔
64
        self.fxa_verify_path = INTROSPECT_TOKEN_URL
1✔
65
        self.uid = "relay-user-fxa-uid"
1✔
66

67
    def tearDown(self):
1✔
68
        cache.clear()
1✔
69

70
    @responses.activate
1✔
71
    def test_introspect_token_catches_JSONDecodeError_raises_AuthenticationFailed(self):
1✔
72
        _setup_fxa_response(200)
1✔
73
        invalid_token = "invalid-123"
1✔
74

75
        try:
1✔
76
            introspect_token(invalid_token)
1✔
77
        except AuthenticationFailed as e:
1✔
78
            assert str(e.detail) == "JSONDecodeError from FXA introspect response"
1✔
79
            assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
80
            return
1✔
81
        self.fail("Should have raised AuthenticationFailed")
×
82

83
    @responses.activate
1✔
84
    def test_introspect_token_returns_fxa_introspect_response(self):
1✔
85
        now_time = int(datetime.now().timestamp())
1✔
86
        # Note: FXA iat and exp are timestamps in *milliseconds*
87
        exp_time = (now_time + 60 * 60) * 1000
1✔
88
        json_data: FxaResponse = {
1✔
89
            "active": True,
90
            "sub": self.uid,
91
            "exp": exp_time,
92
            "scope": "https://identity.mozilla.com/apps/relay",
93
        }
94
        status_code = 200
1✔
95
        expected_fxa_resp_data = {"status_code": status_code, "json": json_data}
1✔
96
        _setup_fxa_response(status_code, json_data)
1✔
97
        valid_token = "valid-123"
1✔
98
        cache_key = get_cache_key(valid_token)
1✔
99

100
        assert cache.get(cache_key) is None
1✔
101

102
        fxa_resp_data = introspect_token(valid_token)
1✔
103
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
104
        assert fxa_resp_data == expected_fxa_resp_data
1✔
105

106
    @responses.activate
1✔
107
    def test_get_fxa_uid_from_oauth_token_returns_cached_response(self):
1✔
108
        user_token = "user-123"
1✔
109
        now_time = int(datetime.now().timestamp())
1✔
110
        # Note: FXA iat and exp are timestamps in *milliseconds*
111
        exp_time = (now_time + 60 * 60) * 1000
1✔
112
        fxa_response = _setup_fxa_response(
1✔
113
            200,
114
            {
115
                "active": True,
116
                "sub": self.uid,
117
                "exp": exp_time,
118
                "scope": "https://identity.mozilla.com/apps/relay",
119
            },
120
        )
121
        cache_key = get_cache_key(user_token)
1✔
122

123
        assert cache.get(cache_key) is None
1✔
124

125
        # get FxA uid for the first time
126
        fxa_uid = get_fxa_uid_from_oauth_token(user_token)
1✔
127
        assert fxa_uid == self.uid
1✔
128
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
129
        assert cache.get(cache_key) == fxa_response
1✔
130

131
        # now check that the 2nd call did NOT make another fxa request
132
        fxa_uid = get_fxa_uid_from_oauth_token(user_token)
1✔
133
        assert fxa_uid == self.uid
1✔
134
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
135

136
    @responses.activate
1✔
137
    def test_get_fxa_uid_from_oauth_token_status_code_None_uses_cached_response_returns_error_response(  # noqa: E501
1✔
138
        self,
139
    ) -> None:
140
        _setup_fxa_response(200)
1✔
141
        invalid_token = "invalid-123"
1✔
142
        cache_key = get_cache_key(invalid_token)
1✔
143

144
        assert cache.get(cache_key) is None
1✔
145

146
        # get fxa response with no status code for the first time
147
        try:
1✔
148
            get_fxa_uid_from_oauth_token(invalid_token)
1✔
149
        except AuthenticationFailed as e:
1✔
150
            assert str(e.detail) == "JSONDecodeError from FXA introspect response"
1✔
151
            assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
152
        assert cache.get(cache_key) == {"status_code": None, "json": {}}
1✔
153

154
        # now check that the 2nd call did NOT make another fxa request
155
        try:
1✔
156
            get_fxa_uid_from_oauth_token(invalid_token)
1✔
157
        except APIException as e:
1✔
158
            assert str(e.detail) == "Previous FXA call failed, wait to retry."
1✔
159
            assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
160
            return
1✔
161
        self.fail("Should have raised APIException")
×
162

163
    @responses.activate
1✔
164
    def test_get_fxa_uid_from_oauth_token_status_code_not_200_uses_cached_response_returns_error_response(  # noqa: E501
1✔
165
        self,
166
    ) -> None:
167
        now_time = int(datetime.now().timestamp())
1✔
168
        # Note: FXA iat and exp are timestamps in *milliseconds*
169
        exp_time = (now_time + 60 * 60) * 1000
1✔
170
        fxa_response = _setup_fxa_response(
1✔
171
            401, {"active": False, "sub": self.uid, "exp": exp_time}
172
        )
173
        invalid_token = "invalid-123"
1✔
174
        cache_key = get_cache_key(invalid_token)
1✔
175

176
        assert cache.get(cache_key) is None
1✔
177

178
        # get fxa response with none 200 response for the first time
179
        try:
1✔
180
            get_fxa_uid_from_oauth_token(invalid_token)
1✔
181
        except APIException as e:
1✔
182
            assert str(e.detail) == "Did not receive a 200 response from FXA."
1✔
183
            assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
184
        assert cache.get(cache_key) == fxa_response
1✔
185

186
        # now check that the 2nd call did NOT make another fxa request
187
        try:
1✔
188
            get_fxa_uid_from_oauth_token(invalid_token)
1✔
189
        except APIException as e:
1✔
190
            assert str(e.detail) == "Did not receive a 200 response from FXA."
1✔
191
            assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
192
            return
1✔
193
        self.fail("Should have raised APIException")
×
194

195
    @responses.activate
1✔
196
    def test_get_fxa_uid_from_oauth_token_not_active_uses_cached_response_returns_error_response(  # noqa: E501
1✔
197
        self,
198
    ) -> None:
199
        now_time = int(datetime.now().timestamp())
1✔
200
        # Note: FXA iat and exp are timestamps in *milliseconds*
201
        old_exp_time = (now_time - 60 * 60) * 1000
1✔
202
        fxa_response = _setup_fxa_response(
1✔
203
            200, {"active": False, "sub": self.uid, "exp": old_exp_time}
204
        )
205
        invalid_token = "invalid-123"
1✔
206
        cache_key = get_cache_key(invalid_token)
1✔
207

208
        assert cache.get(cache_key) is None
1✔
209

210
        # get fxa response with token inactive for the first time
211
        try:
1✔
212
            get_fxa_uid_from_oauth_token(invalid_token)
1✔
213
        except AuthenticationFailed as e:
1✔
214
            assert str(e.detail) == "FXA returned active: False for token."
1✔
215
            assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
216
        assert cache.get(cache_key) == fxa_response
1✔
217

218
        # now check that the 2nd call did NOT make another fxa request
219
        try:
1✔
220
            get_fxa_uid_from_oauth_token(invalid_token)
1✔
221
        except AuthenticationFailed as e:
1✔
222
            assert str(e.detail) == "FXA returned active: False for token."
1✔
223
            assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
224
            return
1✔
225
        self.fail("Should have raised AuthenticationFailed")
×
226

227
    @responses.activate
1✔
228
    def test_get_fxa_uid_from_oauth_token_has_wrong_scope_returns_error_response(  # noqa: E501
1✔
229
        self,
230
    ) -> None:
231
        now_time = int(datetime.now().timestamp())
1✔
232
        # Note: FXA iat and exp are timestamps in *milliseconds*
233
        exp_time = (now_time + 60 * 60) * 1000
1✔
234
        json_data: FxaResponse = {
1✔
235
            "active": True,
236
            "sub": self.uid,
237
            "exp": exp_time,
238
            "scope": "foo",
239
        }
240
        status_code = 200
1✔
241
        fxa_response = _setup_fxa_response(status_code, json_data)
1✔
242
        missing_scopes_token = "missing-scopes-123"
1✔
243
        cache_key = get_cache_key(missing_scopes_token)
1✔
244

245
        assert cache.get(cache_key) is None
1✔
246

247
        # get fxa response with no fxa uid for the first time
248
        try:
1✔
249
            get_fxa_uid_from_oauth_token(missing_scopes_token)
1✔
250
        except AuthenticationFailed as e:
1✔
251
            assert (
1✔
252
                str(e.detail)
253
                == "FXA token is missing scope: https://identity.mozilla.com/apps/relay."
254
            )
255
            assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
256
        assert cache.get(cache_key) == fxa_response
1✔
257

258
        # now check that the 2nd call did NOT make another fxa request
259
        try:
1✔
260
            get_fxa_uid_from_oauth_token(missing_scopes_token)
1✔
261
        except AuthenticationFailed as e:
1✔
262
            assert (
1✔
263
                str(e.detail)
264
                == "FXA token is missing scope: https://identity.mozilla.com/apps/relay."
265
            )
266
            assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
267
            return
1✔
NEW
268
        self.fail("Should have raised AuthenticationFailed")
×
269

270
    @responses.activate
1✔
271
    def test_get_fxa_uid_from_oauth_token_returns_fxa_response_with_no_fxa_uid(self):
1✔
272
        user_token = "user-123"
1✔
273
        now_time = int(datetime.now().timestamp())
1✔
274
        # Note: FXA iat and exp are timestamps in *milliseconds*
275
        exp_time = (now_time + 60 * 60) * 1000
1✔
276
        fxa_response = _setup_fxa_response(
1✔
277
            200,
278
            {
279
                "active": True,
280
                "exp": exp_time,
281
                "scope": "https://identity.mozilla.com/apps/relay",
282
            },
283
        )
284
        cache_key = get_cache_key(user_token)
1✔
285

286
        assert cache.get(cache_key) is None
1✔
287

288
        # get fxa response with no fxa uid for the first time
289
        try:
1✔
290
            get_fxa_uid_from_oauth_token(user_token)
1✔
291
        except NotFound as e:
1✔
292
            assert str(e.detail) == "FXA did not return an FXA UID."
1✔
293
            assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
294
        assert cache.get(cache_key) == fxa_response
1✔
295

296
        # now check that the 2nd call did NOT make another fxa request
297
        try:
1✔
298
            get_fxa_uid_from_oauth_token(user_token)
1✔
299
        except NotFound as e:
1✔
300
            assert str(e.detail) == "FXA did not return an FXA UID."
1✔
301
            assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
302
            return
1✔
303
        self.fail("Should have raised AuthenticationFailed")
×
304

305

306
class FxaTokenAuthenticationTest(TestCase):
1✔
307
    def setUp(self) -> None:
1✔
308
        self.auth = FxaTokenAuthentication()
1✔
309
        self.factory = RequestFactory()
1✔
310
        self.path = "/api/v1/relayaddresses/"
1✔
311
        self.fxa_verify_path = INTROSPECT_TOKEN_URL
1✔
312
        self.uid = "relay-user-fxa-uid"
1✔
313

314
    def tearDown(self) -> None:
1✔
315
        cache.clear()
1✔
316

317
    def test_no_authorization_header_returns_none(self) -> None:
1✔
318
        get_addresses_req = self.factory.get(self.path)
1✔
319
        assert self.auth.authenticate(get_addresses_req) is None
1✔
320

321
    def test_no_bearer_in_authorization_returns_none(self) -> None:
1✔
322
        headers = {"Authorization": "unexpected 123"}
1✔
323
        get_addresses_req = self.factory.get(self.path, headers=headers)
1✔
324
        assert self.auth.authenticate(get_addresses_req) is None
1✔
325

326
    def test_no_token_returns_400(self) -> None:
1✔
327
        client = APIClient()
1✔
328
        client.credentials(HTTP_AUTHORIZATION="Bearer ")
1✔
329
        response = client.get("/api/v1/relayaddresses/")
1✔
330
        assert response.status_code == 400
1✔
331
        assert response.json()["detail"] == "Missing FXA Token after 'Bearer'."
1✔
332

333
    @responses.activate
1✔
334
    def test_non_200_resp_from_fxa_raises_error_and_caches(self) -> None:
1✔
335
        fxa_response = _setup_fxa_response(401, {"error": "401"})
1✔
336
        not_found_token = "not-found-123"
1✔
337
        client = APIClient()
1✔
338
        client.credentials(HTTP_AUTHORIZATION=f"Bearer {not_found_token}")
1✔
339

340
        assert cache.get(get_cache_key(not_found_token)) is None
1✔
341

342
        response = client.get("/api/v1/relayaddresses/")
1✔
343
        assert response.status_code == 500
1✔
344
        assert response.json()["detail"] == "Did not receive a 200 response from FXA."
1✔
345

346
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
347
        assert cache.get(get_cache_key(not_found_token)) == fxa_response
1✔
348

349
        # now check that the code does NOT make another fxa request
350
        response = client.get("/api/v1/relayaddresses/")
1✔
351
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
352

353
    @responses.activate
1✔
354
    def test_non_200_non_json_resp_from_fxa_raises_error_and_caches(self) -> None:
1✔
355
        fxa_response = _setup_fxa_response(503, "Bad Gateway")
1✔
356
        not_found_token = "fxa-gw-error"
1✔
357
        client = APIClient()
1✔
358
        client.credentials(HTTP_AUTHORIZATION=f"Bearer {not_found_token}")
1✔
359

360
        assert cache.get(get_cache_key(not_found_token)) is None
1✔
361

362
        response = client.get("/api/v1/relayaddresses/")
1✔
363
        assert response.status_code == 500
1✔
364

365
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
366
        assert cache.get(get_cache_key(not_found_token)) == fxa_response
1✔
367

368
        # now check that the code does NOT make another fxa request
369
        response = client.get("/api/v1/relayaddresses/")
1✔
370
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
371

372
    @responses.activate
1✔
373
    def test_inactive_token_responds_with_401(self) -> None:
1✔
374
        fxa_response = _setup_fxa_response(200, {"active": False})
1✔
375
        inactive_token = "inactive-123"
1✔
376
        client = APIClient()
1✔
377
        client.credentials(HTTP_AUTHORIZATION=f"Bearer {inactive_token}")
1✔
378

379
        assert cache.get(get_cache_key(inactive_token)) is None
1✔
380

381
        response = client.get("/api/v1/relayaddresses/")
1✔
382
        assert response.status_code == 401
1✔
383
        assert response.json()["detail"] == "FXA returned active: False for token."
1✔
384
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
385
        assert cache.get(get_cache_key(inactive_token)) == fxa_response
1✔
386

387
        # now check that the code does NOT make another fxa request
388
        response = client.get("/api/v1/relayaddresses/")
1✔
389
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
390

391
    @responses.activate
1✔
392
    def test_200_resp_from_fxa_no_matching_user_raises_APIException(self) -> None:
1✔
393
        # I think this scope is realistic for a user that has not not accepted terms
394
        fxa_response = _setup_fxa_response(
1✔
395
            200,
396
            {
397
                "active": True,
398
                "sub": "not-a-relay-user",
399
                "scope": "https://identity.mozilla.com/apps/relay",
400
            },
401
        )
402
        non_user_token = "non-user-123"
1✔
403
        client = APIClient()
1✔
404
        client.credentials(HTTP_AUTHORIZATION=f"Bearer {non_user_token}")
1✔
405

406
        assert cache.get(get_cache_key(non_user_token)) is None
1✔
407

408
        response = client.get("/api/v1/relayaddresses/")
1✔
409
        assert response.status_code == 403
1✔
410
        expected_detail = (
1✔
411
            "Authenticated user does not have a Relay account."
412
            " Have they accepted the terms?"
413
        )
414
        assert response.json()["detail"] == expected_detail
1✔
415
        assert cache.get(get_cache_key(non_user_token)) == fxa_response
1✔
416

417
        # the code does NOT make another fxa request
418
        response = client.get("/api/v1/relayaddresses/")
1✔
419
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
420

421
    @responses.activate
1✔
422
    def test_200_resp_from_fxa_inactive_Relay_user_raises_APIException(self) -> None:
1✔
423
        sa: SocialAccount = baker.make(SocialAccount, uid=self.uid, provider="fxa")
1✔
424
        sa.user.is_active = False
1✔
425
        sa.user.save()
1✔
426
        now_time = int(datetime.now().timestamp())
1✔
427
        # Note: FXA iat and exp are timestamps in *milliseconds*
428
        exp_time = (now_time + 60 * 60) * 1000
1✔
429
        _setup_fxa_response(
1✔
430
            200,
431
            {
432
                "active": True,
433
                "sub": self.uid,
434
                "exp": exp_time,
435
                "scope": "https://identity.mozilla.com/apps/relay",
436
            },
437
        )
438
        inactive_user_token = "inactive-user-123"
1✔
439
        client = APIClient()
1✔
440
        client.credentials(HTTP_AUTHORIZATION=f"Bearer {inactive_user_token}")
1✔
441

442
        response = client.get("/api/v1/relayaddresses/")
1✔
443
        assert response.status_code == 403
1✔
444
        expected_detail = (
1✔
445
            "Authenticated user does not have an active Relay account."
446
            " Have they been deactivated?"
447
        )
448
        assert response.json()["detail"] == expected_detail
1✔
449

450
    @responses.activate
1✔
451
    def test_200_resp_from_fxa_for_user_returns_user_and_caches(self) -> None:
1✔
452
        sa: SocialAccount = baker.make(SocialAccount, uid=self.uid, provider="fxa")
1✔
453
        user_token = "user-123"
1✔
454
        client = APIClient()
1✔
455
        client.credentials(HTTP_AUTHORIZATION=f"Bearer {user_token}")
1✔
456
        now_time = int(datetime.now().timestamp())
1✔
457
        # Note: FXA iat and exp are timestamps in *milliseconds*
458
        exp_time = (now_time + 60 * 60) * 1000
1✔
459
        fxa_response = _setup_fxa_response(
1✔
460
            200,
461
            {
462
                "active": True,
463
                "sub": self.uid,
464
                "exp": exp_time,
465
                "scope": "https://identity.mozilla.com/apps/relay",
466
            },
467
        )
468

469
        assert cache.get(get_cache_key(user_token)) is None
1✔
470

471
        # check the endpoint status code
472
        response = client.get("/api/v1/relayaddresses/")
1✔
473
        assert response.status_code == 200
1✔
474
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
475
        assert cache.get(get_cache_key(user_token)) == fxa_response
1✔
476

477
        # check the function returns the right user
478
        headers = {"Authorization": f"Bearer {user_token}"}
1✔
479
        get_addresses_req = self.factory.get(self.path, headers=headers)
1✔
480
        auth_return = self.auth.authenticate(get_addresses_req)
1✔
481
        assert auth_return == (sa.user, user_token)
1✔
482

483
        # now check that the 2nd call did NOT make another fxa request
484
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
485
        assert cache.get(get_cache_key(user_token)) == fxa_response
1✔
486

487
    @responses.activate
1✔
488
    def test_write_requests_make_calls_to_fxa(self) -> None:
1✔
489
        sa: SocialAccount = baker.make(SocialAccount, uid=self.uid, provider="fxa")
1✔
490
        user_token = "user-123"
1✔
491
        client = APIClient()
1✔
492
        client.credentials(HTTP_AUTHORIZATION=f"Bearer {user_token}")
1✔
493
        now_time = int(datetime.now().timestamp())
1✔
494
        # Note: FXA iat and exp are timestamps in *milliseconds*
495
        exp_time = (now_time + 60 * 60) * 1000
1✔
496
        fxa_response = _setup_fxa_response(
1✔
497
            200,
498
            {
499
                "active": True,
500
                "sub": self.uid,
501
                "exp": exp_time,
502
                "scope": "https://identity.mozilla.com/apps/relay",
503
            },
504
        )
505

506
        assert cache.get(get_cache_key(user_token)) is None
1✔
507

508
        # check the endpoint status code
509
        response = client.get("/api/v1/relayaddresses/")
1✔
510
        assert response.status_code == 200
1✔
511
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
512
        assert cache.get(get_cache_key(user_token)) == fxa_response
1✔
513

514
        # check the function returns the right user
515
        headers = {"Authorization": f"Bearer {user_token}"}
1✔
516
        get_addresses_req = self.factory.get(self.path, headers=headers)
1✔
517
        auth_return = self.auth.authenticate(get_addresses_req)
1✔
518
        assert auth_return == (sa.user, user_token)
1✔
519

520
        # now check that the 2nd GET request did NOT make another fxa request
521
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
522
        assert cache.get(get_cache_key(user_token)) == fxa_response
1✔
523

524
        headers = {"Authorization": f"Bearer {user_token}"}
1✔
525

526
        # send POST to /api/v1/relayaddresses and check that cache is used - i.e.,
527
        # FXA is *NOT* called
528
        post_addresses_req = self.factory.post(self.path, headers=headers)
1✔
529
        auth_return = self.auth.authenticate(post_addresses_req)
1✔
530
        assert responses.assert_call_count(self.fxa_verify_path, 1) is True
1✔
531

532
        # send POST to another API endpoint and check that cache is NOT used
533
        post_webcompat = self.factory.post(
1✔
534
            "/api/v1/report_webcompat_issue", headers=headers
535
        )
536
        auth_return = self.auth.authenticate(post_webcompat)
1✔
537
        assert responses.assert_call_count(self.fxa_verify_path, 2) is True
1✔
538

539
        # send other write requests and check that FXA *IS* called
540
        put_addresses_req = self.factory.put(self.path, headers=headers)
1✔
541
        auth_return = self.auth.authenticate(put_addresses_req)
1✔
542
        assert responses.assert_call_count(self.fxa_verify_path, 3) is True
1✔
543

544
        delete_addresses_req = self.factory.delete(self.path, headers=headers)
1✔
545
        auth_return = self.auth.authenticate(delete_addresses_req)
1✔
546
        assert responses.assert_call_count(self.fxa_verify_path, 4) is True
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc