package service import ( "context" "errors" "testing" "time" "github.com/topfans/backend/services/statisticService/model" "github.com/topfans/backend/services/statisticService/sink" ) // mockSink 记录所有 submit 的事件 type mockSink struct { events []*model.Event err error } func (m *mockSink) Submit(ctx context.Context, e *model.Event) error { if m.err != nil { return m.err } m.events = append(m.events, e) return nil } func (m *mockSink) SubmitBatch(ctx context.Context, es []*model.Event) error { if m.err != nil { return m.err } m.events = append(m.events, es...) return nil } func (m *mockSink) Close() error { return nil } func TestEventService_TrackEvent_Success(t *testing.T) { ms := &mockSink{} svc := NewEventService(ms, []string{"asset.like", "asset.mint"}) resp, err := svc.TrackEvent(context.Background(), &model.Event{ EventID: "u1", UserID: 1, StarID: 1, EventType: "asset.like", OccurredAt: time.Now(), Properties: map[string]string{}, }) if err != nil { t.Fatal(err) } if resp.Accepted != 1 || resp.Rejected != 0 { t.Fatalf("got accepted=%d rejected=%d, want 1/0", resp.Accepted, resp.Rejected) } if len(ms.events) != 1 { t.Fatal("event not submitted") } if ms.events[0].ReceivedAt.IsZero() { t.Fatal("ReceivedAt should be set by service") } } func TestEventService_TrackEvent_InvalidEventType(t *testing.T) { ms := &mockSink{} svc := NewEventService(ms, []string{"asset.like"}) _, err := svc.TrackEvent(context.Background(), &model.Event{EventID: "u1", EventType: "evil.type"}) if err == nil { t.Fatal("expected error for invalid event type") } if !errors.Is(err, ErrInvalidEventType) { t.Fatalf("expected ErrInvalidEventType, got %v", err) } } func TestEventService_TrackEvent_EmptyEventID(t *testing.T) { ms := &mockSink{} svc := NewEventService(ms, []string{"asset.like"}) _, err := svc.TrackEvent(context.Background(), &model.Event{EventType: "asset.like"}) if !errors.Is(err, ErrInvalidEventID) { t.Fatalf("expected ErrInvalidEventID, got %v", err) } } func TestEventService_TrackEvent_PropertiesTooLarge(t *testing.T) { ms := &mockSink{} svc := NewEventService(ms, []string{"asset.like"}) big := make(map[string]string) for i := 0; i < 200; i++ { big["k"+string(rune(i))] = string(make([]byte, 100)) } _, err := svc.TrackEvent(context.Background(), &model.Event{ EventID: "u1", EventType: "asset.like", Properties: big, }) if !errors.Is(err, ErrPropertiesTooLarge) { t.Fatalf("expected ErrPropertiesTooLarge, got %v", err) } } func TestEventService_TrackEvent_SinkError(t *testing.T) { ms := &mockSink{err: sink.ErrChannelFull} svc := NewEventService(ms, []string{"asset.like"}) resp, err := svc.TrackEvent(context.Background(), &model.Event{ EventID: "u1", EventType: "asset.like", }) if err != nil { t.Fatal("expected no error (degraded to rejected), got", err) } if resp.Accepted != 0 || resp.Rejected != 1 { t.Fatalf("expected 0/1, got %d/%d", resp.Accepted, resp.Rejected) } } func TestEventService_BatchTrackEvent_Mixed(t *testing.T) { ms := &mockSink{} svc := NewEventService(ms, []string{"asset.like", "asset.mint"}) events := []*model.Event{ {EventID: "1", EventType: "asset.like", Properties: map[string]string{}}, {EventID: "2", EventType: "asset.mint", Properties: map[string]string{}}, {EventID: "3", EventType: "evil.type"}, // 拒绝 {EventID: "", EventType: "asset.like"}, // 拒绝 } resp, err := svc.BatchTrackEvent(context.Background(), events) if err != nil { t.Fatal(err) } if resp.Accepted != 2 || resp.Rejected != 2 { t.Fatalf("got %d/%d, want 2/2", resp.Accepted, resp.Rejected) } }