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

mozilla / fx-private-relay / cfe47814-e990-4d20-ab80-67d1bdbfd3f0

09 May 2024 12:05AM CUT coverage: 84.63% (-0.01%) from 84.64%
cfe47814-e990-4d20-ab80-67d1bdbfd3f0

push

circleci

actions-user
Merge in latest l10n strings

3531 of 4583 branches covered (77.05%)

Branch coverage included in aggregate %.

14684 of 16940 relevant lines covered (86.68%)

10.91 hits per line

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

98.43
/emails/tests/models_tests.py
1
import random
1✔
2
from datetime import UTC, datetime, timedelta
1✔
3
from hashlib import sha256
1✔
4
from unittest import skip
1✔
5
from unittest.mock import Mock, patch
1✔
6
from uuid import uuid4
1✔
7

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

12
import pytest
1✔
13
from allauth.socialaccount.models import SocialAccount
1✔
14
from model_bakery import baker
1✔
15
from waffle.testutils import override_flag
1✔
16

17
from ..models import (
1✔
18
    AbuseMetrics,
19
    CannotMakeAddressException,
20
    CannotMakeSubdomainException,
21
    DeletedAddress,
22
    DomainAddrDuplicateException,
23
    DomainAddress,
24
    DomainAddrUnavailableException,
25
    Profile,
26
    RegisteredSubdomain,
27
    RelayAddress,
28
    address_hash,
29
    get_domain_numerical,
30
    has_bad_words,
31
    hash_subdomain,
32
    is_blocklisted,
33
    valid_address,
34
    valid_address_pattern,
35
    valid_available_subdomain,
36
)
37
from ..utils import get_domains_from_settings
1✔
38

39
if settings.PHONES_ENABLED:
1!
40
    from phones.models import RealPhone, RelayNumber
1✔
41

42

43
def make_free_test_user(email: str = "") -> User:
1✔
44
    if email:
1✔
45
        user = baker.make(User, email=email)
1✔
46
    else:
47
        user = baker.make(User)
1✔
48
    user.profile.server_storage = True
1✔
49
    user.profile.save()
1✔
50
    baker.make(
1✔
51
        SocialAccount,
52
        user=user,
53
        uid=str(uuid4()),
54
        provider="fxa",
55
        extra_data={"avatar": "avatar.png"},
56
    )
57
    return user
1✔
58

59

60
def make_premium_test_user() -> User:
1✔
61
    premium_user = baker.make(User, email="premium@email.com")
1✔
62
    premium_user.profile.server_storage = True
1✔
63
    premium_user.profile.date_subscribed = datetime.now(tz=UTC)
1✔
64
    premium_user.profile.save()
1✔
65
    upgrade_test_user_to_premium(premium_user)
1✔
66
    return premium_user
1✔
67

68

69
def make_storageless_test_user() -> User:
1✔
70
    storageless_user = baker.make(User)
1✔
71
    storageless_user_profile = storageless_user.profile
1✔
72
    storageless_user_profile.server_storage = False
1✔
73
    storageless_user_profile.subdomain = "mydomain"
1✔
74
    storageless_user_profile.date_subscribed = datetime.now(tz=UTC)
1✔
75
    storageless_user_profile.save()
1✔
76
    upgrade_test_user_to_premium(storageless_user)
1✔
77
    return storageless_user
1✔
78

79

80
def premium_subscription() -> str:
1✔
81
    """Return a Mozilla account subscription that provides unlimited emails"""
82
    assert settings.SUBSCRIPTIONS_WITH_UNLIMITED
1✔
83
    premium_only_plans = list(
1✔
84
        set(settings.SUBSCRIPTIONS_WITH_UNLIMITED)
85
        - set(settings.SUBSCRIPTIONS_WITH_PHONE)
86
        - set(settings.SUBSCRIPTIONS_WITH_VPN)
87
    )
88
    assert premium_only_plans
1✔
89
    return random.choice(premium_only_plans)
1✔
90

91

92
def upgrade_test_user_to_premium(user):
1✔
93
    random_sub = premium_subscription()
1✔
94
    baker.make(
1✔
95
        SocialAccount,
96
        user=user,
97
        uid=str(uuid4()),
98
        provider="fxa",
99
        extra_data={"avatar": "avatar.png", "subscriptions": [random_sub]},
100
    )
101
    return user
1✔
102

103

104
def phone_subscription() -> str:
1✔
105
    """Return a Mozilla account subscription that provides a phone mask"""
106
    assert settings.SUBSCRIPTIONS_WITH_PHONE
1✔
107
    phones_only_plans = list(
1✔
108
        set(settings.SUBSCRIPTIONS_WITH_PHONE)
109
        - set(settings.SUBSCRIPTIONS_WITH_VPN)
110
        - set(settings.SUBSCRIPTIONS_WITH_UNLIMITED)
111
    )
112
    assert phones_only_plans
1✔
113
    return random.choice(phones_only_plans)
1✔
114

115

116
def vpn_subscription() -> str:
1✔
117
    """Return a Mozilla account subscription that provides the VPN"""
118
    assert settings.SUBSCRIPTIONS_WITH_VPN
1✔
119
    vpn_only_plans = list(
1✔
120
        set(settings.SUBSCRIPTIONS_WITH_VPN)
121
        - set(settings.SUBSCRIPTIONS_WITH_PHONE)
122
        - set(settings.SUBSCRIPTIONS_WITH_UNLIMITED)
123
    )
124
    assert vpn_only_plans
1✔
125
    return random.choice(vpn_only_plans)
1✔
126

127

128
class MiscEmailModelsTest(TestCase):
1✔
129
    def test_has_bad_words_with_bad_words(self):
1✔
130
        assert has_bad_words("angry")
1✔
131

132
    def test_has_bad_words_without_bad_words(self):
1✔
133
        assert not has_bad_words("happy")
1✔
134

135
    def test_has_bad_words_exact_match_on_small_words(self):
1✔
136
        assert has_bad_words("ho")
1✔
137
        assert not has_bad_words("horse")
1✔
138
        assert has_bad_words("ass")
1✔
139
        assert not has_bad_words("cassandra")
1✔
140
        assert has_bad_words("hell")
1✔
141
        assert not has_bad_words("shell")
1✔
142
        assert has_bad_words("bra")
1✔
143
        assert not has_bad_words("brain")
1✔
144
        assert has_bad_words("fart")
1✔
145
        assert not has_bad_words("farther")
1✔
146
        assert has_bad_words("fu")
1✔
147
        assert not has_bad_words("funny")
1✔
148
        assert has_bad_words("poo")
1✔
149
        assert not has_bad_words("pools")
1✔
150

151
    def test_is_blocklisted_with_blocked_word(self):
1✔
152
        assert is_blocklisted("mozilla")
1✔
153

154
    def test_is_blocklisted_with_custom_blocked_word(self):
1✔
155
        # custom blocked word
156
        # see MPP-2077 for more details
157
        assert is_blocklisted("customdomain")
1✔
158

159
    def test_is_blocklisted_without_blocked_words(self):
1✔
160
        assert not is_blocklisted("non-blocked-word")
1✔
161

162
    @patch("emails.models.emails_config", return_value=Mock(blocklist=["blocked-word"]))
1✔
163
    def test_is_blocklisted_with_mocked_blocked_words(self, mock_config: Mock) -> None:
1✔
164
        assert is_blocklisted("blocked-word")
1✔
165

166
    @override_settings(RELAY_FIREFOX_DOMAIN="firefox.com")
1✔
167
    def test_address_hash_without_subdomain_domain_firefox(self):
1✔
168
        address = "aaaaaaaaa"
1✔
169
        expected_hash = sha256(f"{address}".encode()).hexdigest()
1✔
170
        assert address_hash(address, domain="firefox.com") == expected_hash
1✔
171

172
    @override_settings(RELAY_FIREFOX_DOMAIN="firefox.com")
1✔
173
    def test_address_hash_without_subdomain_domain_not_firefoxz(self):
1✔
174
        non_default = "test.com"
1✔
175
        address = "aaaaaaaaa"
1✔
176
        expected_hash = sha256(f"{address}@{non_default}".encode()).hexdigest()
1✔
177
        assert address_hash(address, domain=non_default) == expected_hash
1✔
178

179
    def test_address_hash_with_subdomain(self):
1✔
180
        address = "aaaaaaaaa"
1✔
181
        subdomain = "test"
1✔
182
        domain = get_domains_from_settings().get("MOZMAIL_DOMAIN")
1✔
183
        expected_hash = sha256(f"{address}@{subdomain}.{domain}".encode()).hexdigest()
1✔
184
        assert address_hash(address, subdomain, domain) == expected_hash
1✔
185

186
    def test_address_hash_with_additional_domain(self):
1✔
187
        address = "aaaaaaaaa"
1✔
188
        test_domain = "test.com"
1✔
189
        expected_hash = sha256(f"{address}@{test_domain}".encode()).hexdigest()
1✔
190
        assert address_hash(address, domain=test_domain) == expected_hash
1✔
191

192
    def test_get_domain_numerical(self):
1✔
193
        assert get_domain_numerical("default.com") == 1
1✔
194
        assert get_domain_numerical("test.com") == 2
1✔
195

196
    def test_valid_address_pattern_is_valid(self):
1✔
197
        assert valid_address_pattern("foo")
1✔
198
        assert valid_address_pattern("foo-bar")
1✔
199
        assert valid_address_pattern("foo.bar")
1✔
200
        assert valid_address_pattern("f00bar")
1✔
201
        assert valid_address_pattern("123foo")
1✔
202
        assert valid_address_pattern("123")
1✔
203

204
    def test_valid_address_pattern_is_not_valid(self):
1✔
205
        assert not valid_address_pattern("-")
1✔
206
        assert not valid_address_pattern("-foo")
1✔
207
        assert not valid_address_pattern("foo-")
1✔
208
        assert not valid_address_pattern(".foo")
1✔
209
        assert not valid_address_pattern("foo.")
1✔
210
        assert not valid_address_pattern("foo bar")
1✔
211
        assert not valid_address_pattern("Foo")
1✔
212

213

214
class RelayAddressTest(TestCase):
1✔
215
    def setUp(self):
1✔
216
        self.user = make_free_test_user()
1✔
217
        self.user_profile = self.user.profile
