146 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package mysql
import (
"context"
"testing"
"time"
"google.golang.org/protobuf/proto"
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
"hyapp/internal/testutil/mysqlschema"
"hyapp/pkg/appcode"
"hyapp/services/room-service/internal/room/outbox"
)
func TestSaveOutboxScopesBuiltRecordToContextApp(t *testing.T) {
schema := mysqlschema.New(t, mysqlschema.Config{
EnvVar: "ROOM_SERVICE_MYSQL_TEST_DSN",
InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_room_service.sql"),
DatabasePrefix: "hyapp_room_outbox_scope_test",
})
ctx := appcode.WithContext(context.Background(), "huwaa")
repo, err := Open(ctx, schema.DSN, 4, 4)
if err != nil {
t.Fatalf("open repository: %v", err)
}
defer repo.Close()
record, err := outbox.Build("huwaa-room-outbox", "RoomUserJoined", 9, time.UnixMilli(2000), &roomeventsv1.RoomUserJoined{UserId: 99})
if err != nil {
t.Fatalf("build room outbox: %v", err)
}
if err := repo.SaveOutbox(ctx, []outbox.Record{record}); err != nil {
t.Fatalf("save scoped outbox: %v", err)
}
var storedApp string
var envelopeBytes []byte
if err := schema.DB.QueryRowContext(ctx,
`SELECT app_code, envelope FROM room_outbox WHERE app_code = ? AND event_id = ?`,
"huwaa", record.EventID,
).Scan(&storedApp, &envelopeBytes); err != nil {
t.Fatalf("read scoped outbox: %v", err)
}
var envelope roomeventsv1.EventEnvelope
if err := proto.Unmarshal(envelopeBytes, &envelope); err != nil {
t.Fatalf("decode scoped outbox envelope: %v", err)
}
if storedApp != "huwaa" || envelope.GetAppCode() != "huwaa" {
t.Fatalf("SaveOutbox lost scoped app: row=%q envelope=%q", storedApp, envelope.GetAppCode())
}
}
func retentionTestRecord(eventID string, createdAtMS int64) outbox.Record {
return outbox.Record{
AppCode: appcode.Default,
EventID: eventID,
EventType: "RoomGiftSent",
RoomID: "room-retention",
Status: outbox.StatusPending,
Envelope: &roomeventsv1.EventEnvelope{
AppCode: appcode.Default,
EventId: eventID,
RoomId: "room-retention",
EventType: "RoomGiftSent",
},
CreatedAtMS: createdAtMS,
}
}
// 清理只允许命中 delivered 且早于 cutoff 的行;其他状态和窗口内的 delivered 必须原样保留,
// 否则统计重放和人工补偿会丢事实。
func TestDeleteDeliveredOutboxBeforeOnlyPurgesOldDelivered(t *testing.T) {
schema := mysqlschema.New(t, mysqlschema.Config{
EnvVar: "ROOM_SERVICE_MYSQL_TEST_DSN",
InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_room_service.sql"),
DatabasePrefix: "hyapp_room_retention_test",
})
ctx := context.Background()
repo, err := Open(ctx, schema.DSN, 4, 4)
if err != nil {
t.Fatalf("open repository: %v", err)
}
defer repo.Close()
records := []outbox.Record{
retentionTestRecord("evt-old-delivered-1", 1000),
retentionTestRecord("evt-old-delivered-2", 2000),
retentionTestRecord("evt-old-delivered-3", 3000),
retentionTestRecord("evt-fresh-delivered", 4000),
retentionTestRecord("evt-old-pending", 1000),
retentionTestRecord("evt-old-failed", 1000),
}
if err := repo.SaveOutbox(ctx, records); err != nil {
t.Fatalf("save outbox: %v", err)
}
const cutoffMS = int64(1_000_000)
setStatusAt := func(eventID string, status string, updatedAtMS int64) {
t.Helper()
if _, err := schema.DB.ExecContext(ctx, `UPDATE room_outbox SET status = ?, updated_at_ms = ? WHERE app_code = ? AND event_id = ?`, status, updatedAtMS, appcode.Default, eventID); err != nil {
t.Fatalf("rewrite outbox row %s: %v", eventID, err)
}
}
setStatusAt("evt-old-delivered-1", outbox.StatusDelivered, 1000)
setStatusAt("evt-old-delivered-2", outbox.StatusDelivered, 2000)
setStatusAt("evt-old-delivered-3", outbox.StatusDelivered, 3000)
setStatusAt("evt-fresh-delivered", outbox.StatusDelivered, cutoffMS+1)
setStatusAt("evt-old-failed", outbox.StatusFailed, 1000)
// 批量上限必须生效3 条可删行按 limit=2 分两批删完,第三批确认删空。
for step, want := range []int64{2, 1, 0} {
deleted, err := repo.DeleteDeliveredOutboxBefore(ctx, cutoffMS, 2)
if err != nil {
t.Fatalf("delete batch %d: %v", step, err)
}
if deleted != want {
t.Fatalf("delete batch %d deleted %d rows, want %d", step, deleted, want)
}
}
var survivors []string
rows, err := schema.DB.QueryContext(ctx, `SELECT event_id FROM room_outbox WHERE app_code = ? ORDER BY event_id`, appcode.Default)
if err != nil {
t.Fatalf("list survivors: %v", err)
}
defer rows.Close()
for rows.Next() {
var eventID string
if err := rows.Scan(&eventID); err != nil {
t.Fatalf("scan survivor: %v", err)
}
survivors = append(survivors, eventID)
}
if err := rows.Err(); err != nil {
t.Fatalf("iterate survivors: %v", err)
}
want := []string{"evt-fresh-delivered", "evt-old-failed", "evt-old-pending"}
if len(survivors) != len(want) {
t.Fatalf("survivors mismatch got=%v want=%v", survivors, want)
}
for index := range want {
if survivors[index] != want[index] {
t.Fatalf("survivors mismatch got=%v want=%v", survivors, want)
}
}
}