Add Migadu domain admin with shared lists, compaction, and safer apply.

Build the Go API and React UI for managing domain denylist/allowlist/recipient
deny and spam settings, plus shared lists that import/apply across domains with
wildcard compaction, entry verification, and Migadu-friendly list encoding
(including per-domain recipient filtering and rejection bisect on apply).
This commit is contained in:
2026-07-26 21:50:26 +02:00
parent 930f1f7b69
commit a79cb61104
55 changed files with 7600 additions and 1 deletions
+127
View File
@@ -0,0 +1,127 @@
package lists
import (
"reflect"
"testing"
)
func TestCompact(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input []string
want []string
}{
{
name: "empty",
input: nil,
want: []string{},
},
{
name: "exact duplicates case insensitive",
input: []string{"Lisa@Simpsons.com", "lisa@simpsons.com", " LISA@SIMPSONS.COM "},
want: []string{"lisa@simpsons.com"},
},
{
name: "wildcard covers specific",
input: []string{"lisa@simpsons.com", "*@simpsons.com"},
want: []string{"*@simpsons.com"},
},
{
name: "specific dropped when wildcard present first",
input: []string{"*@simpsons.com", "lisa@simpsons.com", "homer@simpsons.com"},
want: []string{"*@simpsons.com"},
},
{
name: "distinct locals kept",
input: []string{"lisa@simpsons.com", "homer@simpsons.com"},
want: []string{"lisa@simpsons.com", "homer@simpsons.com"},
},
{
name: "different domains kept",
input: []string{"*@simpsons.com", "lisa@flanders.com", "*@flanders.com"},
want: []string{"*@simpsons.com", "*@flanders.com"},
},
{
name: "bare hostname exact dedupe only",
input: []string{"simpsons.com", "Simpsons.com", "lisa@simpsons.com"},
want: []string{"simpsons.com", "lisa@simpsons.com"},
},
{
name: "blank entries dropped",
input: []string{"", " ", "a@b.com"},
want: []string{"a@b.com"},
},
{
name: "repair star without at",
input: []string{"*vsa.instalily.ai", "lisa@vsa.instalily.ai"},
want: []string{"*@vsa.instalily.ai"},
},
{
name: "suffix glob covers specific domains",
input: []string{"*@*.shop", "*@alth.shop", "spam@woowstars.shop", "*@other.com"},
want: []string{"*@*.shop", "*@other.com"},
},
{
name: "repair star-domain without dot",
input: []string{"*@*rumble.com", "user@foo.rumble.com"},
want: []string{"*@*.rumble.com"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := Compact(tt.input)
if !reflect.DeepEqual(got, tt.want) {
t.Fatalf("Compact() = %#v, want %#v", got, tt.want)
}
})
}
}
func TestFilterRecipientEntriesForDomain(t *testing.T) {
t.Parallel()
got := FilterRecipientEntriesForDomain("skaab.nu", []string{
"brittis@gronberg.info",
"brgr@tspse.net",
"abuse@skaab.nu",
"postmaster",
"ABUSE@SKAAB.NU",
})
want := []string{"abuse@skaab.nu", "postmaster"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("got %#v want %#v", got, want)
}
}
func TestCovers(t *testing.T) {
t.Parallel()
tests := []struct {
broader string
specific string
want bool
}{
{"*@simpsons.com", "lisa@simpsons.com", true},
{"*@simpsons.com", "*@simpsons.com", true},
{"lisa@simpsons.com", "*@simpsons.com", false},
{"lisa@simpsons.com", "homer@simpsons.com", false},
{"*@simpsons.com", "lisa@flanders.com", false},
{"simpsons.com", "lisa@simpsons.com", false},
{"", "a@b.com", false},
{"*@*.shop", "*@alth.shop", true},
{"*@*.shop", "user@alth.shop", true},
{"*@*.shop", "*@other.com", false},
{"*@simpsons.com", "lisa@mail.simpsons.com", false},
{"*vsa.instalily.ai", "x@vsa.instalily.ai", true},
}
for _, tt := range tests {
got := Covers(tt.broader, tt.specific)
if got != tt.want {
t.Fatalf("Covers(%q, %q) = %v, want %v", tt.broader, tt.specific, got, tt.want)
}
}
}