1✔
218
        self.premium_user = make_premium_test_user()
1✔
219
        self.premium_user_profile = self.premium_user.profile
1✔
220
        self.storageless_user = make_storageless_test_user()
1✔
221

222
    def test_create_assigns_to_user(self):
1✔
223
        relay_address = RelayAddress.objects.create(user=self.user_profile.user)
1✔
224
        assert relay_address.user == self.user_profile.user
1✔
225

226
    @override_settings(MAX_NUM_FREE_ALIASES=5, MAX_ADDRESS_CREATION_PER_DAY=10)
1✔
227
    def test_create_has_limit(self) -> None:
1✔
228
        baker.make(
1✔
229
            RelayAddress,
230
            user=self.premium_user,
231
            _quantity=settings.MAX_ADDRESS_CREATION_PER_DAY,
232
        )
233
        with pytest.raises(CannotMakeAddressException) as exc_info:
1✔
234
            RelayAddress.objects.create(user=self.premium_user)
1✔
235
        assert exc_info.value.get_codes() == "account_is_paused"
1✔
236
        relay_address_count = RelayAddress.objects.filter(
1✔
237
            user=self.premium_user_profile.user
238
        ).count()
239
        assert relay_address_count == 10
1✔
240

241
    def test_create_premium_user_can_exceed_free_limit(self):
1✔
242
        baker.make(
1✔
243
            RelayAddress,
244
            user=self.premium_user,
245
            _quantity=settings.MAX_NUM_FREE_ALIASES + 1,
246
        )
247
        relay_addresses = RelayAddress.objects.filter(
1✔
248
            user=self.premium_user
249
        ).values_list("address", flat=True)
250
        assert len(relay_addresses) == settings.MAX_NUM_FREE_ALIASES + 1
1✔
251

252
    def test_create_non_premium_user_cannot_pass_free_limit(self) -> None:
1✔
253
        baker.make(
1✔
254
            RelayAddress, user=self.user, _quantity=settings.MAX_NUM_FREE_ALIASES
255
        )
256
        with pytest.raises(CannotMakeAddressException) as exc_info:
1✔
257
            RelayAddress.objects.create(user=self.user_profile.user)
1✔
258
        assert exc_info.value.get_codes() == "free_tier_limit"
1✔
259
        relay_addresses = RelayAddress.objects.filter(
1✔
260
            user=self.user_profile.user
261
        ).values_list("address", flat=True)
262
        assert len(relay_addresses) == settings.MAX_NUM_FREE_ALIASES
1✔
263

264
    @skip(reason="ignore test for code path that we don't actually use")
1✔
265
    def test_create_with_specified_domain(self):
1✔
266
        relay_address = RelayAddress.objects.create(
×
267
            user=self.user_profile.user, domain=2
268
        )
269
        assert relay_address.domain == 2
×
270
        assert relay_address.get_domain_display() == "MOZMAIL_DOMAIN"
×
271
        assert relay_address.domain_value == "test.com"
×
272

273
    def test_create_updates_profile_last_engagement(self) -> None:
1✔
274
        relay_address = baker.make(RelayAddress, user=self.user, enabled=True)
1✔
275
        profile = relay_address.user.profile
1✔
276
        profile.refresh_from_db()
1✔
277
        assert profile.last_engagement
1✔
278
        pre_create_last_engagement = profile.last_engagement
1✔
279

280
        baker.make(RelayAddress, user=self.user, enabled=True)
1✔
281

282
        profile.refresh_from_db()
1✔
283
        assert profile.last_engagement > pre_create_last_engagement
1✔
284

285
    def test_save_does_not_update_profile_last_engagement(self) -> None:
1✔
286
        relay_address = baker.make(RelayAddress, user=self.user, enabled=True)
1✔
287
        profile = relay_address.user.profile
1✔
288
        profile.refresh_from_db()
1✔
289
        assert profile.last_engagement
1✔
290
        pre_save_last_engagement = profile.last_engagement
1✔
291

292
        relay_address.enabled = False
1✔
293
        relay_address.save()
1✔
294

295
        profile.refresh_from_db()
1✔
296
        assert profile.last_engagement == pre_save_last_engagement
1✔
297

298
    def test_delete_updates_profile_last_engagement(self) -> None:
1✔
299
        relay_address = baker.make(RelayAddress, user=self.user)
1✔
300
        profile = relay_address.user.profile
1✔
301
        profile.refresh_from_db()
1✔
302
        assert profile.last_engagement
1✔
303
        pre_delete_last_engagement = profile.last_engagement
1✔
304

305
        relay_address.delete()
1✔
306

307
        profile.refresh_from_db()
1✔
308
        assert profile.last_engagement > pre_delete_last_engagement
1✔
309

310
    def test_delete_adds_deleted_address_object(self):
1✔
311
        relay_address = baker.make(RelayAddress, user=self.user)
1✔
312
        address_hash = sha256(relay_address.full_address.encode("utf-8")).hexdigest()
1✔
313
        relay_address.delete()
1✔
314
        deleted_count = DeletedAddress.objects.filter(address_hash=address_hash).count()
1✔
315
        assert deleted_count == 1
1✔
316

317
    def test_delete_mozmail_deleted_address_object(self):
1✔
318
        relay_address = baker.make(RelayAddress, domain=2, user=self.user)
1✔
319
        address_hash = sha256(
1✔
320
            f"{relay_address.address}@{relay_address.domain_value}".encode()
321
        ).hexdigest()
322
        relay_address.delete()
1✔
323
        deleted_count = DeletedAddress.objects.filter(address_hash=address_hash).count()
1✔
324
        assert deleted_count == 1
1✔
325

326
    def test_delete_increments_values_on_profile(self):
1✔
327
        assert self.premium_user_profile.num_address_deleted == 0
1✔
328
        assert self.premium_user_profile.num_email_forwarded_in_deleted_address == 0
1✔
329
        assert self.premium_user_profile.num_email_blocked_in_deleted_address == 0
1✔
330
        assert (
1✔
331
            self.premium_user_profile.num_level_one_trackers_blocked_in_deleted_address
332
            == 0
333
        )
334
        assert self.premium_user_profile.num_email_replied_in_deleted_address == 0
1✔
335
        assert self.premium_user_profile.num_email_spam_in_deleted_address == 0
1✔
336
        assert self.premium_user_profile.num_deleted_relay_addresses == 0
1✔
337
        assert self.premium_user_profile.num_deleted_domain_addresses == 0
1✔
338

339
        relay_address = baker.make(
1✔
340
            RelayAddress,
341
            user=self.premium_user,
342
            num_forwarded=2,
343
            num_blocked=3,
344
            num_level_one_trackers_blocked=4,
345
            num_replied=5,
346
            num_spam=6,
347
        )
348
        relay_address.delete()
1✔
349

350
        self.premium_user_profile.refresh_from_db()
1✔
351
        assert self.premium_user_profile.num_address_deleted == 1
1✔
352
        assert self.premium_user_profile.num_email_forwarded_in_deleted_address == 2
1✔
353
        assert self.premium_user_profile.num_email_blocked_in_deleted_address == 3
1✔
354
        assert (
1✔
355
            self.premium_user_profile.num_level_one_trackers_blocked_in_deleted_address
356
            == 4
357
        )
358
        assert self.premium_user_profile.num_email_replied_in_deleted_address == 5
1✔
359
        assert self.premium_user_profile.num_email_spam_in_deleted_address == 6
1✔
360
        assert self.premium_user_profile.num_deleted_relay_addresses == 1
1✔
361
        assert self.premium_user_profile.num_deleted_domain_addresses == 0
1✔
362

363
    def test_relay_address_create_repeats_deleted_address_invalid(self):
1✔
364
        user = baker.make(User)
1✔
365
        address = "random-address"
1✔
366
        relay_address = RelayAddress.objects.create(user=user, address=address)
1✔
367
        relay_address.delete()
1✔
368
        repeat_deleted_relay_address = RelayAddress.objects.create(
1✔
369
            user=user, address=address
370
        )
371
        assert not repeat_deleted_relay_address.address == address
1✔
372

373
    def test_valid_address_dupe_of_deleted_invalid(self):
1✔
374
        relay_address = RelayAddress.objects.create(user=baker.make(User))
1✔
375
        relay_address.delete()
1✔
376
        assert not valid_address(relay_address.address, relay_address.domain_value)
1✔
377

378
    @patch(
1✔
379
        "emails.models.emails_config",
380
        return_value=Mock(badwords=[], blocklist=["blocked-word"]),
381
    )
382
    def test_address_contains_blocklist_invalid(self, mock_config: Mock) -> None:
1✔
383
        blocked_word = "blocked-word"
1✔
384
        relay_address = RelayAddress.objects.create(
1✔
385
            user=baker.make(User), address=blocked_word
386
        )
387
        assert not relay_address.address == blocked_word
1✔
388

389
    def test_free_user_cant_set_block_list_emails(self):
1✔
390
        relay_address = RelayAddress.objects.create(user=self.user)
1✔
391
        relay_address.block_list_emails = True
1✔
392
        relay_address.save()
1✔
393
        relay_address.refresh_from_db()
1✔
394
        assert relay_address.block_list_emails is False
1✔
395

396
    def test_premium_user_can_set_block_list_emails(self):
1✔
397
        relay_address = RelayAddress.objects.create(user=self.premium_user)
1✔
398
        assert relay_address.block_list_emails is False
1✔
399
        relay_address.block_list_emails = True
1✔
400
        relay_address.save()
1✔
401
        relay_address.refresh_from_db()
1✔
402
        assert relay_address.block_list_emails is True
1✔
403

404
    def test_formerly_premium_user_clears_block_list_emails(self):
1✔
405
        relay_address = RelayAddress.objects.create(
1✔
406
            user=self.premium_user, block_list_emails=True
407
        )
408
        relay_address.refresh_from_db()
1✔
409
        assert relay_address.block_list_emails is True
1✔
410

411
        # Remove premium from user
412
        assert (fxa_account := self.premium_user.profile.fxa) is not None
1✔
413
        fxa_account.extra_data["subscriptions"] = []
1✔
414
        fxa_account.save()
1✔
415
        assert not self.premium_user.profile.has_premium
1✔
416

417
        relay_address.save()
1✔
418
        assert relay_address.block_list_emails is False
1✔
419

