Files
ihasmail-oneshot/internal/render/render_test.go
T
jcoffey-dev d19696dec3 Deploy a fresh Stalwart and ihasmail, linked, in one command
deploy stands up Stalwart 0.16, ihasmail and (for a mail host) Caddy as a
compose project: completes Stalwart's bootstrap over x:Bootstrap, links
ihasmail over the private network, requests certificates for both Caddy
(TLS-ALPN-01) and Stalwart (HTTP-01 through Caddy), makes the auto-ban safe
behind the proxy, and proves the link by signing in through the webmail.
--local gives a loopback-only pair. certs retries Stalwart's certificate;
destroy removes a deployment.

e2e/public.sh runs the whole mail-host path against Pebble with no
internet involved.
2026-09-13 22:02:40 -07:00

123 lines
3.7 KiB
Go

// SPDX-FileCopyrightText: 2026 Coffey Labs
// SPDX-License-Identifier: GPL-3.0-or-later
package render
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/Coffey-Labs/ihasmail-oneshot/internal/config"
)
func plan(t *testing.T, o config.Options) config.Plan {
t.Helper()
p, err := o.Validate()
if err != nil {
t.Fatal(err)
}
return p
}
func TestPublicCompose(t *testing.T) {
p := plan(t, config.Options{Domain: "example.com"})
out, err := Compose(p, "test", false)
if err != nil {
t.Fatal(err)
}
s := string(out)
for _, want := range []string{
"name: ihasmail-example-com",
"hostname: mail.example.com",
`- "127.0.0.1:8081:8080"`,
`- "25:25"`, `- "465:465"`, `- "993:993"`, `- "995:995"`, `- "4190:4190"`,
`- "80:80"`, `- "443:443"`, `- "443:443/udp"`,
"STALWART_URL: http://stalwart:8080",
"APP_SECRET: ${APP_SECRET:?",
"PUSH_URL: https://webmail.example.com",
"read_only: true",
"ipv4_address: 172.31.253.11",
"caddy-data:",
} {
if !strings.Contains(s, want) {
t.Errorf("compose.yaml lacks %q:\n%s", want, s)
}
}
// Caddy's ports belong to Caddy; Stalwart must not also claim them.
stalwart := s[strings.Index(s, " stalwart:"):strings.Index(s, " ihasmail:")]
if strings.Contains(stalwart, `"80:80"`) || strings.Contains(stalwart, `"443:443"`) {
t.Errorf("Stalwart publishes 80 or 443:\n%s", stalwart)
}
if strings.Contains(s, "ca-bundle.crt") || strings.Contains(s, "acme-ca-root.pem") {
t.Error("CA files mounted with no private CA")
}
}
func TestLocalComposeHasNoCaddyAndNoMailPorts(t *testing.T) {
p := plan(t, config.Options{Local: true})
out, err := Compose(p, "test", false)
if err != nil {
t.Fatal(err)
}
s := string(out)
for _, unwanted := range []string{"caddy", `"25:25"`, "PUSH_URL"} {
if strings.Contains(s, unwanted) {
t.Errorf("local compose.yaml has %q:\n%s", unwanted, s)
}
}
}
func TestCaddyfile(t *testing.T) {
p := plan(t, config.Options{Domain: "example.com", MailHost: "mx.example.com", Email: "[email protected]"})
out, err := Caddy(p, "test")
if err != nil {
t.Fatal(err)
}
s := string(out)
for _, want := range []string{
"email [email protected]",
"webmail.example.com {",
"mx.example.com, autoconfig.example.com, autodiscover.example.com, mta-sts.example.com, ua-auto-config.example.com {",
"disable_http_challenge",
"http://mx.example.com, http://autoconfig.example.com, http://autodiscover.example.com, http://mta-sts.example.com, http://ua-auto-config.example.com {",
"handle /.well-known/acme-challenge/* {",
"flush_interval -1",
} {
if !strings.Contains(s, want) {
t.Errorf("Caddyfile lacks %q:\n%s", want, s)
}
}
if strings.Contains(s, "acme_ca") {
t.Error("Caddyfile names a CA without --acme-directory")
}
p = plan(t, config.Options{Domain: "example.com", ACMEDirectory: "https://ca.internal/dir", ACMECARoot: "/tmp/root.pem"})
out, _ = Caddy(p, "test")
for _, want := range []string{"acme_ca https://ca.internal/dir", "dir https://ca.internal/dir", "trusted_roots /etc/caddy/acme-ca-root.pem"} {
if !strings.Contains(string(out), want) {
t.Errorf("Caddyfile with a private CA lacks %q", want)
}
}
}
func TestDirectoryIsNeverOverwritten(t *testing.T) {
dir := filepath.Join(t.TempDir(), "d")
if err := PrepareDir(dir); err != nil {
t.Fatal(err)
}
if err := WriteFile(dir, EnvFile, []byte("x"), true); err != nil {
t.Fatal(err)
}
if fi, _ := os.Stat(filepath.Join(dir, EnvFile)); fi.Mode().Perm() != 0o600 {
t.Errorf("secret file mode %v", fi.Mode().Perm())
}
if err := WriteFile(dir, EnvFile, []byte("y"), true); err == nil {
t.Error("WriteFile replaced an existing file")
}
if err := PrepareDir(dir); err == nil {
t.Error("PrepareDir accepted a directory with files in it")
}
}