@@ -1294,3 +1294,296 @@ async fn test_range_scan_with_partition_key() {
12941294 . unwrap ( ) ;
12951295 client. shutdown ( ) . await . unwrap ( ) ;
12961296}
1297+
1298+ // ============================================================
1299+ // Stress / throughput test
1300+ // ============================================================
1301+
1302+ #[ tokio:: test]
1303+ async fn test_high_throughput_writes ( ) {
1304+ let ( _container, address) = start_oxia ( ) . await ;
1305+ let client = new_client ( & address) . await ;
1306+
1307+ let num_ops = 100 ;
1308+ let mut handles = Vec :: new ( ) ;
1309+
1310+ // Flood with concurrent put operations
1311+ for i in 0 ..num_ops {
1312+ let c = client. clone ( ) ;
1313+ handles. push ( tokio:: spawn ( async move {
1314+ c. put (
1315+ format ! ( "stress/{:04}" , i) ,
1316+ format ! ( "val-{}" , i) . into_bytes ( ) ,
1317+ )
1318+ . await
1319+ } ) ) ;
1320+ }
1321+
1322+ let mut success_count = 0 ;
1323+ for handle in handles {
1324+ if handle. await . unwrap ( ) . is_ok ( ) {
1325+ success_count += 1 ;
1326+ }
1327+ }
1328+ assert_eq ! ( success_count, num_ops, "All puts should succeed" ) ;
1329+
1330+ // Verify all keys exist
1331+ let list = client
1332+ . list ( "stress/" . to_string ( ) , "stress/~" . to_string ( ) )
1333+ . await
1334+ . unwrap ( ) ;
1335+ assert_eq ! ( list. keys. len( ) , num_ops) ;
1336+
1337+ // Range scan all
1338+ let scan = client
1339+ . range_scan ( "stress/" . to_string ( ) , "stress/~" . to_string ( ) )
1340+ . await
1341+ . unwrap ( ) ;
1342+ assert_eq ! ( scan. records. len( ) , num_ops) ;
1343+
1344+ // Flood with concurrent delete operations
1345+ let mut del_handles = Vec :: new ( ) ;
1346+ for i in 0 ..num_ops {
1347+ let c = client. clone ( ) ;
1348+ del_handles. push ( tokio:: spawn ( async move {
1349+ c. delete ( format ! ( "stress/{:04}" , i) ) . await
1350+ } ) ) ;
1351+ }
1352+ for handle in del_handles {
1353+ handle. await . unwrap ( ) . unwrap ( ) ;
1354+ }
1355+
1356+ // Verify all keys deleted
1357+ let list = client
1358+ . list ( "stress/" . to_string ( ) , "stress/~" . to_string ( ) )
1359+ . await
1360+ . unwrap ( ) ;
1361+ assert_eq ! ( list. keys. len( ) , 0 ) ;
1362+
1363+ client. shutdown ( ) . await . unwrap ( ) ;
1364+ }
1365+
1366+ // ============================================================
1367+ // Notification Display test
1368+ // ============================================================
1369+
1370+ #[ tokio:: test]
1371+ async fn test_notification_display ( ) {
1372+ let created = Notification :: KeyCreated ( KeyCreated {
1373+ key : "test/key" . to_string ( ) ,
1374+ version_id : Some ( 42 ) ,
1375+ } ) ;
1376+ assert ! ( format!( "{}" , created) . contains( "test/key" ) ) ;
1377+ assert ! ( format!( "{}" , created) . contains( "42" ) ) ;
1378+
1379+ let deleted = Notification :: KeyDeleted ( KeyDeleted {
1380+ key : "test/key" . to_string ( ) ,
1381+ } ) ;
1382+ assert ! ( format!( "{}" , deleted) . contains( "test/key" ) ) ;
1383+
1384+ let modified = Notification :: KeyModified ( KeyModified {
1385+ key : "test/key" . to_string ( ) ,
1386+ version_id : Some ( 43 ) ,
1387+ } ) ;
1388+ assert ! ( format!( "{}" , modified) . contains( "test/key" ) ) ;
1389+
1390+ let unknown = Notification :: Unknown ( ) ;
1391+ assert_eq ! ( format!( "{}" , unknown) , "Unknown" ) ;
1392+ }
1393+
1394+ // ============================================================
1395+ // Multiple secondary indexes per record
1396+ // ============================================================
1397+
1398+ #[ tokio:: test]
1399+ async fn test_multiple_secondary_indexes ( ) {
1400+ let ( _container, address) = start_oxia ( ) . await ;
1401+ let client = new_client ( & address) . await ;
1402+
1403+ // Put a record with multiple secondary indexes
1404+ client
1405+ . put_with_options (
1406+ "multi-idx/record1" . to_string ( ) ,
1407+ b"some-data" . to_vec ( ) ,
1408+ vec ! [ PutOption :: SecondaryIndexes ( vec![
1409+ SecondaryIndex {
1410+ index_name: "by-type" . to_string( ) ,
1411+ secondary_key: "document" . to_string( ) ,
1412+ } ,
1413+ SecondaryIndex {
1414+ index_name: "by-status" . to_string( ) ,
1415+ secondary_key: "active" . to_string( ) ,
1416+ } ,
1417+ ] ) ] ,
1418+ )
1419+ . await
1420+ . unwrap ( ) ;
1421+
1422+ // Query via first secondary index
1423+ let list1 = client
1424+ . list_with_options (
1425+ "d" . to_string ( ) ,
1426+ "e" . to_string ( ) ,
1427+ vec ! [ ListOption :: UseIndex ( "by-type" . to_string( ) ) ] ,
1428+ )
1429+ . await
1430+ . unwrap ( ) ;
1431+ assert ! (
1432+ !list1. keys. is_empty( ) ,
1433+ "Should find record via by-type index"
1434+ ) ;
1435+
1436+ // Query via second secondary index
1437+ let list2 = client
1438+ . list_with_options (
1439+ "a" . to_string ( ) ,
1440+ "b" . to_string ( ) ,
1441+ vec ! [ ListOption :: UseIndex ( "by-status" . to_string( ) ) ] ,
1442+ )
1443+ . await
1444+ . unwrap ( ) ;
1445+ assert ! (
1446+ !list2. keys. is_empty( ) ,
1447+ "Should find record via by-status index"
1448+ ) ;
1449+
1450+ client
1451+ . delete_range ( "multi-idx/" . to_string ( ) , "multi-idx/~" . to_string ( ) )
1452+ . await
1453+ . unwrap ( ) ;
1454+ client. shutdown ( ) . await . unwrap ( ) ;
1455+ }
1456+
1457+ // ============================================================
1458+ // List and scan partial ranges
1459+ // ============================================================
1460+
1461+ #[ tokio:: test]
1462+ async fn test_list_partial_range ( ) {
1463+ let ( _container, address) = start_oxia ( ) . await ;
1464+ let client = new_client ( & address) . await ;
1465+
1466+ for key in & [ "pr/a" , "pr/b" , "pr/c" , "pr/d" , "pr/e" ] {
1467+ client. put ( key. to_string ( ) , b"v" . to_vec ( ) ) . await . unwrap ( ) ;
1468+ }
1469+
1470+ // List only a subset of the range
1471+ let result = client
1472+ . list ( "pr/b" . to_string ( ) , "pr/d" . to_string ( ) )
1473+ . await
1474+ . unwrap ( ) ;
1475+ assert_eq ! ( result. keys. len( ) , 2 ) ;
1476+ assert ! ( result. keys. contains( & "pr/b" . to_string( ) ) ) ;
1477+ assert ! ( result. keys. contains( & "pr/c" . to_string( ) ) ) ;
1478+
1479+ client
1480+ . delete_range ( "pr/" . to_string ( ) , "pr/~" . to_string ( ) )
1481+ . await
1482+ . unwrap ( ) ;
1483+ client. shutdown ( ) . await . unwrap ( ) ;
1484+ }
1485+
1486+ // ============================================================
1487+ // Put and immediately get - verify consistency
1488+ // ============================================================
1489+
1490+ #[ tokio:: test]
1491+ async fn test_read_your_writes ( ) {
1492+ let ( _container, address) = start_oxia ( ) . await ;
1493+ let client = new_client ( & address) . await ;
1494+
1495+ for i in 0 ..50 {
1496+ let key = format ! ( "ryw/{}" , i) ;
1497+ let value = format ! ( "value-{}" , i) ;
1498+ client
1499+ . put ( key. clone ( ) , value. clone ( ) . into_bytes ( ) )
1500+ . await
1501+ . unwrap ( ) ;
1502+ let result = client. get ( key) . await . unwrap ( ) ;
1503+ assert_eq ! ( result. value, Some ( value. into_bytes( ) ) ) ;
1504+ }
1505+
1506+ client
1507+ . delete_range ( "ryw/" . to_string ( ) , "ryw/~" . to_string ( ) )
1508+ . await
1509+ . unwrap ( ) ;
1510+ client. shutdown ( ) . await . unwrap ( ) ;
1511+ }
1512+
1513+ // ============================================================
1514+ // Multiple clients operating concurrently
1515+ // ============================================================
1516+
1517+ #[ tokio:: test]
1518+ async fn test_multiple_clients ( ) {
1519+ let ( _container, address) = start_oxia ( ) . await ;
1520+ let client1 = new_client ( & address) . await ;
1521+ let client2 = new_client ( & address) . await ;
1522+
1523+ // Client 1 writes
1524+ client1
1525+ . put ( "mc/key1" . to_string ( ) , b"from-client-1" . to_vec ( ) )
1526+ . await
1527+ . unwrap ( ) ;
1528+
1529+ // Client 2 reads
1530+ let result = client2. get ( "mc/key1" . to_string ( ) ) . await . unwrap ( ) ;
1531+ assert_eq ! ( result. value, Some ( b"from-client-1" . to_vec( ) ) ) ;
1532+
1533+ // Client 2 writes
1534+ client2
1535+ . put ( "mc/key2" . to_string ( ) , b"from-client-2" . to_vec ( ) )
1536+ . await
1537+ . unwrap ( ) ;
1538+
1539+ // Client 1 reads
1540+ let result = client1. get ( "mc/key2" . to_string ( ) ) . await . unwrap ( ) ;
1541+ assert_eq ! ( result. value, Some ( b"from-client-2" . to_vec( ) ) ) ;
1542+
1543+ client1
1544+ . delete_range ( "mc/" . to_string ( ) , "mc/~" . to_string ( ) )
1545+ . await
1546+ . unwrap ( ) ;
1547+ client1. shutdown ( ) . await . unwrap ( ) ;
1548+ client2. shutdown ( ) . await . unwrap ( ) ;
1549+ }
1550+
1551+ // ============================================================
1552+ // CAS (Compare-And-Swap) loop pattern
1553+ // ============================================================
1554+
1555+ #[ tokio:: test]
1556+ async fn test_cas_loop ( ) {
1557+ let ( _container, address) = start_oxia ( ) . await ;
1558+ let client = new_client ( & address) . await ;
1559+
1560+ // Create initial record
1561+ let initial = client
1562+ . put ( "cas/counter" . to_string ( ) , b"0" . to_vec ( ) )
1563+ . await
1564+ . unwrap ( ) ;
1565+
1566+ // Simulate a CAS loop: read-modify-write with version check
1567+ let mut current_version = initial. version . version_id ;
1568+ for i in 1 ..=5 {
1569+ let result = client
1570+ . put_with_options (
1571+ "cas/counter" . to_string ( ) ,
1572+ format ! ( "{}" , i) . into_bytes ( ) ,
1573+ vec ! [ PutOption :: ExpectVersionId ( current_version) ] ,
1574+ )
1575+ . await
1576+ . unwrap ( ) ;
1577+ current_version = result. version . version_id ;
1578+ }
1579+
1580+ // Verify final value
1581+ let final_result = client. get ( "cas/counter" . to_string ( ) ) . await . unwrap ( ) ;
1582+ assert_eq ! ( final_result. value, Some ( b"5" . to_vec( ) ) ) ;
1583+
1584+ client
1585+ . delete_range ( "cas/" . to_string ( ) , "cas/~" . to_string ( ) )
1586+ . await
1587+ . unwrap ( ) ;
1588+ client. shutdown ( ) . await . unwrap ( ) ;
1589+ }
0 commit comments