#18: implement SMTP Submission (587/465), SASL PLAIN/LOGIN auth and preflight diagnostics
This commit is contained in:
1 parent
7249419efc
commit
d56f29d084
70 files changed
+5163
-659
No files matched your search
@@ -0,0 +1,179 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"lambdamail/protocols/internal/domain/entity"
|
||||
)
|
||||
|
||||
func conflictTestInput() SyncDnsRecordsInput {
|
||||
return SyncDnsRecordsInput{
|
||||
DomainName: "example.test",
|
||||
MailHost: "mail.example.test",
|
||||
ServerIPv4: "192.0.2.1",
|
||||
RsaDkimPubKey: "rsaKey",
|
||||
EdDkimPubKey: "edKey",
|
||||
}
|
||||
}
|
||||
|
||||
// PLAN.md section 7.5: pre-existing records that are not ours are never
|
||||
// touched, only reported as conflicts. A third-party verification TXT sits at
|
||||
// the zone apex under the same type and name as our SPF record, so a reconciler
|
||||
// keyed only on type+name would overwrite it and break the other service.
|
||||
func TestSyncDnsRecords_ForeignApexTxtIsPreserved(t *testing.T) {
|
||||
dns := newFakeDnsProvider()
|
||||
dns.seed(entity.DnsRecord{
|
||||
Type: "TXT",
|
||||
Name: "example.test",
|
||||
Value: "google-site-verification=abc123",
|
||||
})
|
||||
|
||||
uc := NewSyncDnsRecordsUseCase(dns, nil)
|
||||
out, err := uc.Execute(context.Background(), conflictTestInput())
|
||||
if err != nil {
|
||||
t.Fatalf("Execute: %v", err)
|
||||
}
|
||||
|
||||
apexTxt := dns.find("TXT", "example.test")
|
||||
if len(apexTxt) != 2 {
|
||||
t.Fatalf("expected the foreign TXT and our SPF to coexist, got %d records", len(apexTxt))
|
||||
}
|
||||
|
||||
var foundForeign, foundSPF bool
|
||||
for _, r := range apexTxt {
|
||||
if r.Value == "google-site-verification=abc123" {
|
||||
foundForeign = true
|
||||
}
|
||||
if strings.HasPrefix(r.Value, "v=spf1") {
|
||||
foundSPF = true
|
||||
}
|
||||
}
|
||||
if !foundForeign {
|
||||
t.Error("the foreign verification TXT was destroyed")
|
||||
}
|
||||
if !foundSPF {
|
||||
t.Error("the SPF record was not created alongside the foreign apex TXT")
|
||||
}
|
||||
if out.ConflictCount != 0 {
|
||||
t.Errorf("a coexisting foreign TXT is not a conflict, got ConflictCount=%d", out.ConflictCount)
|
||||
}
|
||||
}
|
||||
|
||||
// A name that admits only one value (the mail host A record) already pointing
|
||||
// somewhere else is a genuine conflict: silently repointing it could take down
|
||||
// an unrelated service.
|
||||
func TestSyncDnsRecords_ForeignARecordIsReportedAsConflict(t *testing.T) {
|
||||
dns := newFakeDnsProvider()
|
||||
dns.seed(entity.DnsRecord{
|
||||
Type: "A",
|
||||
Name: "mail.example.test",
|
||||
Value: "198.51.100.9",
|
||||
Comment: "managed by something else",
|
||||
})
|
||||
|
||||
uc := NewSyncDnsRecordsUseCase(dns, nil)
|
||||
out, err := uc.Execute(context.Background(), conflictTestInput())
|
||||
if err != nil {
|
||||
t.Fatalf("Execute: %v", err)
|
||||
}
|
||||
|
||||
records := dns.find("A", "mail.example.test")
|
||||
if len(records) != 1 || records[0].Value != "198.51.100.9" {
|
||||
t.Errorf("foreign A record was not left untouched: %+v", records)
|
||||
}
|
||||
if out.ConflictCount != 1 {
|
||||
t.Fatalf("ConflictCount = %d, want 1", out.ConflictCount)
|
||||
}
|
||||
if out.Status != SyncStatusPartial {
|
||||
t.Errorf("Status = %s, want %s", out.Status, SyncStatusPartial)
|
||||
}
|
||||
if !strings.Contains(out.Conflicts[0].String(), "198.51.100.9") {
|
||||
t.Errorf("conflict report does not name the blocking value: %s", out.Conflicts[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Our own records must still be updated in place when they drift.
|
||||
func TestSyncDnsRecords_OwnRecordDriftIsCorrected(t *testing.T) {
|
||||
dns := newFakeDnsProvider()
|
||||
uc := NewSyncDnsRecordsUseCase(dns, nil)
|
||||
ctx := context.Background()
|
||||
|
||||
if _, err := uc.Execute(ctx, conflictTestInput()); err != nil {
|
||||
t.Fatalf("initial Execute: %v", err)
|
||||
}
|
||||
|
||||
for i, r := range dns.records {
|
||||
if r.Type == "A" && r.Name == "mail.example.test" {
|
||||
dns.records[i].Value = "203.0.113.7"
|
||||
}
|
||||
}
|
||||
|
||||
out, err := uc.Execute(ctx, conflictTestInput())
|
||||
if err != nil {
|
||||
t.Fatalf("second Execute: %v", err)
|
||||
}
|
||||
|
||||
if out.UpdatedCount != 1 || out.ConflictCount != 0 {
|
||||
t.Errorf("updated=%d conflicts=%d, want updated=1 conflicts=0", out.UpdatedCount, out.ConflictCount)
|
||||
}
|
||||
if records := dns.find("A", "mail.example.test"); records[0].Value != "192.0.2.1" {
|
||||
t.Errorf("drifted A record was not corrected: got %q", records[0].Value)
|
||||
}
|
||||
if out.Status != SyncStatusVerified {
|
||||
t.Errorf("Status = %s, want %s", out.Status, SyncStatusVerified)
|
||||
}
|
||||
}
|
||||
|
||||
type stubDnsVerifier struct {
|
||||
invisible map[string]bool
|
||||
}
|
||||
|
||||
func (s stubDnsVerifier) VerifyRecord(_ context.Context, record entity.DnsRecord) (bool, string) {
|
||||
if s.invisible[record.Name] {
|
||||
return false, "no answer"
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
// PLAN.md section 7.5 verifies through public resolvers after writing. A
|
||||
// record the provider accepted but nobody can resolve is drift, not success.
|
||||
func TestSyncDnsRecords_UnresolvableRecordIsReportedAsDrift(t *testing.T) {
|
||||
dns := newFakeDnsProvider()
|
||||
uc := NewSyncDnsRecordsUseCase(dns, nil)
|
||||
uc.SetVerifier(stubDnsVerifier{invisible: map[string]bool{"mail.example.test": true}})
|
||||
|
||||
out, err := uc.Execute(context.Background(), conflictTestInput())
|
||||
if err != nil {
|
||||
t.Fatalf("Execute: %v", err)
|
||||
}
|
||||
|
||||
if out.Status != SyncStatusDrift {
|
||||
t.Errorf("Status = %s, want DRIFT", out.Status)
|
||||
}
|
||||
if len(out.Unverified) == 0 {
|
||||
t.Fatal("the unresolvable record was not reported")
|
||||
}
|
||||
if !strings.Contains(out.Unverified[0], "mail.example.test") {
|
||||
t.Errorf("Unverified = %v, want it to name the record", out.Unverified)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncDnsRecords_AllRecordsVisibleIsVerified(t *testing.T) {
|
||||
dns := newFakeDnsProvider()
|
||||
uc := NewSyncDnsRecordsUseCase(dns, nil)
|
||||
uc.SetVerifier(stubDnsVerifier{})
|
||||
|
||||
out, err := uc.Execute(context.Background(), conflictTestInput())
|
||||
if err != nil {
|
||||
t.Fatalf("Execute: %v", err)
|
||||
}
|
||||
|
||||
if out.Status != SyncStatusVerified {
|
||||
t.Errorf("Status = %s, want VERIFIED", out.Status)
|
||||
}
|
||||
if len(out.Unverified) != 0 {
|
||||
t.Errorf("Unverified = %v, want empty", out.Unverified)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user