Fixture email ditolak validator: domain uji yang lolos Pydantic
Gerbang sintaks email-validator menolak domain .test dan .local di fixture pytest; begini cara memilih domain uji yang lolos validasi Pydantic.
Ringkasan
Fixture email pakai domain .test atau .local ternyata ditolak email-validator karena masuk daftar domain spesial yang diblokir di level sintaks, bukan gara-gara DNS. Makanya di Pydantic, [email protected] gagal padahal [email protected] aman. Solusinya gampang: ganti ke example.com atau aktifin test_environment=True kalau emang mau tetap pakai .test.
Judul: Fixture email ditolak validator: domain uji yang lolos Pydantic
Pytest berhenti di fixture pertama. Pesan yang muncul bukan kesalahan logika aplikasi, melainkan ValidationError dari validasi email: The part after the @-sign is a special-use or reserved name that cannot be used with email [2]. Email yang dipakai terlihat wajar untuk pengujian: [email protected] dan user@myapp.local. Keduanya gagal sebelum data sempat masuk ke handler.
Dugaan awal wajar. Domain .test dan .local terasa aman karena dicadangkan untuk pengujian menurut RFC 6761. Rekaan itu keliru di konteks validasi. Pustaka email-validator menolak nama tersebut tepat di level sintaks, bukan saat pengecekan DNS. Validasi membandingkan ascii_domain dengan daftar blokir melalui ascii_domain == d or ascii_domain.endswith("." + d) sehingga domain itu sendiri maupun subdomainnya ikut terblokir [2].
Daftar blokirnya didefinisikan sebagai SPECIAL_USE_DOMAIN_NAMES di berkas inisialisasi pustaka dan mencakup invalid, local, localhost, onion, test, serta subdomain arpa tertentu; domain test dan turunannya hanya diizinkan bila parameter test_environment diaktifkan [1]. Perilaku ini selaras dengan registry IANA Special-Use Domain Names yang menyatakan penunjukan Special Use berlaku untuk domain yang terdaftar dan subdomainnya, mencakup test, example.com, example.net, example.org, invalid, localhost, dan onion per pembaruan 2026-05-22 [4].
Perbedaan yang menjebak ada di langkah berikutnya. Tipe EmailStr pada Pydantic memanggil validate_email dengan check_deliverability bernilai False sehingga pengecekan DNS dimatikan dan hanya gerbang sintaks serta SUDN yang berlaku [3]. Akibatnya [email protected] lolos validasi Pydantic, sementara [email protected] tetap ditolak. README pustaka menegaskan alasan teknisnya: domain dokumentasi IANA tidak masuk daftar SPECIAL_USE_DOMAIN_NAMES sehingga boleh dipakai, meski tetap gagal bila pengecekan deliverability diaktifkan karena IANA mempublikasikan NULL MX untuk domain tersebut [1]. Sebuah backend demand-intelligence fiktif, misalnya DemandScope, mematok email-validator minimal versi 2.1 dan Pydantic minimal versi 2.7; perilaku yang diuraikan di sini diambil dari branch main per 2026-10-06, sementara versi yang ter-resolve di CI tidak diinspeksi melalui lockfile.
Mengapa .test dan .local ditolak di level sintaks
Pemeriksaan SUDN terjadi sebelum validasi MX atau SMTP. Tujuannya mencegah alamat yang secara desain tidak boleh dirutekan masuk ke sistem sebagai data valid. Karena pencocokan mencakup akhiran domain, myapp.test, service.myapp.local, atau app.localhost semua terblokir.
Cara memverifikasi cepat di REPL:
from email_validator import validate_email, EmailSyntaxError
try:
validate_email("[email protected]", check_deliverability=False)
except EmailSyntaxError as e:
print(str(e))
# The part after the @-sign is a special-use or reserved name...
Jika keluar pesan yang sama seperti di pipeline, sumber masalah sudah terkunci di gerbang sintaks, bukan di jaringan.
Mengganti fixture ke domain dokumentasi yang lolos validasi
Perbaikan yang paling ringkas dan tetap benar adalah mengganti domain fixture ke domain dokumentasi IANA yang tidak diblokir di level sintaks. Di bawah Pydantic, ini lolos karena DNS tidak diperiksa.
Ganti fixture yang sebelumnya memakai domain khusus yang dicadangkan untuk uji:
from pydantic import BaseModel, EmailStr, ValidationError
class UserIn(BaseModel):
email: EmailStr # validasi memakai check_deliverability=False [3]
# sebelum: gagal di gerbang SUDN
try:
UserIn(email="[email protected]")
except ValidationError as e:
print(e.errors()[0]["ctx"]["reason"])
# sesudah: lolos
print(UserIn(email="[email protected]").email)
print(UserIn(email="[email protected]").email)
print(UserIn(email="[email protected]").email)
Cek hasilnya dengan menjalankan kembali test yang sebelumnya gagal. Perintah yang sama harus hijau tanpa mengubah logika validasi produksi. Untuk memastikan tidak ada sisa fixture yang masih memakai akhiran terblokir, jalankan pencarian di repo:
grep -R "@.*\.test" tests/fixtures/
grep -R "@.*\.local" tests/fixtures/
Jika tidak ada keluaran, migrasi fixture selesai.
Kapan tetap memakai .test dengan test_environment
Ada tim yang memang ingin mempertahankan @test agar jelas bahwa alamat tersebut tidak akan pernah valid di produksi. Pustaka menyediakan pintu keluar berupa test_environment=True yang membuka domain test dan turunannya [1]. Opsi ini ada di level email-validator, bukan di Pydantic secara langsung.
Gunakan langsung bila validasi dilakukan tanpa Pydantic:
from email_validator import validate_email
validate_email("[email protected]", check_deliverability=False, test_environment=True)
print("lolos dalam mode uji")
Jika validasi melalui Pydantic dan tetap ingin mengizinkan @test, bungkus dengan validator kustom alih-alih mengubah EmailStr:
from pydantic import BaseModel
from email_validator import validate_email
class UserTest(BaseModel):
email: str
@classmethod
def with_test_domain(cls, addr: str):
info = validate_email(addr, check_deliverability=False, test_environment=True)
return cls(email=info.normalized)
Pendekatan ini menjaga kontrak validasi tetap sama antara produksi dan pengujian. Fixture yang lolos EmailStr tanpa flag tambahan akan lolos pula di produksi. Flag test_environment dipakai hanya di jalur pengujian yang memang menghendaki domain tersebut, bukan sebagai default global.
Pelajaran yang bertahan bukan soal .test itu sendiri. Fixture harus memenuhi kontrak validasi yang sama dengan input produksi. Domain dokumentasi seperti example.com tersedia justru untuk keperluan ini dan tidak terblokir di gerbang sintaks pustaka [1], sementara domain yang benar-benar dicadangkan untuk tidak dirutekan akan ditolak sebelum pengecekan DNS apa pun dijalankan.
[2] Pesan error SUDN dan pencocokan akhiran domain, berkas syntax.py email-validator:
[3] EmailStr memanggil validate_email dengan check_deliverability False, berkas networks.py Pydantic:
[4] IANA Special-Use Domain Names registry, update 2026-05-22: