2026-06-18 14:19:47 +08:00

196 lines
7.6 KiB
Go

package agencyopening
import (
"context"
"testing"
"time"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
roomv1 "hyapp.local/api/proto/room/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/appcode"
domain "hyapp/services/activity-service/internal/domain/agencyopening"
)
func TestEligibleRequiresHostCountGreaterThanConfigured(t *testing.T) {
cycle := domain.Cycle{MinHostCount: 10}
agency := domain.AgencySnapshot{Status: "active", HostCount: 10}
if ok, reason := eligible(cycle, agency, 1000); ok || reason != "host_count_not_enough" {
t.Fatalf("host_count equal to threshold must be rejected, got ok=%v reason=%q", ok, reason)
}
agency.HostCount = 11
if ok, reason := eligible(cycle, agency, 1000); !ok || reason != "" {
t.Fatalf("host_count above threshold must be eligible, got ok=%v reason=%q", ok, reason)
}
}
func TestGetStatusRejectsOwnerAlreadyAppliedInAnyCycle(t *testing.T) {
repo := &agencyOpeningFakeRepo{
currentCycle: domain.Cycle{CycleID: "current", Status: domain.StatusActive, StartMS: 1, EndMS: 10000, MinHostCount: 10},
hasCurrent: true,
application: domain.Application{
ApplicationID: "old-application",
CycleID: "previous",
AgencyOwnerUserID: 42,
Status: domain.ApplicationStatusApplied,
},
hasApplication: true,
}
svc := New(repo, nil, agencyOpeningFakeAgencySource{
snapshot: domain.AgencySnapshot{AgencyID: 7, OwnerUserID: 42, Status: "active", HostCount: 11},
ok: true,
}, nil)
svc.now = func() time.Time { return time.UnixMilli(5000).UTC() }
status, err := svc.GetStatus(appcode.WithContext(context.Background(), "lalu"), 42)
if err != nil {
t.Fatalf("GetStatus returned error: %v", err)
}
if !status.Joined || status.Eligible || status.IneligibleReason != "already_applied" {
t.Fatalf("expected already-applied owner to be blocked, got joined=%v eligible=%v reason=%q", status.Joined, status.Eligible, status.IneligibleReason)
}
if repo.ownerLookup != 42 {
t.Fatalf("expected repository lookup by owner_user_id, got %d", repo.ownerLookup)
}
}
func TestGetStatusRequiresAgencyOwner(t *testing.T) {
repo := &agencyOpeningFakeRepo{
currentCycle: domain.Cycle{CycleID: "current", Status: domain.StatusActive, StartMS: 1, EndMS: 10000, MinHostCount: 10},
hasCurrent: true,
}
svc := New(repo, nil, agencyOpeningFakeAgencySource{ok: false}, nil)
svc.now = func() time.Time { return time.UnixMilli(5000).UTC() }
status, err := svc.GetStatus(appcode.WithContext(context.Background(), "lalu"), 42)
if err != nil {
t.Fatalf("GetStatus returned error: %v", err)
}
if status.Joined || status.Eligible || status.IneligibleReason != "active_agency_owner_required" {
t.Fatalf("expected non-agency user to be blocked, got joined=%v eligible=%v reason=%q", status.Joined, status.Eligible, status.IneligibleReason)
}
}
func TestHandleRoomEventScoresApplicantRoomOwner(t *testing.T) {
repo := &agencyOpeningFakeRepo{consumeStatus: domain.EventStatusConsumed}
room := &agencyOpeningFakeRoomClient{ownerUserID: 42}
svc := New(repo, nil, nil, room)
svc.now = func() time.Time { return time.UnixMilli(8000).UTC() }
body, err := proto.Marshal(&roomeventsv1.RoomGiftSent{
SenderUserId: 9,
TargetUserId: 88,
GiftValue: 900,
CoinSpent: 1200,
})
if err != nil {
t.Fatalf("marshal gift: %v", err)
}
count, err := svc.HandleRoomEvent(context.Background(), &roomeventsv1.EventEnvelope{
EventId: "gift-event-1",
RoomId: "room-1",
EventType: "RoomGiftSent",
OccurredAtMs: 7000,
AppCode: "lalu",
Body: body,
})
if err != nil {
t.Fatalf("HandleRoomEvent returned error: %v", err)
}
if count != 1 {
t.Fatalf("expected one consumed event, got %d", count)
}
if room.roomID != "room-1" {
t.Fatalf("expected room lookup by event room_id, got %q", room.roomID)
}
if repo.consumed.RoomOwnerUserID != 42 || repo.consumed.TargetUserID != 88 || repo.consumed.CoinSpent != 900 {
t.Fatalf("unexpected consumed event: %+v", repo.consumed)
}
}
type agencyOpeningFakeRepo struct {
currentCycle domain.Cycle
hasCurrent bool
application domain.Application
hasApplication bool
ownerLookup int64
consumeStatus string
consumed domain.GiftEvent
}
func (r *agencyOpeningFakeRepo) ListAgencyOpeningCycles(context.Context, domain.ListQuery) ([]domain.Cycle, int64, error) {
return nil, 0, nil
}
func (r *agencyOpeningFakeRepo) GetAgencyOpeningCycle(context.Context, string) (domain.Cycle, error) {
return domain.Cycle{}, nil
}
func (r *agencyOpeningFakeRepo) UpsertAgencyOpeningCycle(context.Context, domain.CycleCommand, int64) (domain.Cycle, bool, error) {
return domain.Cycle{}, false, nil
}
func (r *agencyOpeningFakeRepo) SetAgencyOpeningCycleStatus(context.Context, string, string, int64, int64) (domain.Cycle, error) {
return domain.Cycle{}, nil
}
func (r *agencyOpeningFakeRepo) FindCurrentAgencyOpeningCycle(context.Context, int64) (domain.Cycle, bool, error) {
return r.currentCycle, r.hasCurrent, nil
}
func (r *agencyOpeningFakeRepo) GetAgencyOpeningApplicationByOwner(_ context.Context, ownerUserID int64) (domain.Application, bool, error) {
r.ownerLookup = ownerUserID
return r.application, r.hasApplication, nil
}
func (r *agencyOpeningFakeRepo) CreateAgencyOpeningApplication(context.Context, domain.Cycle, domain.AgencySnapshot, int64) (domain.Application, bool, error) {
return domain.Application{}, false, nil
}
func (r *agencyOpeningFakeRepo) ApproveAgencyOpeningApplication(context.Context, string, int64, int64) (domain.Application, error) {
return domain.Application{}, nil
}
func (r *agencyOpeningFakeRepo) ListAgencyOpeningApplications(context.Context, domain.ApplicationQuery) ([]domain.Application, int64, error) {
return nil, 0, nil
}
func (r *agencyOpeningFakeRepo) ConsumeAgencyOpeningGiftEvent(_ context.Context, event domain.GiftEvent, _ int64) (domain.EventResult, error) {
r.consumed = event
return domain.EventResult{EventID: event.EventID, Status: r.consumeStatus}, nil
}
func (r *agencyOpeningFakeRepo) ClaimDueAgencyOpeningCycles(context.Context, string, int64, time.Duration, int32) ([]domain.Cycle, error) {
return nil, nil
}
func (r *agencyOpeningFakeRepo) PrepareAgencyOpeningSettlements(context.Context, domain.Cycle, int64) ([]domain.Application, error) {
return nil, nil
}
func (r *agencyOpeningFakeRepo) MarkAgencyOpeningApplicationGranted(context.Context, string, string, int64) (domain.Application, error) {
return domain.Application{}, nil
}
func (r *agencyOpeningFakeRepo) MarkAgencyOpeningApplicationFailed(context.Context, string, string, int64) error {
return nil
}
func (r *agencyOpeningFakeRepo) FinishAgencyOpeningCycleIfComplete(context.Context, string, int64) (bool, error) {
return false, nil
}
type agencyOpeningFakeAgencySource struct {
snapshot domain.AgencySnapshot
ok bool
}
func (s agencyOpeningFakeAgencySource) ResolveOwnerAgency(context.Context, int64) (domain.AgencySnapshot, bool, error) {
return s.snapshot, s.ok, nil
}
type agencyOpeningFakeRoomClient struct {
ownerUserID int64
roomID string
}
func (c *agencyOpeningFakeRoomClient) AdminGetRoom(_ context.Context, req *roomv1.AdminGetRoomRequest, _ ...grpc.CallOption) (*roomv1.AdminGetRoomResponse, error) {
c.roomID = req.GetRoomId()
return &roomv1.AdminGetRoomResponse{
Room: &roomv1.AdminRoomListItem{OwnerUserId: c.ownerUserID},
}, nil
}
type agencyOpeningFakeWalletClient struct{}
func (agencyOpeningFakeWalletClient) CreditAgencyOpeningReward(context.Context, *walletv1.CreditAgencyOpeningRewardRequest, ...grpc.CallOption) (*walletv1.CreditAgencyOpeningRewardResponse, error) {
return &walletv1.CreditAgencyOpeningRewardResponse{}, nil
}