154 lines
6.6 KiB
Go

package threshold_activity
import (
"context"
"testing"
"time"
)
func TestCountEffectiveInvites_OnlyCountsInvitesCreatedAfterActivityStart(t *testing.T) {
svc, _, db := newThresholdTestService(t)
ctx := context.Background()
now := time.Now()
activityStart := now.Add(-2 * time.Hour)
periodStart, periodEnd, _ := periodRange(TypeDaily, now)
mustExec(t, db, `INSERT INTO user_invites (inviter_id, invitee_id, invite_code, created_at, updated_at) VALUES (1, 2, 'INV1', ?, ?)`, activityStart.Add(-time.Hour), activityStart.Add(-time.Hour))
mustExec(t, db, `INSERT INTO user_invites (inviter_id, invitee_id, invite_code, created_at, updated_at) VALUES (1, 3, 'INV1', ?, ?)`, activityStart.Add(10*time.Minute), activityStart.Add(10*time.Minute))
mustExec(t, db, `INSERT INTO orders (user_id, order_no, source_type, actual_amount, total_amount, status, paid_at, created_at, updated_at, cancelled_at) VALUES (2, 'OLD_INV', 1, 1200, 1200, 2, ?, ?, ?, ?)`, now, now, now, time.Unix(0, 0))
mustExec(t, db, `INSERT INTO orders (user_id, order_no, source_type, actual_amount, total_amount, status, paid_at, created_at, updated_at, cancelled_at) VALUES (3, 'NEW_INV', 1, 1500, 1500, 2, ?, ?, ?, ?)`, now, now, now, time.Unix(0, 0))
count, err := svc.countEffectiveInvites(ctx, 1, activityStart, periodStart, periodEnd, 1000)
if err != nil {
t.Fatalf("countEffectiveInvites failed: %v", err)
}
if count != 1 {
t.Fatalf("expected 1 effective invite after activity start, got %d", count)
}
}
func TestJoin_SpendOnlyQualifiedWritesParticipant(t *testing.T) {
svc, _, db := newThresholdTestService(t)
ctx := context.Background()
now := time.Now()
activity := Activity{
ID: 1,
Title: "消费门槛",
Type: TypeDaily,
QualificationMode: QualificationModeSpendOnly,
SpendThresholdAmount: 1000,
MinParticipants: 1,
StartTime: now.Add(-time.Hour),
EndTime: now.Add(24 * time.Hour),
DrawTime: now.Add(25 * time.Hour),
Status: StatusActive,
}
mustInsertActivity(t, db, activity)
mustExec(t, db, `INSERT INTO orders (user_id, order_no, source_type, actual_amount, total_amount, status, paid_at, created_at, updated_at, cancelled_at) VALUES (10, 'SPEND_OK', 1, 1500, 1500, 2, ?, ?, ?, ?)`, now, now, now, time.Unix(0, 0))
if err := svc.Join(ctx, activity.ID, 10); err != nil {
t.Fatalf("Join failed: %v", err)
}
var participant Participant
if err := db.Where("activity_id = ? AND user_id = ?", activity.ID, 10).First(&participant).Error; err != nil {
t.Fatalf("query participant failed: %v", err)
}
if participant.QualificationSource != QualificationSourceSpend {
t.Fatalf("expected qualification source spend, got %s", participant.QualificationSource)
}
if participant.PaidAmountSnapshot != 1500 {
t.Fatalf("expected paid snapshot 1500, got %d", participant.PaidAmountSnapshot)
}
}
func TestJoin_InviteOnlyQualifiedAndDuplicateRejected(t *testing.T) {
svc, _, db := newThresholdTestService(t)
ctx := context.Background()
now := time.Now()
activity := Activity{
ID: 2,
Title: "邀请门槛",
Type: TypeDaily,
QualificationMode: QualificationModeInviteOnly,
InviteThresholdCount: 1,
InviteEffectiveAmount: 1000,
MinParticipants: 1,
StartTime: now.Add(-time.Hour),
EndTime: now.Add(24 * time.Hour),
DrawTime: now.Add(25 * time.Hour),
Status: StatusActive,
}
mustInsertActivity(t, db, activity)
mustExec(t, db, `INSERT INTO user_invites (inviter_id, invitee_id, invite_code, created_at, updated_at) VALUES (20, 21, 'INV20', ?, ?)`, now, now)
mustExec(t, db, `INSERT INTO orders (user_id, order_no, source_type, actual_amount, total_amount, status, paid_at, created_at, updated_at, cancelled_at) VALUES (21, 'INVITE_OK', 1, 1200, 1200, 2, ?, ?, ?, ?)`, now, now, now, time.Unix(0, 0))
if err := svc.Join(ctx, activity.ID, 20); err != nil {
t.Fatalf("Join failed: %v", err)
}
var participant Participant
if err := db.Where("activity_id = ? AND user_id = ?", activity.ID, 20).First(&participant).Error; err != nil {
t.Fatalf("query participant failed: %v", err)
}
if participant.QualificationSource != QualificationSourceInvite {
t.Fatalf("expected qualification source invite, got %s", participant.QualificationSource)
}
if participant.EffectiveInviteCountSnapshot != 1 {
t.Fatalf("expected invite snapshot 1, got %d", participant.EffectiveInviteCountSnapshot)
}
if err := svc.Join(ctx, activity.ID, 20); err == nil {
t.Fatalf("expected duplicate join to fail")
}
var total int64
if err := db.Model(&Participant{}).Where("activity_id = ? AND user_id = ?", activity.ID, 20).Count(&total).Error; err != nil {
t.Fatalf("count participant failed: %v", err)
}
if total != 1 {
t.Fatalf("expected only 1 participant row after duplicate join, got %d", total)
}
}
func TestListJoinableActivitiesForUser_ReflectsJoinedState(t *testing.T) {
svc, _, db := newThresholdTestService(t)
ctx := context.Background()
now := time.Now()
activity := Activity{
ID: 3,
Title: "任一达标",
Type: TypeDaily,
QualificationMode: QualificationModeEither,
SpendThresholdAmount: 1000,
MinParticipants: 1,
StartTime: now.Add(-time.Hour),
EndTime: now.Add(24 * time.Hour),
DrawTime: now.Add(25 * time.Hour),
Status: StatusActive,
}
mustInsertActivity(t, db, activity)
mustExec(t, db, `INSERT INTO orders (user_id, order_no, source_type, actual_amount, total_amount, status, paid_at, created_at, updated_at, cancelled_at) VALUES (30, 'EITHER_OK', 1, 1000, 1000, 2, ?, ?, ?, ?)`, now, now, now, time.Unix(0, 0))
items, err := svc.ListJoinableActivitiesForUser(ctx, 30)
if err != nil {
t.Fatalf("ListJoinableActivitiesForUser failed: %v", err)
}
if len(items) != 1 {
t.Fatalf("expected 1 activity, got %d", len(items))
}
if !items[0].CanJoin || items[0].Joined {
t.Fatalf("expected activity to be joinable before join, got canJoin=%v joined=%v", items[0].CanJoin, items[0].Joined)
}
if err := svc.Join(ctx, activity.ID, 30); err != nil {
t.Fatalf("Join failed: %v", err)
}
items, err = svc.ListJoinableActivitiesForUser(ctx, 30)
if err != nil {
t.Fatalf("ListJoinableActivitiesForUser after join failed: %v", err)
}
if len(items) != 1 {
t.Fatalf("expected 1 activity after join, got %d", len(items))
}
if items[0].CanJoin || !items[0].Joined {
t.Fatalf("expected activity to be joined and not joinable, got canJoin=%v joined=%v", items[0].CanJoin, items[0].Joined)
}
}