package mysql import ( "context" "testing" "hyapp/internal/testutil/mysqlschema" "hyapp/pkg/appcode" roomservice "hyapp/services/room-service/internal/room/service" ) func TestEnsureGiftOperationDoesNotReactivateBeforeServicePayloadCheck(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_gift_reactivate", }) ctx := appcode.WithContext(context.Background(), appcode.Default) repository, err := Open(ctx, schema.DSN, 4, 4) if err != nil { t.Fatalf("open repository: %v", err) } defer repository.Close() stored := roomservice.GiftOperation{ AppCode: appcode.Default, RoomID: "room-reactivate", CommandID: "cmd-reactivate", ActorUserID: 101, CommandPayload: []byte("stored-command"), RequestPayload: []byte("stored-request"), Status: roomservice.GiftOperationStatusCompensated, LastError: "insufficient balance", CreatedAtMS: 1_000, UpdatedAtMS: 1_000, } if _, created, err := repository.EnsureGiftOperation(ctx, stored); err != nil || !created { t.Fatalf("insert compensated operation: created=%v err=%v", created, err) } conflicting := stored conflicting.CommandPayload = []byte("different-command") conflicting.RequestPayload = []byte("different-request") conflicting.UpdatedAtMS = 2_000 got, created, err := repository.EnsureGiftOperation(ctx, conflicting) if err != nil || created { t.Fatalf("read duplicate operation: created=%v err=%v", created, err) } if got.Status != roomservice.GiftOperationStatusCompensated || string(got.CommandPayload) != "stored-command" { t.Fatalf("Ensure mutated an unverified historical operation: %+v", got) } got, reactivated, err := repository.ReactivateCompensatedGiftOperation(ctx, stored.RoomID, stored.CommandID, 3_000) if err != nil || !reactivated || got.Status != roomservice.GiftOperationStatusPending { t.Fatalf("payload-checked CAS did not reactivate: reactivated=%v operation=%+v err=%v", reactivated, got, err) } got, reactivated, err = repository.ReactivateCompensatedGiftOperation(ctx, stored.RoomID, stored.CommandID, 4_000) if err != nil || reactivated || got.Status != roomservice.GiftOperationStatusPending { t.Fatalf("reactivation must be idempotent after status advances: reactivated=%v operation=%+v err=%v", reactivated, got, err) } }