sitcod3 commited on
Commit
9fa1941
·
verified ·
1 Parent(s): e01d702

Upload folder using huggingface_hub

Browse files
app/validator/engine.py CHANGED
@@ -75,9 +75,11 @@ def _build_reason_code(
75
  ) -> str | None:
76
  """Machine cause for the verdict. Ordered most-specific/actionable first.
77
 
78
- disposable outranks everything (frontend classifies it as Disposable);
79
- a catch-all or a dead-A-only route outranks a raw SMTP code so the reason
80
- matches the Unknown verdict the frontend assigns for those rows.
 
 
81
  """
82
  if early:
83
  return early
@@ -85,22 +87,21 @@ def _build_reason_code(
85
  return "disposable"
86
  if dns is not None and dns.a_fallback and not dns.has_mx_records:
87
  return "a_fallback_only"
 
 
 
 
 
 
 
88
  if catch_all is not None and catch_all.is_catch_all:
89
  return "catch_all_detected"
90
- if smtp is not None:
91
- if smtp.verified is False and smtp.smtp_code:
92
- if smtp.smtp_code >= 500:
93
- return "rcpt_5xx"
94
- if smtp.smtp_code >= 400:
95
- return "rcpt_4xx"
96
- if smtp.smtp_code >= 200:
97
- return "rcpt_2xx"
98
- if smtp.verified is None and smtp.error:
99
- return (
100
- "smtp_timeout"
101
- if re.search(r"timed?\s*out", smtp.error, re.IGNORECASE)
102
- else "smtp_unavailable"
103
- )
104
  return None
105
 
106
 
 
75
  ) -> str | None:
76
  """Machine cause for the verdict. Ordered most-specific/actionable first.
77
 
78
+ disposable outranks everything (frontend classifies it as Disposable). A
79
+ definitive SMTP rejection (verified False + code) outranks the domain-level
80
+ catch-all flag: reporting "catch_all_detected" beside status=invalid is
81
+ incoherent — the explicit "mailbox does not exist" is the actionable fact.
82
+ Catch-all then outranks a merely inconclusive probe.
83
  """
84
  if early:
85
  return early
 
87
  return "disposable"
88
  if dns is not None and dns.a_fallback and not dns.has_mx_records:
89
  return "a_fallback_only"
90
+ if smtp is not None and smtp.verified is False and smtp.smtp_code:
91
+ if smtp.smtp_code >= 500:
92
+ return "rcpt_5xx"
93
+ if smtp.smtp_code >= 400:
94
+ return "rcpt_4xx"
95
+ if smtp.smtp_code >= 200:
96
+ return "rcpt_2xx"
97
  if catch_all is not None and catch_all.is_catch_all:
98
  return "catch_all_detected"
99
+ if smtp is not None and smtp.verified is None and smtp.error:
100
+ return (
101
+ "smtp_timeout"
102
+ if re.search(r"timed?\s*out", smtp.error, re.IGNORECASE)
103
+ else "smtp_unavailable"
104
+ )
 
 
 
 
 
 
 
 
105
  return None
106
 
107
 
tests/test_engine_metadata.py CHANGED
@@ -165,6 +165,42 @@ class EngineMetadataTests(unittest.IsolatedAsyncioTestCase):
165
  self.assertEqual(result.reason_code, "catch_all_detected")
166
  self.assertEqual(result.status, ValidationStatus.RISKY)
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  async def test_reason_code_smtp_timeout(self):
169
  engine = EmailValidationEngine()
170
  with (
 
165
  self.assertEqual(result.reason_code, "catch_all_detected")
166
  self.assertEqual(result.status, ValidationStatus.RISKY)
167
 
168
+ async def test_explicit_5xx_rejection_outranks_catch_all(self):
169
+ # A definitive "mailbox does not exist" (5xx) is stronger and more
170
+ # actionable than the domain-level catch-all flag: reporting catch_all
171
+ # beside status=invalid is incoherent. The explicit rejection must win.
172
+ engine = EmailValidationEngine()
173
+ with (
174
+ patch("app.validator.engine.validate_syntax", return_value=_syntax()),
175
+ patch(
176
+ "app.validator.engine.check_dns",
177
+ new=AsyncMock(return_value=_dns()),
178
+ ),
179
+ patch(
180
+ "app.validator.engine.check_disposable",
181
+ return_value=_disposable(),
182
+ ),
183
+ patch(
184
+ "app.validator.mailbox_provider.smtp_verifier.verify",
185
+ new=AsyncMock(
186
+ return_value=SMTPResult(
187
+ verified=False,
188
+ smtp_code=550,
189
+ smtp_message="550 mailbox does not exist (provider confirmed)",
190
+ )
191
+ ),
192
+ ),
193
+ patch(
194
+ "app.validator.mailbox_provider.smtp_verifier.check_catch_all",
195
+ new=AsyncMock(return_value=(True, 0.95)),
196
+ ),
197
+ ):
198
+ result = await engine.validate("person@example.test", deep=True)
199
+
200
+ self.assertEqual(result.reason_code, "rcpt_5xx")
201
+ self.assertEqual(result.scoring.score, 0)
202
+ self.assertEqual(result.status, ValidationStatus.INVALID)
203
+
204
  async def test_reason_code_smtp_timeout(self):
205
  engine = EmailValidationEngine()
206
  with (