package orderLogic import ( "context" "sync" "testing" lotteryhook "github.com/perfect-panel/server/internal/logic/lottery/hook" "github.com/perfect-panel/server/internal/model/order" "github.com/perfect-panel/server/internal/svc" ) // recordingInviteHook 记录每次 OnConversion 调用,让测试直接断言时序与参数。 type recordingInviteHook struct { mu sync.Mutex calls []recordingInviteCall } type recordingInviteCall struct { refererID int64 orderNo string } func (h *recordingInviteHook) OnConversion(_ context.Context, refererID int64, orderNo string) { h.mu.Lock() defer h.mu.Unlock() h.calls = append(h.calls, recordingInviteCall{refererID: refererID, orderNo: orderNo}) } // verify recordingInviteHook satisfies the interface at compile time. var _ lotteryhook.InviteHook = (*recordingInviteHook)(nil) func (h *recordingInviteHook) snapshot() []recordingInviteCall { h.mu.Lock() defer h.mu.Unlock() out := make([]recordingInviteCall, len(h.calls)) copy(out, h.calls) return out } func newLotteryHookTestLogic(hook lotteryhook.InviteHook) *ActivateOrderLogic { return NewActivateOrderLogic(&svc.ServiceContext{LotteryInviteHook: hook}) } // TestInvokeInviteHook_FiresOnFirstPurchase 保证新购激活会触发抽奖钩子。 // Positive-path regression matched against the "首次付款激活" decision. func TestInvokeInviteHook_FiresOnFirstPurchase(t *testing.T) { hook := &recordingInviteHook{} logic := newLotteryHookTestLogic(hook) logic.invokeInviteHookIfEligible(context.Background(), 42, &order.Order{ OrderNo: "ORD-NEW-1", IsNew: true, }) calls := hook.snapshot() if len(calls) != 1 { t.Fatalf("expected exactly 1 OnConversion call on first purchase, got %+v", calls) } if calls[0].refererID != 42 || calls[0].orderNo != "ORD-NEW-1" { t.Fatalf("wrong args: %+v", calls[0]) } } // TestInvokeInviteHook_DoesNotFireOnRenewal 是架构师 R1 打回的关键回归: // 续费付款必须 NOT 触发抽奖钩子,否则 referer 每月拿一次白嫖机会,与"首次 // 付款激活"决策相悖。 func TestInvokeInviteHook_DoesNotFireOnRenewal(t *testing.T) { hook := &recordingInviteHook{} logic := newLotteryHookTestLogic(hook) logic.invokeInviteHookIfEligible(context.Background(), 42, &order.Order{ OrderNo: "ORD-RENEWAL-1", IsNew: false, }) if calls := hook.snapshot(); len(calls) != 0 { t.Fatalf("renewal must NOT trigger invite hook; got %+v", calls) } } // TestInvokeInviteHook_NilOrderIsSafe 保证空 order 输入不 panic(防御性)。 func TestInvokeInviteHook_NilOrderIsSafe(t *testing.T) { hook := &recordingInviteHook{} logic := newLotteryHookTestLogic(hook) logic.invokeInviteHookIfEligible(context.Background(), 42, nil) if calls := hook.snapshot(); len(calls) != 0 { t.Fatalf("nil order must be a no-op; got %+v", calls) } } // TestInvokeInviteHook_NilHookIsSafe 保证 ServiceContext 里没接线时不 panic。 func TestInvokeInviteHook_NilHookIsSafe(t *testing.T) { logic := NewActivateOrderLogic(&svc.ServiceContext{}) // no LotteryInviteHook logic.invokeInviteHookIfEligible(context.Background(), 42, &order.Order{ OrderNo: "ORD-1", IsNew: true, }) // If we got here without panicking, we pass. }