release: 0.4.0: Domain-admin groundwork, send-log auth, packaging fixes

The project's mark is now in use rather than only on file. The README opens
Added:
  - The project's mark is now in use rather than only on file. The README opens
Changed:
  - panel: the sign-in and setup pages are now a column the width of their own
  - panel: the three monitoring pages — Deliveries, Mail queue, System log — are
Fixed:
  - panel: Deliveries now shows the subject as text rather than as its MIME
  - panel: table cells may now break inside a word, so no single long value can
  - panel: the Applications list on a domain page no longer comes apart. It was a

Full detail: CHANGELOG.md (0.4.0).
This commit is contained in:
mix
2026-09-22 15:24:51 +03:00
parent af9f89b628
commit 72b09d60a6
21 changed files with 317 additions and 86 deletions
+29
View File
@@ -3,6 +3,7 @@ package milter
import (
"errors"
"net"
"strings"
"testing"
"time"
@@ -113,6 +114,34 @@ func TestSessionRecordsRowPerRecipient(t *testing.T) {
}
}
// A subject in any non-ASCII alphabet reaches the milter as RFC 2047
// encoded-words; the journal stores the text, not the encoding.
func TestHeaderDecodesEncodedSubject(t *testing.T) {
long := strings.Repeat("я", subjectMaxRunes+10)
for _, tc := range []struct {
name, raw, want string
}{
{"plain", "Hello there", "Hello there"},
{"utf8 q", "=?utf-8?Q?=D0=9F=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B0?=", "Проверка"},
{"utf8 b, folded across two words", "=?utf-8?B?0J/RgNC40LLQtdGC?=\r\n =?utf-8?B?INC80LjRgA==?=", "Привет мир"},
// No decoder for the legacy single-byte charsets: keep the header as
// sent rather than losing the subject entirely.
{"unknown charset", "=?windows-1251?B?z/Do4uXy?=", "=?windows-1251?B?z/Do4uXy?="},
{"too long", long, strings.Repeat("я", subjectMaxRunes) + "…"},
} {
t.Run(tc.name, func(t *testing.T) {
s := &session{rec: &fakeRecorder{}}
if _, err := s.Header("Subject", tc.raw, mods(nil)); err != nil {
t.Fatalf("Header: %v", err)
}
if s.subject != tc.want {
t.Fatalf("subject = %q, want %q", s.subject, tc.want)
}
})
}
}
func TestBodyAcceptsEvenWhenRecorderFails(t *testing.T) {
rec := &fakeRecorder{fail: true}
s := &session{rec: rec}