420
    def test_storageless_user_cant_set_label(self):
1✔
421
        relay_address = RelayAddress.objects.create(user=self.storageless_user)
1✔
422
        assert relay_address.description == ""
1✔
423
        assert relay_address.generated_for == ""
1✔
424
        assert relay_address.used_on in (None, "")
1✔
425
        relay_address.description = "Arbitrary description"
1✔
426
        relay_address.generated_for = "https://example.com"
1✔
427
        relay_address.used_on = "https://example.com"
1✔
428
        relay_address.save()
1✔
429
        relay_address.refresh_from_db()
1✔
430
        assert relay_address.description == ""
1✔
431
        assert relay_address.generated_for == ""
1✔
432
        assert relay_address.used_on in (None, "")
1✔
433

434
    def test_clear_storage_with_update_fields(self) -> None:
1✔
435
        """
436
        With update_fields, the stored data is still cleared for storageless users.
437
        """
438
        relay_address = RelayAddress.objects.create(user=self.storageless_user)
1✔
439
        assert relay_address.description == ""
1✔
440

441
        # Use QuerySet.update to avoid model save method
442
        RelayAddress.objects.filter(id=relay_address.id).update(
1✔
443
            description="the description",
444
            generated_for="https://example.com",
445
            used_on="https://example.com",
446
        )
447
        relay_address.refresh_from_db()
1✔
448
        assert relay_address.description == "the description"
1✔
449
        assert relay_address.generated_for == "https://example.com"
1✔
450
        assert relay_address.used_on == "https://example.com"
1✔
451

452
        # Update a different field with update_fields to avoid full model save
453
        new_last_used_at = datetime(2024, 1, 11, tzinfo=UTC)
1✔
454
        relay_address.last_used_at = new_last_used_at
1✔
455
        relay_address.save(update_fields={"last_used_at"})
1✔
456

457
        # Since .save() added to update_fields, the storage fields are cleared
458
        relay_address.refresh_from_db()
1✔
459
        assert relay_address.last_used_at == new_last_used_at
1✔
460
        assert relay_address.description == ""
1✔
461
        assert relay_address.generated_for == ""
1✔
462
        assert relay_address.used_on in ("", None)
1✔
463

464
    def test_clear_block_list_emails_with_update_fields(self) -> None:
1✔
465
        """
466
        With update_fields, the block_list_emails flag is still cleared for free users.
467
        """
468
        relay_address = RelayAddress.objects.create(user=self.user)
1✔
469
        assert not relay_address.block_list_emails
1✔
470

471
        # Use QuerySet.update to avoid model save method
472
        RelayAddress.objects.filter(id=relay_address.id).update(block_list_emails=True)
1✔
473
        relay_address.refresh_from_db()
1✔
474
        assert relay_address.block_list_emails
1✔
475

476
        # Update a different field with update_fields to avoid full model save
477
        new_last_used_at = datetime(2024, 1, 12, tzinfo=UTC)
1✔
478
        relay_address.last_used_at = new_last_used_at
1✔
479
        relay_address.save(update_fields={"last_used_at"})
1✔
480

481
        # Since .save() added to update_fields, block_list_emails flag is cleared
482
        relay_address.refresh_from_db()
1✔
483
        assert relay_address.last_used_at == new_last_used_at
1✔
484
        assert not relay_address.block_list_emails
1✔
485

486
    def test_metrics_id(self):
1✔
487
        relay_address = RelayAddress.objects.create(user=self.user)
1✔
488
        assert relay_address.metrics_id == f"R{relay_address.id}"
1✔
489

490

491
class ProfileTestCase(TestCase):
1✔
492
    """Base class for Profile tests."""
493

494
    def setUp(self) -> None:
1✔
495
        user = baker.make(User)
1✔
496
        self.profile = user.profile
1✔
497
        assert self.profile.server_storage is True
1✔
498

499
    def get_or_create_social_account(self) -> SocialAccount:
1✔
500
        """Get the test user's social account, creating if needed."""
501
        social_account, _ = SocialAccount.objects.get_or_create(
1✔
502
            user=self.profile.user,
503
            provider="fxa",
504
            defaults={
505
                "uid": str(uuid4()),
506
                "extra_data": {"avatar": "image.png", "subscriptions": []},
507
            },
508
        )
509
        return social_account
1✔
510

511
    def upgrade_to_premium(self) -> None:
1✔
512
        """Add an unlimited emails subscription to the user."""
513
        social_account = self.get_or_create_social_account()
1✔
514
        social_account.extra_data["subscriptions"].append(premium_subscription())
1✔
515
        social_account.save()
1✔
516

517
    def upgrade_to_phone(self) -> None:
1✔
518
        """Add a phone plan to the user."""
519
        social_account = self.get_or_create_social_account()
1✔
520
        social_account.extra_data["subscriptions"].append(phone_subscription())
1✔
521
        if not self.profile.has_premium:
1!
522
            social_account.extra_data["subscriptions"].append(premium_subscription())
1✔
523
        social_account.save()
1✔
524

525
    def upgrade_to_vpn_bundle(self) -> None:
1✔
526
        """Add a phone plan to the user."""
527
        social_account = self.get_or_create_social_account()
1✔
528
        social_account.extra_data["subscriptions"].append(vpn_subscription())
1✔
529
        if not self.profile.has_premium:
1!
530
            social_account.extra_data["subscriptions"].append(premium_subscription())
1✔
531
        if not self.profile.has_phone:
1!
532
            social_account.extra_data["subscriptions"].append(phone_subscription())
1✔
533
        social_account.save()
1✔
534

535

536
class ProfileBounceTestCase(ProfileTestCase):
1✔
537
    """Base class for Profile tests that check for bounces."""
538

539
    def set_hard_bounce(self) -> datetime:
1✔
540
        """
541
        Set a hard bounce pause for the profile, return the bounce time.
542

543
        This happens when the user's email server reports a hard bounce, such as
544
        saying the email does not exist.
545
        """
546
        self.profile.last_hard_bounce = datetime.now(UTC) - timedelta(
1✔
547
            days=settings.HARD_BOUNCE_ALLOWED_DAYS - 1
548
        )
549
        self.profile.save()
1✔
550
        return self.profile.last_hard_bounce
1✔
551

552
    def set_soft_bounce(self) -> datetime:
1✔
553
        """
554
        Set a soft bounce for the profile, return the bounce time.
555

556
        This happens when the user's email server reports a soft bounce, such as
557
        saying the user's mailbox is full.
558
        """
559
        self.profile.last_soft_bounce = datetime.now(UTC) - timedelta(
1✔
560
            days=settings.SOFT_BOUNCE_ALLOWED_DAYS - 1
561
        )
562
        self.profile.save()
1✔
563
        return self.profile.last_soft_bounce
1✔
564

565

566
class ProfileCheckBouncePause(ProfileBounceTestCase):
1✔
567
    """Tests for Profile.check_bounce_pause()"""
568

569
    def test_no_bounces(self) -> None:
1✔
570
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
571

572
        assert bounce_paused is False
1✔
573
        assert bounce_type == ""
1✔
574

575
    def test_hard_bounce_pending(self) -> None:
1✔
576
        self.set_hard_bounce()
1✔
577
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
578
        assert bounce_paused is True
1✔
579
        assert bounce_type == "hard"
1✔
580

581
    def test_soft_bounce_pending(self) -> None:
1✔
582
        self.set_soft_bounce()
1✔
583
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
584
        assert bounce_paused is True
1✔
585
        assert bounce_type == "soft"
1✔
586

587
    def test_hard_and_soft_bounce_pending_shows_hard(self) -> None:
1✔
588
        self.set_hard_bounce()
1✔
589
        self.set_soft_bounce()
1✔
590
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
591
        assert bounce_paused is True
1✔
592
        assert bounce_type == "hard"
1✔
593

594
    def test_hard_bounce_over_resets_timer(self) -> None:
1✔
595
        self.profile.last_hard_bounce = datetime.now(UTC) - timedelta(
1✔
596
            days=settings.HARD_BOUNCE_ALLOWED_DAYS + 1
597
        )
598
        self.profile.save()
1✔
599
        assert self.profile.last_hard_bounce is not None
1✔
600

601
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
602

603
        assert bounce_paused is False
1✔
604
        assert bounce_type == ""
1✔
605
        assert self.profile.last_hard_bounce is None
1✔
606

607
    def test_soft_bounce_over_resets_timer(self) -> None:
1✔
608
        self.profile.last_soft_bounce = datetime.now(UTC) - timedelta(
1✔
609
            days=settings.SOFT_BOUNCE_ALLOWED_DAYS + 1
610
        )
611
        self.profile.save()
1✔
612
        assert self.profile.last_soft_bounce is not None
1✔
613

614
        bounce_paused, bounce_type = self.profile.check_bounce_pause()
1✔
615

616
        assert bounce_paused is False
1✔
617
        assert bounce_type == ""
1✔
618
        assert self.profile.last_soft_bounce is None
1✔
619

620

621
class ProfileNextEmailTryDateTest(ProfileBounceTestCase):
1✔
622
    """Tests for Profile.next_email_try"""
623

624
    def test_no_bounces_returns_today(self) -> None:
1✔
625
        assert self.profile.next_email_try.date() == datetime.now(UTC).date()
1✔
626

627
    def test_hard_bounce_returns_proper_datemath(self) -> None:
1✔
628
        last_hard_bounce = self.set_hard_bounce()
1✔
629
        expected_next_try_date = last_hard_bounce + timedelta(
1✔
630
            days=settings.HARD_BOUNCE_ALLOWED_DAYS
631
        )
632
        assert self.profile.next_email_try.date() == expected_next_try_date.date()
1✔
633

634
    def test_soft_bounce_returns_proper_datemath(self) -> None:
1✔
635
        last_soft_bounce = self.set_soft_bounce()
1✔
636
        expected_next_try_date = last_soft_bounce + timedelta(
1✔
637
            days=settings.SOFT_BOUNCE_ALLOWED_DAYS
638
        )
639
        assert self.profile.next_email_try.date() == expected_next_try_date.date()
1✔
640

641
    def test_hard_and_soft_bounce_returns_hard_datemath(self) -> None:
1✔
642
        last_soft_bounce = self.set_soft_bounce()
1✔
643
        last_hard_bounce = self.set_hard_bounce()
