feat: backend service

This commit is contained in:
Ilia Denisov
2026-05-06 10:14:55 +03:00
committed by GitHub
parent 3e2622757e
commit f446c6a2ac
1486 changed files with 49720 additions and 266401 deletions
+147
View File
@@ -0,0 +1,147 @@
package mail
import (
"strings"
"testing"
"time"
)
func TestRenderLoginCode(t *testing.T) {
t.Parallel()
subject, body := renderLoginCode("123456", 10*time.Minute)
if !strings.Contains(subject, "123456") {
t.Fatalf("subject must include code, got %q", subject)
}
if !strings.Contains(body, "123456") {
t.Fatalf("body must include code, got %q", body)
}
if !strings.Contains(body, "10 minutes") {
t.Fatalf("body must include human-readable TTL, got %q", body)
}
}
func TestRenderLoginCode_RoundsTTL(t *testing.T) {
t.Parallel()
cases := map[string]struct {
ttl time.Duration
expect string
}{
"sub-minute": {ttl: 30 * time.Second, expect: "1 minutes"},
"exact": {ttl: 10 * time.Minute, expect: "10 minutes"},
"with secs": {ttl: 5*time.Minute + 29*time.Second, expect: "5 minutes"},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
t.Parallel()
_, body := renderLoginCode("000000", tc.ttl)
if !strings.Contains(body, tc.expect) {
t.Fatalf("body missing %q for ttl=%s, got %q", tc.expect, tc.ttl, body)
}
})
}
}
func TestNormaliseRecipient(t *testing.T) {
t.Parallel()
cases := map[string]struct {
input string
want string
err bool
}{
"plain": {input: "alice@example.test", want: "alice@example.test"},
"trims": {input: " bob@example.test ", want: "bob@example.test"},
"display-stripped": {input: "Alice <alice@example.test>", want: "alice@example.test"},
"empty": {input: "", err: true},
"whitespace": {input: " ", err: true},
"malformed": {input: "not-an-email", err: true},
"with-spaces": {input: "ali ce@example.test", err: true},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
t.Parallel()
got, err := normaliseRecipient(tc.input)
if tc.err {
if err == nil {
t.Fatalf("expected error, got %q", got)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tc.want {
t.Fatalf("got %q want %q", got, tc.want)
}
})
}
}
func TestTemplateRendererLoginCode(t *testing.T) {
t.Parallel()
render := templateRenderers[TemplateLoginCode]
if render == nil {
t.Fatal("TemplateLoginCode renderer must be registered")
}
subject, body, err := render(map[string]any{"code": "654321", "ttl": 7 * time.Minute})
if err != nil {
t.Fatalf("render: %v", err)
}
if !strings.Contains(subject, "654321") || !strings.Contains(body, "654321") {
t.Fatalf("subject=%q body=%q must mention code", subject, body)
}
if _, _, err := render(map[string]any{"ttl": 7 * time.Minute}); err == nil {
t.Fatal("missing code must error")
}
}
func TestNextBackoffMonotonicAndCapped(t *testing.T) {
t.Parallel()
// Sample many runs per attempt so jitter does not flake the
// invariant: median of attempt N is below median of attempt N+1
// up to the cap.
prev := time.Duration(0)
for n := 1; n <= 12; n++ {
var sum time.Duration
runs := 32
for range runs {
sum += nextBackoff(n)
}
avg := sum / time.Duration(runs)
if avg > backoffMax+backoffMax/4 { // generous upper bound
t.Fatalf("attempt %d avg %s exceeds capped budget", n, avg)
}
if avg < backoffBase/2 {
t.Fatalf("attempt %d avg %s below base/2", n, avg)
}
if n > 1 && avg < prev/2 {
t.Fatalf("backoff decreased dramatically between attempts %d and %d (%s vs %s)", n-1, n, prev, avg)
}
prev = avg
}
}
func TestIsPermanent(t *testing.T) {
t.Parallel()
if IsPermanent(nil) {
t.Fatal("nil must not be permanent")
}
transient := &SendError{Err: errSentinel("transient")}
if IsPermanent(transient) {
t.Fatal("default SendError must not be permanent")
}
permanent := &SendError{Err: errSentinel("permanent"), Permanent: true}
if !IsPermanent(permanent) {
t.Fatal("Permanent=true must report true")
}
}
// errSentinel is a tiny sentinel error helper used only in tests.
type errSentinel string
func (e errSentinel) Error() string { return string(e) }