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

mozilla / fx-private-relay / 0c08bcbc-5b0e-4100-bdba-9b6181e22182

21 Jul 2025 11:05PM UTC coverage: 86.269% (-0.008%) from 86.277%
0c08bcbc-5b0e-4100-bdba-9b6181e22182

Pull #5734

circleci

web-flow
Merge branch 'main' into dependabot/npm_and_yarn/stripe/stripe-js-7.5.0
Pull Request #5734: build(deps): bump @stripe/stripe-js from 7.4.0 to 7.5.0

2734 of 3943 branches covered (69.34%)

Branch coverage included in aggregate %.

17873 of 19944 relevant lines covered (89.62%)

9.97 hits per line

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

98.21
/privaterelay/tests/model_tests.py
1
import random
1✔
2
from datetime import UTC, datetime, timedelta
1✔
3
from unittest.mock import patch
1✔
4
from uuid import uuid4
1✔
5

6
from django.conf import settings
1✔
7
from django.contrib.auth.models import User
1✔
8
from django.test import TestCase, override_settings
1✔
9

10
import pytest
1✔
11
from allauth.socialaccount.models import SocialAccount
1✔
12
from model_bakery import baker
1✔
13

14
from emails.models import AbuseMetrics, DomainAddress, RelayAddress
1✔
15

16
from ..exceptions import CannotMakeSubdomainException
1✔
17
from ..models import Profile
1✔
18
from .utils import (
1✔
19
    make_free_test_user,
20
    phone_subscription,
21
    premium_subscription,
22
    vpn_subscription,
23
)
24

25
if settings.PHONES_ENABLED:
1!
26
    from phones.models import RealPhone, RelayNumber
1✔
27

28

29
class ProfileTestCase(TestCase):
1✔
30
    """Base class for Profile tests."""
31

32
    def setUp(self) -> None:
1✔
33
        user = baker.make(User)
1✔
34
        self.profile = user.profile
1✔
35
        assert self.profile.server_storage is True
1✔
36

37
    def get_or_create_social_account(self) -> SocialAccount:
1✔
38
        """Get the test user's social account, creating if needed."""
39
        social_account, _ = SocialAccount.objects.get_or_create(
1✔
40
            user=self.profile.user,
41
            provider="fxa",
42
            defaults={
43
                "uid": str(uuid4()),
44
                "extra_data": {"avatar": "image.png", "subscriptions": []},
45
            },
46
        )
47
        return social_account
1✔
48

49
    def upgrade_to_premium(self) -> None:
1✔
50
        """Add an unlimited emails subscription to the user."""
51
        social_account = self.get_or_create_social_account()
1✔
52
        social_account.extra_data["subscriptions"].append(premium_subscription())
1✔
53
        social_account.save()
1✔
54

55
    def upgrade_to_phone(self) -> None:
1✔
56
        """Add a phone plan to the user."""
57
        social_account = self.get_or_create_social_account()
1✔
58
        social_account.extra_data["subscriptions"].append(phone_subscription())
1✔
59
        if not self.profile.has_premium:
1!
60
            social_account.extra_data["subscriptions"].append(premium_subscription())
1✔
61
        social_account.save()
1✔
62

63
    def upgrade_to_vpn_bundle(self) -> None:
1✔
64
        """Add a phone plan to the user."""
65
        social_account = self.get_or_create_social_account()
1✔
66
        social_account.extra_data["subscriptions"].append(vpn_subscription())
1✔
67
        if not self.profile.has_premium:
1!
68
            social_account.extra_data["subscriptions"].append(premium_subscription())
1✔
69
        if not self.profile.has_phone:
1!
70
            social_account.extra_data["subscriptions"].append(phone_subscription())
1✔
71
        social_account.save()
1✔
72

73

74
class ProfileBounceTestCase(ProfileTestCase):
1✔
75
    """Base class for Profile tests that check for bounces."""
76

77
    def set_hard_bounce(self) -> datetime:
1✔
78
        """
79
        Set a hard bounce pause for the profile, return the bounce time.
80

81
        This happens when the user's email server reports a hard bounce, such as
82
        saying the email does not exist.
83
        """
84
        self.profile.last_hard_bounce = datetime.now(UTC) - timedelta(
1✔
85
            days=settings.HARD_BOUNCE_ALLOWED_DAYS - 1
86
        )
87
        self.profile.save()
1✔
88
        return self.profile.last_hard_bounce
1✔
89

90
    def set_soft_bounce(self) -> datetime:
1✔
91
        """
92
        Set a soft bounce for the profile, return the bounce time.
93

94
        This happens when the user's email server reports a soft bounce, such as
95
        saying the user's mailbox is full.
96
        """
97
        self.profile.last_soft_bounce = datetime.now(UTC) - timedelta(
1✔
98
            days=settings.SOFT_BOUNCE_ALLOWED_DAYS - 1
99
        )
100
        self.profile.save()
1✔
101
        return self.profile.last_soft_bounce
1✔
102

103

104
class ProfileCheckBouncePause(ProfileBounceTestCase):
1✔
105
    """Tests for Profile.check_bounce_pause()"""
106

107
    def test_no_bounces(self) -> None:
1✔
108
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
109

110
        assert bounce_paused is False
1✔
111
        assert bounce_type == ""
