Files
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

123 lines
3.7 KiB
Go

// SPDX-FileCopyrightText: 2026 Coffey Labs
// SPDX-License-Identifier: AGPL-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")
}
}