@@ -2043,4 +2043,75 @@ mod tests {
20432043 assert_eq ! ( meta[ "vision_generation_id" ] , "gen-1" ) ;
20442044 assert_eq ! ( rows[ 0 ] . content, "" ) ; // untouched
20452045 }
2046+
2047+ /// 0027 cleanup: legacy non-tip gift_user rows (no tips_amount_usd metadata)
2048+ /// are deleted; tips (gift_user with tips_amount_usd) and user rows survive.
2049+ /// Mirrors the 0018 backfill-test pattern: #[sqlx::test] runs 0027 on the
2050+ /// empty DB (a no-op), then we seed rows and re-run the embedded DELETE
2051+ /// (valid because DELETE is idempotent).
2052+ const CLEANUP_SQL_0027 : & str =
2053+ include_str ! ( "../migrations/0027_drop_legacy_gift_user_rows.sql" ) ;
2054+
2055+ #[ sqlx:: test( migrations = "./migrations" ) ]
2056+ async fn migration_0027_drops_legacy_non_tip_gift_user_rows ( pool : PgPool ) {
2057+ let user_id = Uuid :: new_v4 ( ) ;
2058+ let instance_id = Uuid :: new_v4 ( ) ;
2059+ let session_id: Uuid = sqlx:: query_scalar (
2060+ "INSERT INTO engine.chat_sessions (user_id, instance_id) VALUES ($1, $2) RETURNING id" ,
2061+ )
2062+ . bind ( user_id)
2063+ . bind ( instance_id)
2064+ . fetch_one ( & pool)
2065+ . await
2066+ . unwrap ( ) ;
2067+
2068+ sqlx:: query (
2069+ "INSERT INTO engine.chat_messages (session_id, role, content, metadata) VALUES \
2070+ ($1, 'user', 'hi', NULL), \
2071+ ($1, 'gift_user', '(打赏 $20)', '{\" tips_amount_usd\" : 20.0}'::jsonb), \
2072+ ($1, 'gift_user', 'rose', NULL), \
2073+ ($1, 'gift_user', 'rose', '{\" label\" : \" rose\" }'::jsonb)",
2074+ )
2075+ . bind ( session_id)
2076+ . execute ( & pool)
2077+ . await
2078+ . unwrap ( ) ;
2079+
2080+ // Run the embedded cleanup migration.
2081+ sqlx:: query ( CLEANUP_SQL_0027 ) . execute ( & pool) . await . unwrap ( ) ;
2082+
2083+ // Only the tip gift_user row survives.
2084+ let gift_user_count: i64 = sqlx:: query_scalar (
2085+ "SELECT COUNT(*) FROM engine.chat_messages \
2086+ WHERE session_id = $1 AND role = 'gift_user'",
2087+ )
2088+ . bind ( session_id)
2089+ . fetch_one ( & pool)
2090+ . await
2091+ . unwrap ( ) ;
2092+ assert_eq ! ( gift_user_count, 1 , "only the tip gift_user row survives" ) ;
2093+
2094+ // The survivor is the tip (carries tips_amount_usd).
2095+ let surviving_tip: i64 = sqlx:: query_scalar (
2096+ "SELECT COUNT(*) FROM engine.chat_messages \
2097+ WHERE session_id = $1 AND role = 'gift_user' \
2098+ AND metadata ? 'tips_amount_usd'",
2099+ )
2100+ . bind ( session_id)
2101+ . fetch_one ( & pool)
2102+ . await
2103+ . unwrap ( ) ;
2104+ assert_eq ! ( surviving_tip, 1 , "the surviving gift_user row is the tip" ) ;
2105+
2106+ // user rows are untouched.
2107+ let user_count: i64 = sqlx:: query_scalar (
2108+ "SELECT COUNT(*) FROM engine.chat_messages \
2109+ WHERE session_id = $1 AND role = 'user'",
2110+ )
2111+ . bind ( session_id)
2112+ . fetch_one ( & pool)
2113+ . await
2114+ . unwrap ( ) ;
2115+ assert_eq ! ( user_count, 1 , "user rows are untouched" ) ;
2116+ }
20462117}
0 commit comments