@@ -1587,3 +1587,125 @@ async fn test_cas_loop() {
15871587 . unwrap ( ) ;
15881588 client. shutdown ( ) . await . unwrap ( ) ;
15891589}
1590+
1591+ // ============================================================
1592+ // Binary (non-UTF8) data
1593+ // ============================================================
1594+
1595+ #[ tokio:: test]
1596+ async fn test_binary_values ( ) {
1597+ let ( _container, address) = start_oxia ( ) . await ;
1598+ let client = new_client ( & address) . await ;
1599+
1600+ // Store binary data that isn't valid UTF-8
1601+ let binary_data: Vec < u8 > = vec ! [ 0x00 , 0x01 , 0xFF , 0xFE , 0x80 , 0x90 , 0xAB , 0xCD ] ;
1602+ client
1603+ . put ( "bin/data" . to_string ( ) , binary_data. clone ( ) )
1604+ . await
1605+ . unwrap ( ) ;
1606+
1607+ let result = client. get ( "bin/data" . to_string ( ) ) . await . unwrap ( ) ;
1608+ assert_eq ! ( result. value, Some ( binary_data) ) ;
1609+
1610+ client
1611+ . delete_range ( "bin/" . to_string ( ) , "bin/~" . to_string ( ) )
1612+ . await
1613+ . unwrap ( ) ;
1614+ client. shutdown ( ) . await . unwrap ( ) ;
1615+ }
1616+
1617+ // ============================================================
1618+ // Overwrite with different value sizes
1619+ // ============================================================
1620+
1621+ #[ tokio:: test]
1622+ async fn test_overwrite_different_sizes ( ) {
1623+ let ( _container, address) = start_oxia ( ) . await ;
1624+ let client = new_client ( & address) . await ;
1625+
1626+ // Start with a small value
1627+ client
1628+ . put ( "sizes/key" . to_string ( ) , b"small" . to_vec ( ) )
1629+ . await
1630+ . unwrap ( ) ;
1631+
1632+ // Overwrite with a larger value
1633+ let large_value = vec ! [ b'x' ; 10000 ] ;
1634+ client
1635+ . put ( "sizes/key" . to_string ( ) , large_value. clone ( ) )
1636+ . await
1637+ . unwrap ( ) ;
1638+
1639+ let result = client. get ( "sizes/key" . to_string ( ) ) . await . unwrap ( ) ;
1640+ assert_eq ! ( result. value, Some ( large_value) ) ;
1641+
1642+ // Overwrite back to a small value
1643+ client
1644+ . put ( "sizes/key" . to_string ( ) , b"tiny" . to_vec ( ) )
1645+ . await
1646+ . unwrap ( ) ;
1647+
1648+ let result = client. get ( "sizes/key" . to_string ( ) ) . await . unwrap ( ) ;
1649+ assert_eq ! ( result. value, Some ( b"tiny" . to_vec( ) ) ) ;
1650+
1651+ client
1652+ . delete_range ( "sizes/" . to_string ( ) , "sizes/~" . to_string ( ) )
1653+ . await
1654+ . unwrap ( ) ;
1655+ client. shutdown ( ) . await . unwrap ( ) ;
1656+ }
1657+
1658+ // ============================================================
1659+ // Concurrent CAS conflict detection
1660+ // ============================================================
1661+
1662+ #[ tokio:: test]
1663+ async fn test_concurrent_cas_conflict ( ) {
1664+ let ( _container, address) = start_oxia ( ) . await ;
1665+ let client = new_client ( & address) . await ;
1666+
1667+ // Create a key
1668+ let initial = client
1669+ . put ( "conflict/key" . to_string ( ) , b"v0" . to_vec ( ) )
1670+ . await
1671+ . unwrap ( ) ;
1672+ let version = initial. version . version_id ;
1673+
1674+ // Two concurrent CAS updates with the same version - one should fail
1675+ let c1 = client. clone ( ) ;
1676+ let c2 = client. clone ( ) ;
1677+
1678+ let h1 = tokio:: spawn ( async move {
1679+ c1. put_with_options (
1680+ "conflict/key" . to_string ( ) ,
1681+ b"v1-from-c1" . to_vec ( ) ,
1682+ vec ! [ PutOption :: ExpectVersionId ( version) ] ,
1683+ )
1684+ . await
1685+ } ) ;
1686+
1687+ let h2 = tokio:: spawn ( async move {
1688+ c2. put_with_options (
1689+ "conflict/key" . to_string ( ) ,
1690+ b"v1-from-c2" . to_vec ( ) ,
1691+ vec ! [ PutOption :: ExpectVersionId ( version) ] ,
1692+ )
1693+ . await
1694+ } ) ;
1695+
1696+ let r1 = h1. await . unwrap ( ) ;
1697+ let r2 = h2. await . unwrap ( ) ;
1698+
1699+ // Exactly one should succeed and one should fail with UnexpectedVersionId
1700+ let ( successes, failures) : ( Vec < _ > , Vec < _ > ) = vec ! [ r1, r2] . into_iter ( ) . partition ( |r| r. is_ok ( ) ) ;
1701+
1702+ assert_eq ! ( successes. len( ) , 1 , "Exactly one CAS should succeed" ) ;
1703+ assert_eq ! ( failures. len( ) , 1 , "Exactly one CAS should fail" ) ;
1704+ assert ! ( matches!( failures[ 0 ] , Err ( OxiaError :: UnexpectedVersionId ( ) ) ) ) ;
1705+
1706+ client
1707+ . delete_range ( "conflict/" . to_string ( ) , "conflict/~" . to_string ( ) )
1708+ . await
1709+ . unwrap ( ) ;
1710+ client. shutdown ( ) . await . unwrap ( ) ;
1711+ }
0 commit comments