243 lines
9.7 KiB
Go
243 lines
9.7 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"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 setupThresholdAppTestRouter(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,
|
|
UNIQUE(activity_id, user_id, period_key)
|
|
)`,
|
|
`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
|
|
)`,
|
|
`CREATE TABLE user_invites (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
inviter_id INTEGER NOT NULL,
|
|
invitee_id INTEGER NOT NULL,
|
|
invite_code TEXT NOT NULL DEFAULT '',
|
|
reward_points INTEGER NOT NULL DEFAULT 0,
|
|
rewarded_at DATETIME,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
deleted_at DATETIME,
|
|
is_effective BOOLEAN NOT NULL DEFAULT 0,
|
|
accumulated_amount INTEGER NOT NULL DEFAULT 0
|
|
)`,
|
|
`CREATE TABLE orders (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
user_id INTEGER NOT NULL,
|
|
order_no TEXT NOT NULL,
|
|
source_type INTEGER NOT NULL DEFAULT 1,
|
|
total_amount INTEGER NOT NULL DEFAULT 0,
|
|
discount_amount INTEGER NOT NULL DEFAULT 0,
|
|
points_amount INTEGER NOT NULL DEFAULT 0,
|
|
actual_amount INTEGER NOT NULL DEFAULT 0,
|
|
status INTEGER NOT NULL DEFAULT 1,
|
|
pay_preorder_id INTEGER NOT NULL DEFAULT 0,
|
|
paid_at DATETIME,
|
|
cancelled_at DATETIME,
|
|
user_address_id INTEGER NOT NULL DEFAULT 0,
|
|
is_consumed INTEGER NOT NULL DEFAULT 0,
|
|
points_ledger_id INTEGER NOT NULL DEFAULT 0,
|
|
coupon_id INTEGER NOT NULL DEFAULT 0,
|
|
item_card_id INTEGER NOT NULL DEFAULT 0,
|
|
remark TEXT,
|
|
ext_order_id TEXT NOT NULL DEFAULT ''
|
|
)`,
|
|
}
|
|
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)
|
|
}
|
|
h := New(lg, repo, nil, nil)
|
|
public := mux.Group("/api/app")
|
|
public.GET("/threshold-activities", h.ListThresholdActivities())
|
|
public.GET("/threshold-activities/:id", h.GetThresholdActivity())
|
|
dummyAuth := func(ctx core.Context) (proposal.SessionUserInfo, core.BusinessError) {
|
|
return proposal.SessionUserInfo{Id: 88}, nil
|
|
}
|
|
auth := mux.Group("/api/app", core.WrapAuthHandler(dummyAuth))
|
|
auth.GET("/threshold-activities/:id/my", h.GetThresholdActivity())
|
|
auth.POST("/threshold-activities/:id/join", h.JoinThresholdActivity())
|
|
return repo, mux
|
|
}
|
|
|
|
func TestThresholdAppListAndGetMy(t *testing.T) {
|
|
repo, mux := setupThresholdAppTestRouter(t)
|
|
now := time.Now()
|
|
if err := repo.GetDbW().Exec(`INSERT INTO threshold_activities (id, title, type, qualification_mode, spend_threshold_amount, invite_threshold_count, invite_effective_amount, min_participants, start_time, end_time, draw_time, status, description, cover_image, draw_batch, abort_reason, created_at, updated_at) VALUES (1, 'App活动', 'daily', 'spend_only', 1000, 0, 0, 1, ?, ?, ?, 'active', 'desc', '', '', '', ?, ?)`, now.Add(-time.Hour), now.Add(24*time.Hour), now.Add(25*time.Hour), now, now).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repo.GetDbW().Exec(`INSERT INTO orders (user_id, order_no, source_type, actual_amount, total_amount, status, paid_at, created_at, updated_at, cancelled_at) VALUES (88, 'APP_OK', 1, 1200, 1200, 2, ?, ?, ?, ?)`, now, now, now, time.Unix(0, 0)).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rr := httptest.NewRecorder()
|
|
req, _ := http.NewRequest(http.MethodGet, "/api/app/threshold-activities?page=1&page_size=20", 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 app activity, got total=%d len=%d", listed.Total, len(listed.List))
|
|
}
|
|
|
|
rr = httptest.NewRecorder()
|
|
req, _ = http.NewRequest(http.MethodGet, "/api/app/threshold-activities/1/my", bytes.NewReader([]byte{}))
|
|
mux.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("get my 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["can_join"] != true {
|
|
t.Fatalf("expected can_join=true, got %v", detail["can_join"])
|
|
}
|
|
progress := detail["qualification_progress"].(map[string]any)
|
|
if int(progress["current_paid"].(float64)) != 1200 {
|
|
t.Fatalf("expected current_paid=1200, got %v", progress["current_paid"])
|
|
}
|
|
}
|
|
|
|
func TestThresholdAppJoinSuccessAndRejectUnqualified(t *testing.T) {
|
|
repo, mux := setupThresholdAppTestRouter(t)
|
|
now := time.Now()
|
|
if err := repo.GetDbW().Exec(`INSERT INTO threshold_activities (id, title, type, qualification_mode, spend_threshold_amount, invite_threshold_count, invite_effective_amount, min_participants, start_time, end_time, draw_time, status, description, cover_image, draw_batch, abort_reason, created_at, updated_at) VALUES (2, 'Join活动', 'daily', 'spend_only', 1000, 0, 0, 1, ?, ?, ?, 'active', 'desc', '', '', '', ?, ?)`, now.Add(-time.Hour), now.Add(24*time.Hour), now.Add(25*time.Hour), now, now).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repo.GetDbW().Exec(`INSERT INTO orders (user_id, order_no, source_type, actual_amount, total_amount, status, paid_at, created_at, updated_at, cancelled_at) VALUES (88, 'JOIN_OK', 1, 1500, 1500, 2, ?, ?, ?, ?)`, now, now, now, time.Unix(0, 0)).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rr := httptest.NewRecorder()
|
|
req, _ := http.NewRequest(http.MethodPost, "/api/app/threshold-activities/2/join", bytes.NewReader([]byte{}))
|
|
mux.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("join success code=%d body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
var count int64
|
|
if err := repo.GetDbW().Table("threshold_activity_participants").Where("activity_id = ? AND user_id = ?", 2, 88).Count(&count).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if count != 1 {
|
|
t.Fatalf("expected 1 participant after join, got %d", count)
|
|
}
|
|
|
|
if err := repo.GetDbW().Exec(`INSERT INTO threshold_activities (id, title, type, qualification_mode, spend_threshold_amount, invite_threshold_count, invite_effective_amount, min_participants, start_time, end_time, draw_time, status, description, cover_image, draw_batch, abort_reason, created_at, updated_at) VALUES (3, 'Join失败活动', 'daily', 'spend_only', 5000, 0, 0, 1, ?, ?, ?, 'active', 'desc', '', '', '', ?, ?)`, now.Add(-time.Hour), now.Add(24*time.Hour), now.Add(25*time.Hour), now, now).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rr = httptest.NewRecorder()
|
|
req, _ = http.NewRequest(http.MethodPost, "/api/app/threshold-activities/3/join", bytes.NewReader([]byte{}))
|
|
mux.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400 for unqualified join, got %d body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
}
|