Files
ihasmail-oneshot/internal/config/config_test.go
T
jcoffey-dev e71545bbcf Relicense under AGPL-3.0-or-later
The same licence as ihasmail, which this tool deploys. The network clause
is the part that matters for a deploy tool: a modified copy offered as a
hosted service that deploys for other people has to make its source
available to them, which GPL would not require. Running the tool for your
own host is unaffected.

v2026.9.13 was released under GPL-3.0-or-later and stays so; this applies
from the next release.
2026-09-13 22:36:17 -07:00

101 lines
4.3 KiB
Go

// SPDX-FileCopyrightText: 2026 Coffey Labs
// SPDX-License-Identifier: AGPL-3.0-or-later
package config
import (
"slices"
"strings"
"testing"
)
func TestDefaultsFollowTheDomain(t *testing.T) {
p, err := Options{Domain: "Example.COM.", Dir: "/srv/mail"}.Validate()
if err != nil {
t.Fatal(err)
}
for got, want := range map[string]string{
p.Domain: "example.com",
p.MailHost: "mail.example.com",
p.WebmailHost: "webmail.example.com",
p.Email: "[email protected]",
p.Project: "ihasmail-example-com",
p.Dir: "/srv/mail",
} {
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
if p.CaddyIP.String() != "172.31.253.10" || p.IhasmailIP.String() != "172.31.253.11" || p.StalwartIP.String() != "172.31.253.12" {
t.Errorf("addresses %s %s %s", p.CaddyIP, p.IhasmailIP, p.StalwartIP)
}
want := []string{"mail.example.com", "autoconfig.example.com", "autodiscover.example.com", "mta-sts.example.com", "ua-auto-config.example.com"}
if !slices.Equal(p.StalwartNames(), want) {
t.Errorf("StalwartNames = %v", p.StalwartNames())
}
}
func TestLocalNeedsNoDomainAndPublishesNothing(t *testing.T) {
p, err := Options{Local: true}.Validate()
if err != nil {
t.Fatal(err)
}
if p.Domain != "example.test" || len(p.PublishedPorts()) != 0 {
t.Errorf("domain %q, ports %v", p.Domain, p.PublishedPorts())
}
}
func TestRejects(t *testing.T) {
for name, tc := range map[string]struct {
o Options
want string
}{
"no domain": {Options{}, "--domain is required"},
"bad domain": {Options{Domain: "not a domain"}, `--domain "not a domain"`},
"mail host elsewhere": {Options{Domain: "example.com", MailHost: "mx.other.net"}, "one label under the domain"},
"mail host two deep": {Options{Domain: "example.com", MailHost: "a.b.example.com"}, "one label under the domain"},
"webmail is autoconfig": {Options{Domain: "example.com", WebmailHost: "autoconfig.example.com"}, "already one of Stalwart's names"},
"webmail is mail host": {Options{Domain: "example.com", WebmailHost: "mail.example.com"}, "already one of Stalwart's names"},
"user in another domain": {Options{Domain: "example.com", Users: []string{"[email protected]"}}, "not in example.com"},
"user is admin": {Options{Domain: "example.com", Users: []string{"admin"}}, "or is the administrator"},
"duplicate user": {Options{Domain: "example.com", Users: []string{"bob", "[email protected]"}}, "given twice"},
"bad user": {Options{Domain: "example.com", Users: []string{"bob smith"}}, "not a valid mailbox name"},
"bind not host:port": {Options{Domain: "example.com", WebmailBind: "8080"}, "--webmail-bind"},
"bind on a mail port": {Options{Domain: "example.com", StalwartBind: "127.0.0.1:443"}, "collides with port 443"},
"same binds": {Options{Domain: "example.com", WebmailBind: "127.0.0.1:9000", StalwartBind: "127.0.0.1:9000"}, "are both"},
"subnet too small": {Options{Domain: "example.com", Subnet: "10.0.0.0/29"}, "--subnet"},
"subnet not a network": {Options{Domain: "example.com", Subnet: "10.0.0.5/24"}, "--subnet"},
"acme flags with local": {Options{Local: true, ACMEDirectory: "https://ca.internal/dir"}, "no effect with --local"},
"acme over http": {Options{Domain: "example.com", ACMEDirectory: "http://ca.internal/dir"}, "must be an https URL"},
"image with a space": {Options{Domain: "example.com", CaddyImage: "caddy 2"}, "--caddy-image"},
} {
t.Run(name, func(t *testing.T) {
_, err := tc.o.Validate()
if err == nil || !strings.Contains(err.Error(), tc.want) {
t.Fatalf("err = %v, want it to mention %q", err, tc.want)
}
})
}
}
// A bad domain is one mistake, not a cascade of defaults built from it.
func TestBadDomainIsReportedOnce(t *testing.T) {
_, err := Options{Domain: "bad domain"}.Validate()
if err == nil {
t.Fatal("no error")
}
if lines := strings.Split(err.Error(), "\n"); len(lines) != 1 {
t.Errorf("got %d errors: %v", len(lines), err)
}
}
func TestEveryProblemAtOnce(t *testing.T) {
_, err := Options{Domain: "example.com", MailHost: "mx.other.net", Users: []string{"bob smith"}, WebmailBind: "x"}.Validate()
if err == nil {
t.Fatal("no error")
}
if lines := strings.Split(err.Error(), "\n"); len(lines) != 3 {
t.Errorf("got %d errors, want 3: %v", len(lines), err)
}
}