1✔
644
        assert last_soft_bounce != last_hard_bounce
1✔
645
        expected_next_try_date = last_hard_bounce + timedelta(
1✔
646
            days=settings.HARD_BOUNCE_ALLOWED_DAYS
647
        )
648
        assert self.profile.next_email_try.date() == expected_next_try_date.date()
1✔
649

650

651
class ProfileLastBounceDateTest(ProfileBounceTestCase):
1✔
652
    """Tests for Profile.last_bounce_date"""
653

654
    def test_no_bounces_returns_None(self) -> None:
1✔
655
        assert self.profile.last_bounce_date is None
1✔
656

657
    def test_soft_bounce_returns_its_date(self) -> None:
1✔
658
        self.set_soft_bounce()
1✔
659
        assert self.profile.last_bounce_date == self.profile.last_soft_bounce
1✔
660

661
    def test_hard_bounce_returns_its_date(self) -> None:
1✔
662
        self.set_hard_bounce()
1✔
663
        assert self.profile.last_bounce_date == self.profile.last_hard_bounce
1✔
664

665
    def test_hard_and_soft_bounces_returns_hard_date(self) -> None:
1✔
666
        self.set_soft_bounce()
1✔
667
        self.set_hard_bounce()
1✔
668
        assert self.profile.last_bounce_date == self.profile.last_hard_bounce
1✔
669

670

671
class ProfileHasPremiumTest(ProfileTestCase):
1✔
672
    """Tests for Profile.has_premium"""
673

674
    def test_default_False(self) -> None:
1✔
675
        assert self.profile.has_premium is False
1✔
676

677
    def test_premium_subscription_returns_True(self) -> None:
1✔
678
        self.upgrade_to_premium()
1✔
679
        assert self.profile.has_premium is True
1✔
680

681
    def test_phone_returns_True(self) -> None:
1✔
682
        self.upgrade_to_phone()
1✔
683
        assert self.profile.has_premium is True
1✔
684

685
    def test_vpn_bundle_returns_True(self) -> None:
1✔
686
        self.upgrade_to_vpn_bundle()
1✔
687
        assert self.profile.has_premium is True
1✔
688

689

690
class ProfileHasPhoneTest(ProfileTestCase):
1✔
691
    """Tests for Profile.has_phone"""
692

693
    def test_default_False(self) -> None:
1✔
694
        assert self.profile.has_phone is False
1✔
695

696
    def test_premium_subscription_returns_False(self) -> None:
1✔
697
        self.upgrade_to_premium()
1✔
698
        assert self.profile.has_phone is False
1✔
699

700
    def test_phone_returns_True(self) -> None:
1✔
701
        self.upgrade_to_phone()
1✔
702
        assert self.profile.has_phone is True
1✔
703

704
    def test_vpn_bundle_returns_True(self) -> None:
1✔
705
        self.upgrade_to_vpn_bundle()
1✔
706
        assert self.profile.has_phone is True
1✔
707

708

709
@pytest.mark.skipif(not settings.PHONES_ENABLED, reason="PHONES_ENABLED is False")
1✔
710
@override_settings(PHONES_NO_CLIENT_CALLS_IN_TEST=True)
1✔
711
class ProfileDatePhoneRegisteredTest(ProfileTestCase):
1✔
712
    """Tests for Profile.date_phone_registered"""
713

714
    def test_default_None(self) -> None:
1✔
715
        assert self.profile.date_phone_registered is None
1✔
716

717
    def test_real_phone_no_relay_number_returns_verified_date(self) -> None:
1✔
718
        self.upgrade_to_phone()
1✔
719
        datetime_now = datetime.now(UTC)
1✔
720
        RealPhone.objects.create(
1✔
721
            user=self.profile.user,
722
            number="+12223334444",
723
            verified=True,
724
            verified_date=datetime_now,
725
        )
726
        assert self.profile.date_phone_registered == datetime_now
1✔
727

728
    def test_real_phone_and_relay_number_w_created_at_returns_created_at_date(
1✔
729
        self,
730
    ) -> None:
731
        self.upgrade_to_phone()
1✔
732
        datetime_now = datetime.now(UTC)
1✔
733
        phone_user = self.profile.user
1✔
734
        RealPhone.objects.create(
1✔
735
            user=phone_user,
736
            number="+12223334444",
737
            verified=True,
738
            verified_date=datetime_now,
739
        )
740
        relay_number = RelayNumber.objects.create(user=phone_user)
1✔
741
        assert self.profile.date_phone_registered == relay_number.created_at
1✔
742

743
    def test_real_phone_and_relay_number_wo_created_at_returns_verified_date(
1✔
744
        self,
745
    ) -> None:
746
        self.upgrade_to_phone()
1✔
747
        datetime_now = datetime.now(UTC)
1✔
748
        phone_user = self.profile.user
1✔
749
        real_phone = RealPhone.objects.create(
1✔
750
            user=phone_user,
751
            number="+12223334444",
752
            verified=True,
753
            verified_date=datetime_now,
754
        )
755
        relay_number = RelayNumber.objects.create(user=phone_user)
1✔
756
        # since created_at is auto set, update to None
757
        relay_number.created_at = None
1✔
758
        relay_number.save()
1✔
759
        assert self.profile.date_phone_registered == real_phone.verified_date
1✔
760

761

762
class ProfileTotalMasksTest(ProfileTestCase):
1✔
763
    """Tests for Profile.total_masks"""
764

765
    def test_total_masks(self) -> None:
1✔
766
        self.upgrade_to_premium()
1✔
767
        self.profile.add_subdomain("totalmasks")
1✔
768
        assert self.profile.total_masks == 0
1✔
769
        num_relay_addresses = random.randint(0, 2)
1✔
770
        for _ in list(range(num_relay_addresses)):
1!
771
            baker.make(RelayAddress, user=self.profile.user)
×
772
        num_domain_addresses = random.randint(0, 2)
1✔
773
        for i in list(range(num_domain_addresses)):
1✔
774
            baker.make(DomainAddress, user=self.profile.user, address=f"mask{i}")
1✔
775
        assert self.profile.total_masks == num_relay_addresses + num_domain_addresses
1✔
776

777

778
class ProfileAtMaskLimitTest(ProfileTestCase):
1✔
779
    """Tests for Profile.at_mask_limit"""
780

781
    def test_premium_user_returns_False(self) -> None:
1✔
782
        self.upgrade_to_premium()
1✔
783
        assert self.profile.at_mask_limit is False
1✔
784
        baker.make(
1✔
785
            RelayAddress,
786
            user=self.profile.user,
787
            _quantity=settings.MAX_NUM_FREE_ALIASES,
788
        )
789
        assert self.profile.at_mask_limit is False
1✔
790

791
    def test_free_user(self) -> None:
1✔
792
        assert self.profile.at_mask_limit is False
1✔
793
        baker.make(
1✔
794
            RelayAddress,
795
            user=self.profile.user,
796
            _quantity=settings.MAX_NUM_FREE_ALIASES,
797
        )
798
        assert self.profile.at_mask_limit is True
1✔
799

800

801
class ProfileAddSubdomainTest(ProfileTestCase):
1✔
802
    """Tests for Profile.add_subdomain()"""
803

804
    def test_new_unlimited_profile(self) -> None:
1✔
805
        self.upgrade_to_premium()
1✔
806
        assert self.profile.add_subdomain("newpremium") == "newpremium"
1✔
807

808
    def test_lowercases_subdomain_value(self) -> None:
1✔
809
        self.upgrade_to_premium()
1✔
810
        assert self.profile.add_subdomain("mIxEdcAsE") == "mixedcase"
1✔
811

812
    def test_non_premium_user_raises_exception(self) -> None:
1✔
813
        expected_msg = "error-premium-set-subdomain"
1✔
814
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
815
            self.profile.add_subdomain("test")
1✔
816

817
    def test_calling_again_raises_exception(self) -> None:
1✔
818
        self.upgrade_to_premium()
1✔
819
        subdomain = "test"
1✔
820
        self.profile.subdomain = subdomain
1✔
821
        self.profile.save()
1✔
822

823
        expected_msg = "error-premium-cannot-change-subdomain"
1✔
824
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
825
            self.profile.add_subdomain(subdomain)
1✔
826

827
    def test_badword_subdomain_raises_exception(self) -> None:
1✔
828
        self.upgrade_to_premium()
1✔
829
        expected_msg = "error-subdomain-not-available"
1✔
830
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
831
            self.profile.add_subdomain("angry")
1✔
832

833
    def test_blocked_word_subdomain_raises_exception(self) -> None:
1✔
834
        self.upgrade_to_premium()
1✔
835
        expected_msg = "error-subdomain-not-available"
1✔
836
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
837
            self.profile.add_subdomain("mozilla")
1✔
838

839
    def test_empty_subdomain_raises(self) -> None:
1✔
840
        self.upgrade_to_premium()
1✔
841
        expected_msg = "error-subdomain-cannot-be-empty-or-null"
1✔
842

843
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
844
            self.profile.add_subdomain("")
1✔
845

846
    def test_null_subdomain_raises(self) -> None:
1✔
847
        self.upgrade_to_premium()
1✔
848
        expected_msg = "error-subdomain-cannot-be-empty-or-null"
1✔
849

850
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
851
            self.profile.add_subdomain(None)
1✔
852

853
    def test_subdomain_with_space_at_end_raises(self) -> None:
1✔
854
        self.upgrade_to_premium()
1✔
855
        expected_msg = "error-subdomain-not-available"
1✔
856

857
        with self.assertRaisesMessage(CannotMakeSubdomainException, expected_msg):
1✔
858
            self.profile.add_subdomain("mydomain ")
1✔
859

860

861
class ProfileSaveTest(ProfileTestCase):
1✔
862
    """Tests for Profile.save()"""
863

864
    def test_lowercases_subdomain_value(self) -> None:
1✔
865
        self.upgrade_to_premium()
1✔
866
        self.profile.subdomain = "mIxEdcAsE"
1✔
867
        self.profile.save()
1✔
868
        assert self.profile.subdomain == "mixedcase"
1✔
869

870
    def test_lowercases_subdomain_value_with_update_fields(self) -> None:
1✔
871
        """With update_fields, the subdomain is still lowercased."""
872
        self.upgrade_to_premium()
1✔
873
        assert self.profile.subdomain is None
