Skip to content

Commit 2db42fc

Browse files
committed
Add tests for object-level write API
1 parent 2534ddc commit 2db42fc

File tree

1 file changed

+320
-2
lines changed

1 file changed

+320
-2
lines changed

test/realtime/live_objects.test.js

Lines changed: 320 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ define(['ably', 'shared_helper', 'chai', 'live_objects', 'live_objects_helper'],
5757
return `${paddedTimestamp}-${paddedCounter}@${seriesId}` + (paddedIndex ? `:${paddedIndex}` : '');
5858
}
5959

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+
6071
describe('realtime/live_objects', function () {
6172
this.timeout(60 * 1000);
6273

@@ -518,7 +529,7 @@ define(['ably', 'shared_helper', 'chai', 'live_objects', 'live_objects_helper'],
518529
{ name: 'negativeMaxSafeIntegerCounter', count: -Number.MAX_SAFE_INTEGER },
519530
];
520531

521-
const stateSyncSequenceScanarios = [
532+
const stateSyncSequenceScenarios = [
522533
{
523534
description: 'STATE_SYNC sequence with state object "tombstone" property creates tombstoned object',
524535
action: async (ctx) => {
@@ -2060,9 +2071,316 @@ define(['ably', 'shared_helper', 'chai', 'live_objects', 'live_objects_helper'],
20602071
},
20612072
];
20622073

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+
20632376
/** @nospec */
20642377
forScenarios(
2065-
[...stateSyncSequenceScanarios, ...applyOperationsScenarios, ...applyOperationsDuringSyncScenarios],
2378+
[
2379+
...stateSyncSequenceScenarios,
2380+
...applyOperationsScenarios,
2381+
...applyOperationsDuringSyncScenarios,
2382+
...writeApiScenarios,
2383+
],
20662384
async function (helper, scenario) {
20672385
const liveObjectsHelper = new LiveObjectsHelper(helper);
20682386
const client = RealtimeWithLiveObjects(helper);

0 commit comments

Comments
 (0)