mirror of
https://github.com/mjl-/mox.git
synced 2024-12-27 08:53:48 +03:00
more integration tests: start "mox localserve" and submit a message with smtpclient and with "mox sendmail", check that we receive it
This commit is contained in:
parent
7facf9d446
commit
1469b7293e
7 changed files with 104 additions and 28 deletions
2
Makefile
2
Makefile
|
@ -3,7 +3,7 @@ default: build
|
||||||
build:
|
build:
|
||||||
# build early to catch syntax errors
|
# build early to catch syntax errors
|
||||||
CGO_ENABLED=0 go build
|
CGO_ENABLED=0 go build
|
||||||
CGO_ENABLED=0 go vet -tags integration ./...
|
CGO_ENABLED=0 go vet -tags integration
|
||||||
CGO_ENABLED=0 go vet -tags quickstart quickstart_test.go
|
CGO_ENABLED=0 go vet -tags quickstart quickstart_test.go
|
||||||
./gendoc.sh
|
./gendoc.sh
|
||||||
(cd http && CGO_ENABLED=0 go run ../vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/*.go -adjust-function-names none Admin) >http/adminapi.json
|
(cd http && CGO_ENABLED=0 go run ../vendor/github.com/mjl-/sherpadoc/cmd/sherpadoc/*.go -adjust-function-names none Admin) >http/adminapi.json
|
||||||
|
|
2
doc.go
2
doc.go
|
@ -329,6 +329,8 @@ during those commands instead of during "data".
|
||||||
usage: mox localserve
|
usage: mox localserve
|
||||||
-dir string
|
-dir string
|
||||||
configuration storage directory (default "$userconfigdir/mox-localserve")
|
configuration storage directory (default "$userconfigdir/mox-localserve")
|
||||||
|
-ip string
|
||||||
|
serve on this ip instead of default 127.0.0.1 and ::1. only used when writing configuration, at first launch.
|
||||||
|
|
||||||
# mox help
|
# mox help
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./.go:/.go
|
- ./.go:/.go
|
||||||
- ./testdata/integration/resolv.conf:/etc/resolv.conf
|
- ./testdata/integration/resolv.conf:/etc/resolv.conf
|
||||||
|
- ./testdata/integration/moxsubmit.conf:/etc/moxsubmit.conf
|
||||||
- .:/mox
|
- .:/mox
|
||||||
environment:
|
environment:
|
||||||
GOCACHE: /.go/.cache/go-build
|
GOCACHE: /.go/.cache/go-build
|
||||||
|
@ -23,6 +24,8 @@ services:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
postfixmail:
|
postfixmail:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
localserve:
|
||||||
|
condition: service_healthy
|
||||||
networks:
|
networks:
|
||||||
mailnet1:
|
mailnet1:
|
||||||
ipv4_address: 172.28.1.10
|
ipv4_address: 172.28.1.10
|
||||||
|
@ -53,6 +56,31 @@ services:
|
||||||
mailnet1:
|
mailnet1:
|
||||||
ipv4_address: 172.28.1.20
|
ipv4_address: 172.28.1.20
|
||||||
|
|
||||||
|
localserve:
|
||||||
|
hostname: localserve.mox1.example
|
||||||
|
domainname: mox1.example
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile.moxmail
|
||||||
|
context: testdata/integration
|
||||||
|
command: ["sh", "-c", "set -e; chmod o+r /etc/resolv.conf; go run . -- localserve -ip 172.28.1.50"]
|
||||||
|
volumes:
|
||||||
|
- ./.go:/.go
|
||||||
|
- ./testdata/integration/resolv.conf:/etc/resolv.conf
|
||||||
|
- .:/mox
|
||||||
|
environment:
|
||||||
|
GOCACHE: /.go/.cache/go-build
|
||||||
|
healthcheck:
|
||||||
|
test: netstat -nlt | grep ':1025 '
|
||||||
|
interval: 1s
|
||||||
|
timeout: 1s
|
||||||
|
retries: 10
|
||||||
|
depends_on:
|
||||||
|
dns:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
mailnet1:
|
||||||
|
ipv4_address: 172.28.1.50
|
||||||
|
|
||||||
dns:
|
dns:
|
||||||
hostname: dns.example
|
hostname: dns.example
|
||||||
build:
|
build:
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -36,6 +37,7 @@ func tcheck(t *testing.T, err error, msg string) {
|
||||||
// We check if we receive the message.
|
// We check if we receive the message.
|
||||||
func TestDeliver(t *testing.T) {
|
func TestDeliver(t *testing.T) {
|
||||||
mlog.Logfmt = true
|
mlog.Logfmt = true
|
||||||
|
log := mlog.New("test")
|
||||||
|
|
||||||
// Remove state.
|
// Remove state.
|
||||||
os.RemoveAll("testdata/integration/data")
|
os.RemoveAll("testdata/integration/data")
|
||||||
|
@ -79,16 +81,16 @@ func TestDeliver(t *testing.T) {
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
deliver := func(username, desthost, mailfrom, password, rcptto, imapuser string) {
|
testDeliver := func(checkTime bool, imapaddr, imapuser, imappass string, fn func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// Make IMAP connection, we'll wait for a delivery notification with IDLE.
|
// Make IMAP connection, we'll wait for a delivery notification with IDLE.
|
||||||
imapconn, err := net.Dial("tcp", "moxmail1.mox1.example:143")
|
imapconn, err := net.Dial("tcp", imapaddr)
|
||||||
tcheck(t, err, "dial imap server")
|
tcheck(t, err, "dial imap server")
|
||||||
defer imapconn.Close()
|
defer imapconn.Close()
|
||||||
client, err := imapclient.New(imapconn, false)
|
client, err := imapclient.New(imapconn, false)
|
||||||
tcheck(t, err, "new imapclient")
|
tcheck(t, err, "new imapclient")
|
||||||
_, _, err = client.Login(imapuser, "pass1234")
|
_, _, err = client.Login(imapuser, imappass)
|
||||||
tcheck(t, err, "imap client login")
|
tcheck(t, err, "imap client login")
|
||||||
_, _, err = client.Select("inbox")
|
_, _, err = client.Select("inbox")
|
||||||
tcheck(t, err, "imap select inbox")
|
tcheck(t, err, "imap select inbox")
|
||||||
|
@ -114,7 +116,27 @@ func TestDeliver(t *testing.T) {
|
||||||
tcheck(t, err, "aborting idle")
|
tcheck(t, err, "aborting idle")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
conn, err := net.Dial("tcp", desthost+":587")
|
t0 := time.Now()
|
||||||
|
fn()
|
||||||
|
|
||||||
|
// Wait for notification of delivery.
|
||||||
|
select {
|
||||||
|
case resp := <-idle:
|
||||||
|
tcheck(t, resp.err, "idle notification")
|
||||||
|
_, ok := resp.untagged.(imapclient.UntaggedExists)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("got idle %#v, expected untagged exists", resp.untagged)
|
||||||
|
}
|
||||||
|
if d := time.Since(t0); checkTime && d < 1*time.Second {
|
||||||
|
t.Fatalf("delivery took %v, but should have taken at least 1 second, the first-time sender delay", d)
|
||||||
|
}
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatalf("timeout after 5s waiting for IMAP IDLE notification of new message, should take about 1 second")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submit := func(smtphost, smtpport, mailfrom, password, rcptto string) {
|
||||||
|
conn, err := net.Dial("tcp", net.JoinHostPort(smtphost, smtpport))
|
||||||
tcheck(t, err, "dial submission")
|
tcheck(t, err, "dial submission")
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
@ -126,30 +148,36 @@ This is the message.
|
||||||
`, mailfrom, rcptto)
|
`, mailfrom, rcptto)
|
||||||
msg = strings.ReplaceAll(msg, "\n", "\r\n")
|
msg = strings.ReplaceAll(msg, "\n", "\r\n")
|
||||||
auth := []sasl.Client{sasl.NewClientPlain(mailfrom, password)}
|
auth := []sasl.Client{sasl.NewClientPlain(mailfrom, password)}
|
||||||
c, err := smtpclient.New(mox.Context, mlog.New("test"), conn, smtpclient.TLSOpportunistic, mox.Conf.Static.HostnameDomain, dns.Domain{ASCII: desthost}, auth)
|
c, err := smtpclient.New(mox.Context, log, conn, smtpclient.TLSOpportunistic, mox.Conf.Static.HostnameDomain, dns.Domain{ASCII: smtphost}, auth)
|
||||||
tcheck(t, err, "smtp hello")
|
tcheck(t, err, "smtp hello")
|
||||||
t0 := time.Now()
|
|
||||||
err = c.Deliver(mox.Context, mailfrom, rcptto, int64(len(msg)), strings.NewReader(msg), false, false)
|
err = c.Deliver(mox.Context, mailfrom, rcptto, int64(len(msg)), strings.NewReader(msg), false, false)
|
||||||
tcheck(t, err, "deliver with smtp")
|
tcheck(t, err, "deliver with smtp")
|
||||||
err = c.Close()
|
err = c.Close()
|
||||||
tcheck(t, err, "close smtpclient")
|
tcheck(t, err, "close smtpclient")
|
||||||
|
|
||||||
// Wait for notification of delivery.
|
|
||||||
select {
|
|
||||||
case resp := <-idle:
|
|
||||||
tcheck(t, resp.err, "idle notification")
|
|
||||||
_, ok := resp.untagged.(imapclient.UntaggedExists)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("got idle %#v, expected untagged exists", resp.untagged)
|
|
||||||
}
|
|
||||||
if d := time.Since(t0); d < 1*time.Second {
|
|
||||||
t.Fatalf("delivery took %v, bt should have taken at least 1 second, the first-time sender delay", d)
|
|
||||||
}
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatalf("timeout after 5s waiting for IMAP IDLE notification of new message, should take about 1 second")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deliver("moxtest1", "moxmail1.mox1.example", "moxtest1@mox1.example", "pass1234", "root@postfix.example", "moxtest1@mox1.example")
|
testDeliver(true, "moxmail1.mox1.example:143", "moxtest1@mox1.example", "pass1234", func() {
|
||||||
deliver("moxtest3", "moxmail2.mox2.example", "moxtest2@mox2.example", "pass1234", "moxtest3@mox3.example", "moxtest3@mox3.example")
|
submit("moxmail1.mox1.example", "587", "moxtest1@mox1.example", "pass1234", "root@postfix.example")
|
||||||
|
})
|
||||||
|
testDeliver(true, "moxmail1.mox1.example:143", "moxtest3@mox3.example", "pass1234", func() {
|
||||||
|
submit("moxmail2.mox2.example", "587", "moxtest2@mox2.example", "pass1234", "moxtest3@mox3.example")
|
||||||
|
})
|
||||||
|
|
||||||
|
testDeliver(false, "localserve.mox1.example:1143", "mox@localhost", "moxmoxmox", func() {
|
||||||
|
submit("localserve.mox1.example", "1587", "mox@localhost", "moxmoxmox", "any@any.example")
|
||||||
|
})
|
||||||
|
|
||||||
|
testDeliver(false, "localserve.mox1.example:1143", "mox@localhost", "moxmoxmox", func() {
|
||||||
|
cmd := exec.Command("go", "run", ".", "sendmail", "mox@localhost")
|
||||||
|
const msg = `Subject: test
|
||||||
|
|
||||||
|
a message.
|
||||||
|
`
|
||||||
|
cmd.Stdin = strings.NewReader(msg)
|
||||||
|
var out strings.Builder
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
log.Print("sendmail", mlog.Field("output", out.String()))
|
||||||
|
tcheck(t, err, "sendmail")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,8 +65,9 @@ during those commands instead of during "data".
|
||||||
userConfDir = "."
|
userConfDir = "."
|
||||||
}
|
}
|
||||||
|
|
||||||
var dir string
|
var dir, ip string
|
||||||
c.flag.StringVar(&dir, "dir", filepath.Join(userConfDir, "mox-localserve"), "configuration storage directory")
|
c.flag.StringVar(&dir, "dir", filepath.Join(userConfDir, "mox-localserve"), "configuration storage directory")
|
||||||
|
c.flag.StringVar(&ip, "ip", "", "serve on this ip instead of default 127.0.0.1 and ::1. only used when writing configuration, at first launch.")
|
||||||
args := c.Parse()
|
args := c.Parse()
|
||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
c.Usage()
|
c.Usage()
|
||||||
|
@ -78,7 +79,7 @@ during those commands instead of during "data".
|
||||||
|
|
||||||
// Load config, creating a new one if needed.
|
// Load config, creating a new one if needed.
|
||||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||||
err := writeLocalConfig(log, dir)
|
err := writeLocalConfig(log, dir, ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalx("creating mox localserve config", err, mlog.Field("dir", dir))
|
log.Fatalx("creating mox localserve config", err, mlog.Field("dir", dir))
|
||||||
}
|
}
|
||||||
|
@ -86,6 +87,8 @@ during those commands instead of during "data".
|
||||||
log.Fatalx("stat config dir", err, mlog.Field("dir", dir))
|
log.Fatalx("stat config dir", err, mlog.Field("dir", dir))
|
||||||
} else if err := localLoadConfig(log, dir); err != nil {
|
} else if err := localLoadConfig(log, dir); err != nil {
|
||||||
log.Fatalx("loading mox localserve config (hint: when creating a new config with -dir, the directory must not yet exist)", err, mlog.Field("dir", dir))
|
log.Fatalx("loading mox localserve config (hint: when creating a new config with -dir, the directory must not yet exist)", err, mlog.Field("dir", dir))
|
||||||
|
} else if ip != "" {
|
||||||
|
log.Fatal("can only use -ip when writing a new config file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if level, ok := mlog.Levels[loglevel]; loglevel != "" && ok {
|
if level, ok := mlog.Levels[loglevel]; loglevel != "" && ok {
|
||||||
|
@ -181,7 +184,7 @@ during those commands instead of during "data".
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeLocalConfig(log *mlog.Log, dir string) (rerr error) {
|
func writeLocalConfig(log *mlog.Log, dir, ip string) (rerr error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
x := recover()
|
x := recover()
|
||||||
if x != nil {
|
if x != nil {
|
||||||
|
@ -257,9 +260,13 @@ func writeLocalConfig(log *mlog.Log, dir string) (rerr error) {
|
||||||
xcheck(err, "writing adminpasswd file")
|
xcheck(err, "writing adminpasswd file")
|
||||||
|
|
||||||
// Write mox.conf.
|
// Write mox.conf.
|
||||||
|
ips := []string{"127.0.0.1", "::1"}
|
||||||
|
if ip != "" {
|
||||||
|
ips = []string{ip}
|
||||||
|
}
|
||||||
|
|
||||||
local := config.Listener{
|
local := config.Listener{
|
||||||
IPs: []string{"127.0.0.1", "::1"},
|
IPs: ips,
|
||||||
TLS: &config.TLS{
|
TLS: &config.TLS{
|
||||||
KeyCerts: []config.KeyCert{
|
KeyCerts: []config.KeyCert{
|
||||||
{
|
{
|
||||||
|
|
1
testdata/integration/example.zone
vendored
1
testdata/integration/example.zone
vendored
|
@ -10,6 +10,7 @@ moxmail2.mox2 IN A 172.28.2.10
|
||||||
moxmail3.mox3 IN A 172.28.3.10
|
moxmail3.mox3 IN A 172.28.3.10
|
||||||
postfixmail.postfix IN A 172.28.1.20
|
postfixmail.postfix IN A 172.28.1.20
|
||||||
dns IN A 172.28.1.30
|
dns IN A 172.28.1.30
|
||||||
|
localserve.mox1 IN A 172.28.1.50
|
||||||
|
|
||||||
mox1 MX 10 moxmail1.mox1.example.
|
mox1 MX 10 moxmail1.mox1.example.
|
||||||
mox2 MX 10 moxmail2.mox2.example.
|
mox2 MX 10 moxmail2.mox2.example.
|
||||||
|
|
10
testdata/integration/moxsubmit.conf
vendored
Normal file
10
testdata/integration/moxsubmit.conf
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
LocalHostname: localhost
|
||||||
|
Host: localserve.mox1.example
|
||||||
|
Port: 1587
|
||||||
|
TLS: false
|
||||||
|
STARTTLS: false
|
||||||
|
Username: mox@localhost
|
||||||
|
Password: moxmoxmox
|
||||||
|
AuthMethod: PLAIN
|
||||||
|
From: mox@localhost
|
||||||
|
DefaultDestination: mox@localhost
|
Loading…
Reference in a new issue