1✔
874

875
        # Use QuerySet.update to avoid model .save()
876
        Profile.objects.filter(id=self.profile.id).update(subdomain="mIxEdcAsE")
1✔
877
        self.profile.refresh_from_db()
1✔
878
        assert self.profile.subdomain == "mIxEdcAsE"
1✔
879

880
        # Update a different field with update_fields to avoid a full model save
881
        new_date_subscribed = datetime(2023, 3, 3, tzinfo=UTC)
1✔
882
        self.profile.date_subscribed = new_date_subscribed
1✔
883
        self.profile.save(update_fields={"date_subscribed"})
1✔
884

885
        # Since .save() added to update_fields, subdomain is now lowercase
886
        self.profile.refresh_from_db()
1✔
887
        assert self.profile.date_subscribed == new_date_subscribed
1✔
888
        assert self.profile.subdomain == "mixedcase"
1✔
889

890
    TEST_DESCRIPTION = "test description"
1✔
891
    TEST_USED_ON = TEST_GENERATED_FOR = "secret.com"
1✔
892

893
    def add_relay_address(self) -> RelayAddress:
1✔
894
        return baker.make(
1✔
895
            RelayAddress,
896
            user=self.profile.user,
897
            description=self.TEST_DESCRIPTION,
898
            generated_for=self.TEST_GENERATED_FOR,
899
            used_on=self.TEST_USED_ON,
900
        )
901

902
    def add_domain_address(self) -> DomainAddress:
1✔
903
        self.upgrade_to_premium()
1✔
904
        self.profile.subdomain = "somesubdomain"
1✔
905
        self.profile.save()
1✔
906
        return baker.make(
1✔
907
            DomainAddress,
908
            user=self.profile.user,
909
            address="localpart",
910
            description=self.TEST_DESCRIPTION,
911
            used_on=self.TEST_USED_ON,
912
        )
913

914
    def test_save_server_storage_true_doesnt_delete_data(self) -> None:
1✔
915
        relay_address = self.add_relay_address()
1✔
916
        self.profile.server_storage = True
1✔
917
        self.profile.save()
1✔
918

919
        relay_address.refresh_from_db()
1✔
920
        assert relay_address.description == self.TEST_DESCRIPTION
1✔
921
        assert relay_address.generated_for == self.TEST_GENERATED_FOR
1✔
922
        assert relay_address.used_on == self.TEST_USED_ON
1✔
923

924
    def test_save_server_storage_false_deletes_data(self) -> None:
1✔
925
        relay_address = self.add_relay_address()
1✔
926
        domain_address = self.add_domain_address()
1✔
927
        self.profile.server_storage = False
1✔
928
        self.profile.save()
1✔
929

930
        relay_address.refresh_from_db()
1✔
931
        domain_address.refresh_from_db()
1✔
932
        assert relay_address.description == ""
1✔
933
        assert relay_address.generated_for == ""
1✔
934
        assert relay_address.used_on == ""
1✔
935
        assert domain_address.description == ""
1✔
936
        assert domain_address.used_on == ""
1✔
937

938
    def add_four_relay_addresses(self, user: User | None = None) -> list[RelayAddress]:
1✔
939
        if user is None:
1✔
940
            user = self.profile.user
1✔
941
        return baker.make(
1✔
942
            RelayAddress,
943
            user=user,
944
            description=self.TEST_DESCRIPTION,
945
            generated_for=self.TEST_GENERATED_FOR,
946
            used_on=self.TEST_USED_ON,
947
            _quantity=4,
948
        )
949

950
    def test_save_server_storage_false_deletes_ALL_data(self) -> None:
1✔
951
        self.add_four_relay_addresses()
1✔
952
        self.profile.server_storage = False
1✔
953
        self.profile.save()
1✔
954

955
        for relay_address in RelayAddress.objects.filter(user=self.profile.user):
1✔
956
            assert relay_address.description == ""
1✔
957
            assert relay_address.generated_for == ""
1✔
958

959
    def test_save_server_storage_false_only_deletes_that_profiles_data(self) -> None:
1✔
960
        other_user = make_free_test_user()
1✔
961
        assert other_user.profile.server_storage is True
1✔
962
        self.add_four_relay_addresses()
1✔
963
        self.add_four_relay_addresses(user=other_user)
1✔
964
        self.profile.server_storage = False
1✔
965
        self.profile.save()
1✔
966

967
        for relay_address in RelayAddress.objects.filter(user=self.profile.user):
1✔
968
            assert relay_address.description == ""
1✔
969
            assert relay_address.generated_for == ""
1✔
970
            assert relay_address.used_on == ""
1✔
971

972
        for relay_address in RelayAddress.objects.filter(user=other_user):
1✔
973
            assert relay_address.description == self.TEST_DESCRIPTION
1✔
974
            assert relay_address.generated_for == self.TEST_GENERATED_FOR
1✔
975
            assert relay_address.used_on == self.TEST_USED_ON
1✔
976

977

978
class ValidAvailableSubdomainTest(TestCase):
1✔
979
    """Tests for valid_available_subdomain()"""
980

981
    ERR_NOT_AVAIL = "error-subdomain-not-available"
1✔
982
    ERR_EMPTY_OR_NULL = "error-subdomain-cannot-be-empty-or-null"
1✔
983

984
    def reserve_subdomain_for_new_user(self, subdomain: str) -> User:
1✔
985
        user = make_premium_test_user()
1✔
986
        user.profile.add_subdomain(subdomain)
1✔
987
        return user
1✔
988

989
    def test_bad_word_raises(self) -> None:
1✔
990
        with self.assertRaisesMessage(CannotMakeSubdomainException, self.ERR_NOT_AVAIL):
1✔
991
            valid_available_subdomain("angry")
1✔
992

993
    def test_blocked_word_raises(self) -> None:
1✔
994
        with self.assertRaisesMessage(CannotMakeSubdomainException, self.ERR_NOT_AVAIL):
1✔
995
            valid_available_subdomain("mozilla")
1✔
996

997
    def test_taken_subdomain_raises(self) -> None:
1✔
998
        subdomain = "thisisfine"
1✔
999
        self.reserve_subdomain_for_new_user(subdomain)
1✔
1000
        with self.assertRaisesMessage(CannotMakeSubdomainException, self.ERR_NOT_AVAIL):
1✔
1001
            valid_available_subdomain(subdomain)
1✔
1002

1003
    def test_taken_subdomain_different_case_raises(self) -> None:
1✔
1004
        self.reserve_subdomain_for_new_user("thIsIsfInE")
1✔
1005
        with self.assertRaisesMessage(CannotMakeSubdomainException, self.ERR_NOT_AVAIL):
1✔
1006
            valid_available_subdomain("THiSiSFiNe")
1✔
1007

1008
    def test_inactive_subdomain_raises(self) -> None:
1✔
1009
        """subdomains registered by now deleted profiles are not available."""
1010
        subdomain = "thisisfine"
1✔
1011
        user = self.reserve_subdomain_for_new_user(subdomain)
1✔
1012
        user.delete()
1✔
1013

1014
        registered_subdomain_count = RegisteredSubdomain.objects.filter(
1✔
1015
            subdomain_hash=hash_subdomain(subdomain)
1016
        ).count()
1017
        assert Profile.objects.filter(subdomain=subdomain).count() == 0
1✔
1018
        assert registered_subdomain_count == 1
1✔
1019
        with self.assertRaisesMessage(CannotMakeSubdomainException, self.ERR_NOT_AVAIL):
1✔
1020
            valid_available_subdomain(subdomain)
1✔
1021

1022
    def test_subdomain_with_space_raises(self) -> None:
1✔
1023
        with self.assertRaisesMessage(CannotMakeSubdomainException, self.ERR_NOT_AVAIL):
1✔
1024
            valid_available_subdomain("my domain")
1✔
1025

1026
    def test_subdomain_with_special_char_raises(self) -> None:
1✔
1027
        with self.assertRaisesMessage(CannotMakeSubdomainException, self.ERR_NOT_AVAIL):
1✔
1028
            valid_available_subdomain("my@domain")
1✔
1029

1030
    def test_subdomain_with_dash_returns_True(self) -> None:
1✔
1031
        assert valid_available_subdomain("my-domain") is True
1✔
1032

1033
    def test_subdomain_with_dash_at_front_raises(self) -> None:
1✔
1034
        with self.assertRaisesMessage(CannotMakeSubdomainException, self.ERR_NOT_AVAIL):
1✔
1035
            valid_available_subdomain("-mydomain")
1✔
1036

1037
    def test_empty_subdomain_raises(self) -> None:
1✔
1038
        with self.assertRaisesMessage(
1✔
1039
            CannotMakeSubdomainException, self.ERR_EMPTY_OR_NULL
1040
        ):
1041
            valid_available_subdomain("")
1✔
1042

1043
    def test_null_subdomain_raises(self) -> None:
1✔
1044
        with self.assertRaisesMessage(
1✔
1045
            CannotMakeSubdomainException, self.ERR_EMPTY_OR_NULL
1046
        ):
1047
            valid_available_subdomain(None)
1✔
1048

1049
    def test_subdomain_with_space_at_end_raises(self) -> None:
1✔
1050
        with self.assertRaisesMessage(CannotMakeSubdomainException, self.ERR_NOT_AVAIL):
1✔
1051
            valid_available_subdomain("mydomain ")
1✔
1052

1053

1054
class ProfileDisplayNameTest(ProfileTestCase):
1✔
1055
    """Tests for Profile.display_name"""
1056

1057
    def test_exists(self) -> None:
1✔
1058
        display_name = "Display Name"
1✔
1059
        social_account = self.get_or_create_social_account()
1✔
1060
        social_account.extra_data["displayName"] = display_name
1✔
1061
        social_account.save()
1✔
1062
        assert self.profile.display_name == display_name
1✔
1063

1064
    def test_display_name_does_not_exist(self) -> None:
1✔
1065
        self.get_or_create_social_account()
1✔
1066
        assert self.profile.display_name is None
1✔
1067

1068

1069
class ProfileLanguageTest(ProfileTestCase):
1✔
1070
    """Test Profile.language"""
1071

1072
    def test_no_fxa_extra_data_locale_returns_default_en(self) -> None:
1✔
1073
        social_account = self.get_or_create_social_account()