1✔
112

113
    def test_hard_bounce_pending(self) -> None:
1✔
114
        self.set_hard_bounce()
1✔
115
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
116
        assert bounce_paused is True
1✔
117
        assert bounce_type == "hard"
1✔
118

119
    def test_soft_bounce_pending(self) -> None:
1✔
120
        self.set_soft_bounce()
1✔
121
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
122
        assert bounce_paused is True
1✔
123
        assert bounce_type == "soft"
1✔
124

125
    def test_hard_and_soft_bounce_pending_shows_hard(self) -> None:
1✔
126
        self.set_hard_bounce()
1✔
127
        self.set_soft_bounce()
1✔
128
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
129
        assert bounce_paused is True
1✔
130
        assert bounce_type == "hard"
1✔
131

132
    def test_hard_bounce_over_resets_timer(self) -> None:
1✔
133
        self.profile.last_hard_bounce = datetime.now(UTC) - timedelta(
1✔
134
            days=settings.HARD_BOUNCE_ALLOWED_DAYS + 1
135
        )
136
        self.profile.save()
1✔
137
        assert self.profile.last_hard_bounce is not None
1✔
138

139
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
140

141
        assert bounce_paused is False
1✔
142
        assert bounce_type == ""
1✔
143
        assert self.profile.last_hard_bounce is None
1✔
144

145
    def test_soft_bounce_over_resets_timer(self) -> None:
1✔
146
        self.profile.last_soft_bounce = datetime.now(UTC) - timedelta(
1✔
147
            days=settings.SOFT_BOUNCE_ALLOWED_DAYS + 1
148
        )
149
        self.profile.save()
1✔
150
        assert self.profile.last_soft_bounce is not None
1✔
151

152
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
153

154
        assert bounce_paused is False
1✔
155
        assert bounce_type == ""
1✔
156
        assert self.profile.last_soft_bounce is None
1✔
157

158

159
class ProfileNextEmailTryDateTest(ProfileBounceTestCase):
1✔
160
    """Tests for Profile.next_email_try"""
161

162
    def test_no_bounces_returns_today(self) -> None:
1✔
163
        assert self.profile.next_email_try.date() == datetime.now(UTC).date()
1✔
164

165
    def test_hard_bounce_returns_proper_datemath(self) -> None:
1✔
166
        last_hard_bounce = self.set_hard_bounce()
1✔
167
        expected_next_try_date = last_hard_bounce + timedelta(
1✔
168
            days=settings.HARD_BOUNCE_ALLOWED_DAYS
169
        )
170
        assert self.profile.next_email_try.date() == expected_next_try_date.date()
1✔
171

172
    def test_soft_bounce_returns_proper_datemath(self) -> None:
1✔
173
        last_soft_bounce = self.set_soft_bounce()
1✔
174
        expected_next_try_date = last_soft_bounce + timedelta(
1✔
175
            days=settings.SOFT_BOUNCE_ALLOWED_DAYS
176
        )
177
        assert self.profile.next_email_try.date() == expected_next_try_date.date()
1✔
178

179
    def test_hard_and_soft_bounce_returns_hard_datemath(self) -> None:
1✔
180
        last_soft_bounce = self.set_soft_bounce()
1✔
181
        last_hard_bounce = self.set_hard_bounce()
1✔
182
        assert last_soft_bounce != last_hard_bounce
1✔
183
        expected_next_try_date = last_hard_bounce + timedelta(
1✔
184
            days=settings.HARD_BOUNCE_ALLOWED_DAYS
185
        )
186
        assert self.profile.next_email_try.date() == expected_next_try_date.date()
1✔
187

188

189
class ProfileLastBounceDateTest(ProfileBounceTestCase):
1✔
190
    """Tests for Profile.last_bounce_date"""
191

192
    def test_no_bounces_returns_None(self) -> None:
1✔
193
        assert self.profile.last_bounce_date is None
1✔
194

195
    def test_soft_bounce_returns_its_date(self) -> None:
1✔
196
        self.set_soft_bounce()
1✔
197
        assert self.profile.last_bounce_date == self.profile.last_soft_bounce
1✔
198

199
    def test_hard_bounce_returns_its_date(self) -> None:
1✔
200
        self.set_hard_bounce()
1✔
201
        assert self.profile.last_bounce_date == self.profile.last_hard_bounce
1✔
202

203
    def test_hard_and_soft_bounces_returns_hard_date(self) -> None:
1✔
204
        self.set_soft_bounce()
1✔
205
        self.set_hard_bounce()
1✔
206
        assert self.profile.last_bounce_date == self.profile.last_hard_bounce
1✔
207

208

209
class ProfileHasPremiumTest(ProfileTestCase):
1✔
210
    """Tests for Profile.has_premium"""
211

212
    def test_default_False(self) -> None:
1✔
213
        assert self.profile.has_premium is False
1✔
214

215
    def test_premium_subscription_returns_True(self) -> None:
1✔
216
        self.upgrade_to_premium()
1✔
217
        assert self.profile.has_premium is True
1✔
218

219
    def test_phone_returns_True(self) -> None:
1✔
220
        self.upgrade_to_phone()
1✔
221
        assert self.profile.has_premium is True
1✔
222

