118 lines
4.1 KiB
Go
118 lines
4.1 KiB
Go
package config
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestValidateConfigAuditQueryAcceptsSupportedFilters(t *testing.T) {
|
|
query := ConfigAuditQuery{
|
|
From: time.Date(2026, 8, 1, 0, 0, 0, 0, time.UTC),
|
|
To: time.Date(2026, 8, 30, 0, 0, 0, 0, time.UTC),
|
|
ActorID: "42",
|
|
Operation: ConfigOperationUpdate,
|
|
Section: SectionMail,
|
|
Limit: 1000,
|
|
}
|
|
if err := ValidateConfigAuditQuery(query); err != nil {
|
|
t.Fatalf("valid audit query rejected: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestConfigAuditQueryFiltersCombinedMailSection(t *testing.T) {
|
|
writer := &MemoryAuditWriter{}
|
|
for _, event := range []ConfigAuditEvent{
|
|
{Operation: ConfigOperationUpdate, Section: SectionMail, ActorID: "42", ActorRole: "super_admin", FromGeneration: 1, ToGeneration: 2, Result: "success", CreatedAt: time.Now().UTC()},
|
|
{Operation: ConfigOperationUpdate, Section: SectionSMTP, ActorID: "42", ActorRole: "super_admin", FromGeneration: 2, ToGeneration: 3, Result: "success", CreatedAt: time.Now().UTC()},
|
|
} {
|
|
if err := writer.WriteConfigAudit(context.Background(), event); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
items, err := writer.Query(context.Background(), ConfigAuditQuery{Section: SectionMail, Limit: 10})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(items) != 1 || items[0].Section != SectionMail {
|
|
t.Fatalf("mail audit filter returned %+v", items)
|
|
}
|
|
}
|
|
|
|
func TestValidateConfigAuditQueryRejectsAmbiguousFilters(t *testing.T) {
|
|
tests := []ConfigAuditQuery{
|
|
{Limit: -1},
|
|
{Limit: 1001},
|
|
{From: time.Date(2026, 8, 30, 0, 0, 0, 0, time.UTC), To: time.Date(2026, 8, 1, 0, 0, 0, 0, time.UTC)},
|
|
{ActorID: "bad\nactor"},
|
|
{Operation: ConfigOperation("unknown")},
|
|
{Section: Section("unknown")},
|
|
}
|
|
for index, query := range tests {
|
|
if err := ValidateConfigAuditQuery(query); !errors.Is(err, ErrInvalidAuditQuery) {
|
|
t.Fatalf("case %d returned %v, want ErrInvalidAuditQuery", index, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConfigManagerRejectsInvalidAuditQueryBeforeStorageAccess(t *testing.T) {
|
|
var manager *ConfigManager
|
|
_, err := manager.AuditQuery(nil, ConfigAuditQuery{Limit: 1001})
|
|
if !errors.Is(err, ErrInvalidAuditQuery) {
|
|
t.Fatalf("AuditQuery returned %v, want ErrInvalidAuditQuery", err)
|
|
}
|
|
}
|
|
|
|
func TestConfigManagerAuditDetailUsesStableIDAndRedactsExport(t *testing.T) {
|
|
writer := &MemoryAuditWriter{}
|
|
manager := &ConfigManager{security: ConfigSecurity{Audit: writer}}
|
|
event := ConfigAuditEvent{
|
|
Operation: ConfigOperationUpdate,
|
|
Section: SectionSMTP,
|
|
ActorID: "42",
|
|
ActorRole: "super_admin",
|
|
FromGeneration: 1,
|
|
ToGeneration: 2,
|
|
ChangedFields: []string{"relay.password"},
|
|
ConfirmationMethod: "recent_auth",
|
|
Result: "success",
|
|
CreatedAt: time.Date(2026, 8, 30, 0, 0, 0, 0, time.UTC),
|
|
}
|
|
if err := writer.WriteConfigAudit(nil, event); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
items := writer.Events()
|
|
if len(items) != 1 || items[0].ID == "" || !strings.HasPrefix(items[0].ID, "audit-") {
|
|
t.Fatalf("unexpected stable audit ID: %+v", items)
|
|
}
|
|
detail, err := manager.AuditDetail(nil, items[0].ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if detail.ID != items[0].ID || detail.ChangedFields[0] != "relay.password" {
|
|
t.Fatalf("unexpected audit detail: %+v", detail)
|
|
}
|
|
if _, err := manager.AuditDetail(nil, "audit-not-valid"); !errors.Is(err, ErrInvalidAuditQuery) {
|
|
t.Fatalf("invalid audit ID returned %v", err)
|
|
}
|
|
if _, err := manager.AuditDetail(nil, "audit-00000000000000000000000000000000"); !errors.Is(err, ErrAuditNotFound) {
|
|
t.Fatalf("missing audit ID returned %v", err)
|
|
}
|
|
jsonData, err := manager.ExportAuditJSON(nil, ConfigAuditQuery{ID: items[0].ID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(string(jsonData), "password-value") || !strings.Contains(string(jsonData), items[0].ID) {
|
|
t.Fatalf("unexpected audit export: %s", jsonData)
|
|
}
|
|
csvData, err := manager.ExportAuditCSV(nil, ConfigAuditQuery{ID: items[0].ID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(string(csvData), "id,operation") || !strings.Contains(string(csvData), items[0].ID) {
|
|
t.Fatalf("CSV export does not include stable ID: %s", csvData)
|
|
}
|
|
}
|