1✔
1074
        assert "locale" not in social_account.extra_data
1✔
1075
        assert self.profile.language == "en"
1✔
1076

1077
    def test_no_fxa_locale_returns_default_en(self) -> None:
1✔
1078
        assert self.profile.language == "en"
1✔
1079

1080
    def test_fxa_locale_de_returns_de(self) -> None:
1✔
1081
        social_account = self.get_or_create_social_account()
1✔
1082
        social_account.extra_data["locale"] = "de,en-US;q=0.9,en;q=0.8"
1✔
1083
        social_account.save()
1✔
1084
        assert self.profile.language == "de"
1✔
1085

1086

1087
class ProfileFxaLocaleInPremiumCountryTest(ProfileTestCase):
1✔
1088
    """Tests for Profile.fxa_locale_in_premium_country"""
1089

1090
    def set_fxa_locale(self, locale: str) -> None:
1✔
1091
        social_account = self.get_or_create_social_account()
1✔
1092
        social_account.extra_data["locale"] = locale
1✔
1093
        social_account.save()
1✔
1094

1095
    def test_when_premium_available_returns_True(self) -> None:
1✔
1096
        self.set_fxa_locale("de-DE,en-xx;q=0.9,en;q=0.8")
1✔
1097
        assert self.profile.fxa_locale_in_premium_country is True
1✔
1098

1099
    def test_en_implies_premium_available(self) -> None:
1✔
1100
        self.set_fxa_locale("en;q=0.8")
1✔
1101
        assert self.profile.fxa_locale_in_premium_country is True
1✔
1102

1103
    def test_when_premium_unavailable_returns_False(self) -> None:
1✔
1104
        self.set_fxa_locale("en-IN, en;q=0.8")
1✔
1105
        assert self.profile.fxa_locale_in_premium_country is False
1✔
1106

1107
    def test_when_premium_available_by_language_code_returns_True(self) -> None:
1✔
1108
        self.set_fxa_locale("de;q=0.8")
1✔
1109
        assert self.profile.fxa_locale_in_premium_country is True
1✔
1110

1111
    def test_invalid_language_code_returns_False(self) -> None:
1✔
1112
        self.set_fxa_locale("xx;q=0.8")
1✔
1113
        assert self.profile.fxa_locale_in_premium_country is False
1✔
1114

1115
    def test_when_premium_unavailable_by_language_code_returns_False(self) -> None:
1✔
1116
        self.set_fxa_locale("zh;q=0.8")
1✔
1117
        assert self.profile.fxa_locale_in_premium_country is False
1✔
1118

1119
    def test_no_fxa_account_returns_False(self) -> None:
1✔
1120
        assert self.profile.fxa_locale_in_premium_country is False
1✔
1121

1122
    def test_in_estonia(self):
1✔
1123
        """Estonia (EE) was added in August 2023."""
1124
        self.set_fxa_locale("et-ee,et;q=0.8")
1✔
1125
        assert self.profile.fxa_locale_in_premium_country is True
1✔
1126

1127

1128
class ProfileJoinedBeforePremiumReleaseTest(ProfileTestCase):
1✔
1129
    """Tests for Profile.joined_before_premium_release"""
1130

1131
    def test_returns_True(self) -> None:
1✔
1132
        before = "2021-10-18 17:00:00+00:00"
1✔
1133
        self.profile.user.date_joined = datetime.fromisoformat(before)
1✔
1134
        assert self.profile.joined_before_premium_release
1✔
1135

1136
    def test_returns_False(self) -> None:
1✔
1137
        after = "2021-10-28 17:00:00+00:00"
1✔
1138
        self.profile.user.date_joined = datetime.fromisoformat(after)
1✔
1139
        assert self.profile.joined_before_premium_release is False
1✔
1140

1141

1142
class ProfileDefaultsTest(ProfileTestCase):
1✔
1143
    """Tests for default Profile values"""
1144

1145
    def test_user_created_after_premium_release_server_storage_True(self) -> None:
1✔
1146
        assert self.profile.server_storage
1✔
1147

1148
    def test_emails_replied_new_user_aggregates_sum_of_replies_to_zero(self) -> None:
1✔
1149
        assert self.profile.emails_replied == 0
1✔
1150

1151

1152
class ProfileEmailsRepliedTest(ProfileTestCase):
1✔
1153
    """Tests for Profile.emails_replied"""
1154

1155
    def test_premium_user_aggregates_replies_from_all_addresses(self) -> None:
1✔
1156
        self.upgrade_to_premium()
1✔
1157
        self.profile.subdomain = "test"
1✔
1158
        self.profile.num_email_replied_in_deleted_address = 1
1✔
1159
        self.profile.save()
1✔
1160
        baker.make(RelayAddress, user=self.profile.user, num_replied=3)
1✔
1161
        baker.make(
1✔
1162
            DomainAddress, user=self.profile.user, address="lower-case", num_replied=5
1163
        )
1164

1165
        assert self.profile.emails_replied == 9
1✔
1166

1167
    def test_free_user_aggregates_replies_from_relay_addresses(self) -> None:
1✔
1168
        baker.make(RelayAddress, user=self.profile.user, num_replied=3)
1✔
1169
        baker.make(RelayAddress, user=self.profile.user, num_replied=5)
1✔
1170

1171
        assert self.profile.emails_replied == 8
1✔
1172

1173

1174
class ProfileUpdateAbuseMetricTest(ProfileTestCase):
1✔
1175
    """Tests for Profile.update_abuse_metric()"""
1176

1177
    def setUp(self) -> None:
1✔
1178
        super().setUp()
1✔
1179
        self.get_or_create_social_account()
1✔
1180
        self.abuse_metric = baker.make(AbuseMetrics, user=self.profile.user)
1✔
1181

1182
        patcher_logger = patch("emails.models.abuse_logger.info")
1✔
1183
        self.mocked_abuse_info = patcher_logger.start()
1✔
1184
        self.addCleanup(patcher_logger.stop)
1✔
1185

1186
        # Selectively patch datatime.now() for emails models
1187
        # https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking
1188
        patcher = patch("emails.models.datetime")
1✔
1189
        mocked_datetime = patcher.start()
1✔
1190
        self.addCleanup(patcher.stop)
1✔
1191

1192
        self.expected_now = datetime.now(UTC)
1✔
1193
        mocked_datetime.combine.return_value = datetime.combine(
1✔
1194
            datetime.now(UTC).date(), datetime.min.time()
1195
        )
1196
        mocked_datetime.now.return_value = self.expected_now
1✔
1197
        mocked_datetime.side_effect = lambda *args, **kw: datetime(*args, **kw)
1!
1198

1199
    @override_settings(MAX_FORWARDED_PER_DAY=5)
1✔
1200
    def test_flags_profile_when_emails_forwarded_abuse_threshold_met(self) -> None:
1✔
1201
        self.abuse_metric.num_email_forwarded_per_day = 4
1✔
1202
        self.abuse_metric.save()
1✔
1203
        assert self.profile.last_account_flagged is None
1✔
1204

1205
        self.profile.update_abuse_metric(email_forwarded=True)
1✔
1206
        self.abuse_metric.refresh_from_db()
1✔
1207

1208
        assert self.profile.fxa
1✔
1209
        self.mocked_abuse_info.assert_called_once_with(
1✔
1210
            "Abuse flagged",
1211
            extra={
1212
                "uid": self.profile.fxa.uid,
1213
                "flagged": self.expected_now.timestamp(),
1214
                "replies": 0,
1215
                "addresses": 0,
1216
                "forwarded": 5,
1217
                "forwarded_size_in_bytes": 0,
1218
            },
1219
        )
1220
        assert self.abuse_metric.num_email_forwarded_per_day == 5
1✔
1221
        assert self.profile.last_account_flagged == self.expected_now
1✔
1222

1223
    @override_settings(MAX_FORWARDED_EMAIL_SIZE_PER_DAY=100)
1✔
1224
    def test_flags_profile_when_forwarded_email_size_abuse_threshold_met(self) -> None:
1✔
1225
        self.abuse_metric.forwarded_email_size_per_day = 50
1✔
1226
        self.abuse_metric.save()
1✔
1227
        assert self.profile.last_account_flagged is None
1✔
1228

1229
        self.profile.update_abuse_metric(forwarded_email_size=50)
1✔
1230
        self.abuse_metric.refresh_from_db()
1✔
1231

1232
        assert self.profile.fxa
1✔
1233
        self.mocked_abuse_info.assert_called_once_with(
1✔
1234
            "Abuse flagged",
1235
            extra={
1236
                "uid": self.profile.fxa.uid,
1237
                "flagged": self.expected_now.timestamp(),
1238
                "replies": 0,
1239
                "addresses": 0,
1240
                "forwarded": 0,
1241
                "forwarded_size_in_bytes": 100,
1242
            },
1243
        )
1244
        assert self.abuse_metric.forwarded_email_size_per_day == 100
1✔
1245
        assert self.profile.last_account_flagged == self.expected_now
1✔
1246

1247

1248
class ProfileMetricsEnabledTest(ProfileTestCase):
1✔
1249
    def test_no_fxa_means_metrics_enabled(self) -> None:
1✔
1250
        assert not self.profile.fxa
1✔
1251
        assert self.profile.metrics_enabled
1✔
1252

1253
    def test_fxa_legacy_means_metrics_enabled(self) -> None:
1✔
1254
        self.get_or_create_social_account()
1✔
1255
        assert self.profile.fxa
1✔
1256
        assert "metricsEnabled" not in self.profile.fxa.extra_data
1✔
1257
        assert self.profile.metrics_enabled
1✔
1258

1259
    def test_fxa_opt_in_means_metrics_enabled(self) -> None:
1✔
1260
        social_account = self.get_or_create_social_account()
1✔
1261
        social_account.extra_data["metricsEnabled"] = True
1✔
1262
        social_account.save()
1✔
1263
        assert self.profile.fxa
1✔
1264
        assert self.profile.metrics_enabled
1✔
1265

1266
    def test_fxa_opt_out_means_metrics_disabled(self) -> None:
1✔
1267
        social_account = self.get_or_create_social_account()
1✔
1268
        social_account.extra_data["metricsEnabled"] = False
1✔
1269
        social_account.save()
1✔
1270
        assert self.profile.fxa
1✔
1271
        assert not self.profile.metrics_enabled
