Two-tier notification system: new_booking (all public bookings) + pending_booking (notes/today). Priority-sorted queue, unread count polling, enriched responses with user_name/booking_start_time. Fix critical bug: edit_requested cleanup was broken (wrong reason string in 3 handlers). Add 15 new tests covering priority ordering, enrichment, and notification creation flows. Update Admin Manual, Technical Manual, and gap backlog docs.
587 lines
18 KiB
Go
587 lines
18 KiB
Go
//go:build test
|
|
// +build test
|
|
|
|
package notifications
|
|
|
|
// Package notifications contains tests for admin notification endpoints.
|
|
//
|
|
// Test Coverage:
|
|
// - GetNotifications: GET /api/admin/notifications - List unacknowledged notifications
|
|
// - AcknowledgeNotification: POST /api/admin/notifications/{id}/acknowledge - Acknowledge a notification
|
|
//
|
|
// Features tested:
|
|
// - Pagination (page, per_page)
|
|
// - Filtering by reason
|
|
// - Acknowledging notifications (idempotent, not found cases)
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"crussell/db"
|
|
"crussell/mw"
|
|
"crussell/testutils/jwt"
|
|
"crussell/testutils/testdb"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
pool, _ := testdb.NewPool("")
|
|
testdb.Migrate(&testing.T{}, pool)
|
|
db.DB = pool
|
|
jwt.Init()
|
|
code := m.Run()
|
|
pool.Close()
|
|
os.Exit(code)
|
|
}
|
|
|
|
func resetTestData(t *testing.T) {
|
|
t.Helper()
|
|
testdb.TruncateTables(t, db.DB)
|
|
}
|
|
|
|
// makeAdminRequest creates a request with admin context
|
|
func makeAdminRequest(handler http.Handler, method, path string, body interface{}) *httptest.ResponseRecorder {
|
|
return makeRequestWithContext(handler, method, path, body, "admin001", "admin")
|
|
}
|
|
|
|
// makeUserRequest creates a request with regular user context
|
|
func makeUserRequest(handler http.Handler, method, path string, body interface{}) *httptest.ResponseRecorder {
|
|
return makeRequestWithContext(handler, method, path, body, "user001", "verified_email")
|
|
}
|
|
|
|
// makeRequestWithContext creates a request with specific user context
|
|
func makeRequestWithContext(handler http.Handler, method, path string, body interface{}, userID, role string) *httptest.ResponseRecorder {
|
|
var req *http.Request
|
|
if body != nil {
|
|
bodyBytes, _ := json.Marshal(body)
|
|
req = httptest.NewRequest(method, path, bytes.NewReader(bodyBytes))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
} else {
|
|
req = httptest.NewRequest(method, path, nil)
|
|
}
|
|
|
|
// Set up chi routing context for path params
|
|
rctx := chi.NewRouteContext()
|
|
if id, paramName := extractIDFromPath(path); id != "" {
|
|
rctx.URLParams.Add(paramName, id)
|
|
}
|
|
ctx := context.WithValue(req.Context(), chi.RouteCtxKey, rctx)
|
|
ctx = context.WithValue(ctx, mw.UserIDKey, userID)
|
|
ctx = context.WithValue(ctx, mw.UserRoleKey, role)
|
|
req = req.WithContext(ctx)
|
|
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
return w
|
|
}
|
|
|
|
// extractIDFromPath extracts the ID from URL paths
|
|
func extractIDFromPath(path string) (string, string) {
|
|
prefix := "/api/admin/notifications/"
|
|
if len(path) > len(prefix) && path[:len(prefix)] == prefix {
|
|
// Extract ID after prefix, up to next / or end
|
|
remainder := path[len(prefix):]
|
|
for i, c := range remainder {
|
|
if c == '/' {
|
|
return remainder[:i], "id"
|
|
}
|
|
}
|
|
return remainder, "id"
|
|
}
|
|
return "", ""
|
|
}
|
|
|
|
// =============================================================================
|
|
// GetNotifications Tests
|
|
// =============================================================================
|
|
|
|
// TestNotifications_List tests that an admin can list all unacknowledged notifications
|
|
func TestNotifications_List(t *testing.T) {
|
|
resetTestData(t)
|
|
|
|
// Create test user for notification reference
|
|
var userID string
|
|
err := db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
|
VALUES ('Test', 'User', 'test@test.com', '+1234567890', '1990-01-01', 'hash', 'verified_email', 'email')
|
|
RETURNING id
|
|
`).Scan(&userID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create user: %v", err)
|
|
}
|
|
|
|
// Create a notification
|
|
var notificationID int
|
|
err = db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO admin_notifications (reason, user_id)
|
|
VALUES ('pending_booking', $1)
|
|
RETURNING id
|
|
`, userID).Scan(¬ificationID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create notification: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(GetNotifications)
|
|
w := makeAdminRequest(handler, "GET", "/api/admin/notifications", nil)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var resp AdminNotificationListResponse
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if len(resp.Notifications) != 1 {
|
|
t.Errorf("expected 1 notification, got %d", len(resp.Notifications))
|
|
}
|
|
|
|
if len(resp.Notifications) > 0 {
|
|
if resp.Notifications[0].Reason != "pending_booking" {
|
|
t.Errorf("expected reason 'pending_booking', got %s", resp.Notifications[0].Reason)
|
|
}
|
|
if resp.Notifications[0].ID != notificationID {
|
|
t.Errorf("expected notification ID %d, got %d", notificationID, resp.Notifications[0].ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestNotifications_ListEmpty tests that an empty list is returned when no notifications exist
|
|
func TestNotifications_ListEmpty(t *testing.T) {
|
|
resetTestData(t)
|
|
|
|
handler := http.HandlerFunc(GetNotifications)
|
|
w := makeAdminRequest(handler, "GET", "/api/admin/notifications", nil)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp AdminNotificationListResponse
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if len(resp.Notifications) != 0 {
|
|
t.Errorf("expected 0 notifications, got %d", len(resp.Notifications))
|
|
}
|
|
|
|
// Total should be 0
|
|
if resp.Total != 0 {
|
|
t.Errorf("expected total 0, got %d", resp.Total)
|
|
}
|
|
}
|
|
|
|
// TestNotifications_ListFilterByReason tests that notifications can be filtered by reason
|
|
func TestNotifications_ListFilterByReason(t *testing.T) {
|
|
resetTestData(t)
|
|
|
|
// Create test user
|
|
var userID string
|
|
err := db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
|
VALUES ('Test', 'User', 'test@test.com', '+1234567890', '1990-01-01', 'hash', 'verified_email', 'email')
|
|
RETURNING id
|
|
`).Scan(&userID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create user: %v", err)
|
|
}
|
|
|
|
// Create notifications with different reasons
|
|
reasons := []string{"pending_booking", "cancelled_booking", "edit_requested"}
|
|
for _, reason := range reasons {
|
|
_, err := db.DB.Exec(context.Background(), `
|
|
INSERT INTO admin_notifications (reason, user_id)
|
|
VALUES ($1, $2)
|
|
`, reason, userID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create notification: %v", err)
|
|
}
|
|
}
|
|
|
|
handler := http.HandlerFunc(GetNotifications)
|
|
|
|
// Filter by pending_booking
|
|
w := makeAdminRequest(handler, "GET", "/api/admin/notifications?reason=pending_booking", nil)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp AdminNotificationListResponse
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if len(resp.Notifications) != 1 {
|
|
t.Errorf("expected 1 notification with reason=pending_booking, got %d", len(resp.Notifications))
|
|
}
|
|
|
|
if len(resp.Notifications) > 0 && resp.Notifications[0].Reason != "pending_booking" {
|
|
t.Errorf("expected reason 'pending_booking', got %s", resp.Notifications[0].Reason)
|
|
}
|
|
}
|
|
|
|
// TestNotifications_ListPagination tests that pagination works correctly
|
|
func TestNotifications_ListPagination(t *testing.T) {
|
|
resetTestData(t)
|
|
|
|
// Create test user
|
|
var userID string
|
|
err := db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
|
VALUES ('Test', 'User', 'test@test.com', '+1234567890', '1990-01-01', 'hash', 'verified_email', 'email')
|
|
RETURNING id
|
|
`).Scan(&userID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create user: %v", err)
|
|
}
|
|
|
|
// Create 25 notifications
|
|
for i := 0; i < 25; i++ {
|
|
_, err := db.DB.Exec(context.Background(), `
|
|
INSERT INTO admin_notifications (reason, user_id)
|
|
VALUES ('pending_booking', $1)
|
|
`, userID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create notification: %v", err)
|
|
}
|
|
}
|
|
|
|
handler := http.HandlerFunc(GetNotifications)
|
|
|
|
// Get first page (default 20 items)
|
|
w := makeAdminRequest(handler, "GET", "/api/admin/notifications?page=1&per_page=10", nil)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp AdminNotificationListResponse
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if len(resp.Notifications) != 10 {
|
|
t.Errorf("expected 10 notifications on page 1, got %d", len(resp.Notifications))
|
|
}
|
|
|
|
if resp.Total != 25 {
|
|
t.Errorf("expected total 25, got %d", resp.Total)
|
|
}
|
|
|
|
if resp.Page != 1 {
|
|
t.Errorf("expected page 1, got %d", resp.Page)
|
|
}
|
|
|
|
if resp.PerPage != 10 {
|
|
t.Errorf("expected per_page 10, got %d", resp.PerPage)
|
|
}
|
|
}
|
|
|
|
// TestNotifications_ListExcludesAcknowledged tests that acknowledged notifications are not returned
|
|
func TestNotifications_ListExcludesAcknowledged(t *testing.T) {
|
|
resetTestData(t)
|
|
|
|
// Create test user
|
|
var userID string
|
|
err := db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
|
VALUES ('Test', 'User', 'test@test.com', '+1234567890', '1990-01-01', 'hash', 'verified_email', 'email')
|
|
RETURNING id
|
|
`).Scan(&userID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create user: %v", err)
|
|
}
|
|
|
|
// Create acknowledged notification
|
|
_, err = db.DB.Exec(context.Background(), `
|
|
INSERT INTO admin_notifications (reason, user_id, acknowledged_at)
|
|
VALUES ('pending_booking', $1, NOW())
|
|
`, userID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create acknowledged notification: %v", err)
|
|
}
|
|
|
|
// Create unacknowledged notification
|
|
var unackID int
|
|
err = db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO admin_notifications (reason, user_id)
|
|
VALUES ('cancelled_booking', $1)
|
|
RETURNING id
|
|
`, userID).Scan(&unackID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create unacknowledged notification: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(GetNotifications)
|
|
w := makeAdminRequest(handler, "GET", "/api/admin/notifications", nil)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp AdminNotificationListResponse
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
// Should only return the unacknowledged notification
|
|
if len(resp.Notifications) != 1 {
|
|
t.Errorf("expected 1 unacknowledged notification, got %d", len(resp.Notifications))
|
|
}
|
|
|
|
if len(resp.Notifications) > 0 && resp.Notifications[0].ID != unackID {
|
|
t.Errorf("expected unacknowledged notification ID %d, got %d", unackID, resp.Notifications[0].ID)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// AcknowledgeNotification Tests
|
|
// =============================================================================
|
|
|
|
// TestNotifications_Acknowledge tests that an admin can acknowledge a notification
|
|
func TestNotifications_Acknowledge(t *testing.T) {
|
|
resetTestData(t)
|
|
|
|
// Create test user
|
|
var userID string
|
|
err := db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
|
VALUES ('Test', 'User', 'test@test.com', '+1234567890', '1990-01-01', 'hash', 'verified_email', 'email')
|
|
RETURNING id
|
|
`).Scan(&userID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create user: %v", err)
|
|
}
|
|
|
|
// Create notification
|
|
var notificationID int
|
|
err = db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO admin_notifications (reason, user_id)
|
|
VALUES ('pending_booking', $1)
|
|
RETURNING id
|
|
`, userID).Scan(¬ificationID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create notification: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(AcknowledgeNotification)
|
|
w := makeAdminRequest(handler, "POST", fmt.Sprintf("/api/admin/notifications/%d/acknowledge", notificationID), nil)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
// Verify the notification is acknowledged
|
|
var acknowledgedAt *time.Time
|
|
err = db.DB.QueryRow(context.Background(), `
|
|
SELECT acknowledged_at FROM admin_notifications WHERE id = $1
|
|
`, notificationID).Scan(&acknowledgedAt)
|
|
if err != nil {
|
|
t.Fatalf("failed to query notification: %v", err)
|
|
}
|
|
|
|
if acknowledgedAt == nil {
|
|
t.Error("expected acknowledged_at to be set, got nil")
|
|
}
|
|
}
|
|
|
|
// TestNotifications_AcknowledgeNotFound tests that acknowledging a non-existent notification returns 404
|
|
func TestNotifications_AcknowledgeNotFound(t *testing.T) {
|
|
resetTestData(t)
|
|
|
|
handler := http.HandlerFunc(AcknowledgeNotification)
|
|
w := makeAdminRequest(handler, "POST", "/api/admin/notifications/99999/acknowledge", nil)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Errorf("expected status 404 for non-existent notification, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestNotifications_AcknowledgeAlreadyAcknowledged tests that acknowledging an already-acknowledged notification returns 404
|
|
func TestNotifications_AcknowledgeAlreadyAcknowledged(t *testing.T) {
|
|
resetTestData(t)
|
|
|
|
// Create test user
|
|
var userID string
|
|
err := db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
|
VALUES ('Test', 'User', 'test@test.com', '+1234567890', '1990-01-01', 'hash', 'verified_email', 'email')
|
|
RETURNING id
|
|
`).Scan(&userID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create user: %v", err)
|
|
}
|
|
|
|
// Create already-acknowledged notification
|
|
var notificationID int
|
|
err = db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO admin_notifications (reason, user_id, acknowledged_at)
|
|
VALUES ('pending_booking', $1, NOW())
|
|
RETURNING id
|
|
`, userID).Scan(¬ificationID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create notification: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(AcknowledgeNotification)
|
|
w := makeAdminRequest(handler, "POST", fmt.Sprintf("/api/admin/notifications/%d/acknowledge", notificationID), nil)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Errorf("expected status 404 for already acknowledged notification, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestNotifications_AcknowledgeInvalidID tests that invalid notification IDs are handled
|
|
func TestNotifications_AcknowledgeInvalidID(t *testing.T) {
|
|
resetTestData(t)
|
|
|
|
handler := http.HandlerFunc(AcknowledgeNotification)
|
|
|
|
tests := []struct {
|
|
name string
|
|
id string
|
|
expectCode int
|
|
}{
|
|
{"non_numeric_id", "abc", http.StatusBadRequest}, // Invalid format
|
|
{"negative_id", "-1", http.StatusNotFound}, // Valid int, not found
|
|
{"zero_id", "0", http.StatusNotFound}, // Valid int, not found
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create a custom request to test invalid ID handling
|
|
req := httptest.NewRequest("POST", "/api/admin/notifications/"+tt.id+"/acknowledge", nil)
|
|
|
|
// Set up chi routing context with the invalid ID
|
|
rctx := chi.NewRouteContext()
|
|
rctx.URLParams.Add("id", tt.id)
|
|
ctx := context.WithValue(req.Context(), chi.RouteCtxKey, rctx)
|
|
ctx = context.WithValue(ctx, mw.UserIDKey, "admin001")
|
|
ctx = context.WithValue(ctx, mw.UserRoleKey, "admin")
|
|
req = req.WithContext(ctx)
|
|
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != tt.expectCode {
|
|
t.Errorf("expected status %d for ID '%s', got %d", tt.expectCode, tt.id, w.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNotifications_AcknowledgeMissingID tests that missing ID returns 400
|
|
func TestNotifications_AcknowledgeMissingID(t *testing.T) {
|
|
resetTestData(t)
|
|
|
|
// Create a custom request with no ID in path
|
|
req := httptest.NewRequest("POST", "/api/admin/notifications//acknowledge", nil)
|
|
|
|
// Set up chi routing context without ID
|
|
rctx := chi.NewRouteContext()
|
|
ctx := context.WithValue(req.Context(), chi.RouteCtxKey, rctx)
|
|
ctx = context.WithValue(ctx, mw.UserIDKey, "admin001")
|
|
ctx = context.WithValue(ctx, mw.UserRoleKey, "admin")
|
|
req = req.WithContext(ctx)
|
|
|
|
w := httptest.NewRecorder()
|
|
handler := http.HandlerFunc(AcknowledgeNotification)
|
|
handler.ServeHTTP(w, req)
|
|
|
|
// Should return 400 for missing ID
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for missing ID, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Notification with Booking Reference Tests
|
|
// =============================================================================
|
|
|
|
// TestNotifications_WithBookingReference tests that notifications include booking_id when applicable
|
|
func TestNotifications_WithBookingReference(t *testing.T) {
|
|
resetTestData(t)
|
|
|
|
// Create test user
|
|
var userID string
|
|
err := db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
|
VALUES ('Test', 'User', 'test@test.com', '+1234567890', '1990-01-01', 'hash', 'verified_email', 'email')
|
|
RETURNING id
|
|
`).Scan(&userID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create user: %v", err)
|
|
}
|
|
|
|
// Create a service
|
|
var serviceID string
|
|
err = db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO services (name, description, price, duration_minutes, is_active)
|
|
VALUES ('Manicure', 'Test service', 25.00, 30, true)
|
|
RETURNING id
|
|
`).Scan(&serviceID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create service: %v", err)
|
|
}
|
|
|
|
// Create a booking
|
|
var bookingID string
|
|
err = db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO bookings (user_id, start_time, status)
|
|
VALUES ($1, NOW() + INTERVAL '1 day', 'pending')
|
|
RETURNING id
|
|
`, userID).Scan(&bookingID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create booking: %v", err)
|
|
}
|
|
|
|
// Create notification with booking reference
|
|
var notificationID int
|
|
err = db.DB.QueryRow(context.Background(), `
|
|
INSERT INTO admin_notifications (reason, booking_id, user_id)
|
|
VALUES ('pending_booking', $1, $2)
|
|
RETURNING id
|
|
`, bookingID, userID).Scan(¬ificationID)
|
|
if err != nil {
|
|
t.Fatalf("failed to create notification: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(GetNotifications)
|
|
w := makeAdminRequest(handler, "GET", "/api/admin/notifications", nil)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp AdminNotificationListResponse
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if len(resp.Notifications) != 1 {
|
|
t.Fatalf("expected 1 notification, got %d", len(resp.Notifications))
|
|
}
|
|
|
|
// Note: booking_id in DB is stored as bigint, but our struct uses int
|
|
// The booking_id should be present
|
|
if resp.Notifications[0].BookingID == nil {
|
|
t.Error("expected booking_id to be set, got nil")
|
|
}
|
|
}
|
|
|
|
// Ensure test compilation - import pgxpool to avoid unused import
|
|
var _ = func() *pgxpool.Pool { return nil }
|