223
    def test_vpn_bundle_returns_True(self) -> None:
1✔
224
        self.upgrade_to_vpn_bundle()
1✔
225
        assert self.profile.has_premium is True
1✔
226

227

228
class ProfileHasPhoneTest(ProfileTestCase):
1✔
229
    """Tests for Profile.has_phone"""
230

231
    def test_default_False(self) -> None:
1✔
232
        assert self.profile.has_phone is False
1✔
233

234
    def test_premium_subscription_returns_False(self) -> None:
1✔
235
        self.upgrade_to_premium()
1✔
236
        assert self.profile.has_phone is False
1✔
237

238
    def test_phone_returns_True(self) -> None:
1✔
239
        self.upgrade_to_phone()
1✔
240
        assert self.profile.has_phone is True
1✔
241

242
    def test_vpn_bundle_returns_True(self) -> None:
1✔
243
        self.upgrade_to_vpn_bundle()
1✔
244
        assert self.profile.has_phone is True
1✔
245

246

247
class ProfileHasMegabundleTest(ProfileTestCase):
1✔
248
    """Tests for Profile.has_megabundle"""
249

250
    def test_default_False(self) -> None:
1✔
251
        assert self.profile.has_megabundle is False
1✔
252

253
    def test_has_megabundle_returns_True(self) -> None:
1✔
254
        social_account = self.get_or_create_social_account()
1✔
255
        social_account.extra_data["subscriptions"] = (
1✔
256
            settings.SUBSCRIPTIONS_THAT_MEGABUNDLE_PROVIDES.copy()
257
        )
258
        social_account.save()
1✔
259

260
        assert self.profile.has_megabundle is True
1✔
261

262
    def test_has_megabundle_returns_False_with_partial_subscription(self) -> None:
1✔
263
        if len(settings.SUBSCRIPTIONS_THAT_MEGABUNDLE_PROVIDES) < 2:
1!
264
            pytest.skip(
×
265
                "Test requires multiple subscriptions in "
266
                "settings.SUBSCRIPTIONS_THAT_MEGABUNDLE_PROVIDES"
267
            )
268

269
        partial_subs = settings.SUBSCRIPTIONS_THAT_MEGABUNDLE_PROVIDES[:-1]
1✔
270
        social_account = self.get_or_create_social_account()
1✔
271
        social_account.extra_data["subscriptions"] = partial_subs
1✔
272
        social_account.save()
1✔
273

274
        assert self.profile.has_megabundle is False
1✔
275

276
    def test_has_megabundle_returns_False_with_no_fxa(self) -> None:
1✔
277
        SocialAccount.objects.filter(user=self.profile.user).delete()
1✔
278
        assert self.profile.has_megabundle is False
1✔
279

280

281
@pytest.mark.skipif(not settings.PHONES_ENABLED, reason="PHONES_ENABLED is False")
1✔
282
@override_settings(PHONES_NO_CLIENT_CALLS_IN_TEST=True)
1✔
283
class ProfileDatePhoneRegisteredTest(ProfileTestCase):
1✔
284
    """Tests for Profile.date_phone_registered"""
285

286
    def test_default_None(self) -> None:
1✔
287
        assert self.profile.date_phone_registered is None
1✔
288

289
    def test_real_phone_no_relay_number_returns_verified_date(self) -> None:
1✔
290
        self.upgrade_to_phone()
1✔
291
        datetime_now = datetime.now(UTC)
1✔
292
        RealPhone.objects.create(
1✔
293
            user=self.profile.user,
294
            number="+12223334444",
295
            verified=True,
296
            verified_date=datetime_now,
297
        )
298
        assert self.profile.date_phone_registered == datetime_now
1✔
299

300
    def test_two_real_phones_returns_verified_date(self) -> None:
1✔
301
        self.upgrade_to_phone()
1✔
302
        datetime_now = datetime.now(UTC)
1✔
303
        RealPhone.objects.create(
1✔
304
            user=self.profile.user, number="+12223335555", verified=False
305
        )
306
        RealPhone.objects.create(
1✔
307
            user=self.profile.user,
308
            number="+12223334444",
309
            verified=True,
310
            verified_date=datetime_now,
311
        )
312
        assert self.profile.date_phone_registered == datetime_now
1✔
313

314
    def test_real_phone_and_relay_number_w_created_at_returns_created_at_date(
1✔
315
        self,
316
    ) -> None:
317
        self.upgrade_to_phone()
1✔
318
        datetime_now = datetime.now(UTC)
1✔
319
        phone_user = self.profile.user
1✔
320
        RealPhone.objects.create(
1✔
321
            user=phone_user,
322
            number="+12223334444",
323
            verified=True,
324
            verified_date=datetime_now,
325
        )
326
        relay_number = RelayNumber.objects.create(user=phone_user)
1✔
327
        assert self.profile.date_phone_registered == relay_number.created_at
1✔
328

329
    def test_real_phone_and_relay_number_wo_created_at_returns_verified_date(
1✔
330
        self,
331
    ) -> None:
332
        self.upgrade_to_phone()
1✔
333
        datetime_now = datetime.now(UTC)
1✔
334
        phone_user = self.profile.user
1✔
335
        real_phone = RealPhone.objects.create(
1✔
336
            user=phone_user,
337
            number="+12223334444",
338
            verified=True,
339
            verified_date=datetime_now,
340
        )
