Upload folder using huggingface_hub
Browse files- app/validator/disposable.py +12 -1
- tests/test_free_providers.py +48 -0
app/validator/disposable.py
CHANGED
|
@@ -11,7 +11,18 @@ FREE_PROVIDERS = {
|
|
| 11 |
"yahoo.de", "yahoo.it", "yahoo.es", "yahoo.com.br",
|
| 12 |
"hotmail.fr", "hotmail.de", "hotmail.it", "hotmail.es",
|
| 13 |
"hotmail.co.uk", "fastmail.com", "tutanota.com",
|
| 14 |
-
"pm.me", "proton.me", "hey.com", "duck.com"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
}
|
| 16 |
|
| 17 |
# Role-based email prefixes
|
|
|
|
| 11 |
"yahoo.de", "yahoo.it", "yahoo.es", "yahoo.com.br",
|
| 12 |
"hotmail.fr", "hotmail.de", "hotmail.it", "hotmail.es",
|
| 13 |
"hotmail.co.uk", "fastmail.com", "tutanota.com",
|
| 14 |
+
"pm.me", "proton.me", "hey.com", "duck.com",
|
| 15 |
+
# ISP mailbox domains: legitimate consumer mailboxes handled by the
|
| 16 |
+
# deterministic free-provider path, never by the ML advisory (the model has
|
| 17 |
+
# no training signal for them and flagged sbcglobal.net as disposable).
|
| 18 |
+
"sbcglobal.net", "att.net", "comcast.net", "verizon.net",
|
| 19 |
+
"bellsouth.net", "cox.net", "charter.net", "earthlink.net",
|
| 20 |
+
"roadrunner.com", "rr.com", "shaw.ca", "rogers.com",
|
| 21 |
+
"btinternet.com", "sky.com", "virginmedia.com", "orange.fr",
|
| 22 |
+
"wanadoo.fr", "free.fr", "laposte.net", "web.de",
|
| 23 |
+
"t-online.de", "arcor.de", "libero.it", "virgilio.it",
|
| 24 |
+
"terra.com.br", "uol.com.br", "bol.com.br", "seznam.cz",
|
| 25 |
+
"naver.com", "daum.net", "hanmail.net", "rediffmail.com",
|
| 26 |
}
|
| 27 |
|
| 28 |
# Role-based email prefixes
|
tests/test_free_providers.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import unittest
|
| 2 |
+
|
| 3 |
+
from app.validator.disposable import FREE_PROVIDERS, check_disposable, domain_is_known
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class FreeProviderCoverageTests(unittest.TestCase):
|
| 7 |
+
"""ISP and consumer mailbox domains must be recognised deterministically.
|
| 8 |
+
|
| 9 |
+
Found live: `g.lombardi@sbcglobal.net` (AT&T's consumer mail) was passed to
|
| 10 |
+
the ML advisory, which flagged it DISPOSABLE at 0.995. Consumer ISP domains
|
| 11 |
+
are not disposable — they belong to the deterministic free-provider path,
|
| 12 |
+
which also removes them from the model's remit entirely.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
ISP_DOMAINS = [
|
| 16 |
+
"sbcglobal.net", # AT&T
|
| 17 |
+
"att.net", "comcast.net", "verizon.net", "bellsouth.net",
|
| 18 |
+
"cox.net", "charter.net", "earthlink.net",
|
| 19 |
+
"btinternet.com", "sky.com", "virginmedia.com",
|
| 20 |
+
"web.de", "t-online.de", "laposte.net", "free.fr",
|
| 21 |
+
"naver.com", "rediffmail.com",
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
def test_isp_domains_are_free_providers(self):
|
| 25 |
+
missing = [d for d in self.ISP_DOMAINS if d not in FREE_PROVIDERS]
|
| 26 |
+
self.assertEqual(missing, [], f"ISP domains missing: {missing}")
|
| 27 |
+
|
| 28 |
+
def test_isp_domains_are_known_and_not_disposable(self):
|
| 29 |
+
for domain in self.ISP_DOMAINS:
|
| 30 |
+
with self.subTest(domain=domain):
|
| 31 |
+
self.assertTrue(domain_is_known(domain))
|
| 32 |
+
result = check_disposable(f"user@{domain}", domain)
|
| 33 |
+
self.assertTrue(result.is_free_provider)
|
| 34 |
+
self.assertFalse(result.is_disposable)
|
| 35 |
+
|
| 36 |
+
def test_known_domains_are_never_sent_to_the_ml_advisory(self):
|
| 37 |
+
# The engine gates the model on `not domain_is_known(domain)`; this
|
| 38 |
+
# asserts the gate input for every ISP domain.
|
| 39 |
+
for domain in self.ISP_DOMAINS:
|
| 40 |
+
with self.subTest(domain=domain):
|
| 41 |
+
self.assertTrue(
|
| 42 |
+
domain_is_known(domain),
|
| 43 |
+
f"{domain} would reach the ML advisory",
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
if __name__ == "__main__":
|
| 48 |
+
unittest.main()
|