package dkim import ( "io" "net/http" "net/http/httptest" "strings" "testing" "mailgoserver/internal/dnspublish" ) // TestGenerateGlobalDKIMKeyWithoutHostnameSkipsPublish confirms regenerating with no // hostname/creds configured just rotates the key, no publish attempt. func TestGenerateGlobalDKIMKeyWithoutHostnameSkipsPublish(t *testing.T) { database := openTestDKIMDB(t) mgr := New(database, 1024) publishErr, err := mgr.GenerateGlobalDKIMKey("", nil) if err != nil { t.Fatal(err) } if publishErr != nil { t.Fatalf("expected no publish attempt with no hostname/creds, got %v", publishErr) } key, err := mgr.GetActiveGlobalDKIMKey() if err != nil || key == nil { t.Fatalf("expected a global key to exist, got %v %v", key, err) } } // TestGenerateGlobalDKIMKeyPublishesAndRotates confirms a real publish happens against // the configured hostname, and a second regenerate deactivates the first key. func TestGenerateGlobalDKIMKeyPublishesAndRotates(t *testing.T) { var bodies []string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case r.URL.Path == "/zones": w.Write([]byte(`{"success":true,"errors":[],"result":[{"id":"zone1"}]}`)) case r.URL.Path == "/zones/zone1/dns_records" && r.Method == http.MethodGet: w.Write([]byte(`{"success":true,"errors":[],"result":[]}`)) case r.URL.Path == "/zones/zone1/dns_records" && r.Method == http.MethodPost: body, _ := io.ReadAll(r.Body) bodies = append(bodies, string(body)) w.Write([]byte(`{"success":true,"errors":[],"result":{"id":"rec1"}}`)) default: w.WriteHeader(http.StatusNotFound) } })) defer srv.Close() orig := dnspublish.CloudflareAPIBase dnspublish.CloudflareAPIBase = srv.URL defer func() { dnspublish.CloudflareAPIBase = orig }() database := openTestDKIMDB(t) mgr := New(database, 1024) creds := &dnspublish.Credentials{Provider: "cloudflare", ZoneName: "example.com", CloudflareAPIToken: "tok"} publishErr, err := mgr.GenerateGlobalDKIMKey("dkim.example.com", creds) if err != nil || publishErr != nil { t.Fatalf("first generate: err=%v publishErr=%v", err, publishErr) } first, _ := mgr.GetActiveGlobalDKIMKey() if first == nil { t.Fatal("expected an active global key after first generate") } if len(bodies) != 1 || !strings.Contains(bodies[0], "_domainkey.dkim.example.com") { t.Fatalf("expected a publish call naming the configured hostname, got %v", bodies) } publishErr, err = mgr.GenerateGlobalDKIMKey("dkim.example.com", creds) if err != nil || publishErr != nil { t.Fatalf("second generate: err=%v publishErr=%v", err, publishErr) } second, _ := mgr.GetActiveGlobalDKIMKey() if second == nil || second.ID == first.ID { t.Fatalf("expected a distinct, newly active key after rotation: first=%+v second=%+v", first, second) } } // TestGetActiveDKIMKeySwapsToGlobalKeyMaterial confirms a domain with UseGlobalDKIM // signs with the global key's material while keeping its own selector, and falls back // to its own key when no global key exists yet. func TestGetActiveDKIMKeySwapsToGlobalKeyMaterial(t *testing.T) { database := openTestDKIMDB(t) domainID, err := database.CreateDomain("useglobal.example") if err != nil { t.Fatal(err) } mgr := New(database, 1024) if ok, err := mgr.GenerateDKIMKeypair("useglobal.example", "own-sel", true); err != nil || !ok { t.Fatalf("seed domain key: ok=%v err=%v", ok, err) } ownKey, _ := mgr.GetActiveDKIMKey("useglobal.example") if err := database.SetDomainUseGlobalDKIM(domainID, true); err != nil { t.Fatal(err) } // No global key generated yet — falls back to the domain's own material. fallback, err := mgr.GetActiveDKIMKey("useglobal.example") if err != nil || fallback == nil || fallback.PrivateKey != ownKey.PrivateKey || fallback.Selector != "own-sel" { t.Fatalf("expected fallback to the domain's own key material, got %+v", fallback) } if _, err := mgr.GenerateGlobalDKIMKey("", nil); err != nil { t.Fatal(err) } globalKey, _ := mgr.GetActiveGlobalDKIMKey() swapped, err := mgr.GetActiveDKIMKey("useglobal.example") if err != nil || swapped == nil { t.Fatalf("GetActiveDKIMKey: %v %v", swapped, err) } if swapped.Selector != "own-sel" { t.Fatalf("expected the domain's own selector preserved, got %q", swapped.Selector) } if swapped.PrivateKey != globalKey.PrivateKey || swapped.PublicKey != globalKey.PublicKey { t.Fatalf("expected the global key's material substituted in, got %+v want private=%q public=%q", swapped, globalKey.PrivateKey, globalKey.PublicKey) } if swapped.PrivateKey == ownKey.PrivateKey { t.Fatal("expected the domain's own private key to no longer be used once global mode is on") } }