341
        relay_number = RelayNumber.objects.create(user=phone_user)
1✔
342
        # since created_at is auto set, update to None
343
        relay_number.created_at = None
1✔
344
        relay_number.save()
1✔
345
        assert self.profile.date_phone_registered == real_phone.verified_date
1✔
346

347

348
class ProfileTotalMasksTest(ProfileTestCase):
1✔
349
    """Tests for Profile.total_masks"""
350

351
    def test_total_masks(self) -> None:
1✔
352
        self.upgrade_to_premium()
1✔
353
        self.profile.add_subdomain("totalmasks")
1✔
354
        assert self.profile.total_masks == 0
1✔
355
        num_relay_addresses = random.randint(0, 2)
1✔
356
        for _ in list(range(num_relay_addresses)):
1!
357
            baker.make(RelayAddress, user=self.profile.user)
×
358
        num_domain_addresses = random.randint(0, 2)
1✔
359
        for i in list(range(num_domain_addresses)):
1!
360
            baker.make(DomainAddress, user=self.profile.user, address=f"mask{i}")
×
361
        assert self.profile.total_masks == num_relay_addresses + num_domain_addresses
1✔
362

363

364
class ProfileAtMaskLimitTest(ProfileTestCase):
1✔
365
    """Tests for Profile.at_mask_limit"""
366

367
    def test_premium_user_returns_False(self) -> None:
1✔
368
        self.upgrade_to_premium()
1✔
369
        assert self.profile.at_mask_limit is False
1✔
370
        baker.make(
1✔
371
            RelayAddress,
372
            user=self.profile.user,
373
            _quantity=settings.MAX_NUM_FREE_ALIASES,
374
        )
375
        assert self.profile.at_mask_limit is False
1✔
376

377
    def test_free_user(self) -> None:
1✔
378
        assert self.profile.at_mask_limit is False
1✔
379
        baker.make(
1✔
380
            RelayAddress,
381
            user=self.profile.user,
382
            _quantity=settings.MAX_NUM_FREE_ALIASES,
383
        )
384
        assert self.profile.at_mask_limit is True
1✔
385

386

387
class ProfileAddSubdomainTest(ProfileTestCase):
1✔
388
    """Tests for Profile.add_subdomain()"""
389

390
    def test_new_unlimited_profile(self) -> None:
1✔
391
        self.upgrade_to_premium()
1✔
392
        assert self.profile.add_subdomain("newpremium") == "newpremium"
1✔
393

394
    def test_lowercases_subdomain_value(self) -> None:
1✔
395
        self.upgrade_to_premium()
1✔
396
        assert self.profile.add_subdomain("mIxEdcAsE") == "mixedcase"
1✔
397

398
    def test_non_premium_user_raises_exception(self) -> None:
1✔
399
        expected_msg = "error-premium-set-subdomain"
1✔
400
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
401
            self.profile.add_subdomain("test")
1✔
402

403
    def test_calling_again_raises_exception(self) -> None:
1✔
404
        self.upgrade_to_premium()
1✔
405
        subdomain = "test"
1✔
406
        self.profile.subdomain = subdomain
1✔
407
        self.profile.save()
1✔
408

409
        expected_msg = "error-premium-cannot-change-subdomain"
1✔
410
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
411
            self.profile.add_subdomain(subdomain)
1✔
412

413
    def test_badword_subdomain_raises_exception(self) -> None:
1✔
414
        self.upgrade_to_premium()
1✔
415
        expected_msg = "error-subdomain-not-available"
1✔
416
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
417
            self.profile.add_subdomain("angry")
1✔
418

419
    def test_blocked_word_subdomain_raises_exception(self) -> None:
1✔
420
        self.upgrade_to_premium()
1✔
421
        expected_msg = "error-subdomain-not-available"
1✔
422
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
423
            self.profile.add_subdomain("mozilla")
1✔
424

425
    def test_empty_subdomain_raises(self) -> None:
1✔
426
        self.upgrade_to_premium()
1✔
427
        expected_msg = "error-subdomain-cannot-be-empty-or-null"
1✔
428

429
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
430
            self.profile.add_subdomain("")
1✔
431

432
    def test_null_subdomain_raises(self) -> None:
1✔
433
        self.upgrade_to_premium()
1✔
434
        expected_msg = "error-subdomain-cannot-be-empty-or-null"
1✔
435

436
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
437
            self.profile.add_subdomain(None)
1✔
438

439
    def test_subdomain_with_space_at_end_raises(self) -> None:
1✔
440
        self.upgrade_to_premium()
1✔
441
        expected_msg = "error-subdomain-not-available"
1✔
442

443
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
444
            self.profile.add_subdomain("mydomain ")
1✔
445

446

447
class ProfileSaveTest(ProfileTestCase):
1✔
448
    """Tests for Profile.save()"""
449

450
    def test_lowercases_subdomain_value(self) -> None:
1✔
451
        self.upgrade_to_premium()
1✔
452
        self.profile.subdomain = "mIxEdcAsE"
1✔
453
        self.profile.save()
1✔
454
        assert self.profile.subdomain == "mixedcase"
1✔
455

456
    def test_lowercases_subdomain_value_with_update_fields(self) -> None:
1✔
457
        """With update_fields, the subdomain is still lowercased."""
458
        self.upgrade_to_premium()
1✔
459
        assert self.profile.subdomain is None
1✔
460

461
        # Use QuerySet.update to avoid model .save()
462
        Profile.objects.filter(id=self.profile.id).update(subdomain="mIxEdcAsE")
1✔
463
        self.profile.refresh_from_db()
1✔
464
        assert self.profile.subdomain == "mIxEdcAsE"
1✔
465

466
        # Update a different field with update_fields to avoid a full model save
467
        new_date_subscribed = datetime(2023, 3, 3, tzinfo=UTC)
1✔
468
        self.profile.date_subscribed = new_date_subscribed
1✔
469
        self.profile.save(update_fields={"date_subscribed"})
1✔
470

471
        # Since .save() added to update_fields, subdomain is now lowercase
472
        self.profile.refresh_from_db()
1✔
473
        assert self.profile.date_subscribed == new_date_subscribed
1✔
474
        assert self.profile.subdomain == "mixedcase"
1✔
475

476
    TEST_DESCRIPTION = "test description"
1✔
477
    TEST_USED_ON = TEST_GENERATED_FOR = "secret.com"
1✔
478

479
    def add_relay_address(self) -> RelayAddress:
1✔
480
        return baker.make(
1✔
481
            RelayAddress,
482
            user=self.profile.user,
483
            description=self.TEST_DESCRIPTION,
484
            generated_for=self.TEST_GENERATED_FOR,
485
            used_on=self.TEST_USED_ON,
486
        )
487

488
    def add_domain_address(self) -> DomainAddress:
1✔
489
        self.upgrade_to_premium()
1✔
490
        self.profile.subdomain = "somesubdomain"
1✔
491
        self.profile.save()
1✔
492
        return baker.make(
1✔
493
            DomainAddress,
494
            user=self.profile.user,
495
            address="localpart",
496
            description=self.TEST_DESCRIPTION,
497
            used_on=self.TEST_USED_ON,
498
        )
499

500
    def test_save_server_storage_true_doesnt_delete_data(self) -> None:
1✔
501
        relay_address = self.add_relay_address()
1✔
502
        self.profile.server_storage = True
1✔
503
        self.profile.save()
1✔
504

505
        relay_address.refresh_from_db()
1✔
506
        assert relay_address.description == self.TEST_DESCRIPTION
1✔
507
        assert relay_address.generated_for == self.TEST_GENERATED_FOR
1✔
508
        assert relay_address.used_on == self.TEST_USED_ON
1✔
509

510
    def test_save_server_storage_false_deletes_data(self) -> None:
1✔
511
        relay_address = self.add_relay_address()
1✔
512
        domain_address = self.add_domain_address()
1✔
513
        self.profile.server_storage = False
1✔
514
        self.profile.save()
1✔
515

516
        relay_address.refresh_from_db()
1✔
517
        domain_address.refresh_from_db()
1✔
518
        assert relay_address.description == ""
1✔
519
        assert relay_address.generated_for == ""
1✔
520
        assert relay_address.used_on == ""
1✔
521
        assert domain_address.description == ""
1✔
522
        assert domain_address.used_on == ""
1✔
523

524
    def add_four_relay_addresses(self, user: User | None = None) -> list[RelayAddress]:
1✔
525
        if user is None:
1✔
526
            user = self.profile.user
1✔
527
        return baker.make(
1✔
528
            RelayAddress,
529
            user=user,
530
            description=self.TEST_DESCRIPTION,
531
            generated_for=self.TEST_GENERATED_FOR,
532
            used_on=self.TEST_USED_ON,
533
            _quantity=4,
534
        )
535

536
    def test_save_server_storage_false_deletes_ALL_data(self) -> None:
1✔
537
        self.add_four_relay_addresses()
1✔
538
        self.profile.server_storage = False
1✔
539
        self.profile.save()
1✔
540

541
        for relay_address in RelayAddress.objects.filter(user=self.profile.user):
1✔
542
            assert relay_address.description == ""
1✔
543
            assert relay_address.generated_for == ""
1✔
544

545
    def test_save_server_storage_false_only_deletes_that_profiles_data(self) -> None:
1✔
546
        other_user = make_free_test_user()
1✔
547
        assert other_user.profile.server_storage is True
1✔
548
        self.add_four_relay_addresses()
1✔
549
        self.add_four_relay_addresses(user=other_user)
1✔
550
        self.profile.server_storage = False
1✔
551
        self.profile.save()
1✔
552

553
        for relay_address in RelayAddress.objects.filter(user=self.profile.user):
1✔
554
            assert relay_address.description == ""
1✔
555
            assert relay_address.generated_for == ""
1✔
556
            assert relay_address.used_on == ""
1✔
557

558
        for relay_address in RelayAddress.objects.filter(user=other_user):
1✔
559
            assert relay_address.description == self.TEST_DESCRIPTION
1✔
560
            assert relay_address.generated_for == self.TEST_GENERATED_FOR
1✔
561
            assert relay_address.used_on == self.TEST_USED_ON
1✔
562

563

564
class ProfileDisplayNameTest(ProfileTestCase):
1✔
565
    """Tests for Profile.display_name"""
