Skip to content

Commit 6b66b7c

Browse files
committed
Add tests for object-level write API
1 parent ec49323 commit 6b66b7c

File tree

1 file changed

+285
-2
lines changed

1 file changed

+285
-2
lines changed

test/realtime/live_objects.test.js

Lines changed: 285 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ define(['ably', 'shared_helper', 'chai', 'live_objects', 'live_objects_helper'],
518518
{ name: 'negativeMaxSafeIntegerCounter', count: -Number.MAX_SAFE_INTEGER },
519519
];
520520

521-
const stateSyncSequenceScanarios = [
521+
const stateSyncSequenceScenarios = [
522522
{
523523
description: 'STATE_SYNC sequence with state object "tombstone" property creates tombstoned object',
524524
action: async (ctx) => {
@@ -2060,9 +2060,292 @@ define(['ably', 'shared_helper', 'chai', 'live_objects', 'live_objects_helper'],
20602060
},
20612061
];
20622062

2063+
const writeApiScenarios = [
2064+
{
2065+
description: 'LiveCounter.increment sends COUNTER_INC operation',
2066+
action: async (ctx) => {
2067+
const { root, liveObjectsHelper, channelName } = ctx;
2068+
2069+
await liveObjectsHelper.createAndSetOnMap(channelName, {
2070+
mapObjectId: 'root',
2071+
key: 'counter',
2072+
createOp: liveObjectsHelper.counterCreateOp(),
2073+
});
2074+
2075+
const counter = root.get('counter');
2076+
const increments = [
2077+
1, // value=1
2078+
10, // value=11
2079+
-11, // value=0
2080+
-1, // value=-1
2081+
-10, // value=-11
2082+
11, // value=0
2083+
Number.MAX_SAFE_INTEGER, // value=9007199254740991
2084+
-Number.MAX_SAFE_INTEGER, // value=0
2085+
-Number.MAX_SAFE_INTEGER, // value=-9007199254740991
2086+
];
2087+
let expectedCounterValue = 0;
2088+
2089+
for (let i = 0; i < increments.length; i++) {
2090+
const increment = increments[i];
2091+
expectedCounterValue += increment;
2092+
await counter.increment(increment);
2093+
2094+
expect(counter.value()).to.equal(
2095+
expectedCounterValue,
2096+
`Check counter has correct value after ${i + 1} LiveCounter.increment calls`,
2097+
);
2098+
}
2099+
},
2100+
},
2101+
2102+
{
2103+
description: 'LiveCounter.increment throws on invalid input',
2104+
action: async (ctx) => {
2105+
const { root, liveObjectsHelper, channelName } = ctx;
2106+
2107+
await liveObjectsHelper.createAndSetOnMap(channelName, {
2108+
mapObjectId: 'root',
2109+
key: 'counter',
2110+
createOp: liveObjectsHelper.counterCreateOp(),
2111+
});
2112+
2113+
const counter = root.get('counter');
2114+
2115+
expect(() => counter.increment()).to.throw('Counter value increment should be a number');
2116+
expect(() => counter.increment(null)).to.throw('Counter value increment should be a number');
2117+
expect(() => counter.increment('foo')).to.throw('Counter value increment should be a number');
2118+
expect(() => counter.increment(BigInt(1))).to.throw('Counter value increment should be a number');
2119+
expect(() => counter.increment(true)).to.throw('Counter value increment should be a number');
2120+
expect(() => counter.increment(Symbol())).to.throw('Counter value increment should be a number');
2121+
expect(() => counter.increment({})).to.throw('Counter value increment should be a number');
2122+
expect(() => counter.increment([])).to.throw('Counter value increment should be a number');
2123+
expect(() => counter.increment(counter)).to.throw('Counter value increment should be a number');
2124+
},
2125+
},
2126+
2127+
{
2128+
description: 'LiveCounter.decrement sends COUNTER_INC operation',
2129+
action: async (ctx) => {
2130+
const { root, liveObjectsHelper, channelName } = ctx;
2131+
2132+
await liveObjectsHelper.createAndSetOnMap(channelName, {
2133+
mapObjectId: 'root',
2134+
key: 'counter',
2135+
createOp: liveObjectsHelper.counterCreateOp(),
2136+
});
2137+
2138+
const counter = root.get('counter');
2139+
const decrements = [
2140+
1, // value=-1
2141+
10, // value=-11
2142+
-11, // value=0
2143+
-1, // value=1
2144+
-10, // value=11
2145+
11, // value=0
2146+
Number.MAX_SAFE_INTEGER, // value=-9007199254740991
2147+
-Number.MAX_SAFE_INTEGER, // value=0
2148+
-Number.MAX_SAFE_INTEGER, // value=9007199254740991
2149+
];
2150+
let expectedCounterValue = 0;
2151+
2152+
for (let i = 0; i < decrements.length; i++) {
2153+
const decrement = decrements[i];
2154+
expectedCounterValue -= decrement;
2155+
await counter.decrement(decrement);
2156+
2157+
expect(counter.value()).to.equal(
2158+
expectedCounterValue,
2159+
`Check counter has correct value after ${i + 1} LiveCounter.decrement calls`,
2160+
);
2161+
}
2162+
},
2163+
},
2164+
2165+
{
2166+
description: 'LiveCounter.decrement throws on invalid input',
2167+
action: async (ctx) => {
2168+
const { root, liveObjectsHelper, channelName } = ctx;
2169+
2170+
await liveObjectsHelper.createAndSetOnMap(channelName, {
2171+
mapObjectId: 'root',
2172+
key: 'counter',
2173+
createOp: liveObjectsHelper.counterCreateOp(),
2174+
});
2175+
2176+
const counter = root.get('counter');
2177+
2178+
expect(() => counter.decrement()).to.throw('Counter value decrement should be a number');
2179+
expect(() => counter.decrement(null)).to.throw('Counter value decrement should be a number');
2180+
expect(() => counter.decrement('foo')).to.throw('Counter value decrement should be a number');
2181+
expect(() => counter.decrement(BigInt(1))).to.throw('Counter value decrement should be a number');
2182+
expect(() => counter.decrement(true)).to.throw('Counter value decrement should be a number');
2183+
expect(() => counter.decrement(Symbol())).to.throw('Counter value decrement should be a number');
2184+
expect(() => counter.decrement({})).to.throw('Counter value decrement should be a number');
2185+
expect(() => counter.decrement([])).to.throw('Counter value decrement should be a number');
2186+
expect(() => counter.decrement(counter)).to.throw('Counter value decrement should be a number');
2187+
},
2188+
},
2189+
2190+
{
2191+
description: 'LiveMap.set sends MAP_SET operation for primitive values',
2192+
action: async (ctx) => {
2193+
const { root } = ctx;
2194+
2195+
await Promise.all(
2196+
primitiveKeyData.map(async (keyData) => {
2197+
const value = keyData.data.encoding ? BufferUtils.base64Decode(keyData.data.value) : keyData.data.value;
2198+
await root.set(keyData.key, value);
2199+
}),
2200+
);
2201+
2202+
// check everything is applied correctly
2203+
primitiveKeyData.forEach((keyData) => {
2204+
if (keyData.data.encoding) {
2205+
expect(
2206+
BufferUtils.areBuffersEqual(root.get(keyData.key), BufferUtils.base64Decode(keyData.data.value)),
2207+
`Check root has correct value for "${keyData.key}" key after LiveMap.set call`,
2208+
).to.be.true;
2209+
} else {
2210+
expect(root.get(keyData.key)).to.equal(
2211+
keyData.data.value,
2212+
`Check root has correct value for "${keyData.key}" key after LiveMap.set call`,
2213+
);
2214+
}
2215+
});
2216+
},
2217+
},
2218+
2219+
{
2220+
description: 'LiveMap.set sends MAP_SET operation with reference to another LiveObject',
2221+
action: async (ctx) => {
2222+
const { root, liveObjectsHelper, channelName } = ctx;
2223+
2224+
await liveObjectsHelper.createAndSetOnMap(channelName, {
2225+
mapObjectId: 'root',
2226+
key: 'counter',
2227+
createOp: liveObjectsHelper.counterCreateOp(),
2228+
});
2229+
await liveObjectsHelper.createAndSetOnMap(channelName, {
2230+
mapObjectId: 'root',
2231+
key: 'map',
2232+
createOp: liveObjectsHelper.counterCreateOp(),
2233+
});
2234+
2235+
const counter = root.get('counter');
2236+
const map = root.get('map');
2237+
2238+
await root.set('counter2', counter);
2239+
await root.set('map2', map);
2240+
2241+
expect(root.get('counter2')).to.equal(
2242+
counter,
2243+
'Check can set a reference to a LiveCounter object on a root via a LiveMap.set call',
2244+
);
2245+
expect(root.get('map2')).to.equal(
2246+
map,
2247+
'Check can set a reference to a LiveMap object on a root via a LiveMap.set call',
2248+
);
2249+
},
2250+
},
2251+
2252+
{
2253+
description: 'LiveMap.set throws on invalid input',
2254+
action: async (ctx) => {
2255+
const { root, liveObjectsHelper, channelName } = ctx;
2256+
2257+
await liveObjectsHelper.createAndSetOnMap(channelName, {
2258+
mapObjectId: 'root',
2259+
key: 'map',
2260+
createOp: liveObjectsHelper.mapCreateOp(),
2261+
});
2262+
2263+
const map = root.get('map');
2264+
2265+
expect(() => map.set()).to.throw('Map key should be string');
2266+
expect(() => map.set(null)).to.throw('Map key should be string');
2267+
expect(() => map.set(1)).to.throw('Map key should be string');
2268+
expect(() => map.set(BigInt(1))).to.throw('Map key should be string');
2269+
expect(() => map.set(true)).to.throw('Map key should be string');
2270+
expect(() => map.set(Symbol())).to.throw('Map key should be string');
2271+
expect(() => map.set({})).to.throw('Map key should be string');
2272+
expect(() => map.set([])).to.throw('Map key should be string');
2273+
expect(() => map.set(map)).to.throw('Map key should be string');
2274+
2275+
expect(() => map.set('key')).to.throw('Map value data type is unsupported');
2276+
expect(() => map.set('key', null)).to.throw('Map value data type is unsupported');
2277+
expect(() => map.set('key', BigInt(1))).to.throw('Map value data type is unsupported');
2278+
expect(() => map.set('key', Symbol())).to.throw('Map value data type is unsupported');
2279+
expect(() => map.set('key', {})).to.throw('Map value data type is unsupported');
2280+
expect(() => map.set('key', [])).to.throw('Map value data type is unsupported');
2281+
},
2282+
},
2283+
2284+
{
2285+
description: 'LiveMap.remove sends MAP_REMOVE operation',
2286+
action: async (ctx) => {
2287+
const { root, liveObjectsHelper, channelName } = ctx;
2288+
2289+
await liveObjectsHelper.createAndSetOnMap(channelName, {
2290+
mapObjectId: 'root',
2291+
key: 'map',
2292+
createOp: liveObjectsHelper.mapCreateOp({
2293+
entries: {
2294+
foo: { data: { value: 1 } },
2295+
bar: { data: { value: 1 } },
2296+
baz: { data: { value: 1 } },
2297+
},
2298+
}),
2299+
});
2300+
2301+
const map = root.get('map');
2302+
2303+
await map.remove('foo');
2304+
await map.remove('bar');
2305+
2306+
expect(map.get('foo'), 'Check can remove a key from a root via a LiveMap.remove call').to.not.exist;
2307+
expect(map.get('bar'), 'Check can remove a key from a root via a LiveMap.remove call').to.not.exist;
2308+
expect(
2309+
map.get('baz'),
2310+
'Check non-removed keys are still present on a root after LiveMap.remove call for another keys',
2311+
).to.equal(1);
2312+
},
2313+
},
2314+
2315+
{
2316+
description: 'LiveMap.remove throws on invalid input',
2317+
action: async (ctx) => {
2318+
const { root, liveObjectsHelper, channelName } = ctx;
2319+
2320+
await liveObjectsHelper.createAndSetOnMap(channelName, {
2321+
mapObjectId: 'root',
2322+
key: 'map',
2323+
createOp: liveObjectsHelper.mapCreateOp(),
2324+
});
2325+
2326+
const map = root.get('map');
2327+
2328+
expect(() => map.remove()).to.throw('Map key should be string');
2329+
expect(() => map.remove(null)).to.throw('Map key should be string');
2330+
expect(() => map.remove(1)).to.throw('Map key should be string');
2331+
expect(() => map.remove(BigInt(1))).to.throw('Map key should be string');
2332+
expect(() => map.remove(true)).to.throw('Map key should be string');
2333+
expect(() => map.remove(Symbol())).to.throw('Map key should be string');
2334+
expect(() => map.remove({})).to.throw('Map key should be string');
2335+
expect(() => map.remove([])).to.throw('Map key should be string');
2336+
expect(() => map.remove(map)).to.throw('Map key should be string');
2337+
},
2338+
},
2339+
];
2340+
20632341
/** @nospec */
20642342
forScenarios(
2065-
[...stateSyncSequenceScanarios, ...applyOperationsScenarios, ...applyOperationsDuringSyncScenarios],
2343+
[
2344+
...stateSyncSequenceScenarios,
2345+
...applyOperationsScenarios,
2346+
...applyOperationsDuringSyncScenarios,
2347+
...writeApiScenarios,
2348+
],
20662349
async function (helper, scenario) {
20672350
const liveObjectsHelper = new LiveObjectsHelper(helper);
20682351
const client = RealtimeWithLiveObjects(helper);

0 commit comments

Comments
 (0)