feat(mail-admin): read-only account view for mail.produktor.io (#74)
- admin/: Go stdlib-only HTTP viewer, lists accounts from config/postfix-accounts.cf with per-mailbox message counts (INBOX and total, same numbers doveadm reports) and storage; quota from dovecot-quotas.cf; Basic Auth from .env; offline tests vs fixtures (go test -race ./...) - compose: mail-admin service, build admin/Dockerfile, publishes 127.0.0.1:19945 / 172.17.0.1:19945; config and mail-data mounted :ro, no docker socket - NPM proxy host 66: location /admin/ -> 172.17.0.1:19945 - README: account admin section
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testServer(t *testing.T) *server {
|
||||
t.Helper()
|
||||
return &server{
|
||||
accountsPath: "testdata/accounts.cf",
|
||||
quotaPath: "testdata/quotas.cf",
|
||||
maildirRoot: "testdata/mail",
|
||||
basePath: "/admin",
|
||||
user: "bot",
|
||||
pass: "s3cret",
|
||||
}
|
||||
}
|
||||
|
||||
func doAuth(t *testing.T, h http.Handler, path, user, pass string) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
if user != "" {
|
||||
req.SetBasicAuth(user, pass)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, req)
|
||||
return rec
|
||||
}
|
||||
|
||||
func TestBasicAuth(t *testing.T) {
|
||||
h := testServer(t).handler()
|
||||
if rec := doAuth(t, h, "/admin/", "", ""); rec.Code != http.StatusUnauthorized {
|
||||
t.Errorf("no creds: code = %d, want 401", rec.Code)
|
||||
}
|
||||
if rec := doAuth(t, h, "/admin/", "bot", "wrong"); rec.Code != http.StatusUnauthorized {
|
||||
t.Errorf("wrong pass: code = %d, want 401", rec.Code)
|
||||
}
|
||||
if rec := doAuth(t, h, "/admin/", "bot", "s3cret"); rec.Code != http.StatusOK {
|
||||
t.Errorf("right creds: code = %d, want 200", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasePathRedirect(t *testing.T) {
|
||||
h := testServer(t).handler()
|
||||
rec := doAuth(t, h, "/", "bot", "s3cret")
|
||||
if rec.Code != http.StatusFound {
|
||||
t.Fatalf("code = %d, want 302", rec.Code)
|
||||
}
|
||||
if loc := rec.Header().Get("Location"); loc != "/admin/" {
|
||||
t.Errorf("Location = %q, want /admin/", loc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotFoundOutsideBasePath(t *testing.T) {
|
||||
h := testServer(t).handler()
|
||||
rec := doAuth(t, h, "/other", "bot", "s3cret")
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Errorf("code = %d, want 404", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIAccounts(t *testing.T) {
|
||||
h := testServer(t).handler()
|
||||
rec := doAuth(t, h, "/admin/api/accounts", "bot", "s3cret")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("code = %d, want 200: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var resp accountsResponse
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.Accounts) != 5 {
|
||||
t.Fatalf("got %d accounts, want 5", len(resp.Accounts))
|
||||
}
|
||||
byEmail := map[string]viewAccount{}
|
||||
for _, a := range resp.Accounts {
|
||||
byEmail[a.Email] = a
|
||||
}
|
||||
info, ok := byEmail["info@produktor.io"]
|
||||
if !ok {
|
||||
t.Fatal("info@produktor.io missing")
|
||||
}
|
||||
if info.Messages != 3 {
|
||||
t.Errorf("info Messages = %d, want 3 (fixture)", info.Messages)
|
||||
}
|
||||
if info.Total != 4 {
|
||||
t.Errorf("info Total = %d, want 4 (incl. .Sent)", info.Total)
|
||||
}
|
||||
if info.Quota != "500M" {
|
||||
t.Errorf("info Quota = %q, want 500M", info.Quota)
|
||||
}
|
||||
if byEmail["postman@produktor.io"].Messages != 0 {
|
||||
t.Errorf("postman Messages = %d, want 0", byEmail["postman@produktor.io"].Messages)
|
||||
}
|
||||
if byEmail["andriy.oblivantsev@produktor.io"].Messages != 0 {
|
||||
t.Errorf("andriy Messages = %d, want 0 (no maildir)", byEmail["andriy.oblivantsev@produktor.io"].Messages)
|
||||
}
|
||||
if byEmail["postmaster@produktor.io"].Messages != 1 {
|
||||
t.Errorf("postmaster Messages = %d, want 1", byEmail["postmaster@produktor.io"].Messages)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexPageRenders(t *testing.T) {
|
||||
h := testServer(t).handler()
|
||||
rec := doAuth(t, h, "/admin/", "bot", "s3cret")
|
||||
body := rec.Body.String()
|
||||
if !strings.Contains(body, "info@produktor.io") {
|
||||
t.Error("page does not list info@produktor.io")
|
||||
}
|
||||
if !strings.Contains(body, "Mail accounts") {
|
||||
t.Error("page title missing")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user