566

567
    def test_exists(self) -> None:
1✔
568
        display_name = "Display Name"
1✔
569
        social_account = self.get_or_create_social_account()
1✔
570
        social_account.extra_data["displayName"] = display_name
1✔
571
        social_account.save()
1✔
572
        assert self.profile.display_name == display_name
1✔
573

574
    def test_display_name_does_not_exist(self) -> None:
1✔
575
        self.get_or_create_social_account()
1✔
576
        assert self.profile.display_name is None
1✔
577

578

579
class ProfileLanguageTest(ProfileTestCase):
1✔
580
    """Test Profile.language"""
581

582
    def test_no_fxa_extra_data_locale_returns_default_en(self) -> None:
1✔
583
        social_account = self.get_or_create_social_account()
1✔
584
        assert "locale" not in social_account.extra_data
1✔
585
        assert self.profile.language == "en"
1✔
586

587
    def test_no_fxa_locale_returns_default_en(self) -> None:
1✔
588
        assert self.profile.language == "en"
1✔
589

590
    def test_fxa_locale_de_returns_de(self) -> None:
1✔
591
        social_account = self.get_or_create_social_account()
1✔
592
        social_account.extra_data["locale"] = "de,en-US;q=0.9,en;q=0.8"
1✔
593
        social_account.save()
1✔
594
        assert self.profile.language == "de"
1✔
595

596

597
class ProfileFxaLocaleInPremiumCountryTest(ProfileTestCase):
1✔
598
    """Tests for Profile.fxa_locale_in_premium_country"""
599

600
    def set_fxa_locale(self, locale: str) -> None:
1✔
601
        social_account = self.get_or_create_social_account()
1✔
602
        social_account.extra_data["locale"] = locale
1✔
603
        social_account.save()
1✔
604

605
    def test_when_premium_available_returns_True(self) -> None:
1✔
606
        self.set_fxa_locale("de-DE,en-xx;q=0.9,en;q=0.8")
1✔
607
        assert self.profile.fxa_locale_in_premium_country is True
1✔
608

609
    def test_en_implies_premium_available(self) -> None:
1✔
610
        self.set_fxa_locale("en;q=0.8")
1✔
611
        assert self.profile.fxa_locale_in_premium_country is True
1✔
612

613
    def test_when_premium_unavailable_returns_False(self) -> None:
1✔
614
        self.set_fxa_locale("en-IN, en;q=0.8")
1✔
615
        assert self.profile.fxa_locale_in_premium_country is False
1✔
616

617
    def test_when_premium_available_by_language_code_returns_True(self) -> None:
1✔
618
        self.set_fxa_locale("de;q=0.8")
1✔
619
        assert self.profile.fxa_locale_in_premium_country is True
1✔
620

621
    def test_invalid_language_code_returns_False(self) -> None:
1✔
622
        self.set_fxa_locale("xx;q=0.8")
1✔
623
        assert self.profile.fxa_locale_in_premium_country is False
1✔
624

625
    def test_when_premium_unavailable_by_language_code_returns_False(self) -> None:
1✔
626
        self.set_fxa_locale("zh;q=0.8")
1✔
627
        assert self.profile.fxa_locale_in_premium_country is False
1✔
628

629
    def test_no_fxa_account_returns_False(self) -> None:
1✔
630
        assert self.profile.fxa_locale_in_premium_country is False
1✔
631

632
    def test_in_estonia(self):
1✔
633
        """Estonia (EE) was added in August 2023."""
634
        self.set_fxa_locale("et-ee,et;q=0.8")
1✔
635
        assert self.profile.fxa_locale_in_premium_country is True
1✔
636

637

638
class ProfileJoinedBeforePremiumReleaseTest(ProfileTestCase):
1✔
639
    """Tests for Profile.joined_before_premium_release"""
640

641
    def test_returns_True(self) -> None:
1✔
642
        before = "2021-10-18 17:00:00+00:00"
1✔
643
        self.profile.user.date_joined = datetime.fromisoformat(before)
1✔
644
        assert self.profile.joined_before_premium_release
1✔
645

646
    def test_returns_False(self) -> None:
1✔
647
        after = "2021-10-28 17:00:00+00:00"
1✔
648
        self.profile.user.date_joined = datetime.fromisoformat(after)
1✔
649
        assert self.profile.joined_before_premium_release is False
1✔
650

651

652
class ProfileDefaultsTest(ProfileTestCase):
1✔
653
    """Tests for default Profile values"""
654

655
    def test_user_created_after_premium_release_server_storage_True(self) -> None:
1✔
656
        assert self.profile.server_storage
1✔
657

658
    def test_emails_replied_new_user_aggregates_sum_of_replies_to_zero(self) -> None:
1✔
659
        assert self.profile.emails_replied == 0
1✔
660

661

662
class ProfileEmailsRepliedTest(ProfileTestCase):
1✔
663
    """Tests for Profile.emails_replied"""
664

665
    def test_premium_user_aggregates_replies_from_all_addresses(self) -> None:
1✔
666
        self.upgrade_to_premium()
1✔
667
        self.profile.subdomain = "test"
1✔
668
        self.profile.num_email_replied_in_deleted_address = 1
1✔
669
        self.profile.save()
