Files
meshcore-analyzer/cmd/server/user_mgmt_config_test.go
T
CoderNemesisandCoderNemesis d9342bbebd (feat): UserManagement, Add Support for Postal Email Delivery Provider (#2154)
This PR extends email support to add another email delivery provider
called Postal for user management. It is a, open source, self hosted
email delivery and reputation management service.

It adds 2 new additional configuration settings to the
userManagement.mail object which are "postalBaseUrl" and "postalApiKey".
It also adds "postal" as a supported value for the "provider" setting.
All existing functionality including email delivery notification is
supported by Postal.

Included test cases to support the new functionality and all pipelines
pass.

---------

Co-authored-by: CoderNemesis <rfedor@comchan.net>
2026-10-11 11:28:20 +02:00

234 lines
9.8 KiB
Go

package main
import (
"path/filepath"
"strings"
"testing"
"time"
)
func validUM() *UserManagementConfig {
return &UserManagementConfig{
Enabled: true,
AdminEmails: []string{" Boss@Example.org "},
PublicBaseURL: "https://scope.example.org/",
Mail: UserMailConfig{BrevoAPIKey: "xkeysib-abc", FromEmail: "noreply@example.org"},
}
}
func noEnv(string) string { return "" }
func TestResolveUserManagementDefaults(t *testing.T) {
set, err := resolveUserManagement(validUM(), filepath.Join("data", "meshcore.db"), noEnv)
if err != nil {
t.Fatal(err)
}
if set.dbPath != filepath.Join("data", "users.db") {
t.Errorf("dbPath = %q", set.dbPath)
}
if !set.adminEmails["boss@example.org"] {
t.Errorf("adminEmails not normalized: %v", set.adminEmails)
}
if set.baseURL.String() != "https://scope.example.org" || !set.secureCookie {
t.Errorf("baseURL = %q secure=%v", set.baseURL, set.secureCookie)
}
if set.sessionTTL != 30*24*time.Hour || set.provider != "brevo" || set.fromName != "CoreScope" {
t.Errorf("defaults: ttl=%v provider=%q fromName=%q", set.sessionTTL, set.provider, set.fromName)
}
}
func TestResolveUserManagementEnvWins(t *testing.T) {
env := map[string]string{"CORESCOPE_BREVO_API_KEY": "from-env", "CORESCOPE_BREVO_WEBHOOK_SECRET": "env-secret-0123456789"}
set, err := resolveUserManagement(validUM(), "meshcore.db", func(k string) string { return env[k] })
if err != nil {
t.Fatal(err)
}
if set.brevoAPIKey != "from-env" || set.webhookSecret != "env-secret-0123456789" {
t.Fatalf("env not applied: %q %q", set.brevoAPIKey, set.webhookSecret)
}
}
func TestResolveUserManagementErrors(t *testing.T) {
cases := map[string]func(u *UserManagementConfig){
"publicBaseUrl": func(u *UserManagementConfig) { u.PublicBaseURL = "scope.example.org" },
"fromEmail": func(u *UserManagementConfig) { u.Mail.FromEmail = "" },
"Brevo API key": func(u *UserManagementConfig) { u.Mail.BrevoAPIKey = "" },
"adminEmails": func(u *UserManagementConfig) { u.AdminEmails = []string{"not-an-address"} },
"not supported": func(u *UserManagementConfig) { u.Mail.Provider = "smtp" },
"e2etest builds": func(u *UserManagementConfig) { u.Mail.Provider = "fake" },
"webhookSecret": func(u *UserManagementConfig) { u.Mail.WebhookSecret = "short" },
}
for want, mutate := range cases {
if want == "e2etest builds" && fakeMailerAllowed {
continue // the e2etest build accepts the fake provider by design
}
u := validUM()
mutate(u)
_, err := resolveUserManagement(u, "meshcore.db", noEnv)
if err == nil || !strings.Contains(err.Error(), want) {
t.Errorf("%s: err = %v", want, err)
}
}
}
func validPostalUM() *UserManagementConfig {
u := validUM()
u.Mail = UserMailConfig{Provider: "Postal", PostalBaseURL: "https://postal.example.org/",
PostalAPIKey: "postal-key", FromEmail: "noreply@example.org", WebhookSecret: "AbCd0123456789xyz"}
return u
}
func TestResolveUserManagementPostal(t *testing.T) {
set, err := resolveUserManagement(validPostalUM(), "meshcore.db", noEnv)
if err != nil {
t.Fatal(err)
}
if set.provider != "postal" || set.postalBaseURL != "https://postal.example.org" ||
set.postalAPIKey != "postal-key" || set.webhookSecret != "AbCd0123456789xyz" {
t.Fatalf("postal settings = %q %q %q %q", set.provider, set.postalBaseURL, set.postalAPIKey, set.webhookSecret)
}
env := map[string]string{
"CORESCOPE_POSTAL_API_KEY": "env-key", "CORESCOPE_POSTAL_WEBHOOK_SECRET": "env-secret-0123456789",
"CORESCOPE_BREVO_API_KEY": "brevo-env", "CORESCOPE_BREVO_WEBHOOK_SECRET": "brevo-secret-0123456789",
}
set, err = resolveUserManagement(validPostalUM(), "meshcore.db", func(k string) string { return env[k] })
if err != nil {
t.Fatal(err)
}
if set.postalAPIKey != "env-key" || set.webhookSecret != "env-secret-0123456789" {
t.Fatalf("postal env not applied: %q %q", set.postalAPIKey, set.webhookSecret)
}
u := validPostalUM()
u.Mail.PostalBaseURL = "http://127.0.0.1:5000"
if set, err := resolveUserManagement(u, "meshcore.db", noEnv); err != nil || set.postalBaseURL != "http://127.0.0.1:5000" {
t.Fatalf("loopback http: %v", err)
}
}
func TestResolveUserManagementPostalErrors(t *testing.T) {
cases := map[string]func(u *UserManagementConfig){
"Postal API key": func(u *UserManagementConfig) { u.Mail.PostalAPIKey = "" },
"postalBaseUrl must be": func(u *UserManagementConfig) { u.Mail.PostalBaseURL = "" },
"postalBaseUrl must use": func(u *UserManagementConfig) { u.Mail.PostalBaseURL = "http://postal.example.org" },
"postalBaseUrl must not": func(u *UserManagementConfig) { u.Mail.PostalBaseURL = "https://user:pw@postal.example.org" },
"postalBaseUrl must not ": func(u *UserManagementConfig) { u.Mail.PostalBaseURL = "https://postal.example.org/?x=1" },
"letters, digits": func(u *UserManagementConfig) { u.Mail.WebhookSecret = "has:colon-0123456789" },
"webhookSecret": func(u *UserManagementConfig) { u.Mail.WebhookSecret = "short" },
}
for want, mutate := range cases {
u := validPostalUM()
mutate(u)
_, err := resolveUserManagement(u, "meshcore.db", noEnv)
if err == nil || !strings.Contains(err.Error(), strings.TrimSpace(want)) {
t.Errorf("%s: err = %v", want, err)
}
}
}
func TestUserManagementEnabled(t *testing.T) {
var nilCfg *Config
if nilCfg.UserManagementEnabled() || (&Config{}).UserManagementEnabled() ||
(&Config{UserManagement: &UserManagementConfig{}}).UserManagementEnabled() {
t.Fatal("off states reported as enabled")
}
if !(&Config{UserManagement: &UserManagementConfig{Enabled: true}}).UserManagementEnabled() {
t.Fatal("enabled not reported")
}
}
func TestResolveChannelProposals(t *testing.T) {
u := validUM()
set, err := resolveUserManagement(u, "meshcore.db", noEnv)
if err != nil {
t.Fatal(err)
}
if set.proposals != (proposalSettings{}) {
t.Fatalf("absent block = %+v; want off", set.proposals)
}
u.ChannelProposals = &ChannelProposalsConfig{Enabled: true}
set, _ = resolveUserManagement(u, "meshcore.db", noEnv)
if set.proposals != (proposalSettings{enabled: true, maxPending: 100, maxApproved: 128, perUserPerDay: 5}) {
t.Fatalf("defaults = %+v", set.proposals)
}
u.ChannelProposals = &ChannelProposalsConfig{Enabled: true, MaxPending: 3, MaxApproved: 7, PerUserPerDay: 1}
set, _ = resolveUserManagement(u, "meshcore.db", noEnv)
if set.proposals != (proposalSettings{enabled: true, maxPending: 3, maxApproved: 7, perUserPerDay: 1}) {
t.Fatalf("explicit = %+v", set.proposals)
}
u.ChannelProposals = &ChannelProposalsConfig{Enabled: true, MaxPending: -1, MaxApproved: -1, PerUserPerDay: -1}
set, _ = resolveUserManagement(u, "meshcore.db", noEnv)
if set.proposals != (proposalSettings{enabled: true, maxPending: 100, maxApproved: 128, perUserPerDay: 5}) {
t.Fatalf("negative values = %+v; want defaults", set.proposals)
}
u.ChannelProposals = &ChannelProposalsConfig{Enabled: false, MaxApproved: 7}
set, _ = resolveUserManagement(u, "meshcore.db", noEnv)
if set.proposals.enabled {
t.Fatal("enabled false resolved as on")
}
}
func TestResolveNotifications(t *testing.T) {
u := validUM()
set, err := resolveUserManagement(u, "meshcore.db", noEnv)
if err != nil {
t.Fatal(err)
}
if set.notify != (notifySettings{}) {
t.Fatalf("absent block = %+v; want off", set.notify)
}
defaults := notifySettings{enabled: true, interval: 5 * time.Minute, perUserPerDay: 20, maxMailsPerDay: 100, maxWatchesPerUser: 50}
u.Notifications = &NotificationsConfig{Enabled: true}
if set, _ = resolveUserManagement(u, "meshcore.db", noEnv); set.notify != defaults {
t.Fatalf("defaults = %+v", set.notify)
}
u.Notifications = &NotificationsConfig{Enabled: true, IntervalMinutes: 1, PerUserPerDay: 3, MaxMailsPerDay: 9, MaxWatchesPerUser: 2}
if set, _ = resolveUserManagement(u, "meshcore.db", noEnv); set.notify != (notifySettings{enabled: true, interval: time.Minute, perUserPerDay: 3, maxMailsPerDay: 9, maxWatchesPerUser: 2}) {
t.Fatalf("explicit = %+v", set.notify)
}
u.Notifications = &NotificationsConfig{Enabled: true, IntervalMinutes: -5, PerUserPerDay: -1, MaxWatchesPerUser: -2}
if set, _ = resolveUserManagement(u, "meshcore.db", noEnv); set.notify != defaults {
t.Fatalf("zero and negative values = %+v; want defaults", set.notify)
}
u.Notifications = &NotificationsConfig{Enabled: false, PerUserPerDay: 3}
if set, _ = resolveUserManagement(u, "meshcore.db", noEnv); set.notify.enabled {
t.Fatal("enabled false resolved as on")
}
}
func TestResolveUsersBackup(t *testing.T) {
u := validUM()
meas := filepath.Join("data", "meshcore.db")
defaults := backupSettings{enabled: true, dir: filepath.Join("data", "backups"), keep: 7}
set, err := resolveUserManagement(u, meas, noEnv)
if err != nil {
t.Fatal(err)
}
if set.backup != defaults {
t.Fatalf("absent block = %+v; want %+v", set.backup, defaults)
}
u.DBPath = filepath.Join("accounts", "users.db")
if set, _ = resolveUserManagement(u, meas, noEnv); set.backup.dir != filepath.Join("accounts", "backups") {
t.Fatalf("dir follows dbPath: %q", set.backup.dir)
}
u.DBPath = ""
off, on := false, true
u.Backup = &UsersBackupConfig{Enabled: &off, Dir: "x", Keep: 3}
if set, _ = resolveUserManagement(u, meas, noEnv); set.backup != (backupSettings{}) {
t.Fatalf("enabled false = %+v; want off", set.backup)
}
u.Backup = &UsersBackupConfig{Dir: " bk ", Keep: 3}
if set, _ = resolveUserManagement(u, meas, noEnv); set.backup != (backupSettings{enabled: true, dir: "bk", keep: 3}) {
t.Fatalf("enabled omitted = %+v; want on with dir bk, keep 3", set.backup)
}
u.Backup = &UsersBackupConfig{Enabled: &on, Dir: " ", Keep: -1}
if set, _ = resolveUserManagement(u, meas, noEnv); set.backup != defaults {
t.Fatalf("blank dir and negative keep = %+v; want defaults", set.backup)
}
u.Backup = &UsersBackupConfig{Keep: 0}
if set, _ = resolveUserManagement(u, meas, noEnv); set.backup.keep != 7 {
t.Fatalf("keep 0 = %d; want 7", set.backup.keep)
}
}