209 lines
7.0 KiB
Go
209 lines
7.0 KiB
Go
package admin
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"bindbox-game/internal/pkg/core"
|
|
"bindbox-game/internal/pkg/logger"
|
|
"bindbox-game/internal/proposal"
|
|
"bindbox-game/internal/repository/mysql"
|
|
)
|
|
|
|
func setupThresholdAdminTestRouter(t *testing.T) (mysql.Repo, http.Handler) {
|
|
t.Helper()
|
|
repo, err := mysql.NewSQLiteRepoForTest()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ddls := []string{
|
|
`CREATE TABLE threshold_activities (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
title TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
qualification_mode TEXT NOT NULL,
|
|
spend_threshold_amount INTEGER NOT NULL DEFAULT 0,
|
|
invite_threshold_count INTEGER NOT NULL DEFAULT 0,
|
|
invite_effective_amount INTEGER NOT NULL DEFAULT 0,
|
|
min_participants INTEGER NOT NULL DEFAULT 1,
|
|
start_time DATETIME NOT NULL,
|
|
end_time DATETIME NOT NULL,
|
|
draw_time DATETIME NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
description TEXT,
|
|
cover_image TEXT NOT NULL DEFAULT '',
|
|
draw_batch TEXT NOT NULL DEFAULT '',
|
|
abort_reason TEXT NOT NULL DEFAULT '',
|
|
aborted_at DATETIME,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
deleted_at DATETIME
|
|
)`,
|
|
`CREATE TABLE threshold_activity_prizes (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
activity_id INTEGER NOT NULL,
|
|
reward_type TEXT NOT NULL,
|
|
reward_ref_id INTEGER NOT NULL,
|
|
reward_name_snapshot TEXT NOT NULL DEFAULT '',
|
|
reward_image_snapshot TEXT NOT NULL DEFAULT '',
|
|
reward_value_snapshot_cents INTEGER NOT NULL DEFAULT 0,
|
|
cost_snapshot_cents INTEGER NOT NULL DEFAULT 0,
|
|
quantity INTEGER NOT NULL DEFAULT 0,
|
|
remaining_quantity INTEGER NOT NULL DEFAULT 0,
|
|
sort INTEGER NOT NULL DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`,
|
|
`CREATE TABLE threshold_activity_participants (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
activity_id INTEGER NOT NULL,
|
|
user_id INTEGER NOT NULL,
|
|
period_key TEXT NOT NULL,
|
|
qualification_source TEXT NOT NULL DEFAULT '',
|
|
paid_amount_snapshot INTEGER NOT NULL DEFAULT 0,
|
|
effective_invite_count_snapshot INTEGER NOT NULL DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`,
|
|
`CREATE TABLE threshold_activity_winners (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
activity_id INTEGER NOT NULL,
|
|
prize_id INTEGER NOT NULL,
|
|
reward_type TEXT NOT NULL,
|
|
reward_ref_id INTEGER NOT NULL,
|
|
prize_name_snapshot TEXT NOT NULL DEFAULT '',
|
|
prize_image_snapshot TEXT NOT NULL DEFAULT '',
|
|
prize_value_snapshot_cents INTEGER NOT NULL DEFAULT 0,
|
|
user_id INTEGER NOT NULL,
|
|
grant_record_type TEXT NOT NULL DEFAULT '',
|
|
grant_record_id INTEGER NOT NULL DEFAULT 0,
|
|
cost_cents INTEGER NOT NULL DEFAULT 0,
|
|
draw_batch TEXT NOT NULL DEFAULT '',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`,
|
|
`CREATE TABLE users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
nickname TEXT NOT NULL,
|
|
avatar TEXT,
|
|
invite_code TEXT NOT NULL,
|
|
inviter_id INTEGER,
|
|
status INTEGER NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
deleted_at DATETIME
|
|
)`,
|
|
}
|
|
for _, ddl := range ddls {
|
|
if err := repo.GetDbW().Exec(ddl).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
lg, err := logger.NewCustomLogger(logger.WithOutputInConsole())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mux, err := core.New(lg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dummyAuth := func(ctx core.Context) (proposal.SessionUserInfo, core.BusinessError) {
|
|
return proposal.SessionUserInfo{Id: 1, IsSuper: 1}, nil
|
|
}
|
|
g := mux.Group("/api/admin", core.WrapAuthHandler(dummyAuth))
|
|
h := New(lg, repo, nil)
|
|
g.GET("/threshold-activities", h.ListThresholdActivities())
|
|
g.POST("/threshold-activities", h.CreateThresholdActivity())
|
|
g.GET("/threshold-activities/:id", h.GetThresholdActivity())
|
|
g.PUT("/threshold-activities/:id", h.UpdateThresholdActivity())
|
|
return repo, mux
|
|
}
|
|
|
|
func TestThresholdAdminCreateListAndGet(t *testing.T) {
|
|
_, mux := setupThresholdAdminTestRouter(t)
|
|
start := time.Now().Add(-time.Hour).Format("2006-01-02 15:04:05")
|
|
end := time.Now().Add(24 * time.Hour).Format("2006-01-02 15:04:05")
|
|
draw := time.Now().Add(25 * time.Hour).Format("2006-01-02 15:04:05")
|
|
body := map[string]any{
|
|
"title": "后台测试活动",
|
|
"type": "daily",
|
|
"qualification_mode": "invite_only",
|
|
"spend_threshold_amount": 0,
|
|
"invite_threshold_count": 2,
|
|
"invite_effective_amount": 1000,
|
|
"min_participants": 3,
|
|
"start_time": start,
|
|
"end_time": end,
|
|
"draw_time": draw,
|
|
"status": "active",
|
|
"description": "admin create",
|
|
"prizes": []any{},
|
|
}
|
|
payload, _ := json.Marshal(body)
|
|
rr := httptest.NewRecorder()
|
|
req, _ := http.NewRequest(http.MethodPost, "/api/admin/threshold-activities", bytes.NewReader(payload))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
mux.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("create code=%d body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
var created map[string]any
|
|
if err := json.Unmarshal(rr.Body.Bytes(), &created); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
id := int(created["id"].(float64))
|
|
if id <= 0 {
|
|
t.Fatalf("expected positive activity id, got %v", created["id"])
|
|
}
|
|
|
|
rr = httptest.NewRecorder()
|
|
req, _ = http.NewRequest(http.MethodGet, "/api/admin/threshold-activities?page=1&page_size=20&status=active", bytes.NewReader([]byte{}))
|
|
mux.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("list code=%d body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
var listed struct {
|
|
Total int64 `json:"total"`
|
|
List []map[string]any `json:"list"`
|
|
}
|
|
if err := json.Unmarshal(rr.Body.Bytes(), &listed); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if listed.Total != 1 || len(listed.List) != 1 {
|
|
t.Fatalf("expected 1 listed activity, got total=%d len=%d", listed.Total, len(listed.List))
|
|
}
|
|
|
|
rr = httptest.NewRecorder()
|
|
req, _ = http.NewRequest(http.MethodGet, fmt.Sprintf("/api/admin/threshold-activities/%d", id), bytes.NewReader([]byte{}))
|
|
mux.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("get code=%d body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
var detail map[string]any
|
|
if err := json.Unmarshal(rr.Body.Bytes(), &detail); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if detail["title"] != "后台测试活动" {
|
|
t.Fatalf("unexpected title: %v", detail["title"])
|
|
}
|
|
if detail["qualification_mode"] != "invite_only" {
|
|
t.Fatalf("unexpected qualification mode: %v", detail["qualification_mode"])
|
|
}
|
|
}
|
|
|
|
func TestThresholdAdminCreateRejectsInvalidPayload(t *testing.T) {
|
|
_, mux := setupThresholdAdminTestRouter(t)
|
|
body := []byte(`{"title":"bad","type":"daily","start_time":"2026-01-01 00:00:00","end_time":"2026-01-02 00:00:00","draw_time":"2026-01-03 00:00:00"}`)
|
|
rr := httptest.NewRecorder()
|
|
req, _ := http.NewRequest(http.MethodPost, "/api/admin/threshold-activities", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
mux.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400 for invalid payload, got %d body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
}
|