1✔
1272

1273

1274
class ProfilePlanTest(ProfileTestCase):
1✔
1275
    def test_free_user(self) -> None:
1✔
1276
        assert self.profile.plan == "free"
1✔
1277

1278
    def test_premium_user(self) -> None:
1✔
1279
        self.upgrade_to_premium()
1✔
1280
        assert self.profile.plan == "email"
1✔
1281

1282
    def test_phone_user(self) -> None:
1✔
1283
        self.upgrade_to_phone()
1✔
1284
        assert self.profile.plan == "phone"
1✔
1285

1286
    def test_vpn_bundle_user(self) -> None:
1✔
1287
        self.upgrade_to_vpn_bundle()
1✔
1288
        assert self.profile.plan == "bundle"
1✔
1289

1290

1291
class ProfilePlanTermTest(ProfileTestCase):
1✔
1292
    def test_free_user(self) -> None:
1✔
1293
        assert self.profile.plan_term is None
1✔
1294

1295
    def test_premium_user(self) -> None:
1✔
1296
        self.upgrade_to_premium()
1✔
1297
        assert self.profile.plan_term == "unknown"
1✔
1298

1299
    def test_phone_user(self) -> None:
1✔
1300
        self.upgrade_to_phone()
1✔
1301
        assert self.profile.plan_term == "unknown"
1✔
1302

1303
    def test_phone_user_1_month(self) -> None:
1✔
1304
        self.upgrade_to_phone()
1✔
1305
        self.profile.date_phone_subscription_start = datetime(2024, 1, 1, tzinfo=UTC)
1✔
1306

1307
        self.profile.date_phone_subscription_end = datetime(2024, 2, 1, tzinfo=UTC)
1✔
1308
        assert self.profile.plan_term == "1_month"
1✔
1309

1310
    def test_phone_user_1_year(self) -> None:
1✔
1311
        self.upgrade_to_phone()
1✔
1312
        self.profile.date_phone_subscription_start = datetime(2024, 1, 1, tzinfo=UTC)
1✔
1313

1314
        self.profile.date_phone_subscription_end = datetime(2025, 1, 1, tzinfo=UTC)
1✔
1315
        assert self.profile.plan_term == "1_year"
1✔
1316

1317
    def test_vpn_bundle_user(self) -> None:
1✔
1318
        self.upgrade_to_vpn_bundle()
1✔
1319
        assert self.profile.plan_term == "unknown"
1✔
1320

1321

1322
class ProfileMetricsPremiumStatus(ProfileTestCase):
1✔
1323
    def test_free_user(self):
1✔
1324
        assert self.profile.metrics_premium_status == "free"
1✔
1325

1326
    def test_premium_user(self) -> None:
1✔
1327
        self.upgrade_to_premium()
1✔
1328
        assert self.profile.metrics_premium_status == "email_unknown"
1✔
1329

1330
    def test_phone_user(self) -> None:
1✔
1331
        self.upgrade_to_phone()
1✔
1332
        assert self.profile.metrics_premium_status == "phone_unknown"
1✔
1333

1334
    def test_phone_user_1_month(self) -> None:
1✔
1335
        self.upgrade_to_phone()
1✔
1336
        self.profile.date_phone_subscription_start = datetime(2024, 1, 1, tzinfo=UTC)
1✔
1337

1338
        self.profile.date_phone_subscription_end = datetime(2024, 2, 1, tzinfo=UTC)
1✔
1339
        assert self.profile.metrics_premium_status == "phone_1_month"
1✔
1340

1341
    def test_phone_user_1_year(self) -> None:
1✔
1342
        self.upgrade_to_phone()
1✔
1343
        self.profile.date_phone_subscription_start = datetime(2024, 1, 1, tzinfo=UTC)
1✔
1344

1345
        self.profile.date_phone_subscription_end = datetime(2025, 1, 1, tzinfo=UTC)
1✔
1346
        assert self.profile.metrics_premium_status == "phone_1_year"
1✔
1347

1348
    def test_vpn_bundle_user(self) -> None:
1✔
1349
        self.upgrade_to_vpn_bundle()
1✔
1350
        assert self.profile.metrics_premium_status == "bundle_unknown"
1✔
1351

1352

1353
class DomainAddressTest(TestCase):
1✔
1354
    def setUp(self):
1✔
1355
        self.subdomain = "test"
1✔
1356
        self.user = make_premium_test_user()
1✔
1357
        self.storageless_user = make_storageless_test_user()
1✔
1358
        self.user_profile = self.user.profile
1✔
1359
        self.user_profile.subdomain = self.subdomain
1✔
1360
        self.user_profile.save()
1✔
1361

1362
    def test_make_domain_address_assigns_to_user(self):
1✔
1363
        domain_address = DomainAddress.make_domain_address(
1✔
1364
            self.user_profile, "test-assigns"
1365
        )
1366
        assert domain_address.user == self.user
1✔
1367

1368
    @skip(reason="test not reliable, look at FIXME comment")
1✔
1369
    def test_make_domain_address_makes_different_addresses(self):
1✔
1370
        # FIXME: sometimes this test will fail because it randomly generates
1371
        # alias with bad words. See make_domain_address for why this has
1372
        # not been fixed yet
1373
        for i in range(5):
×
1374
            domain_address = DomainAddress.make_domain_address(
×
1375
                self.user_profile, f"test-different-{i}"
1376
            )
1377
            assert domain_address.first_emailed_at is None
×
1378
        domain_addresses = DomainAddress.objects.filter(user=self.user).values_list(
×
1379
            "address", flat=True
1380
        )
1381
        # checks that there are 5 unique DomainAddress
1382
        assert len(set(domain_addresses)) == 5
×
1383

1384
    def test_make_domain_address_makes_requested_address(self):
1✔
1385
        domain_address = DomainAddress.make_domain_address(self.user_profile, "foobar")
1✔
1386
        assert domain_address.address == "foobar"
1✔
1387
        assert domain_address.first_emailed_at is None
1✔
1388

1389
    @override_settings(MAX_ADDRESS_CREATION_PER_DAY=10)
1✔
1390
    def test_make_domain_address_has_limit(self) -> None:
1✔
1391
        for i in range(10):
1✔
1392
            DomainAddress.make_domain_address(self.user_profile, "foobar" + str(i))
1✔
1393
        with pytest.raises(CannotMakeAddressException) as exc_info:
1✔
1394
            DomainAddress.make_domain_address(self.user_profile, "one-too-many")
1✔
1395
        assert exc_info.value.get_codes() == "account_is_paused"
1✔
1396
        domain_address_count = DomainAddress.objects.filter(
1✔
1397
            user=self.user_profile.user
1398
        ).count()
1399
        assert domain_address_count == 10
1✔
1400

1401
    def test_make_domain_address_makes_requested_address_via_email(self):
1✔
1402
        domain_address = DomainAddress.make_domain_address(
1✔
1403
            self.user_profile, "foobar", True
1404
        )
1405
        assert domain_address.address == "foobar"
1✔
1406
        assert domain_address.first_emailed_at is not None
1✔
1407

1408
    def test_make_domain_address_non_premium_user(self) -> None:
1✔
1409
        non_premium_user_profile = baker.make(User).profile
1✔
1410
        with pytest.raises(CannotMakeAddressException) as exc_info:
1✔
1411
            DomainAddress.make_domain_address(
1✔
1412
                non_premium_user_profile, "test-non-premium"
1413
            )
1414
        assert exc_info.value.get_codes() == "free_tier_no_subdomain_masks"
1✔
1415

1416
    def test_make_domain_address_can_make_blocklisted_address(self):
1✔
1417
        domain_address = DomainAddress.make_domain_address(self.user_profile, "testing")
1✔
1418
        assert domain_address.address == "testing"
1✔
1419

1420
    def test_make_domain_address_valid_premium_user_with_no_subdomain(self) -> None:
1✔
1421
        user = baker.make(User)
1✔
1422
        baker.make(
1✔
1423
            SocialAccount,
1424
            user=user,
1425
            provider="fxa",
1426
            extra_data={"subscriptions": [premium_subscription()]},
1427
        )
1428
        user_profile = Profile.objects.get(user=user)
1✔
1429
        with pytest.raises(CannotMakeAddressException) as exc_info:
1✔
1430
            DomainAddress.make_domain_address(user_profile, "test-nosubdomain")
1✔
1431
        assert exc_info.value.get_codes() == "need_subdomain"
1✔
1432

1433
    def test_make_domain_address_dupe_of_existing_raises(self):
1✔
1434
        address = "same-address"
1✔
1435
        DomainAddress.make_domain_address(self.user_profile, address=address)
1✔
1436
        with pytest.raises(DomainAddrDuplicateException) as exc_info:
1✔
1437
            DomainAddress.make_domain_address(self.user_profile, address=address)
1✔
1438
        assert exc_info.value.get_codes() == "duplicate_address"
1✔
1439

1440
    @override_flag("custom_domain_management_redesign", active=False)
1✔
1441
    def test_make_domain_address_can_make_dupe_of_deleted(self):
1✔
1442
        address = "same-address"
1✔
1443
        domain_address = DomainAddress.make_domain_address(
1✔
1444
            self.user_profile, address=address
1445
        )
1446
        domain_address_hash = address_hash(
1✔
1447
            domain_address.address,
1448
            domain_address.user_profile.subdomain,
1449
            domain_address.domain_value,
1450
        )
1451
        domain_address.delete()
1✔
1452
        dupe_domain_address = DomainAddress.make_domain_address(
1✔
1453
            self.user_profile, address=address
1454
        )
1455
        assert (
1✔
1456
            DeletedAddress.objects.filter(address_hash=domain_address_hash).count() == 1
1457
        )
1458
        assert dupe_domain_address.full_address == domain_address.full_address
1✔
1459

1460
    @override_flag("custom_domain_management_redesign", active=True)
1✔
1461
    def test_valid_address_dupe_domain_address_of_deleted_is_not_valid(self):
1✔
1462
        address = "same-address"
1✔
1463
        domain_address = DomainAddress.make_domain_address(
1✔
1464
            self.user_profile, address=address
1465
        )
1466
        domain_address.delete()
1✔
1467
        assert not valid_address(
1✔
1468
            address, domain_address.domain_value, self.user_profile.subdomain
1469
        )
