forked from emersion/go-milter
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from d--j/integration
Add Integration Tests
- Loading branch information
Showing
67 changed files
with
5,624 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
GO_MILTER_DIR := $(shell go list -f '{{.Dir}}' github.com/d--j/go-milter) | ||
|
||
integration: | ||
docker build -q --progress=plain -t go-milter-integration "$(GO_MILTER_DIR)/integration/docker" && \ | ||
docker run --rm -w /usr/src/root/integration -v $(PWD):/usr/src/root go-milter-integration \ | ||
go run github.com/d--j/go-milter/integration/runner -filter '.*' ./tests | ||
|
||
.PHONY: integration |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
# go-milter integration tests | ||
|
||
## How it works | ||
|
||
The integration test runner starts a receiving SMTP server and test milter servers. It then configures different MTAs to | ||
use the test milter servers and send all emails to the receiving SMTP server. When all this is set up and running, | ||
the test runner send the testcases as SMTP transactions to the MTA and checks if the right filter decision at the right | ||
time was made and whether the outgoing SMTP message is as expected. | ||
|
||
## Testcases | ||
|
||
A testcase is a text file that has three parts: input steps, the expected milter decision (accept, reject etc.) and | ||
optional output data (mail from, header etc.) that gets compared with the actual output of the MTA. | ||
|
||
### Input steps | ||
|
||
You can omit input steps. Necessary input steps get automatically added to the testcase. | ||
|
||
#### `HELO [hello-hostname]` | ||
|
||
Sends a HELO/EHLO to the SMTP server | ||
|
||
#### `STARTTLS` | ||
|
||
Start TLS encryption of connection | ||
|
||
#### `AUTH [[email protected]|[email protected]]` | ||
|
||
Authenticates SMTP connection. There are only two users hard-coded [email protected] (password `password1`) and [email protected] (password `password2`). | ||
|
||
#### `FROM <addr> args` | ||
|
||
Sends a `MAIL FROM` SMTP command. | ||
|
||
#### `TO <addr> args` | ||
|
||
Sends a `RCPT TO` SMTP command. | ||
|
||
#### `RESET` | ||
|
||
Sends a `RSET` SMTP command. | ||
|
||
#### `HEADER` | ||
|
||
Sends the `DATA` SMTP command and then the header. The header to send follows the `HEADER` line. The end of | ||
the header is marked with a single `.` in a line (like in SMTP connections) | ||
|
||
#### `BODY` | ||
|
||
Sends the body part of the DATA. The end of the body part is also marked with a single `.`. | ||
|
||
### `DECISION [decision]@[step]` | ||
|
||
Every testcase needs to have a `DECISION`. Valid `decision`s are: `ACCEPT`, `TEMPFAIL`, `REJECT`, `DISCARD-OR-QUARANTINE` and `CUSTOM`. | ||
If you specify `CUSTOM` then the lines after the `DECISION` line get parsed as a SMTP response and the mitler should | ||
set this SMTP response. | ||
|
||
The `step` can be `HELO`, `FROM`, `TO`, `DATA`, `EOM` and `*`. If the step is omitted `*` is assumed. | ||
`*` means that the decision can happen after any step. | ||
|
||
### Output | ||
|
||
If you specified `ACCEPT` as decision you can add `FROM`, `TO`, `HEADER` and `BODY` lines (see syntax above) after the `DECISION` line. | ||
These values get compared with the actual result the MTA send to our receiving SMTP server. | ||
|
||
## How to add integration tests to your go-milter based mail filter | ||
|
||
You need docker since the test are run inside a docker container. | ||
|
||
Add a Makefile | ||
```makefile | ||
GO_MILTER_DIR := $(shell go list -f '{{.Dir}}' github.com/d--j/go-milter) | ||
|
||
integration: | ||
docker build -q --progress=plain -t go-milter-integration "$(GO_MILTER_DIR)/integration/docker" && \ | ||
docker run --rm -w /usr/src/root/integration -v $(PWD):/usr/src/root go-milter-integration \ | ||
go run github.com/d--j/go-milter/integration/runner -filter '.*' ./tests | ||
|
||
.PHONY: integration | ||
``` | ||
|
||
Add an `integration` directory. Execute the following inside: | ||
```shell | ||
go mod init | ||
go mod edit -require github.com/d--j/go-milter | ||
go mod edit -require github.com/d--j/go-milter/integration | ||
go mod edit -replace $(cd .. && go list '{{.Path}}')=.. | ||
mkdir tests | ||
``` | ||
|
||
Tests consist of a test milter and testcases that get feed into an MTA that is configured to use the test milter. | ||
|
||
A test milter can look something like this: | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/d--j/go-milter/integration" | ||
"github.com/d--j/go-milter/mailfilter" | ||
) | ||
|
||
func main() { | ||
integration.RequiredTags("auth-plain", "auth-no", "tls-starttls", "tls-no") | ||
integration.Test(func(ctx context.Context, trx *mailfilter.Transaction) (mailfilter.Decision, error) { | ||
return mailfilter.CustomErrorResponse(501, "Test"), nil | ||
}, mailfilter.WithDecisionAt(mailfilter.DecisionAtMailFrom)) | ||
} | ||
``` | ||
|
||
A testcase for this milter would be: | ||
``` | ||
DECISION CUSTOM | ||
501 Test | ||
``` | ||
|
||
## How to handle dynamic data | ||
|
||
If your milter is time dependent or relies on external data you can use monkey pathing to make the output of your milter | ||
static. E.g. the following sets a constant time for `time.Now` and mocks the SPF checks of your milter to static values: | ||
|
||
```go | ||
package patches | ||
|
||
import ( | ||
"net" | ||
"strings" | ||
"time" | ||
|
||
"blitiri.com.ar/go/spf" | ||
"github.com/agiledragon/gomonkey/v2" | ||
) | ||
|
||
var ConstantDate = time.Date(2023, time.January, 1, 12, 0, 0, 0, time.UTC) | ||
|
||
func Apply() *gomonkey.Patches { | ||
return gomonkey. | ||
ApplyFuncReturn(time.Now, ConstantDate). | ||
ApplyFunc(spf.CheckHostWithSender, func(_ net.IP, helo, sender string, _ ...spf.Option) (spf.Result, error) { | ||
if strings.HasSuffix(sender, "@example.com") || helo == "example.com" { | ||
return spf.Pass, nil | ||
} | ||
if strings.HasSuffix(sender, "@example.net") || helo == "example.net" { | ||
return spf.Fail, nil | ||
} | ||
return spf.None, nil | ||
}) | ||
} | ||
``` | ||
|
||
The `Received` line that the MTA add contains dynamic data (date, queue id). Your test milter will see this dynamic header, | ||
but before comparing the SMTP message with the testcase output data the test runner replaces the first | ||
`Recieved` header with the static header `Received: placeholder`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
FROM golang:1-bullseye | ||
RUN apt-get update -q \ | ||
&& apt-get install -y sudo syslog-ng sasl2-bin libsasl2-2 libsasl2-modules ssl-cert m4 expect tcl-expect cpio \ | ||
&& mkdir /pkgs && cd /pkgs \ | ||
&& apt-get download \ | ||
postfix sendmail sendmail-base sendmail-bin sendmail-cf sensible-mda \ | ||
libsigsegv2 maildrop libicu67 libnsl2 \ | ||
courier-authlib libcourier-unicode4 liblockfile-bin liblockfile1 \ | ||
libltdl7 libwrap0 lockfile-progs \ | ||
&& dpkg --force-all -i *.deb \ | ||
&& rm -rf /pkgs /var/lib/apt/lists/* | ||
RUN mkdir /.cache && chmod 0777 /.cache | ||
RUN git config --global --add safe.directory /usr/src/root | ||
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf | ||
WORKDIR /usr/src/root/integration | ||
CMD ["go", "run", "github.com/d--j/go-milter/integration/runner", "./tests"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
@version: 3.28 | ||
@include "scl.conf" | ||
|
||
source s_local { | ||
internal(); | ||
}; | ||
|
||
destination d_local { | ||
file("/var/log/messages"); | ||
}; | ||
|
||
log { | ||
source(s_local); | ||
destination(d_local); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// Package integration has integration tests and utilities for integration tests. | ||
package integration | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"log" | ||
"os" | ||
"strings" | ||
|
||
"github.com/d--j/go-milter/mailfilter" | ||
"golang.org/x/tools/go/buildutil" | ||
) | ||
|
||
var Network = flag.String("network", "", "network") | ||
var Address = flag.String("address", "", "address") | ||
var Tags []string | ||
|
||
const ExitSkip = 99 | ||
|
||
func init() { | ||
flag.Var((*buildutil.TagsFlag)(&Tags), "tags", buildutil.TagsFlagDoc) | ||
} | ||
|
||
func Test(decider mailfilter.DecisionModificationFunc, opts ...mailfilter.Option) { | ||
if !flag.Parsed() { | ||
flag.Parse() | ||
} | ||
if Network == nil || *Network == "" { | ||
log.Fatal("no network specified") | ||
} | ||
if Address == nil || *Address == "" { | ||
log.Fatal("no address specified") | ||
} | ||
filter, err := mailfilter.New(*Network, *Address, decider, opts...) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
log.Printf("Started milter on %s:%s", filter.Addr().Network(), filter.Addr().String()) | ||
filter.Wait() | ||
} | ||
|
||
func HasTag(tag string) bool { | ||
if !flag.Parsed() { | ||
flag.Parse() | ||
} | ||
for _, t := range Tags { | ||
if t == tag { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func Skip(reason string) { | ||
log.Printf("skip test: %s", reason) | ||
os.Exit(ExitSkip) | ||
} | ||
|
||
func RequiredTags(tags ...string) { | ||
for _, t := range tags { | ||
if !HasTag(t) { | ||
Skip(fmt.Sprintf("required tags not met: %s", strings.Join(tags, ","))) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
module github.com/d--j/go-milter/integration | ||
|
||
go 1.18 | ||
|
||
require ( | ||
github.com/d--j/go-milter v0.6.0 | ||
github.com/emersion/go-message v0.16.0 | ||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 | ||
github.com/emersion/go-smtp v0.16.0 | ||
golang.org/x/tools v0.1.12 | ||
) | ||
|
||
require ( | ||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect | ||
golang.org/x/net v0.7.0 // indirect | ||
golang.org/x/text v0.7.0 // indirect | ||
) | ||
|
||
replace github.com/d--j/go-milter => ../ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
github.com/emersion/go-message v0.16.0 h1:uZLz8ClLv3V5fSFF/fFdW9jXjrZkXIpE1Fn8fKx7pO4= | ||
github.com/emersion/go-message v0.16.0/go.mod h1:pDJDgf/xeUIF+eicT6B/hPX/ZbEorKkUMPOxrPVG2eQ= | ||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= | ||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= | ||
github.com/emersion/go-smtp v0.16.0 h1:eB9CY9527WdEZSs5sWisTmilDX7gG+Q/2IdRcmubpa8= | ||
github.com/emersion/go-smtp v0.16.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= | ||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY= | ||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= | ||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= | ||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= | ||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= | ||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= | ||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= | ||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= |
Oops, something went wrong.