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()) } }