1470

1471
    @override_flag("custom_domain_management_redesign", active=True)
1✔
1472
    def test_make_domain_address_cannot_make_dupe_of_deleted(self):
1✔
1473
        address = "same-address"
1✔
1474
        domain_address = DomainAddress.make_domain_address(
1✔
1475
            self.user_profile, address=address
1476
        )
1477
        domain_address_hash = address_hash(
1✔
1478
            domain_address.address,
1479
            domain_address.user_profile.subdomain,
1480
            domain_address.domain_value,
1481
        )
1482
        domain_address.delete()
1✔
1483
        with pytest.raises(DomainAddrUnavailableException) as exc_info:
1✔
1484
            DomainAddress.make_domain_address(self.user_profile, address=address)
1✔
1485
        assert exc_info.value.get_codes() == "address_unavailable"
1✔
1486
        assert (
1✔
1487
            DeletedAddress.objects.filter(address_hash=domain_address_hash).count() == 1
1488
        )
1489

1490
    @patch("emails.models.address_default")
1✔
1491
    def test_make_domain_address_doesnt_randomly_generate_bad_word(
1✔
1492
        self, address_default_mocked: Mock
1493
    ) -> None:
1494
        address_default_mocked.return_value = "angry0123"
1✔
1495
        with pytest.raises(CannotMakeAddressException) as exc_info:
1✔
1496
            DomainAddress.make_domain_address(self.user_profile)
1✔
1497
        assert exc_info.value.get_codes() == "address_unavailable"
1✔
1498

1499
    def test_delete_adds_deleted_address_object(self):
1✔
1500
        domain_address = baker.make(DomainAddress, address="lower-case", user=self.user)
1✔
1501
        domain_address_hash = sha256(
1✔
1502
            domain_address.full_address.encode("utf-8")
1503
        ).hexdigest()
1504
        domain_address.delete()
1✔
1505
        deleted_address_qs = DeletedAddress.objects.filter(
1✔
1506
            address_hash=domain_address_hash
1507
        )
1508
        assert deleted_address_qs.count() == 1
1✔
1509
        assert deleted_address_qs.get().address_hash == domain_address_hash
1✔
1510

1511
    def test_premium_user_can_set_block_list_emails(self):
1✔
1512
        domain_address = DomainAddress.objects.create(
1✔
1513
            user=self.user, address="lower-case"
1514
        )
1515
        assert domain_address.block_list_emails is False
1✔
1516
        domain_address.block_list_emails = True
1✔
1517
        domain_address.save()
1✔
1518
        domain_address.refresh_from_db()
1✔
1519
        assert domain_address.block_list_emails is True
1✔
1520

1521
    def test_delete_increments_values_on_profile(self):
1✔
1522
        assert self.user_profile.num_address_deleted == 0
1✔
1523
        assert self.user_profile.num_email_forwarded_in_deleted_address == 0
1✔
1524
        assert self.user_profile.num_email_blocked_in_deleted_address == 0
1✔
1525
        assert self.user_profile.num_level_one_trackers_blocked_in_deleted_address == 0
1✔
1526
        assert self.user_profile.num_email_replied_in_deleted_address == 0
1✔
1527
        assert self.user_profile.num_email_spam_in_deleted_address == 0
1✔
1528
        assert self.user_profile.num_deleted_relay_addresses == 0
1✔
1529
        assert self.user_profile.num_deleted_domain_addresses == 0
1✔
1530

1531
        domain_address = DomainAddress.objects.create(
1✔
1532
            user=self.user,
1533
            address="lower-case",
1534
            num_forwarded=2,
1535
            num_blocked=3,
1536
            num_level_one_trackers_blocked=4,
1537
            num_replied=5,
1538
            num_spam=6,
1539
        )
1540
        domain_address.delete()
1✔
1541

1542
        self.user_profile.refresh_from_db()
1✔
1543
        assert self.user_profile.num_address_deleted == 1
1✔
1544
        assert self.user_profile.num_email_forwarded_in_deleted_address == 2
1✔
1545
        assert self.user_profile.num_email_blocked_in_deleted_address == 3
1✔
1546
        assert self.user_profile.num_level_one_trackers_blocked_in_deleted_address == 4
1✔
1547
        assert self.user_profile.num_email_replied_in_deleted_address == 5
1✔
1548
        assert self.user_profile.num_email_spam_in_deleted_address == 6
1✔
1549
        assert self.user_profile.num_deleted_relay_addresses == 0
1✔
1550
        assert self.user_profile.num_deleted_domain_addresses == 1
1✔
1551

1552
    def test_formerly_premium_user_clears_block_list_emails(self):
1✔
1553
        domain_address = DomainAddress.objects.create(
1✔
1554
            user=self.user, address="coupons", block_list_emails=True
1555
        )
1556
        domain_address.refresh_from_db()
1✔
1557
        assert domain_address.block_list_emails is True
1✔
1558

1559
        # Remove premium from user
1560
        assert (fxa_account := self.user.profile.fxa) is not None
1✔
1561
        fxa_account.extra_data["subscriptions"] = []
1✔
1562
        fxa_account.save()
1✔
1563
        assert not self.user.profile.has_premium
1✔
1564

1565
        domain_address.save()
1✔
1566
        assert domain_address.block_list_emails is False
1✔
1567

1568
    def test_storageless_user_cant_set_labels(self):
1✔
1569
        domain_address = DomainAddress.objects.create(
1✔
1570
            user=self.storageless_user, address="lower-case"
1571
        )
1572
        assert domain_address.description == ""
1✔
1573
        domain_address.description = "Arbitrary description"
1✔
1574
        domain_address.save()
1✔
1575
        domain_address.refresh_from_db()
1✔
1576
        assert domain_address.description == ""
1✔
1577

1578
    def test_clear_storage_with_update_fields(self) -> None:
1✔
1579
        """With update_fields, the stored data is cleared for storageless users."""
1580
        domain_address = DomainAddress.objects.create(
1✔
1581
            user=self.storageless_user, address="no-storage"
1582
        )
1583
        assert domain_address.used_on is None
1✔
1584
        assert domain_address.description == ""
1✔
1585

1586
        # Use QuerySet.update to avoid model save method
1587
        DomainAddress.objects.filter(id=domain_address.id).update(
1✔
1588
            description="the description",
1589
            used_on="https://example.com",
1590
        )
1591
        domain_address.refresh_from_db()
1✔
1592
        assert domain_address.description == "the description"
1✔
1593
        assert domain_address.used_on == "https://example.com"
1✔
1594

1595
        # Update a different field with update_fields to avoid full model save
1596
        new_last_used_at = datetime(2024, 1, 11, tzinfo=UTC)
1✔
1597
        domain_address.last_used_at = new_last_used_at
1✔
1598
        domain_address.save(update_fields={"last_used_at"})
1✔
1599

1600
        # Since .save() added to update_fields, the storage fields are cleared
1601
        domain_address.refresh_from_db()
1✔
1602
        assert domain_address.last_used_at == new_last_used_at
1✔
1603
        assert domain_address.description == ""
1✔
1604
        assert domain_address.used_on == ""
1✔
1605

1606
    def test_clear_block_list_emails_with_update_fields(self) -> None:
1✔
1607
        """
1608
        With update_fields, the block_list_emails flag is still cleared for free users.
1609
        """
1610
        domain_address = DomainAddress.objects.create(
1✔
1611
            user=self.user, address="block-list-emails", block_list_emails=True
1612
        )
1613

1614
        # Remove premium from user
1615
        assert (fxa_account := self.user.profile.fxa) is not None
1✔
1616
        fxa_account.extra_data["subscriptions"] = []
1✔
1617
        fxa_account.save()
1✔
1618
        assert not self.user.profile.has_premium
1✔
1619
        assert domain_address.block_list_emails
1✔
1620

1621
        # Update a different field with update_fields to avoid full model save
1622
        new_last_used_at = datetime(2024, 1, 12, tzinfo=UTC)
1✔
1623
        assert domain_address.last_used_at != new_last_used_at
1✔
1624
        domain_address.last_used_at = new_last_used_at
1✔
1625
        domain_address.save(update_fields={"last_used_at"})
1✔
1626

1627
        # Since .save() added to update_fields, block_list_emails flag is cleared
1628
        domain_address.refresh_from_db()
1✔
1629
        assert domain_address.last_used_at == new_last_used_at
1✔
1630
        assert not domain_address.block_list_emails
1✔
1631

1632
    def test_create_updates_profile_last_engagement(self) -> None:
1✔
1633
        DomainAddress.make_domain_address(self.user.profile, address="create")
1✔
1634
        assert self.user.profile.last_engagement
1✔
1635
        pre_create_last_engagement = self.user.profile.last_engagement
1✔
1636

1637
        DomainAddress.make_domain_address(self.user.profile, address="create2")
1✔
1638

1639
        self.user.profile.refresh_from_db()
1✔
1640
        assert self.user.profile.last_engagement > pre_create_last_engagement
1✔
1641

1642
    def test_save_does_not_update_profile_last_engagement(self) -> None:
1✔
1643
        domain_address = DomainAddress.make_domain_address(
1✔
1644
            self.user.profile, address="save"
1645
        )
1646
        assert self.user.profile.last_engagement
1✔
1647
        pre_save_last_engagement = self.user.profile.last_engagement
1✔
1648

1649
        domain_address.enabled = False
1✔
1650
        domain_address.save()
1✔
1651

1652
        self.user.profile.refresh_from_db()
1✔
1653
        assert self.user.profile.last_engagement == pre_save_last_engagement
1✔
1654

1655
    def test_delete_updates_profile_last_engagement(self) -> None:
1✔
1656
        domain_address = DomainAddress.make_domain_address(
1✔
1657
            self.user.profile, address="delete"
1658
        )
1659
        assert self.user.profile.last_engagement
1✔
1660
        pre_delete_last_engagement = self.user.profile.last_engagement
1✔
1661

1662
        domain_address.delete()
1✔
1663

1664
        self.user.profile.refresh_from_db()
1✔
1665
        assert self.user.profile.last_engagement > pre_delete_last_engagement
1✔
1666

1667
    def test_metrics_id(self):
1✔
1668
        address = DomainAddress.objects.create(user=self.user, address="metrics")
1✔
1669
        assert address.metrics_id == f"D{address.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