package api import ( "context" "errors" "git.ipao.vip/rogee/creator-hub/internal/creator" "testing" "time" ) type coverWriterStub struct { listenerWriterStub saved chan []creator.ListenerDelivery err error } func (s *coverWriterStub) UpdateListenerEventCovers(_ context.Context, _, _ string, items []creator.ListenerDelivery) error { s.saved <- items return s.err } type coverEnricherStub struct { listenerSessionStub entered chan struct{} release chan struct{} err error } func (s *coverEnricherStub) enrich(ctx context.Context, items []creator.ListenerDelivery) ([]creator.ListenerDelivery, error) { select { case s.entered <- struct{}{}: default: } select { case <-ctx.Done(): return nil, ctx.Err() case <-s.release: } for i := range items { items[i].Notice.WorkCoverError = "cached result" } return items, s.err } func coverDelivery() []creator.ListenerDelivery { return []creator.ListenerDelivery{{Kind: "notice", DeliveryID: "cover", Notice: &creator.ListenerNotice{EventKey: "cover", EventType: "like", WorkID: "9", WorkAuthorUID: "1", WorkCoverURL: "https://example.com/cover"}}} } func TestEventCoverWorkerDoesNotBlockReceptionAndReportsOverload(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() writer := &coverWriterStub{saved: make(chan []creator.ListenerDelivery, 64)} session := &coverEnricherStub{entered: make(chan struct{}, 1), release: make(chan struct{})} queue := startListenerCoverWorker(ctx, writer, "account", "generation", session) original := coverDelivery() queue(original)() select { case <-session.entered: case <-time.After(time.Second): t.Fatal("cover worker did not start") } for i := 0; i < 32; i++ { queue(coverDelivery())() } overloaded := coverDelivery() queue(overloaded)() if overloaded[0].Notice.WorkCoverError != coverQueueFullReason { t.Fatal("overload must be included in notification persistence") } select { case <-writer.saved: t.Fatal("overload performed a synchronous cover-result write") default: } if original[0].Notice.WorkCoverError != "" { t.Fatal("delivery was mutated before persistence") } close(session.release) select { case saved := <-writer.saved: if saved[0].Notice.WorkCoverError != "cached result" { t.Fatal("result missing") } case <-time.After(time.Second): t.Fatal("cover not saved") } } func TestEventCoverFailureIsRecordedAndDoesNotChangeListenerStatus(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() release := make(chan struct{}) close(release) writer := &coverWriterStub{saved: make(chan []creator.ListenerDelivery, 4), err: creator.ErrConflict} session := &coverEnricherStub{entered: make(chan struct{}, 1), release: release, err: errors.New("cover unavailable")} queue := startListenerCoverWorker(ctx, writer, "account", "generation", session) queue(nil)() queue(coverDelivery())() select { case saved := <-writer.saved: if saved[0].Notice.WorkCoverError != "cover unavailable" { t.Fatal("error hidden") } case <-time.After(time.Second): t.Fatal("error not saved") } if writer.batches != 0 { t.Fatal("cover failure changed listener health") } }