135 lines
5.1 KiB
Go
135 lines
5.1 KiB
Go
package tasks
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.118im.com/im_team/olons_server/internal/store"
|
|
)
|
|
|
|
type fakeRepository struct {
|
|
config json.RawMessage
|
|
eligibility *store.TaskEligibility
|
|
order *store.TaskOrder
|
|
claimed store.ClaimTaskInput
|
|
submitted store.TaskSubmissionInput
|
|
}
|
|
|
|
func (f *fakeRepository) CurrentSiteConfig(context.Context, int64) (json.RawMessage, error) {
|
|
return f.config, nil
|
|
}
|
|
func (f *fakeRepository) TaskHalls(context.Context, int64, int64) ([]store.TaskHall, error) {
|
|
return []store.TaskHall{{ID: 1, Code: "DEFAULT", Name: "任务大厅"}}, nil
|
|
}
|
|
func (f *fakeRepository) TaskEligibility(context.Context, int64, int64, int64) (*store.TaskEligibility, error) {
|
|
return f.eligibility, nil
|
|
}
|
|
func (f *fakeRepository) ClaimTask(_ context.Context, input store.ClaimTaskInput) (*store.TaskOrder, error) {
|
|
f.claimed = input
|
|
return &store.TaskOrder{ID: 9, OrderNo: input.OrderNo, OrderStatus: 1, Currency: "CNY",
|
|
DeadlineAt: input.DeadlineAt.Format(time.RFC3339)}, nil
|
|
}
|
|
func (f *fakeRepository) AutoMatchTaskOrder(context.Context, int64, int64, int64, int, int64, int64) error {
|
|
if f.order != nil && f.order.OrderStatus == 1 {
|
|
f.order.OrderStatus = 2
|
|
f.order.ProductID = 7
|
|
f.order.ProductName = "自动匹配商品"
|
|
f.order.Quantity = 1
|
|
f.order.TotalAmount = 10000
|
|
f.order.CommissionAmount = 100
|
|
}
|
|
return nil
|
|
}
|
|
func (f *fakeRepository) TaskOrders(context.Context, int64, int64, int, int, int) (*store.TaskOrderPage, error) {
|
|
return &store.TaskOrderPage{Items: []store.TaskOrder{*f.order}, Total: 1}, nil
|
|
}
|
|
func (f *fakeRepository) TaskOrder(context.Context, int64, int64, int64) (*store.TaskOrder, error) {
|
|
return f.order, nil
|
|
}
|
|
func (f *fakeRepository) TaskStats(context.Context, int64, int64) (*store.TaskStats, error) {
|
|
return &store.TaskStats{AvailableAmount: 100, FrozenAmount: 25}, nil
|
|
}
|
|
func (f *fakeRepository) SubmitTask(_ context.Context, input store.TaskSubmissionInput) (*store.TaskSubmissionResult, error) {
|
|
f.submitted = input
|
|
return &store.TaskSubmissionResult{SubmissionNo: input.SubmissionNo, OrderStatus: 4,
|
|
SubmittedAt: time.Date(2026, 10, 9, 2, 0, 0, 0, time.UTC)}, nil
|
|
}
|
|
func (f *fakeRepository) OrderLedgers(context.Context, int64, int64, []string) ([]map[string]any, error) {
|
|
return []map[string]any{}, nil
|
|
}
|
|
|
|
func TestEligibilityAppliesPublishedPolicy(t *testing.T) {
|
|
repository := &fakeRepository{config: json.RawMessage(`{"features":{"task_enabled":true},"task_minimum_amount":50000,"task_business_start_at":"09:00","task_business_end_at":"22:00"}`),
|
|
eligibility: &store.TaskEligibility{MemberStatus: 1, HallEligible: true, AvailableAmount: 49999, WalletStatus: 1, DailyLimit: 5, TodayCount: 2}}
|
|
service := New(repository)
|
|
service.now = func() time.Time { return time.Date(2026, 10, 9, 10, 0, 0, 0, shanghai) }
|
|
|
|
result, err := service.Eligibility(context.Background(), 0, 10, 1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Eligible || result.Reason != "INSUFFICIENT_BALANCE" || result.MinimumAmount != 50000 {
|
|
t.Fatalf("unexpected eligibility: %#v", result)
|
|
}
|
|
}
|
|
|
|
func TestClaimFailsClosedWithoutTaskSwitch(t *testing.T) {
|
|
repository := &fakeRepository{config: json.RawMessage(`{}`)}
|
|
service := New(repository)
|
|
|
|
_, err := service.Claim(context.Background(), 0, 10, 1)
|
|
if !errors.Is(err, ErrTaskDisabled) {
|
|
t.Fatalf("expected task disabled, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClaimBuildsMatchingOrder(t *testing.T) {
|
|
repository := &fakeRepository{config: json.RawMessage(`{"task_enabled":true,"task_deadline_minutes":20}`)}
|
|
service := New(repository)
|
|
now := time.Date(2026, 10, 9, 2, 0, 0, 0, time.UTC)
|
|
service.now = func() time.Time { return now }
|
|
|
|
result, err := service.Claim(context.Background(), 0, 10, 2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Status != "matching" || repository.claimed.HallID != 2 ||
|
|
!repository.claimed.DeadlineAt.Equal(now.Add(20*time.Minute)) {
|
|
t.Fatalf("unexpected claim: result=%#v input=%#v", result, repository.claimed)
|
|
}
|
|
}
|
|
|
|
func TestSubmitAutomaticallySettlesWithoutEvidence(t *testing.T) {
|
|
repository := &fakeRepository{}
|
|
service := New(repository)
|
|
service.now = func() time.Time { return time.Date(2026, 10, 9, 2, 0, 0, 0, time.UTC) }
|
|
|
|
result, err := service.Submit(context.Background(), 0, 10, 9, SubmitInput{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Status != "settled" || result.OrderStatus != "completed" || repository.submitted.IdempotencyKey != "task:9:auto-settle" || repository.submitted.LedgerNo == "" {
|
|
t.Fatalf("unexpected submission: result=%#v input=%#v", result, repository.submitted)
|
|
}
|
|
}
|
|
|
|
func TestSubmitStillValidatesOptionalEvidence(t *testing.T) {
|
|
service := New(&fakeRepository{})
|
|
tooLong := strings.Repeat("字", 2001)
|
|
if _, err := service.Submit(context.Background(), 0, 10, 9, SubmitInput{EvidenceText: tooLong}); !errors.Is(err, ErrEvidenceInvalid) {
|
|
t.Fatalf("expected evidence error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOrdersRejectsUnknownStatus(t *testing.T) {
|
|
service := New(&fakeRepository{})
|
|
_, err := service.Orders(context.Background(), 0, 10, 1, 20, "not-a-status")
|
|
if !errors.Is(err, ErrStatusInvalid) {
|
|
t.Fatalf("expected status error, got %v", err)
|
|
}
|
|
}
|