maddy/internal/msgpipeline/config_test.go
2025-01-24 23:34:09 +03:00

440 lines
8.6 KiB
Go

/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package msgpipeline
import (
"reflect"
"strings"
"testing"
parser "github.com/foxcpp/maddy/framework/cfgparser"
"github.com/foxcpp/maddy/framework/exterrors"
)
func policyError(code int) error {
return &exterrors.SMTPError{
Message: "Message rejected due to a local policy",
Code: code,
EnhancedCode: exterrors.EnhancedCode{5, 7, 0},
Reason: "reject directive used",
}
}
func TestMsgPipelineCfg(t *testing.T) {
cases := []struct {
name string
str string
value msgpipelineCfg
fail bool
}{
{
name: "basic",
str: `
source example.com {
destination example.org {
reject 410
}
default_destination {
reject 420
}
}
default_source {
destination example.org {
reject 430
}
default_destination {
reject 440
}
}`,
value: msgpipelineCfg{
perSource: map[string]sourceBlock{
"example.com": {
perRcpt: map[string]*rcptBlock{
"example.org": {
rejectErr: policyError(410),
},
},
defaultRcpt: &rcptBlock{
rejectErr: policyError(420),
},
},
},
defaultSource: sourceBlock{
perRcpt: map[string]*rcptBlock{
"example.org": {
rejectErr: policyError(430),
},
},
defaultRcpt: &rcptBlock{
rejectErr: policyError(440),
},
},
},
},
{
name: "implied default destination",
str: `
source example.com {
reject 410
}
default_source {
reject 420
}`,
value: msgpipelineCfg{
perSource: map[string]sourceBlock{
"example.com": {
perRcpt: map[string]*rcptBlock{},
defaultRcpt: &rcptBlock{
rejectErr: policyError(410),
},
},
},
defaultSource: sourceBlock{
perRcpt: map[string]*rcptBlock{},
defaultRcpt: &rcptBlock{
rejectErr: policyError(420),
},
},
},
},
{
name: "implied default sender",
str: `
destination example.com {
reject 410
}
default_destination {
reject 420
}`,
value: msgpipelineCfg{
perSource: map[string]sourceBlock{},
defaultSource: sourceBlock{
perRcpt: map[string]*rcptBlock{
"example.com": {
rejectErr: policyError(410),
},
},
defaultRcpt: &rcptBlock{
rejectErr: policyError(420),
},
},
},
},
{
name: "missing default source handler",
str: `
source example.org {
reject 410
}`,
fail: true,
},
{
name: "missing default destination handler",
str: `
destination example.org {
reject 410
}`,
fail: true,
},
{
name: "invalid domain",
str: `
destination .. {
reject 410
}
default_destination {
reject 500
}`,
fail: true,
},
{
name: "invalid address",
str: `
destination @example. {
reject 410
}
default_destination {
reject 500
}`,
fail: true,
},
{
name: "invalid address",
str: `
destination @example. {
reject 421
}
default_destination {
reject 500
}`,
fail: true,
},
{
name: "invalid reject code",
str: `
destination example.com {
reject 200
}
default_destination {
reject 500
}`,
fail: true,
},
{
name: "destination together with source",
str: `
destination example.com {
reject 410
}
source example.org {
reject 420
}
default_source {
reject 430
}`,
fail: true,
},
{
name: "empty destination rule",
str: `
destination {
reject 410
}
default_destination {
reject 420
}`,
fail: true,
},
}
for _, case_ := range cases {
t.Run(case_.name, func(t *testing.T) {
cfg, _ := parser.Read(strings.NewReader(case_.str), "literal")
parsed, err := parseMsgPipelineRootCfg(nil, cfg)
if err != nil && !case_.fail {
t.Fatalf("unexpected parse error: %v", err)
}
if err == nil && case_.fail {
t.Fatalf("unexpected parse success")
}
if case_.fail {
t.Log(err)
return
}
if !reflect.DeepEqual(parsed, case_.value) {
t.Errorf("Wrong parsed configuration")
t.Errorf("Wanted: %+v", case_.value)
t.Errorf("Got: %+v", parsed)
}
})
}
}
func TestMsgPipelineCfg_SourceIn(t *testing.T) {
str := `
source_in dummy {
deliver_to dummy
}
default_source {
reject 500
}
`
cfg, _ := parser.Read(strings.NewReader(str), "literal")
parsed, err := parseMsgPipelineRootCfg(nil, cfg)
if err != nil {
t.Fatalf("unexpected parse error: %v", err)
}
if len(parsed.sourceIn) == 0 {
t.Fatalf("missing source_in dummy")
}
}
func TestMsgPipelineCfg_DestIn(t *testing.T) {
str := `
destination_in dummy {
deliver_to dummy
}
default_destination {
reject 500
}
`
cfg, _ := parser.Read(strings.NewReader(str), "literal")
parsed, err := parseMsgPipelineRootCfg(nil, cfg)
if err != nil {
t.Fatalf("unexpected parse error: %v", err)
}
if len(parsed.defaultSource.rcptIn) == 0 {
t.Fatalf("missing destination_in dummy")
}
}
func TestMsgPipelineCfg_GlobalChecks(t *testing.T) {
str := `
check {
test_check
}
default_destination {
reject 500
}
`
cfg, _ := parser.Read(strings.NewReader(str), "literal")
parsed, err := parseMsgPipelineRootCfg(nil, cfg)
if err != nil {
t.Fatalf("unexpected parse error: %v", err)
}
if len(parsed.globalChecks) == 0 {
t.Fatalf("missing test_check in globalChecks")
}
}
func TestMsgPipelineCfg_GlobalChecksMultiple(t *testing.T) {
str := `
check {
test_check
}
check {
test_check
}
default_destination {
reject 500
}
`
cfg, _ := parser.Read(strings.NewReader(str), "literal")
parsed, err := parseMsgPipelineRootCfg(nil, cfg)
if err != nil {
t.Fatalf("unexpected parse error: %v", err)
}
if len(parsed.globalChecks) != 2 {
t.Fatalf("wrong amount of test_check's in globalChecks: %d", len(parsed.globalChecks))
}
}
func TestMsgPipelineCfg_SourceChecks(t *testing.T) {
str := `
source example.org {
check {
test_check
}
reject 500
}
default_source {
reject 500
}
`
cfg, _ := parser.Read(strings.NewReader(str), "literal")
parsed, err := parseMsgPipelineRootCfg(nil, cfg)
if err != nil {
t.Fatalf("unexpected parse error: %v", err)
}
if len(parsed.perSource["example.org"].checks) == 0 {
t.Fatalf("missing test_check in source checks")
}
}
func TestMsgPipelineCfg_SourceChecks_Multiple(t *testing.T) {
str := `
source example.org {
check {
test_check
}
check {
test_check
}
reject 500
}
default_source {
reject 500
}
`
cfg, _ := parser.Read(strings.NewReader(str), "literal")
parsed, err := parseMsgPipelineRootCfg(nil, cfg)
if err != nil {
t.Fatalf("unexpected parse error: %v", err)
}
if len(parsed.perSource["example.org"].checks) != 2 {
t.Fatalf("wrong amount of test_check's in source checks: %d", len(parsed.perSource["example.org"].checks))
}
}
func TestMsgPipelineCfg_RcptChecks(t *testing.T) {
str := `
destination example.org {
check {
test_check
}
reject 500
}
default_destination {
reject 500
}
`
cfg, _ := parser.Read(strings.NewReader(str), "literal")
parsed, err := parseMsgPipelineRootCfg(nil, cfg)
if err != nil {
t.Fatalf("unexpected parse error: %v", err)
}
if len(parsed.defaultSource.perRcpt["example.org"].checks) == 0 {
t.Fatalf("missing test_check in rcpt checks")
}
}
func TestMsgPipelineCfg_RcptChecks_Multiple(t *testing.T) {
str := `
destination example.org {
check {
test_check
}
check {
test_check
}
reject 500
}
default_destination {
reject 500
}
`
cfg, _ := parser.Read(strings.NewReader(str), "literal")
parsed, err := parseMsgPipelineRootCfg(nil, cfg)
if err != nil {
t.Fatalf("unexpected parse error: %v", err)
}
if len(parsed.defaultSource.perRcpt["example.org"].checks) != 2 {
t.Fatalf("wrong amount of test_check's in rcpt checks: %d", len(parsed.defaultSource.perRcpt["example.org"].checks))
}
}