@@ -57,6 +57,17 @@ define(['ably', 'shared_helper', 'chai', 'live_objects', 'live_objects_helper'],
57
57
return `${ paddedTimestamp } -${ paddedCounter } @${ seriesId } ` + ( paddedIndex ? `:${ paddedIndex } ` : '' ) ;
58
58
}
59
59
60
+ async function expectRejectedWith ( fn , errorStr ) {
61
+ let verifiedError = false ;
62
+ try {
63
+ await fn ( ) ;
64
+ } catch ( error ) {
65
+ expect ( error . message ) . to . have . string ( errorStr ) ;
66
+ verifiedError = true ;
67
+ }
68
+ expect ( verifiedError , 'Expected async function to throw an error' ) . to . be . true ;
69
+ }
70
+
60
71
describe ( 'realtime/live_objects' , function ( ) {
61
72
this . timeout ( 60 * 1000 ) ;
62
73
@@ -518,7 +529,7 @@ define(['ably', 'shared_helper', 'chai', 'live_objects', 'live_objects_helper'],
518
529
{ name : 'negativeMaxSafeIntegerCounter' , count : - Number . MAX_SAFE_INTEGER } ,
519
530
] ;
520
531
521
- const stateSyncSequenceScanarios = [
532
+ const stateSyncSequenceScenarios = [
522
533
{
523
534
description : 'STATE_SYNC sequence with state object "tombstone" property creates tombstoned object' ,
524
535
action : async ( ctx ) => {
@@ -2060,9 +2071,316 @@ define(['ably', 'shared_helper', 'chai', 'live_objects', 'live_objects_helper'],
2060
2071
} ,
2061
2072
] ;
2062
2073
2074
+ const writeApiScenarios = [
2075
+ {
2076
+ description : 'LiveCounter.increment sends COUNTER_INC operation' ,
2077
+ action : async ( ctx ) => {
2078
+ const { root, liveObjectsHelper, channelName } = ctx ;
2079
+
2080
+ await liveObjectsHelper . createAndSetOnMap ( channelName , {
2081
+ mapObjectId : 'root' ,
2082
+ key : 'counter' ,
2083
+ createOp : liveObjectsHelper . counterCreateOp ( ) ,
2084
+ } ) ;
2085
+
2086
+ const counter = root . get ( 'counter' ) ;
2087
+ const increments = [
2088
+ 1 , // value=1
2089
+ 10 , // value=11
2090
+ - 11 , // value=0
2091
+ - 1 , // value=-1
2092
+ - 10 , // value=-11
2093
+ 11 , // value=0
2094
+ Number . MAX_SAFE_INTEGER , // value=9007199254740991
2095
+ - Number . MAX_SAFE_INTEGER , // value=0
2096
+ - Number . MAX_SAFE_INTEGER , // value=-9007199254740991
2097
+ ] ;
2098
+ let expectedCounterValue = 0 ;
2099
+
2100
+ for ( let i = 0 ; i < increments . length ; i ++ ) {
2101
+ const increment = increments [ i ] ;
2102
+ expectedCounterValue += increment ;
2103
+ await counter . increment ( increment ) ;
2104
+
2105
+ expect ( counter . value ( ) ) . to . equal (
2106
+ expectedCounterValue ,
2107
+ `Check counter has correct value after ${ i + 1 } LiveCounter.increment calls` ,
2108
+ ) ;
2109
+ }
2110
+ } ,
2111
+ } ,
2112
+
2113
+ {
2114
+ description : 'LiveCounter.increment throws on invalid input' ,
2115
+ action : async ( ctx ) => {
2116
+ const { root, liveObjectsHelper, channelName } = ctx ;
2117
+
2118
+ await liveObjectsHelper . createAndSetOnMap ( channelName , {
2119
+ mapObjectId : 'root' ,
2120
+ key : 'counter' ,
2121
+ createOp : liveObjectsHelper . counterCreateOp ( ) ,
2122
+ } ) ;
2123
+
2124
+ const counter = root . get ( 'counter' ) ;
2125
+
2126
+ await expectRejectedWith ( async ( ) => counter . increment ( ) , 'Counter value increment should be a number' ) ;
2127
+ await expectRejectedWith ( async ( ) => counter . increment ( null ) , 'Counter value increment should be a number' ) ;
2128
+ await expectRejectedWith (
2129
+ async ( ) => counter . increment ( 'foo' ) ,
2130
+ 'Counter value increment should be a number' ,
2131
+ ) ;
2132
+ await expectRejectedWith (
2133
+ async ( ) => counter . increment ( BigInt ( 1 ) ) ,
2134
+ 'Counter value increment should be a number' ,
2135
+ ) ;
2136
+ await expectRejectedWith ( async ( ) => counter . increment ( true ) , 'Counter value increment should be a number' ) ;
2137
+ await expectRejectedWith (
2138
+ async ( ) => counter . increment ( Symbol ( ) ) ,
2139
+ 'Counter value increment should be a number' ,
2140
+ ) ;
2141
+ await expectRejectedWith ( async ( ) => counter . increment ( { } ) , 'Counter value increment should be a number' ) ;
2142
+ await expectRejectedWith ( async ( ) => counter . increment ( [ ] ) , 'Counter value increment should be a number' ) ;
2143
+ await expectRejectedWith (
2144
+ async ( ) => counter . increment ( counter ) ,
2145
+ 'Counter value increment should be a number' ,
2146
+ ) ;
2147
+ } ,
2148
+ } ,
2149
+
2150
+ {
2151
+ description : 'LiveCounter.decrement sends COUNTER_INC operation' ,
2152
+ action : async ( ctx ) => {
2153
+ const { root, liveObjectsHelper, channelName } = ctx ;
2154
+
2155
+ await liveObjectsHelper . createAndSetOnMap ( channelName , {
2156
+ mapObjectId : 'root' ,
2157
+ key : 'counter' ,
2158
+ createOp : liveObjectsHelper . counterCreateOp ( ) ,
2159
+ } ) ;
2160
+
2161
+ const counter = root . get ( 'counter' ) ;
2162
+ const decrements = [
2163
+ 1 , // value=-1
2164
+ 10 , // value=-11
2165
+ - 11 , // value=0
2166
+ - 1 , // value=1
2167
+ - 10 , // value=11
2168
+ 11 , // value=0
2169
+ Number . MAX_SAFE_INTEGER , // value=-9007199254740991
2170
+ - Number . MAX_SAFE_INTEGER , // value=0
2171
+ - Number . MAX_SAFE_INTEGER , // value=9007199254740991
2172
+ ] ;
2173
+ let expectedCounterValue = 0 ;
2174
+
2175
+ for ( let i = 0 ; i < decrements . length ; i ++ ) {
2176
+ const decrement = decrements [ i ] ;
2177
+ expectedCounterValue -= decrement ;
2178
+ await counter . decrement ( decrement ) ;
2179
+
2180
+ expect ( counter . value ( ) ) . to . equal (
2181
+ expectedCounterValue ,
2182
+ `Check counter has correct value after ${ i + 1 } LiveCounter.decrement calls` ,
2183
+ ) ;
2184
+ }
2185
+ } ,
2186
+ } ,
2187
+
2188
+ {
2189
+ description : 'LiveCounter.decrement throws on invalid input' ,
2190
+ action : async ( ctx ) => {
2191
+ const { root, liveObjectsHelper, channelName } = ctx ;
2192
+
2193
+ await liveObjectsHelper . createAndSetOnMap ( channelName , {
2194
+ mapObjectId : 'root' ,
2195
+ key : 'counter' ,
2196
+ createOp : liveObjectsHelper . counterCreateOp ( ) ,
2197
+ } ) ;
2198
+
2199
+ const counter = root . get ( 'counter' ) ;
2200
+
2201
+ await expectRejectedWith ( async ( ) => counter . decrement ( ) , 'Counter value decrement should be a number' ) ;
2202
+ await expectRejectedWith ( async ( ) => counter . decrement ( null ) , 'Counter value decrement should be a number' ) ;
2203
+ await expectRejectedWith (
2204
+ async ( ) => counter . decrement ( 'foo' ) ,
2205
+ 'Counter value decrement should be a number' ,
2206
+ ) ;
2207
+ await expectRejectedWith (
2208
+ async ( ) => counter . decrement ( BigInt ( 1 ) ) ,
2209
+ 'Counter value decrement should be a number' ,
2210
+ ) ;
2211
+ await expectRejectedWith ( async ( ) => counter . decrement ( true ) , 'Counter value decrement should be a number' ) ;
2212
+ await expectRejectedWith (
2213
+ async ( ) => counter . decrement ( Symbol ( ) ) ,
2214
+ 'Counter value decrement should be a number' ,
2215
+ ) ;
2216
+ await expectRejectedWith ( async ( ) => counter . decrement ( { } ) , 'Counter value decrement should be a number' ) ;
2217
+ await expectRejectedWith ( async ( ) => counter . decrement ( [ ] ) , 'Counter value decrement should be a number' ) ;
2218
+ await expectRejectedWith (
2219
+ async ( ) => counter . decrement ( counter ) ,
2220
+ 'Counter value decrement should be a number' ,
2221
+ ) ;
2222
+ } ,
2223
+ } ,
2224
+
2225
+ {
2226
+ description : 'LiveMap.set sends MAP_SET operation with primitive values' ,
2227
+ action : async ( ctx ) => {
2228
+ const { root } = ctx ;
2229
+
2230
+ await Promise . all (
2231
+ primitiveKeyData . map ( async ( keyData ) => {
2232
+ const value = keyData . data . encoding ? BufferUtils . base64Decode ( keyData . data . value ) : keyData . data . value ;
2233
+ await root . set ( keyData . key , value ) ;
2234
+ } ) ,
2235
+ ) ;
2236
+
2237
+ // check everything is applied correctly
2238
+ primitiveKeyData . forEach ( ( keyData ) => {
2239
+ if ( keyData . data . encoding ) {
2240
+ expect (
2241
+ BufferUtils . areBuffersEqual ( root . get ( keyData . key ) , BufferUtils . base64Decode ( keyData . data . value ) ) ,
2242
+ `Check root has correct value for "${ keyData . key } " key after LiveMap.set call` ,
2243
+ ) . to . be . true ;
2244
+ } else {
2245
+ expect ( root . get ( keyData . key ) ) . to . equal (
2246
+ keyData . data . value ,
2247
+ `Check root has correct value for "${ keyData . key } " key after LiveMap.set call` ,
2248
+ ) ;
2249
+ }
2250
+ } ) ;
2251
+ } ,
2252
+ } ,
2253
+
2254
+ {
2255
+ description : 'LiveMap.set sends MAP_SET operation with reference to another LiveObject' ,
2256
+ action : async ( ctx ) => {
2257
+ const { root, liveObjectsHelper, channelName } = ctx ;
2258
+
2259
+ await liveObjectsHelper . createAndSetOnMap ( channelName , {
2260
+ mapObjectId : 'root' ,
2261
+ key : 'counter' ,
2262
+ createOp : liveObjectsHelper . counterCreateOp ( ) ,
2263
+ } ) ;
2264
+ await liveObjectsHelper . createAndSetOnMap ( channelName , {
2265
+ mapObjectId : 'root' ,
2266
+ key : 'map' ,
2267
+ createOp : liveObjectsHelper . mapCreateOp ( ) ,
2268
+ } ) ;
2269
+
2270
+ const counter = root . get ( 'counter' ) ;
2271
+ const map = root . get ( 'map' ) ;
2272
+
2273
+ await root . set ( 'counter2' , counter ) ;
2274
+ await root . set ( 'map2' , map ) ;
2275
+
2276
+ expect ( root . get ( 'counter2' ) ) . to . equal (
2277
+ counter ,
2278
+ 'Check can set a reference to a LiveCounter object on a root via a LiveMap.set call' ,
2279
+ ) ;
2280
+ expect ( root . get ( 'map2' ) ) . to . equal (
2281
+ map ,
2282
+ 'Check can set a reference to a LiveMap object on a root via a LiveMap.set call' ,
2283
+ ) ;
2284
+ } ,
2285
+ } ,
2286
+
2287
+ {
2288
+ description : 'LiveMap.set throws on invalid input' ,
2289
+ action : async ( ctx ) => {
2290
+ const { root, liveObjectsHelper, channelName } = ctx ;
2291
+
2292
+ await liveObjectsHelper . createAndSetOnMap ( channelName , {
2293
+ mapObjectId : 'root' ,
2294
+ key : 'map' ,
2295
+ createOp : liveObjectsHelper . mapCreateOp ( ) ,
2296
+ } ) ;
2297
+
2298
+ const map = root . get ( 'map' ) ;
2299
+
2300
+ await expectRejectedWith ( async ( ) => map . set ( ) , 'Map key should be string' ) ;
2301
+ await expectRejectedWith ( async ( ) => map . set ( null ) , 'Map key should be string' ) ;
2302
+ await expectRejectedWith ( async ( ) => map . set ( 1 ) , 'Map key should be string' ) ;
2303
+ await expectRejectedWith ( async ( ) => map . set ( BigInt ( 1 ) ) , 'Map key should be string' ) ;
2304
+ await expectRejectedWith ( async ( ) => map . set ( true ) , 'Map key should be string' ) ;
2305
+ await expectRejectedWith ( async ( ) => map . set ( Symbol ( ) ) , 'Map key should be string' ) ;
2306
+ await expectRejectedWith ( async ( ) => map . set ( { } ) , 'Map key should be string' ) ;
2307
+ await expectRejectedWith ( async ( ) => map . set ( [ ] ) , 'Map key should be string' ) ;
2308
+ await expectRejectedWith ( async ( ) => map . set ( map ) , 'Map key should be string' ) ;
2309
+
2310
+ await expectRejectedWith ( async ( ) => map . set ( 'key' ) , 'Map value data type is unsupported' ) ;
2311
+ await expectRejectedWith ( async ( ) => map . set ( 'key' , null ) , 'Map value data type is unsupported' ) ;
2312
+ await expectRejectedWith ( async ( ) => map . set ( 'key' , BigInt ( 1 ) ) , 'Map value data type is unsupported' ) ;
2313
+ await expectRejectedWith ( async ( ) => map . set ( 'key' , Symbol ( ) ) , 'Map value data type is unsupported' ) ;
2314
+ await expectRejectedWith ( async ( ) => map . set ( 'key' , { } ) , 'Map value data type is unsupported' ) ;
2315
+ await expectRejectedWith ( async ( ) => map . set ( 'key' , [ ] ) , 'Map value data type is unsupported' ) ;
2316
+ } ,
2317
+ } ,
2318
+
2319
+ {
2320
+ description : 'LiveMap.remove sends MAP_REMOVE operation' ,
2321
+ action : async ( ctx ) => {
2322
+ const { root, liveObjectsHelper, channelName } = ctx ;
2323
+
2324
+ await liveObjectsHelper . createAndSetOnMap ( channelName , {
2325
+ mapObjectId : 'root' ,
2326
+ key : 'map' ,
2327
+ createOp : liveObjectsHelper . mapCreateOp ( {
2328
+ entries : {
2329
+ foo : { data : { value : 1 } } ,
2330
+ bar : { data : { value : 1 } } ,
2331
+ baz : { data : { value : 1 } } ,
2332
+ } ,
2333
+ } ) ,
2334
+ } ) ;
2335
+
2336
+ const map = root . get ( 'map' ) ;
2337
+
2338
+ await map . remove ( 'foo' ) ;
2339
+ await map . remove ( 'bar' ) ;
2340
+
2341
+ expect ( map . get ( 'foo' ) , 'Check can remove a key from a root via a LiveMap.remove call' ) . to . not . exist ;
2342
+ expect ( map . get ( 'bar' ) , 'Check can remove a key from a root via a LiveMap.remove call' ) . to . not . exist ;
2343
+ expect (
2344
+ map . get ( 'baz' ) ,
2345
+ 'Check non-removed keys are still present on a root after LiveMap.remove call for another keys' ,
2346
+ ) . to . equal ( 1 ) ;
2347
+ } ,
2348
+ } ,
2349
+
2350
+ {
2351
+ description : 'LiveMap.remove throws on invalid input' ,
2352
+ action : async ( ctx ) => {
2353
+ const { root, liveObjectsHelper, channelName } = ctx ;
2354
+
2355
+ await liveObjectsHelper . createAndSetOnMap ( channelName , {
2356
+ mapObjectId : 'root' ,
2357
+ key : 'map' ,
2358
+ createOp : liveObjectsHelper . mapCreateOp ( ) ,
2359
+ } ) ;
2360
+
2361
+ const map = root . get ( 'map' ) ;
2362
+
2363
+ await expectRejectedWith ( async ( ) => map . remove ( ) , 'Map key should be string' ) ;
2364
+ await expectRejectedWith ( async ( ) => map . remove ( null ) , 'Map key should be string' ) ;
2365
+ await expectRejectedWith ( async ( ) => map . remove ( 1 ) , 'Map key should be string' ) ;
2366
+ await expectRejectedWith ( async ( ) => map . remove ( BigInt ( 1 ) ) , 'Map key should be string' ) ;
2367
+ await expectRejectedWith ( async ( ) => map . remove ( true ) , 'Map key should be string' ) ;
2368
+ await expectRejectedWith ( async ( ) => map . remove ( Symbol ( ) ) , 'Map key should be string' ) ;
2369
+ await expectRejectedWith ( async ( ) => map . remove ( { } ) , 'Map key should be string' ) ;
2370
+ await expectRejectedWith ( async ( ) => map . remove ( [ ] ) , 'Map key should be string' ) ;
2371
+ await expectRejectedWith ( async ( ) => map . remove ( map ) , 'Map key should be string' ) ;
2372
+ } ,
2373
+ } ,
2374
+ ] ;
2375
+
2063
2376
/** @nospec */
2064
2377
forScenarios (
2065
- [ ...stateSyncSequenceScanarios , ...applyOperationsScenarios , ...applyOperationsDuringSyncScenarios ] ,
2378
+ [
2379
+ ...stateSyncSequenceScenarios ,
2380
+ ...applyOperationsScenarios ,
2381
+ ...applyOperationsDuringSyncScenarios ,
2382
+ ...writeApiScenarios ,
2383
+ ] ,
2066
2384
async function ( helper , scenario ) {
2067
2385
const liveObjectsHelper = new LiveObjectsHelper ( helper ) ;
2068
2386
const client = RealtimeWithLiveObjects ( helper ) ;
0 commit comments