1✔
670
        baker.make(RelayAddress, user=self.profile.user, num_replied=3)
1✔
671
        baker.make(
1✔
672
            DomainAddress, user=self.profile.user, address="lower-case", num_replied=5
673
        )
674

675
        assert self.profile.emails_replied == 9
1✔
676

677
    def test_free_user_aggregates_replies_from_relay_addresses(self) -> None:
1✔
678
        baker.make(RelayAddress, user=self.profile.user, num_replied=3)
1✔
679
        baker.make(RelayAddress, user=self.profile.user, num_replied=5)
1✔
680

681
        assert self.profile.emails_replied == 8
1✔
682

683

684
class ProfileUpdateAbuseMetricTest(ProfileTestCase):
1✔
685
    """Tests for Profile.update_abuse_metric()"""
686

687
    def setUp(self) -> None:
1✔
688
        super().setUp()
1✔
689
        self.get_or_create_social_account()
1✔
690
        self.abuse_metric = baker.make(AbuseMetrics, user=self.profile.user)
1✔
691

692
        patcher_logger = patch("privaterelay.models.abuse_logger.info")
1✔
693
        self.mocked_abuse_info = patcher_logger.start()
1✔
694
        self.addCleanup(patcher_logger.stop)
1✔
695

696
        # Selectively patch datatime.now() for emails models
697
        # https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking
698
        patcher = patch("privaterelay.models.datetime")
1✔
699
        mocked_datetime = patcher.start()
1✔
700
        self.addCleanup(patcher.stop)
1✔
701

702
        self.expected_now = datetime.now(UTC)
1✔
703
        mocked_datetime.combine.return_value = datetime.combine(
1✔
704
            datetime.now(UTC).date(), datetime.min.time()
705
        )
706
        mocked_datetime.now.return_value = self.expected_now
1✔
707
        mocked_datetime.side_effect = lambda *args, **kw: datetime(*args, **kw)
1✔
708

709
    @override_settings(MAX_FORWARDED_PER_DAY=5)
1✔
710
    def test_flags_profile_when_emails_forwarded_abuse_threshold_met(self) -> None:
1✔
711
        self.abuse_metric.num_email_forwarded_per_day = 4
1✔
712
        self.abuse_metric.save()
1✔
713
        assert self.profile.last_account_flagged is None
1✔
714

715
        self.profile.update_abuse_metric(email_forwarded=True)
1✔
716
        self.abuse_metric.refresh_from_db()
1✔
717

718
        assert self.profile.fxa
1✔
719
        self.mocked_abuse_info.assert_called_once_with(
1✔
720
            "Abuse flagged",
721
            extra={
722
                "uid": self.profile.fxa.uid,
723
                "flagged": self.expected_now.timestamp(),
724
                "replies": 0,
725
                "addresses": 0,
726
                "forwarded": 5,
727
                "forwarded_size_in_bytes": 0,
728
            },
729
        )
730
        assert self.abuse_metric.num_email_forwarded_per_day == 5
1✔
731
        assert self.profile.last_account_flagged == self.expected_now
1✔
732

733
    @override_settings(MAX_FORWARDED_EMAIL_SIZE_PER_DAY=100)
1✔
734
    def test_flags_profile_when_forwarded_email_size_abuse_threshold_met(self) -> None:
1✔
735
        self.abuse_metric.forwarded_email_size_per_day = 50
1✔
736
        self.abuse_metric.save()
1✔
737
        assert self.profile.last_account_flagged is None
1✔
738

739
        self.profile.update_abuse_metric(forwarded_email_size=50)
1✔
740
        self.abuse_metric.refresh_from_db()
1✔
741

742
        assert self.profile.fxa
1✔
743
        self.mocked_abuse_info.assert_called_once_with(
1✔
744
            "Abuse flagged",
745
            extra={
746
                "uid": self.profile.fxa.uid,
747
                "flagged": self.expected_now.timestamp(),
748
                "replies": 0,
749
                "addresses": 0,
750
                "forwarded": 0,
751
                "forwarded_size_in_bytes": 100,
752
            },
753
        )
754
        assert self.abuse_metric.forwarded_email_size_per_day == 100
1✔
755
        assert self.profile.last_account_flagged == self.expected_now
1✔
756

757

758
class ProfileMetricsEnabledTest(ProfileTestCase):
1✔
759
    def test_no_fxa_means_metrics_enabled(self) -> None:
1✔
760
        assert not self.profile.fxa
1✔
761
        assert self.profile.metrics_enabled
1✔
762

763
    def test_fxa_legacy_means_metrics_enabled(self) -> None:
1✔
764
        self.get_or_create_social_account()
1✔
765
        assert self.profile.fxa
1✔
766
        assert "metricsEnabled" not in self.profile.fxa.extra_data
1✔
767
        assert self.profile.metrics_enabled
1✔
768

769
    def test_fxa_opt_in_means_metrics_enabled(self) -> None:
1✔
770
        social_account = self.get_or_create_social_account()
1✔
771
        social_account.extra_data["metricsEnabled"] = True
1✔
772
        social_account.save()
1✔
773
        assert self.profile.fxa
1✔
774
        assert self.profile.metrics_enabled
1✔
775

776
    def test_fxa_opt_out_means_metrics_disabled(self) -> None:
