mirror of
https://github.com/marcusDenslow/terminalShop.git
synced 2026-10-25 02:52:20 +02:00
feat(audit): emit cart_rejected event when per-order cap blocks checkout (#38)
* feat(audit): emit cart_rejected event when per-order cap blocks checkout * refactor(audit): drop dead persist call + harden cart_rejected test capture
This commit is contained in:
@@ -382,6 +382,7 @@ func (h *CartHandler) ConvertCart(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if h.maxOrderCents > 0 && total > h.maxOrderCents {
|
||||
middleware.RecordCartConversion("validation_over_limit")
|
||||
audit.CartRejected(userID, total, h.maxOrderCents)
|
||||
utils.RespondError(w, http.StatusBadRequest, "CART_OVER_LIMIT",
|
||||
fmt.Sprintf("order total must be at most $%.2f", float64(h.maxOrderCents)/100),
|
||||
map[string]any{
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -746,6 +747,19 @@ func TestConvertCartLimit(t *testing.T) {
|
||||
t.Fatalf("SetCard: %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Capture audit slog ONLY for ConvertCart's emit window. Placed
|
||||
// here (not at top of t.Run) so setup INFO logs from
|
||||
// database.Connect/Migrate/Seed don't pollute the buffer with
|
||||
// non-audit records - json.Unmarshal below requires a single
|
||||
// JSON object, not multi-document input. slog.Default is
|
||||
// process-global - do NOT add t.Parallel() to subtests here.
|
||||
var auditBuf bytes.Buffer
|
||||
if tc.expectOverCap {
|
||||
prev := slog.Default()
|
||||
slog.SetDefault(slog.New(slog.NewJSONHandler(&auditBuf, nil)))
|
||||
t.Cleanup(func() { slog.SetDefault(prev) })
|
||||
}
|
||||
|
||||
// Convert
|
||||
req = authRequest("POST", "/api/v1/cart/convert", nil, user.ID)
|
||||
w = httptest.NewRecorder()
|
||||
@@ -772,6 +786,27 @@ func TestConvertCartLimit(t *testing.T) {
|
||||
if got, _ := resp.Error.Details["total_cents"].(float64); int(got) != tc.quantity*pinnedPrice {
|
||||
t.Errorf("total_cents in details: want %d, got %v", tc.quantity*pinnedPrice, resp.Error.Details["total_cents"])
|
||||
}
|
||||
|
||||
if auditBuf.Len() == 0 {
|
||||
t.Fatalf("audit: expected cart_rejected slog line, got nothing")
|
||||
}
|
||||
|
||||
var rec map[string]any
|
||||
if err := json.Unmarshal(auditBuf.Bytes(), &rec); err != nil {
|
||||
t.Fatalf("audit: decode slog record: %v\nraw: %s", err, auditBuf.String())
|
||||
}
|
||||
if got := rec["event"]; got != "cart_rejected" {
|
||||
t.Errorf("audit event: want cart_rejected, got %v", got)
|
||||
}
|
||||
if n, _ := rec["user_id"].(float64); uint(n) != user.ID {
|
||||
t.Errorf("audit user_id: want %d, got %v", user.ID, rec["user_id"])
|
||||
}
|
||||
if n, _ := rec["total_cents"].(float64); int(n) != tc.quantity*pinnedPrice {
|
||||
t.Errorf("audit total_cents: want %d, got %v", tc.quantity*pinnedPrice, rec["total_cents"])
|
||||
}
|
||||
if n, _ := rec["cap_cents"].(float64); int(n) != tc.capCents {
|
||||
t.Errorf("audit cap_cents: want %d, got %v", tc.capCents, rec["cap_cents"])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user