package service_test import ( "context" "fmt" "testing" "time" roomeventsv1 "hyapp.local/api/proto/events/room/v1" "hyapp/pkg/appcode" "hyapp/services/room-service/internal/integration" "hyapp/services/room-service/internal/room/outbox" roomservice "hyapp/services/room-service/internal/room/service" "hyapp/services/room-service/internal/router" "hyapp/services/room-service/internal/testutil/mysqltest" ) // 一轮清理必须按批删空所有过期 delivered 事件,同时保留窗口内 delivered 和未完成投递的事件。 func TestProcessOutboxRetentionDrainsExpiredDelivered(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) svc := roomservice.New(roomservice.Config{ NodeID: "node-outbox-retention-test", }, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) roomID := "room-outbox-retention" newRecord := func(eventID string) outbox.Record { return outbox.Record{ AppCode: appcode.Default, EventID: eventID, EventType: "RoomGiftSent", RoomID: roomID, Status: outbox.StatusPending, Envelope: &roomeventsv1.EventEnvelope{ AppCode: appcode.Default, EventId: eventID, RoomId: roomID, EventType: "RoomGiftSent", }, CreatedAtMS: 1000, } } expiredMS := time.Now().UTC().Add(-48 * time.Hour).UnixMilli() records := make([]outbox.Record, 0, 7) expired := make([]string, 0, 5) for index := 0; index < 5; index++ { eventID := fmt.Sprintf("evt-retention-expired-%d", index) expired = append(expired, eventID) records = append(records, newRecord(eventID)) } records = append(records, newRecord("evt-retention-fresh"), newRecord("evt-retention-pending")) if err := repository.SaveOutbox(ctx, records); err != nil { t.Fatalf("save outbox failed: %v", err) } for _, eventID := range expired { repository.SetOutboxDeliveredAt(eventID, expiredMS) } if err := repository.MarkOutboxDelivered(ctx, "evt-retention-fresh"); err != nil { t.Fatalf("mark fresh delivered failed: %v", err) } // BatchSize=2 强制一轮内分 3 批删完 5 条过期事件,覆盖“整批删满继续、删空退出”的循环边界。 total, err := svc.ProcessOutboxRetention(ctx, roomservice.OutboxRetentionOptions{ MaxAge: 24 * time.Hour, BatchSize: 2, }) if err != nil { t.Fatalf("process outbox retention failed: %v", err) } if total != int64(len(expired)) { t.Fatalf("retention deleted %d rows, want %d", total, len(expired)) } for _, eventID := range expired { if _, ok := repository.OutboxRecord(eventID); ok { t.Fatalf("expired delivered event %s must be purged", eventID) } } if record, ok := repository.OutboxRecord("evt-retention-fresh"); !ok || record.Status != outbox.StatusDelivered { t.Fatalf("fresh delivered event must survive: ok=%v record=%+v", ok, record) } if record, ok := repository.OutboxRecord("evt-retention-pending"); !ok || record.Status != outbox.StatusPending { t.Fatalf("pending event must survive: ok=%v record=%+v", ok, record) } }