1✔
777
        social_account = self.get_or_create_social_account()
1✔
778
        social_account.extra_data["metricsEnabled"] = False
1✔
779
        social_account.save()
1✔
780
        assert self.profile.fxa
1✔
781
        assert not self.profile.metrics_enabled
1✔
782

783

784
class ProfilePlanTest(ProfileTestCase):
1✔
785
    def test_free_user(self) -> None:
1✔
786
        assert self.profile.plan == "free"
1✔
787

788
    def test_premium_user(self) -> None:
1✔
789
        self.upgrade_to_premium()
1✔
790
        assert self.profile.plan == "email"
1✔
791

792
    def test_phone_user(self) -> None:
1✔
793
        self.upgrade_to_phone()
1✔
794
        assert self.profile.plan == "phone"
1✔
795

796
    def test_vpn_bundle_user(self) -> None:
1✔
797
        self.upgrade_to_vpn_bundle()
1✔
798
        assert self.profile.plan == "bundle"
1✔
799

800

801
class ProfilePlanTermTest(ProfileTestCase):
1✔
802
    def test_free_user(self) -> None:
1✔
803
        assert self.profile.plan_term is None
1✔
804

805
    def test_premium_user(self) -> None:
1✔
806
        self.upgrade_to_premium()
1✔
807
        assert self.profile.plan_term == "unknown"
1✔
808

809
    def test_phone_user(self) -> None:
1✔
810
        self.upgrade_to_phone()
1✔
811
        assert self.profile.plan_term == "unknown"
1✔
812

813
    def test_phone_user_1_month(self) -> None:
1✔
814
        self.upgrade_to_phone()
1✔
815
        self.profile.date_phone_subscription_start = datetime(2024, 1, 1, tzinfo=UTC)
1✔
816

817
        self.profile.date_phone_subscription_end = datetime(2024, 2, 1, tzinfo=UTC)
1✔
818
        assert self.profile.plan_term == "1_month"
1✔
819

820
    def test_phone_user_1_year(self) -> None:
1✔
821
        self.upgrade_to_phone()
1✔
822
        self.profile.date_phone_subscription_start = datetime(2024, 1, 1, tzinfo=UTC)
1✔
823

824
        self.profile.date_phone_subscription_end = datetime(2025, 1, 1, tzinfo=UTC)
1✔
825
        assert self.profile.plan_term == "1_year"
1✔
826

827
    def test_vpn_bundle_user(self) -> None:
1✔
828
        self.upgrade_to_vpn_bundle()
1✔
829
        assert self.profile.plan_term == "unknown"
1✔
830

831

832
class ProfileMetricsPremiumStatus(ProfileTestCase):
1✔
833
    def test_free_user(self):
1✔
834
        assert self.profile.metrics_premium_status == "free"
1✔
835

836
    def test_premium_user(self) -> None:
1✔
837
        self.upgrade_to_premium()
1✔
838
        assert self.profile.metrics_premium_status == "email_unknown"
1✔
839

840
    def test_phone_user(self) -> None:
1✔
841
        self.upgrade_to_phone()
1✔
842
        assert self.profile.metrics_premium_status == "phone_unknown"
1✔
843

844
    def test_phone_user_1_month(self) -> None:
1✔
845
        self.upgrade_to_phone()
1✔
846
        self.profile.date_phone_subscription_start = datetime(2024, 1, 1, tzinfo=UTC)
1✔
847

848
        self.profile.date_phone_subscription_end = datetime(2024, 2, 1, tzinfo=UTC)
1✔
849
        assert self.profile.metrics_premium_status == "phone_1_month"
1✔
850

851
    def test_phone_user_1_year(self) -> None:
1✔
852
        self.upgrade_to_phone()
1✔
853
        self.profile.date_phone_subscription_start = datetime(2024, 1, 1, tzinfo=UTC)
1✔
854

855
        self.profile.date_phone_subscription_end = datetime(2025, 1, 1, tzinfo=UTC)
1✔
856
        assert self.profile.metrics_premium_status == "phone_1_year"
1✔
857

858
    def test_vpn_bundle_user(self) -> None:
1✔
859
        self.upgrade_to_vpn_bundle()
1✔
860
        assert self.profile.metrics_premium_status == "bundle_unknown"
1✔
861

862

863
class ProfileMetricsFxaID(ProfileTestCase):
1✔
864
    def test_metrics_fxa_id_no_social_account(self) -> None:
1✔
865
        assert not SocialAccount.objects.filter(user=self.profile.user).exists()
1✔
866
        assert self.profile.metrics_fxa_id == ""
1✔
867

868
    def test_metrics_fxa_id_metrics_enabled(self) -> None:
1✔
869
        social_account = self.get_or_create_social_account()
1✔
870
        assert self.profile.metrics_enabled
1✔
871
        assert self.profile.metrics_fxa_id == social_account.uid
1✔
872

873
    def test_metrics_fxa_id_metrics_disabled(self) -> None:
1✔
874
        social_account = self.get_or_create_social_account()
1✔
875
        social_account.extra_data["metricsEnabled"] = False
1✔
876
        social_account.save()
1✔
877
        assert not self.profile.metrics_enabled
1✔
878
        assert self.profile.metrics_fxa_id == ""
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc