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 }