feat(backend): update admin discount campaigns handler
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -38,17 +38,17 @@ type DiscountCampaign struct {
|
||||
|
||||
// CreateCampaignRequest represents the request payload for creating a new campaign
|
||||
type CreateCampaignRequest struct {
|
||||
Name string `json:"name" validate:"required,min=1,max=200"`
|
||||
Description *string `json:"description,omitempty" validate:"omitempty,max=1000"`
|
||||
CampaignType string `json:"campaign_type"` // "time_based" or "milestone"
|
||||
DiscountPercent float64 `json:"discount_percent"`
|
||||
Scope *string `json:"scope,omitempty"`
|
||||
StartDate *string `json:"start_date,omitempty"` // ISO 8601
|
||||
EndDate *string `json:"end_date,omitempty"` // ISO 8601
|
||||
MilestoneType *string `json:"milestone_type,omitempty"`
|
||||
MilestoneValue *int `json:"milestone_value,omitempty"`
|
||||
MilestoneUnit *string `json:"milestone_unit,omitempty"`
|
||||
MaxRedemptions *int `json:"max_redemptions,omitempty"`
|
||||
Name string `json:"name" validate:"required,min=1,max=200"`
|
||||
Description *string `json:"description,omitempty" validate:"omitempty,max=1000"`
|
||||
CampaignType string `json:"campaign_type"` // "time_based" or "milestone"
|
||||
DiscountPercent float64 `json:"discount_percent"`
|
||||
Scope *string `json:"scope,omitempty"`
|
||||
StartDate *string `json:"start_date,omitempty"` // ISO 8601
|
||||
EndDate *string `json:"end_date,omitempty"` // ISO 8601
|
||||
MilestoneType *string `json:"milestone_type,omitempty"`
|
||||
MilestoneValue *int `json:"milestone_value,omitempty"`
|
||||
MilestoneUnit *string `json:"milestone_unit,omitempty"`
|
||||
MaxRedemptions *int `json:"max_redemptions,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateCampaignRequest represents the request payload for updating a campaign
|
||||
@@ -70,7 +70,7 @@ type UpdateCampaignRequest struct {
|
||||
type CampaignStats struct {
|
||||
Campaign DiscountCampaign `json:"campaign"`
|
||||
TotalDiscounts float64 `json:"total_discount_amount"`
|
||||
BookingCount int `json:"booking_count"`
|
||||
BookingCount int `json:"booking_count"`
|
||||
}
|
||||
|
||||
// GetDiscountCampaigns handles GET /api/admin/discount-campaigns
|
||||
@@ -738,4 +738,4 @@ func GetCampaignStats(w http.ResponseWriter, r *http.Request) {
|
||||
if err := json.NewEncoder(w).Encode(stats); err != nil {
|
||||
log.Printf("Error encoding stats: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,539 @@
|
||||
//go:build test
|
||||
// +build test
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"crussell/db"
|
||||
"crussell/mw"
|
||||
"crussell/testutils/fixtures"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func makeCampaignRequest(handler http.Handler, method, path string, body interface{}) *httptest.ResponseRecorder {
|
||||
var req *http.Request
|
||||
if body != nil {
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
req = httptest.NewRequest(method, path, strings.NewReader(string(bodyBytes)))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else {
|
||||
req = httptest.NewRequest(method, path, nil)
|
||||
}
|
||||
|
||||
rctx := chi.NewRouteContext()
|
||||
prefix := "/api/admin/discount-campaigns/"
|
||||
if strings.HasPrefix(path, prefix) {
|
||||
suffix := path[len(prefix):]
|
||||
if slashIdx := strings.Index(suffix, "/"); slashIdx >= 0 {
|
||||
rctx.URLParams.Add("id", suffix[:slashIdx])
|
||||
} else if suffix != "" {
|
||||
rctx.URLParams.Add("id", suffix)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.WithValue(req.Context(), chi.RouteCtxKey, rctx)
|
||||
ctx = context.WithValue(ctx, mw.UserIDKey, testAdminID)
|
||||
ctx = context.WithValue(ctx, mw.UserRoleKey, "admin")
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
func insertTimeBasedCampaign(t *testing.T, name string, discount float64, status string) string {
|
||||
t.Helper()
|
||||
startDate := fmt.Sprintf("%sZ", time.Now().Add(-1*time.Hour).Format("2006-01-02T15:04:05"))
|
||||
endDate := fmt.Sprintf("%sZ", time.Now().Add(7*24*time.Hour).Format("2006-01-02T15:04:05"))
|
||||
var id string
|
||||
err := db.DB.QueryRow(context.Background(), `
|
||||
INSERT INTO discount_campaigns (name, campaign_type, discount_percent, status, start_date, end_date, created_by)
|
||||
VALUES ($1, 'time_based', $2, $3, $4::timestamptz, $5::timestamptz, $6)
|
||||
RETURNING id
|
||||
`, name, discount, status, startDate, endDate, testAdminID).Scan(&id)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to insert time_based campaign: %v", err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func insertMilestoneCampaign(t *testing.T, name string, discount float64, status string) string {
|
||||
t.Helper()
|
||||
mt := "per_user_booking_count"
|
||||
mv := 5
|
||||
mu := "bookings"
|
||||
var id string
|
||||
err := db.DB.QueryRow(context.Background(), `
|
||||
INSERT INTO discount_campaigns (name, campaign_type, discount_percent, status,
|
||||
milestone_type, milestone_value, milestone_unit, created_by)
|
||||
VALUES ($1, 'milestone', $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id
|
||||
`, name, discount, status, mt, mv, mu, testAdminID).Scan(&id)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to insert milestone campaign: %v", err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// List Campaigns Tests
|
||||
// =============================================================================
|
||||
|
||||
func TestGetDiscountCampaigns_Empty(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
handler := http.HandlerFunc(GetDiscountCampaigns)
|
||||
w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns", nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var campaigns []DiscountCampaign
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &campaigns); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
if len(campaigns) != 0 {
|
||||
t.Errorf("expected empty list, got %d items", len(campaigns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDiscountCampaigns_WithCampaigns(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
insertTimeBasedCampaign(t, "Summer Sale", 15, "active")
|
||||
|
||||
handler := http.HandlerFunc(GetDiscountCampaigns)
|
||||
w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns", nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var campaigns []DiscountCampaign
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &campaigns); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
if len(campaigns) != 1 {
|
||||
t.Fatalf("expected 1 campaign, got %d", len(campaigns))
|
||||
}
|
||||
if campaigns[0].Name != "Summer Sale" {
|
||||
t.Errorf("expected 'Summer Sale', got %q", campaigns[0].Name)
|
||||
}
|
||||
if campaigns[0].DiscountPercent != 15 {
|
||||
t.Errorf("expected 15%% discount, got %.0f%%", campaigns[0].DiscountPercent)
|
||||
}
|
||||
if campaigns[0].CampaignType != "time_based" {
|
||||
t.Errorf("expected 'time_based', got %q", campaigns[0].CampaignType)
|
||||
}
|
||||
if campaigns[0].Status != "active" {
|
||||
t.Errorf("expected 'active', got %q", campaigns[0].Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDiscountCampaigns_FilterByStatus(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
insertMilestoneCampaign(t, "Draft Campaign", 10, "draft")
|
||||
insertMilestoneCampaign(t, "Active Campaign", 20, "active")
|
||||
insertMilestoneCampaign(t, "Cancelled Campaign", 5, "cancelled")
|
||||
|
||||
handler := http.HandlerFunc(GetDiscountCampaigns)
|
||||
|
||||
t.Run("filter_by_active", func(t *testing.T) {
|
||||
w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns?status=active", nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", w.Code)
|
||||
}
|
||||
var campaigns []DiscountCampaign
|
||||
json.Unmarshal(w.Body.Bytes(), &campaigns)
|
||||
if len(campaigns) != 1 {
|
||||
t.Errorf("expected 1 active campaign, got %d", len(campaigns))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("filter_by_invalid_status", func(t *testing.T) {
|
||||
w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns?status=invalid", nil)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 for invalid status filter, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Create Campaign Tests
|
||||
// =============================================================================
|
||||
|
||||
func TestCreateDiscountCampaign_TimeBased(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
req := CreateCampaignRequest{
|
||||
Name: "Summer Sale",
|
||||
CampaignType: "time_based",
|
||||
DiscountPercent: 15,
|
||||
StartDate: strPtr(fmt.Sprintf("%sZ", time.Now().Format("2006-01-02T15:04:05"))),
|
||||
EndDate: strPtr(fmt.Sprintf("%sZ", time.Now().Add(7*24*time.Hour).Format("2006-01-02T15:04:05"))),
|
||||
MaxRedemptions: intPtr(100),
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(CreateDiscountCampaign)
|
||||
w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req)
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var campaign DiscountCampaign
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &campaign); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
if campaign.Name != "Summer Sale" {
|
||||
t.Errorf("expected 'Summer Sale', got %q", campaign.Name)
|
||||
}
|
||||
if campaign.Status != "draft" {
|
||||
t.Errorf("expected initial status 'draft', got %q", campaign.Status)
|
||||
}
|
||||
if campaign.ID == "" {
|
||||
t.Error("expected campaign ID to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDiscountCampaign_Milestone(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
mt := "per_user_booking_count"
|
||||
mv := 5
|
||||
mu := "bookings"
|
||||
req := CreateCampaignRequest{
|
||||
Name: "Loyalty Milestone",
|
||||
CampaignType: "milestone",
|
||||
DiscountPercent: 25,
|
||||
MilestoneType: &mt,
|
||||
MilestoneValue: &mv,
|
||||
MilestoneUnit: &mu,
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(CreateDiscountCampaign)
|
||||
w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req)
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("expected 201, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var campaign DiscountCampaign
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &campaign); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
if campaign.CampaignType != "milestone" {
|
||||
t.Errorf("expected 'milestone', got %q", campaign.CampaignType)
|
||||
}
|
||||
if campaign.MilestoneValue == nil || *campaign.MilestoneValue != 5 {
|
||||
t.Errorf("expected milestone value 5, got %v", campaign.MilestoneValue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDiscountCampaign_ValidationErrors(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
handler := http.HandlerFunc(CreateDiscountCampaign)
|
||||
|
||||
t.Run("empty_name", func(t *testing.T) {
|
||||
req := CreateCampaignRequest{
|
||||
Name: "",
|
||||
CampaignType: "time_based",
|
||||
DiscountPercent: 10,
|
||||
}
|
||||
w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 for empty name, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid_discount_percent_zero", func(t *testing.T) {
|
||||
req := CreateCampaignRequest{
|
||||
Name: "Test",
|
||||
CampaignType: "time_based",
|
||||
DiscountPercent: 0,
|
||||
}
|
||||
w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 for zero discount, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid_discount_percent_over_100", func(t *testing.T) {
|
||||
req := CreateCampaignRequest{
|
||||
Name: "Test",
|
||||
CampaignType: "time_based",
|
||||
DiscountPercent: 150,
|
||||
}
|
||||
w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 for discount >100, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid_campaign_type", func(t *testing.T) {
|
||||
req := CreateCampaignRequest{
|
||||
Name: "Test",
|
||||
CampaignType: "invalid_type",
|
||||
DiscountPercent: 10,
|
||||
}
|
||||
w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 for invalid type, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("time_based_missing_dates", func(t *testing.T) {
|
||||
req := CreateCampaignRequest{
|
||||
Name: "Test",
|
||||
CampaignType: "time_based",
|
||||
DiscountPercent: 10,
|
||||
}
|
||||
w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 for missing dates, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("time_based_end_before_start", func(t *testing.T) {
|
||||
future := time.Now().Add(7 * 24 * time.Hour)
|
||||
past := time.Now().Add(-7 * 24 * time.Hour)
|
||||
req := CreateCampaignRequest{
|
||||
Name: "Test",
|
||||
CampaignType: "time_based",
|
||||
DiscountPercent: 10,
|
||||
StartDate: strPtr(fmt.Sprintf("%sZ", future.Format("2006-01-02T15:04:05"))),
|
||||
EndDate: strPtr(fmt.Sprintf("%sZ", past.Format("2006-01-02T15:04:05"))),
|
||||
}
|
||||
w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 for end before start, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("milestone_missing_required_fields", func(t *testing.T) {
|
||||
req := CreateCampaignRequest{
|
||||
Name: "Test",
|
||||
CampaignType: "milestone",
|
||||
DiscountPercent: 10,
|
||||
}
|
||||
w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 for missing milestone fields, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Update Campaign Tests
|
||||
// =============================================================================
|
||||
|
||||
func TestUpdateDiscountCampaign_UpdateName(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
campaignID := insertMilestoneCampaign(t, "Old Name", 10, "draft")
|
||||
|
||||
newName := "New Name"
|
||||
req := UpdateCampaignRequest{Name: &newName}
|
||||
handler := http.HandlerFunc(UpdateDiscountCampaign)
|
||||
w := makeCampaignRequest(handler, "PUT", "/api/admin/discount-campaigns/"+campaignID, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var campaign DiscountCampaign
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &campaign); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
if campaign.Name != "New Name" {
|
||||
t.Errorf("expected 'New Name', got %q", campaign.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDiscountCampaign_NotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
newName := "Test"
|
||||
req := UpdateCampaignRequest{Name: &newName}
|
||||
handler := http.HandlerFunc(UpdateDiscountCampaign)
|
||||
w := makeCampaignRequest(handler, "PUT", "/api/admin/discount-campaigns/nonexistent-id", req)
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("expected 404 for nonexistent campaign, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDiscountCampaign_InvalidStatus(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
campaignID := insertMilestoneCampaign(t, "Test", 10, "draft")
|
||||
|
||||
badStatus := "invalid_status"
|
||||
req := UpdateCampaignRequest{Status: &badStatus}
|
||||
handler := http.HandlerFunc(UpdateDiscountCampaign)
|
||||
w := makeCampaignRequest(handler, "PUT", "/api/admin/discount-campaigns/"+campaignID, req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400 for invalid status, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Delete Campaign Tests
|
||||
// =============================================================================
|
||||
|
||||
func TestDeleteDiscountCampaign_HappyPath(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
campaignID := insertMilestoneCampaign(t, "Test", 10, "active")
|
||||
|
||||
handler := http.HandlerFunc(DeleteDiscountCampaign)
|
||||
w := makeCampaignRequest(handler, "DELETE", "/api/admin/discount-campaigns/"+campaignID, nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var status string
|
||||
err = db.DB.QueryRow(context.Background(),
|
||||
"SELECT status FROM discount_campaigns WHERE id = $1", campaignID).Scan(&status)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query campaign: %v", err)
|
||||
}
|
||||
if status != "cancelled" {
|
||||
t.Errorf("expected status 'cancelled', got %q", status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteDiscountCampaign_NotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
handler := http.HandlerFunc(DeleteDiscountCampaign)
|
||||
w := makeCampaignRequest(handler, "DELETE", "/api/admin/discount-campaigns/nonexistent-id", nil)
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("expected 404 for nonexistent campaign, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Campaign Stats Tests
|
||||
// =============================================================================
|
||||
|
||||
func TestGetCampaignStats_NoUsage(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
campaignID := insertMilestoneCampaign(t, "Test", 10, "active")
|
||||
|
||||
handler := http.HandlerFunc(GetCampaignStats)
|
||||
w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns/"+campaignID+"/stats", nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var stats CampaignStats
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &stats); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
if stats.Campaign.ID != campaignID {
|
||||
t.Errorf("expected campaign ID %q, got %q", campaignID, stats.Campaign.ID)
|
||||
}
|
||||
if stats.TotalDiscounts != 0 {
|
||||
t.Errorf("expected 0 total discounts, got %.2f", stats.TotalDiscounts)
|
||||
}
|
||||
if stats.BookingCount != 0 {
|
||||
t.Errorf("expected 0 booking count, got %d", stats.BookingCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCampaignStats_NotFound(t *testing.T) {
|
||||
resetTestData(t)
|
||||
adminID, err := fixtures.CreateTestAdminUser(db.DB)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create admin: %v", err)
|
||||
}
|
||||
testAdminID = adminID
|
||||
defer func() { testAdminID = "" }()
|
||||
|
||||
handler := http.HandlerFunc(GetCampaignStats)
|
||||
w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns/nonexistent-id/stats", nil)
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("expected 404 for nonexistent campaign, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user