@webex/plugin-meetings 3.11.0-next.23 → 3.11.0-next.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/hashTree/hashTreeParser.js +226 -114
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/types/hashTree/hashTreeParser.d.ts +22 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +1 -1
- package/src/hashTree/hashTreeParser.ts +180 -88
- package/test/unit/spec/hashTree/hashTreeParser.ts +477 -4
|
@@ -532,6 +532,46 @@ describe('HashTreeParser', () => {
|
|
|
532
532
|
});
|
|
533
533
|
});
|
|
534
534
|
});
|
|
535
|
+
|
|
536
|
+
it('handles sync response that has locusStateElements undefined', async () => {
|
|
537
|
+
const minimalInitialLocus = {
|
|
538
|
+
dataSets: [],
|
|
539
|
+
locus: null,
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const parser = createHashTreeParser(minimalInitialLocus, null);
|
|
543
|
+
|
|
544
|
+
const mainDataSet = createDataSet('main', 16, 1100);
|
|
545
|
+
|
|
546
|
+
// Mock getAllVisibleDataSetsFromLocus to return the main dataset
|
|
547
|
+
mockGetAllDataSetsMetadata(webexRequest, visibleDataSetsUrl, [mainDataSet]);
|
|
548
|
+
|
|
549
|
+
// Mock the sync response to have locusStateElements: undefined
|
|
550
|
+
// This is what sendInitializationSyncRequestToLocus will receive and pass to parseMessage
|
|
551
|
+
mockSyncRequest(webexRequest, mainDataSet.url, {
|
|
552
|
+
dataSets: [mainDataSet],
|
|
553
|
+
visibleDataSetsUrl,
|
|
554
|
+
locusUrl,
|
|
555
|
+
locusStateElements: undefined,
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// Trigger sendInitializationSyncRequestToLocus via initializeFromMessage
|
|
559
|
+
await parser.initializeFromMessage({
|
|
560
|
+
dataSets: [],
|
|
561
|
+
visibleDataSetsUrl,
|
|
562
|
+
locusUrl,
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Verify the hash tree was created for main dataset
|
|
566
|
+
expect(parser.dataSets.main.hashTree).to.be.instanceOf(HashTree);
|
|
567
|
+
|
|
568
|
+
// updateItems should NOT have been called because locusStateElements is undefined
|
|
569
|
+
const mainUpdateItemsStub = sinon.spy(parser.dataSets.main.hashTree, 'updateItems');
|
|
570
|
+
assert.notCalled(mainUpdateItemsStub);
|
|
571
|
+
|
|
572
|
+
// callback should not be called, because there are no updates
|
|
573
|
+
assert.notCalled(callback);
|
|
574
|
+
});
|
|
535
575
|
});
|
|
536
576
|
|
|
537
577
|
describe('#initializeFromGetLociResponse', () => {
|
|
@@ -855,10 +895,6 @@ describe('HashTreeParser', () => {
|
|
|
855
895
|
// Verify putItem was called on self hash tree with metadata
|
|
856
896
|
assert.calledOnceWithExactly(selfPutItemSpy, {type: 'metadata', id: 5, version: 51});
|
|
857
897
|
|
|
858
|
-
console.log(
|
|
859
|
-
'callback calls',
|
|
860
|
-
callback.getCalls().map((call) => JSON.stringify(call.args, null, 2))
|
|
861
|
-
);
|
|
862
898
|
// Verify callback was called with metadata object and removed dataset objects
|
|
863
899
|
assert.calledOnceWithExactly(callback, LocusInfoUpdateType.OBJECTS_UPDATED, {
|
|
864
900
|
updatedObjects: [
|
|
@@ -1860,6 +1896,443 @@ describe('HashTreeParser', () => {
|
|
|
1860
1896
|
assert.notCalled(callback);
|
|
1861
1897
|
});
|
|
1862
1898
|
});
|
|
1899
|
+
|
|
1900
|
+
describe('heartbeat watchdog', () => {
|
|
1901
|
+
it('initiates sync immediately only for the specific data set whose heartbeat watchdog fires', async () => {
|
|
1902
|
+
const parser = createHashTreeParser();
|
|
1903
|
+
const heartbeatIntervalMs = 5000;
|
|
1904
|
+
|
|
1905
|
+
// Send initial heartbeat message for 'main' only
|
|
1906
|
+
const heartbeatMessage = {
|
|
1907
|
+
dataSets: [
|
|
1908
|
+
{
|
|
1909
|
+
...createDataSet('main', 16, 1100),
|
|
1910
|
+
root: parser.dataSets.main.hashTree.getRootHash(),
|
|
1911
|
+
},
|
|
1912
|
+
],
|
|
1913
|
+
visibleDataSetsUrl,
|
|
1914
|
+
locusUrl,
|
|
1915
|
+
heartbeatIntervalMs,
|
|
1916
|
+
};
|
|
1917
|
+
|
|
1918
|
+
await parser.handleMessage(heartbeatMessage, 'initial heartbeat');
|
|
1919
|
+
|
|
1920
|
+
// Verify only 'main' watchdog timer is set
|
|
1921
|
+
expect(parser.dataSets.main.heartbeatWatchdogTimer).to.not.be.undefined;
|
|
1922
|
+
expect(parser.dataSets.self.heartbeatWatchdogTimer).to.be.undefined;
|
|
1923
|
+
expect(parser.dataSets['atd-unmuted'].heartbeatWatchdogTimer).to.be.undefined;
|
|
1924
|
+
|
|
1925
|
+
// Mock responses for performSync (GET hashtree then POST sync for leafCount > 1)
|
|
1926
|
+
const mainDataSetUrl = parser.dataSets.main.url;
|
|
1927
|
+
mockGetHashesFromLocusResponse(
|
|
1928
|
+
mainDataSetUrl,
|
|
1929
|
+
new Array(16).fill('00000000000000000000000000000000'),
|
|
1930
|
+
createDataSet('main', 16, 1101)
|
|
1931
|
+
);
|
|
1932
|
+
mockSendSyncRequestResponse(mainDataSetUrl, null);
|
|
1933
|
+
|
|
1934
|
+
// Advance time past heartbeatIntervalMs + backoff (Math.random returns 0, so backoff = 0)
|
|
1935
|
+
// performSync is called immediately when the watchdog fires - no additional delay
|
|
1936
|
+
await clock.tickAsync(heartbeatIntervalMs);
|
|
1937
|
+
|
|
1938
|
+
// Verify sync request was sent immediately for 'main' (GET hashtree + POST sync)
|
|
1939
|
+
assert.calledWith(
|
|
1940
|
+
webexRequest,
|
|
1941
|
+
sinon.match({
|
|
1942
|
+
method: 'GET',
|
|
1943
|
+
uri: `${mainDataSetUrl}/hashtree`,
|
|
1944
|
+
})
|
|
1945
|
+
);
|
|
1946
|
+
|
|
1947
|
+
// Verify no sync requests were sent for other datasets
|
|
1948
|
+
assert.neverCalledWith(
|
|
1949
|
+
webexRequest,
|
|
1950
|
+
sinon.match({
|
|
1951
|
+
method: 'POST',
|
|
1952
|
+
uri: `${parser.dataSets.self.url}/sync`,
|
|
1953
|
+
})
|
|
1954
|
+
);
|
|
1955
|
+
assert.neverCalledWith(
|
|
1956
|
+
webexRequest,
|
|
1957
|
+
sinon.match({
|
|
1958
|
+
method: 'GET',
|
|
1959
|
+
uri: `${parser.dataSets['atd-unmuted'].url}/hashtree`,
|
|
1960
|
+
})
|
|
1961
|
+
);
|
|
1962
|
+
});
|
|
1963
|
+
|
|
1964
|
+
it('calls POST sync directly for leafCount === 1 data sets', async () => {
|
|
1965
|
+
const parser = createHashTreeParser();
|
|
1966
|
+
const heartbeatIntervalMs = 5000;
|
|
1967
|
+
|
|
1968
|
+
// Send heartbeat for 'self' (leafCount === 1)
|
|
1969
|
+
const heartbeatMessage = {
|
|
1970
|
+
dataSets: [
|
|
1971
|
+
{
|
|
1972
|
+
...createDataSet('self', 1, 2100),
|
|
1973
|
+
url: parser.dataSets.self.url,
|
|
1974
|
+
root: parser.dataSets.self.hashTree.getRootHash(),
|
|
1975
|
+
},
|
|
1976
|
+
],
|
|
1977
|
+
visibleDataSetsUrl,
|
|
1978
|
+
locusUrl,
|
|
1979
|
+
heartbeatIntervalMs,
|
|
1980
|
+
};
|
|
1981
|
+
|
|
1982
|
+
await parser.handleMessage(heartbeatMessage, 'self heartbeat');
|
|
1983
|
+
|
|
1984
|
+
// Mock sync response for self
|
|
1985
|
+
mockSendSyncRequestResponse(parser.dataSets.self.url, null);
|
|
1986
|
+
|
|
1987
|
+
// Advance time past watchdog delay
|
|
1988
|
+
await clock.tickAsync(heartbeatIntervalMs);
|
|
1989
|
+
|
|
1990
|
+
// For leafCount === 1, performSync skips GET hashtree and goes straight to POST sync
|
|
1991
|
+
assert.neverCalledWith(
|
|
1992
|
+
webexRequest,
|
|
1993
|
+
sinon.match({
|
|
1994
|
+
method: 'GET',
|
|
1995
|
+
uri: `${parser.dataSets.self.url}/hashtree`,
|
|
1996
|
+
})
|
|
1997
|
+
);
|
|
1998
|
+
assert.calledWith(
|
|
1999
|
+
webexRequest,
|
|
2000
|
+
sinon.match({
|
|
2001
|
+
method: 'POST',
|
|
2002
|
+
uri: `${parser.dataSets.self.url}/sync`,
|
|
2003
|
+
})
|
|
2004
|
+
);
|
|
2005
|
+
});
|
|
2006
|
+
|
|
2007
|
+
it('sets watchdog timers for each data set in the message', async () => {
|
|
2008
|
+
const parser = createHashTreeParser();
|
|
2009
|
+
const heartbeatIntervalMs = 5000;
|
|
2010
|
+
|
|
2011
|
+
// Send heartbeat with multiple datasets
|
|
2012
|
+
const heartbeatMessage = {
|
|
2013
|
+
dataSets: [
|
|
2014
|
+
{
|
|
2015
|
+
...createDataSet('main', 16, 1100),
|
|
2016
|
+
root: parser.dataSets.main.hashTree.getRootHash(),
|
|
2017
|
+
},
|
|
2018
|
+
{
|
|
2019
|
+
...createDataSet('self', 1, 2100),
|
|
2020
|
+
url: parser.dataSets.self.url,
|
|
2021
|
+
root: parser.dataSets.self.hashTree.getRootHash(),
|
|
2022
|
+
},
|
|
2023
|
+
],
|
|
2024
|
+
visibleDataSetsUrl,
|
|
2025
|
+
locusUrl,
|
|
2026
|
+
heartbeatIntervalMs,
|
|
2027
|
+
};
|
|
2028
|
+
|
|
2029
|
+
await parser.handleMessage(heartbeatMessage, 'multi-dataset heartbeat');
|
|
2030
|
+
|
|
2031
|
+
// Watchdog timers should be set for both datasets in the message
|
|
2032
|
+
expect(parser.dataSets.main.heartbeatWatchdogTimer).to.not.be.undefined;
|
|
2033
|
+
expect(parser.dataSets.self.heartbeatWatchdogTimer).to.not.be.undefined;
|
|
2034
|
+
// But not for datasets not in the message
|
|
2035
|
+
expect(parser.dataSets['atd-unmuted'].heartbeatWatchdogTimer).to.be.undefined;
|
|
2036
|
+
});
|
|
2037
|
+
|
|
2038
|
+
it('resets the watchdog timer for a specific data set when a new heartbeat for it is received', async () => {
|
|
2039
|
+
const parser = createHashTreeParser();
|
|
2040
|
+
const heartbeatIntervalMs = 5000;
|
|
2041
|
+
|
|
2042
|
+
// Send first heartbeat for 'main'
|
|
2043
|
+
const heartbeat1 = {
|
|
2044
|
+
dataSets: [
|
|
2045
|
+
{
|
|
2046
|
+
...createDataSet('main', 16, 1100),
|
|
2047
|
+
root: parser.dataSets.main.hashTree.getRootHash(),
|
|
2048
|
+
},
|
|
2049
|
+
],
|
|
2050
|
+
visibleDataSetsUrl,
|
|
2051
|
+
locusUrl,
|
|
2052
|
+
heartbeatIntervalMs,
|
|
2053
|
+
};
|
|
2054
|
+
|
|
2055
|
+
await parser.handleMessage(heartbeat1, 'first heartbeat');
|
|
2056
|
+
|
|
2057
|
+
const firstTimer = parser.dataSets.main.heartbeatWatchdogTimer;
|
|
2058
|
+
expect(firstTimer).to.not.be.undefined;
|
|
2059
|
+
|
|
2060
|
+
// Advance time to just before the watchdog would fire
|
|
2061
|
+
clock.tick(4000);
|
|
2062
|
+
|
|
2063
|
+
// Send second heartbeat for 'main' - this should reset the watchdog
|
|
2064
|
+
const heartbeat2 = {
|
|
2065
|
+
dataSets: [
|
|
2066
|
+
{
|
|
2067
|
+
...createDataSet('main', 16, 1101),
|
|
2068
|
+
root: parser.dataSets.main.hashTree.getRootHash(),
|
|
2069
|
+
},
|
|
2070
|
+
],
|
|
2071
|
+
visibleDataSetsUrl,
|
|
2072
|
+
locusUrl,
|
|
2073
|
+
heartbeatIntervalMs,
|
|
2074
|
+
};
|
|
2075
|
+
|
|
2076
|
+
await parser.handleMessage(heartbeat2, 'second heartbeat');
|
|
2077
|
+
|
|
2078
|
+
const secondTimer = parser.dataSets.main.heartbeatWatchdogTimer;
|
|
2079
|
+
expect(secondTimer).to.not.be.undefined;
|
|
2080
|
+
expect(secondTimer).to.not.equal(firstTimer);
|
|
2081
|
+
|
|
2082
|
+
// Advance another 4000ms (total 8000ms from start, but only 4000ms since last heartbeat)
|
|
2083
|
+
// The watchdog should NOT fire yet
|
|
2084
|
+
await clock.tickAsync(4000);
|
|
2085
|
+
|
|
2086
|
+
// No sync requests should have been sent
|
|
2087
|
+
assert.notCalled(webexRequest);
|
|
2088
|
+
});
|
|
2089
|
+
|
|
2090
|
+
it('resets the watchdog timer when a normal message (with locusStateElements) is received', async () => {
|
|
2091
|
+
const parser = createHashTreeParser();
|
|
2092
|
+
const heartbeatIntervalMs = 5000;
|
|
2093
|
+
|
|
2094
|
+
// Send initial heartbeat to start the watchdog for 'main'
|
|
2095
|
+
const heartbeat = {
|
|
2096
|
+
dataSets: [
|
|
2097
|
+
{
|
|
2098
|
+
...createDataSet('main', 16, 1100),
|
|
2099
|
+
root: parser.dataSets.main.hashTree.getRootHash(),
|
|
2100
|
+
},
|
|
2101
|
+
],
|
|
2102
|
+
visibleDataSetsUrl,
|
|
2103
|
+
locusUrl,
|
|
2104
|
+
heartbeatIntervalMs,
|
|
2105
|
+
};
|
|
2106
|
+
|
|
2107
|
+
await parser.handleMessage(heartbeat, 'initial heartbeat');
|
|
2108
|
+
|
|
2109
|
+
const firstTimer = parser.dataSets.main.heartbeatWatchdogTimer;
|
|
2110
|
+
expect(firstTimer).to.not.be.undefined;
|
|
2111
|
+
|
|
2112
|
+
// Advance time partially
|
|
2113
|
+
clock.tick(3000);
|
|
2114
|
+
|
|
2115
|
+
// Stub updateItems so the normal message is processed
|
|
2116
|
+
sinon.stub(parser.dataSets.main.hashTree, 'updateItems').returns([true]);
|
|
2117
|
+
|
|
2118
|
+
// Send a normal message (with locusStateElements) for 'main' - should also reset watchdog
|
|
2119
|
+
const normalMessage = {
|
|
2120
|
+
dataSets: [createDataSet('main', 16, 1101)],
|
|
2121
|
+
visibleDataSetsUrl,
|
|
2122
|
+
locusUrl,
|
|
2123
|
+
locusStateElements: [
|
|
2124
|
+
{
|
|
2125
|
+
htMeta: {
|
|
2126
|
+
elementId: {type: 'locus' as const, id: 0, version: 201},
|
|
2127
|
+
dataSetNames: ['main'],
|
|
2128
|
+
},
|
|
2129
|
+
data: {someData: 'value'},
|
|
2130
|
+
},
|
|
2131
|
+
],
|
|
2132
|
+
heartbeatIntervalMs,
|
|
2133
|
+
};
|
|
2134
|
+
|
|
2135
|
+
await parser.handleMessage(normalMessage, 'normal message');
|
|
2136
|
+
|
|
2137
|
+
const secondTimer = parser.dataSets.main.heartbeatWatchdogTimer;
|
|
2138
|
+
expect(secondTimer).to.not.be.undefined;
|
|
2139
|
+
expect(secondTimer).to.not.equal(firstTimer);
|
|
2140
|
+
});
|
|
2141
|
+
|
|
2142
|
+
it('does not set the watchdog timer when heartbeatIntervalMs is not set', async () => {
|
|
2143
|
+
const parser = createHashTreeParser();
|
|
2144
|
+
|
|
2145
|
+
// Send a heartbeat message without heartbeatIntervalMs
|
|
2146
|
+
const heartbeatMessage = createHeartbeatMessage(
|
|
2147
|
+
'main',
|
|
2148
|
+
16,
|
|
2149
|
+
1100,
|
|
2150
|
+
parser.dataSets.main.hashTree.getRootHash()
|
|
2151
|
+
);
|
|
2152
|
+
|
|
2153
|
+
await parser.handleMessage(heartbeatMessage, 'heartbeat without interval');
|
|
2154
|
+
|
|
2155
|
+
expect(parser.dataSets.main.heartbeatWatchdogTimer).to.be.undefined;
|
|
2156
|
+
});
|
|
2157
|
+
|
|
2158
|
+
it('stops all watchdog timers when meeting ends', async () => {
|
|
2159
|
+
const parser = createHashTreeParser();
|
|
2160
|
+
const heartbeatIntervalMs = 5000;
|
|
2161
|
+
|
|
2162
|
+
// Send heartbeat for multiple datasets
|
|
2163
|
+
const heartbeat = {
|
|
2164
|
+
dataSets: [
|
|
2165
|
+
{
|
|
2166
|
+
...createDataSet('main', 16, 1100),
|
|
2167
|
+
root: parser.dataSets.main.hashTree.getRootHash(),
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
...createDataSet('self', 1, 2100),
|
|
2171
|
+
url: parser.dataSets.self.url,
|
|
2172
|
+
root: parser.dataSets.self.hashTree.getRootHash(),
|
|
2173
|
+
},
|
|
2174
|
+
],
|
|
2175
|
+
visibleDataSetsUrl,
|
|
2176
|
+
locusUrl,
|
|
2177
|
+
heartbeatIntervalMs,
|
|
2178
|
+
};
|
|
2179
|
+
|
|
2180
|
+
await parser.handleMessage(heartbeat, 'initial heartbeat');
|
|
2181
|
+
|
|
2182
|
+
expect(parser.dataSets.main.heartbeatWatchdogTimer).to.not.be.undefined;
|
|
2183
|
+
expect(parser.dataSets.self.heartbeatWatchdogTimer).to.not.be.undefined;
|
|
2184
|
+
|
|
2185
|
+
// Stub updateItems to return true for the roster drop detection
|
|
2186
|
+
sinon.stub(parser.dataSets.self.hashTree, 'updateItems').returns([true]);
|
|
2187
|
+
|
|
2188
|
+
// Send a roster drop message that triggers MEETING_ENDED
|
|
2189
|
+
const rosterDropMessage = {
|
|
2190
|
+
dataSets: [createDataSet('self', 1, 2101)],
|
|
2191
|
+
visibleDataSetsUrl,
|
|
2192
|
+
locusUrl,
|
|
2193
|
+
locusStateElements: [
|
|
2194
|
+
{
|
|
2195
|
+
htMeta: {
|
|
2196
|
+
elementId: {type: 'self' as const, id: 4, version: 102},
|
|
2197
|
+
dataSetNames: ['self'],
|
|
2198
|
+
},
|
|
2199
|
+
data: undefined,
|
|
2200
|
+
},
|
|
2201
|
+
],
|
|
2202
|
+
heartbeatIntervalMs,
|
|
2203
|
+
};
|
|
2204
|
+
|
|
2205
|
+
await parser.handleMessage(rosterDropMessage, 'roster drop');
|
|
2206
|
+
|
|
2207
|
+
// All watchdog timers should have been stopped and NOT restarted
|
|
2208
|
+
expect(parser.dataSets.main.heartbeatWatchdogTimer).to.be.undefined;
|
|
2209
|
+
expect(parser.dataSets.self.heartbeatWatchdogTimer).to.be.undefined;
|
|
2210
|
+
});
|
|
2211
|
+
|
|
2212
|
+
it("uses each data set's own backoff for its watchdog delay", async () => {
|
|
2213
|
+
// Create a parser where datasets have different backoff configs
|
|
2214
|
+
const initialLocus = {
|
|
2215
|
+
dataSets: [
|
|
2216
|
+
{
|
|
2217
|
+
...createDataSet('main', 16, 1000),
|
|
2218
|
+
backoff: {maxMs: 500, exponent: 2},
|
|
2219
|
+
},
|
|
2220
|
+
{
|
|
2221
|
+
...createDataSet('self', 1, 2000),
|
|
2222
|
+
url: 'https://locus-a.wbx2.com/locus/api/v1/loci/97d64a5f/participant/713e9f99/datasets/self',
|
|
2223
|
+
backoff: {maxMs: 2000, exponent: 3},
|
|
2224
|
+
},
|
|
2225
|
+
],
|
|
2226
|
+
locus: {
|
|
2227
|
+
...exampleInitialLocus.locus,
|
|
2228
|
+
},
|
|
2229
|
+
};
|
|
2230
|
+
|
|
2231
|
+
const metadata = {
|
|
2232
|
+
...exampleMetadata,
|
|
2233
|
+
visibleDataSets: [
|
|
2234
|
+
{name: 'main', url: initialLocus.dataSets[0].url},
|
|
2235
|
+
{name: 'self', url: initialLocus.dataSets[1].url},
|
|
2236
|
+
],
|
|
2237
|
+
};
|
|
2238
|
+
|
|
2239
|
+
const parser = createHashTreeParser(initialLocus, metadata);
|
|
2240
|
+
const heartbeatIntervalMs = 5000;
|
|
2241
|
+
|
|
2242
|
+
// Set Math.random to return 1 so that backoff = 1^exponent * maxMs = maxMs
|
|
2243
|
+
mathRandomStub.returns(1);
|
|
2244
|
+
|
|
2245
|
+
// Send heartbeat for both datasets
|
|
2246
|
+
const heartbeat = {
|
|
2247
|
+
dataSets: [
|
|
2248
|
+
{
|
|
2249
|
+
...createDataSet('main', 16, 1100),
|
|
2250
|
+
backoff: {maxMs: 500, exponent: 2},
|
|
2251
|
+
root: parser.dataSets.main.hashTree.getRootHash(),
|
|
2252
|
+
},
|
|
2253
|
+
{
|
|
2254
|
+
...createDataSet('self', 1, 2100),
|
|
2255
|
+
url: parser.dataSets.self.url,
|
|
2256
|
+
backoff: {maxMs: 2000, exponent: 3},
|
|
2257
|
+
root: parser.dataSets.self.hashTree.getRootHash(),
|
|
2258
|
+
},
|
|
2259
|
+
],
|
|
2260
|
+
visibleDataSetsUrl,
|
|
2261
|
+
locusUrl,
|
|
2262
|
+
heartbeatIntervalMs,
|
|
2263
|
+
};
|
|
2264
|
+
|
|
2265
|
+
await parser.handleMessage(heartbeat, 'heartbeat');
|
|
2266
|
+
|
|
2267
|
+
// 'main' watchdog delay = 5000 + 1^2 * 500 = 5500ms
|
|
2268
|
+
// 'self' watchdog delay = 5000 + 1^3 * 2000 = 7000ms
|
|
2269
|
+
|
|
2270
|
+
// Mock sync responses
|
|
2271
|
+
mockGetHashesFromLocusResponse(
|
|
2272
|
+
parser.dataSets.main.url,
|
|
2273
|
+
new Array(16).fill('00000000000000000000000000000000'),
|
|
2274
|
+
createDataSet('main', 16, 1101)
|
|
2275
|
+
);
|
|
2276
|
+
mockSendSyncRequestResponse(parser.dataSets.main.url, null);
|
|
2277
|
+
mockSendSyncRequestResponse(parser.dataSets.self.url, null);
|
|
2278
|
+
|
|
2279
|
+
// At 5499ms, neither watchdog should have fired
|
|
2280
|
+
await clock.tickAsync(5499);
|
|
2281
|
+
assert.notCalled(webexRequest);
|
|
2282
|
+
|
|
2283
|
+
// At 5500ms, 'main' watchdog fires and performSync runs immediately
|
|
2284
|
+
await clock.tickAsync(1);
|
|
2285
|
+
|
|
2286
|
+
// main sync should have triggered immediately (GET hashtree + POST sync)
|
|
2287
|
+
assert.calledWith(
|
|
2288
|
+
webexRequest,
|
|
2289
|
+
sinon.match({
|
|
2290
|
+
method: 'GET',
|
|
2291
|
+
uri: `${parser.dataSets.main.url}/hashtree`,
|
|
2292
|
+
})
|
|
2293
|
+
);
|
|
2294
|
+
|
|
2295
|
+
webexRequest.resetHistory();
|
|
2296
|
+
|
|
2297
|
+
// At 7000ms, 'self' watchdog fires and performSync runs immediately
|
|
2298
|
+
await clock.tickAsync(1500);
|
|
2299
|
+
|
|
2300
|
+
// self sync should have also triggered (POST sync only, leafCount === 1)
|
|
2301
|
+
assert.calledWith(
|
|
2302
|
+
webexRequest,
|
|
2303
|
+
sinon.match({
|
|
2304
|
+
method: 'POST',
|
|
2305
|
+
uri: `${parser.dataSets.self.url}/sync`,
|
|
2306
|
+
})
|
|
2307
|
+
);
|
|
2308
|
+
});
|
|
2309
|
+
|
|
2310
|
+
it('does not set watchdog for data sets without a hash tree', async () => {
|
|
2311
|
+
const parser = createHashTreeParser();
|
|
2312
|
+
const heartbeatIntervalMs = 5000;
|
|
2313
|
+
|
|
2314
|
+
// 'atd-active' is in the initial locus but is not visible (no hash tree)
|
|
2315
|
+
// Send heartbeat mentioning a non-visible dataset
|
|
2316
|
+
const heartbeatMessage = {
|
|
2317
|
+
dataSets: [
|
|
2318
|
+
{
|
|
2319
|
+
...createDataSet('main', 16, 1100),
|
|
2320
|
+
root: parser.dataSets.main.hashTree.getRootHash(),
|
|
2321
|
+
},
|
|
2322
|
+
createDataSet('atd-active', 16, 4000),
|
|
2323
|
+
],
|
|
2324
|
+
visibleDataSetsUrl,
|
|
2325
|
+
locusUrl,
|
|
2326
|
+
heartbeatIntervalMs,
|
|
2327
|
+
};
|
|
2328
|
+
|
|
2329
|
+
await parser.handleMessage(heartbeatMessage, 'heartbeat with non-visible dataset');
|
|
2330
|
+
|
|
2331
|
+
// Watchdog set for main (visible) but not for atd-active (no hash tree)
|
|
2332
|
+
expect(parser.dataSets.main.heartbeatWatchdogTimer).to.not.be.undefined;
|
|
2333
|
+
expect(parser.dataSets['atd-active']?.heartbeatWatchdogTimer).to.be.undefined;
|
|
2334
|
+
});
|
|
2335
|
+
});
|
|
1863
2336
|
});
|
|
1864
2337
|
|
|
1865
2338
|
describe('#callLocusInfoUpdateCallback filtering', () => {
|