@webex/plugin-meetings 3.10.0-next.8 → 3.10.0-webex-services-ready.1
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/annotation/annotation.types.js.map +1 -1
- package/dist/annotation/constants.js.map +1 -1
- package/dist/annotation/index.js +19 -22
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +6 -6
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/collection.js.map +1 -1
- package/dist/breakouts/edit-lock-error.js +9 -11
- package/dist/breakouts/edit-lock-error.js.map +1 -1
- package/dist/breakouts/events.js.map +1 -1
- package/dist/breakouts/index.js +126 -127
- package/dist/breakouts/index.js.map +1 -1
- package/dist/breakouts/request.js +6 -8
- package/dist/breakouts/request.js.map +1 -1
- package/dist/breakouts/utils.js.map +1 -1
- package/dist/common/browser-detection.js.map +1 -1
- package/dist/common/collection.js +1 -2
- package/dist/common/collection.js.map +1 -1
- package/dist/common/config.js.map +1 -1
- package/dist/common/errors/captcha-error.js +9 -11
- package/dist/common/errors/captcha-error.js.map +1 -1
- package/dist/common/errors/intent-to-join.js +10 -12
- package/dist/common/errors/intent-to-join.js.map +1 -1
- package/dist/common/errors/join-forbidden-error.js +10 -12
- package/dist/common/errors/join-forbidden-error.js.map +1 -1
- package/dist/common/errors/join-meeting.js +10 -12
- package/dist/common/errors/join-meeting.js.map +1 -1
- package/dist/common/errors/join-webinar-error.js +9 -11
- package/dist/common/errors/join-webinar-error.js.map +1 -1
- package/dist/common/errors/media.js +9 -11
- package/dist/common/errors/media.js.map +1 -1
- package/dist/common/errors/multistream-not-supported-error.js +9 -11
- package/dist/common/errors/multistream-not-supported-error.js.map +1 -1
- package/dist/common/errors/no-meeting-info.js +9 -11
- package/dist/common/errors/no-meeting-info.js.map +1 -1
- package/dist/common/errors/parameter.js +11 -14
- package/dist/common/errors/parameter.js.map +1 -1
- package/dist/common/errors/password-error.js +9 -11
- package/dist/common/errors/password-error.js.map +1 -1
- package/dist/common/errors/permission.js +9 -11
- package/dist/common/errors/permission.js.map +1 -1
- package/dist/common/errors/reclaim-host-role-errors.js +32 -38
- package/dist/common/errors/reclaim-host-role-errors.js.map +1 -1
- package/dist/common/errors/reconnection-not-started.js +5 -6
- package/dist/common/errors/reconnection-not-started.js.map +1 -1
- package/dist/common/errors/reconnection.js +9 -11
- package/dist/common/errors/reconnection.js.map +1 -1
- package/dist/common/errors/stats.js +9 -11
- package/dist/common/errors/stats.js.map +1 -1
- package/dist/common/errors/webex-errors.js +20 -29
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/common/errors/webex-meetings-error.js +9 -12
- package/dist/common/errors/webex-meetings-error.js.map +1 -1
- package/dist/common/events/events-scope.js +9 -10
- package/dist/common/events/events-scope.js.map +1 -1
- package/dist/common/events/events.js +9 -10
- package/dist/common/events/events.js.map +1 -1
- package/dist/common/events/trigger-proxy.js.map +1 -1
- package/dist/common/events/util.js.map +1 -1
- package/dist/common/logs/logger-config.js.map +1 -1
- package/dist/common/logs/logger-proxy.js.map +1 -1
- package/dist/common/logs/request.js +17 -17
- package/dist/common/logs/request.js.map +1 -1
- package/dist/common/queue.js +1 -2
- package/dist/common/queue.js.map +1 -1
- package/dist/config.js +0 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +11 -8
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/enums.js.map +1 -1
- package/dist/controls-options-manager/index.js +1 -2
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/types.js.map +1 -1
- package/dist/controls-options-manager/util.js +1 -2
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +20 -0
- package/dist/hashTree/constants.js.map +1 -0
- package/dist/hashTree/hashTree.js +515 -0
- package/dist/hashTree/hashTree.js.map +1 -0
- package/dist/hashTree/hashTreeParser.js +1266 -0
- package/dist/hashTree/hashTreeParser.js.map +1 -0
- package/dist/hashTree/types.js +21 -0
- package/dist/hashTree/types.js.map +1 -0
- package/dist/hashTree/utils.js +48 -0
- package/dist/hashTree/utils.js.map +1 -0
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/interceptors/index.js.map +1 -1
- package/dist/interceptors/locusRetry.js +6 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interceptors/locusRouteToken.js +6 -8
- package/dist/interceptors/locusRouteToken.js.map +1 -1
- package/dist/interpretation/collection.js.map +1 -1
- package/dist/interpretation/index.js +1 -2
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/interpretation/siLanguage.js.map +1 -1
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
- package/dist/locus-info/fullState.js.map +1 -1
- package/dist/locus-info/hostUtils.js.map +1 -1
- package/dist/locus-info/index.js +551 -94
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/infoUtils.js.map +1 -1
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/locus-info/parser.js +3 -4
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/locus-info/types.js +7 -0
- package/dist/locus-info/types.js.map +1 -0
- package/dist/media/MediaConnectionAwaiter.js +1 -2
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/index.js +0 -2
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +15 -17
- package/dist/media/properties.js.map +1 -1
- package/dist/media/util.js.map +1 -1
- package/dist/meeting/brbState.js +8 -9
- package/dist/meeting/brbState.js.map +1 -1
- package/dist/meeting/connectionStateHandler.js +10 -13
- package/dist/meeting/connectionStateHandler.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1555 -1527
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +13 -17
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/muteState.js +11 -12
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +101 -104
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/state.js.map +1 -1
- package/dist/meeting/type.js.map +1 -1
- package/dist/meeting/util.js +24 -23
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting/voicea-meeting.js +3 -3
- package/dist/meeting/voicea-meeting.js.map +1 -1
- package/dist/meeting-info/collection.js +7 -10
- package/dist/meeting-info/collection.js.map +1 -1
- package/dist/meeting-info/index.js +1 -2
- package/dist/meeting-info/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +135 -146
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/request.js +1 -2
- package/dist/meeting-info/request.js.map +1 -1
- package/dist/meeting-info/util.js +36 -37
- package/dist/meeting-info/util.js.map +1 -1
- package/dist/meeting-info/utilv2.js +30 -31
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +6 -8
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +179 -141
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/request.js +6 -8
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +25 -23
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +1 -2
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js +6 -3
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js.map +1 -1
- package/dist/members/collection.js +1 -2
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +18 -21
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +8 -11
- package/dist/members/request.js.map +1 -1
- package/dist/members/types.js.map +1 -1
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +3 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +3 -4
- package/dist/metrics/index.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +1 -2
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/receiveSlot.js +34 -45
- package/dist/multistream/receiveSlot.js.map +1 -1
- package/dist/multistream/receiveSlotManager.js +8 -9
- package/dist/multistream/receiveSlotManager.js.map +1 -1
- package/dist/multistream/remoteMedia.js +12 -15
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.js +1 -2
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +122 -123
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +29 -30
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/personal-meeting-room/index.js +16 -19
- package/dist/personal-meeting-room/index.js.map +1 -1
- package/dist/personal-meeting-room/request.js +7 -10
- package/dist/personal-meeting-room/request.js.map +1 -1
- package/dist/personal-meeting-room/util.js.map +1 -1
- package/dist/reachability/clusterReachability.js +56 -373
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +203 -205
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/reachability.types.js +14 -1
- package/dist/reachability/reachability.types.js.map +1 -1
- package/dist/reachability/reachabilityPeerConnection.js +445 -0
- package/dist/reachability/reachabilityPeerConnection.js.map +1 -0
- package/dist/reachability/request.js.map +1 -1
- package/dist/reachability/util.js.map +1 -1
- package/dist/reactions/constants.js.map +1 -1
- package/dist/reactions/reactions.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/reconnection-manager/index.js +178 -176
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/recording-controller/enums.js.map +1 -1
- package/dist/recording-controller/index.js +1 -2
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/recording-controller/util.js.map +1 -1
- package/dist/roap/index.js +12 -15
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +24 -26
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +75 -76
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/roap/types.js.map +1 -1
- package/dist/transcription/index.js +4 -5
- package/dist/transcription/index.js.map +1 -1
- package/dist/types/constants.d.ts +26 -21
- package/dist/types/hashTree/constants.d.ts +8 -0
- package/dist/types/hashTree/hashTree.d.ts +129 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +260 -0
- package/dist/types/hashTree/types.d.ts +25 -0
- package/dist/types/hashTree/utils.d.ts +9 -0
- package/dist/types/locus-info/index.d.ts +91 -42
- package/dist/types/locus-info/types.d.ts +46 -0
- package/dist/types/meeting/index.d.ts +22 -9
- package/dist/types/meetings/index.d.ts +9 -2
- package/dist/types/metrics/constants.d.ts +2 -0
- package/dist/types/reachability/clusterReachability.d.ts +10 -88
- package/dist/types/reachability/reachability.types.d.ts +12 -1
- package/dist/types/reachability/reachabilityPeerConnection.d.ts +111 -0
- package/dist/webinar/collection.js +1 -2
- package/dist/webinar/collection.js.map +1 -1
- package/dist/webinar/index.js +148 -158
- package/dist/webinar/index.js.map +1 -1
- package/package.json +22 -21
- package/src/constants.ts +13 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTree.ts +463 -0
- package/src/hashTree/hashTreeParser.ts +1161 -0
- package/src/hashTree/types.ts +30 -0
- package/src/hashTree/utils.ts +42 -0
- package/src/locus-info/index.ts +556 -85
- package/src/locus-info/types.ts +48 -0
- package/src/meeting/index.ts +58 -26
- package/src/meeting/util.ts +1 -0
- package/src/meetings/index.ts +104 -51
- package/src/metrics/constants.ts +2 -0
- package/src/reachability/clusterReachability.ts +50 -347
- package/src/reachability/reachability.types.ts +15 -1
- package/src/reachability/reachabilityPeerConnection.ts +416 -0
- package/test/unit/spec/hashTree/hashTree.ts +655 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +1532 -0
- package/test/unit/spec/hashTree/utils.ts +103 -0
- package/test/unit/spec/locus-info/index.js +667 -1
- package/test/unit/spec/meeting/index.js +91 -20
- package/test/unit/spec/meeting/utils.js +77 -0
- package/test/unit/spec/meetings/index.js +71 -26
- package/test/unit/spec/reachability/clusterReachability.ts +281 -138
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
import HashTree from '@webex/plugin-meetings/src/hashTree/hashTree';
|
|
2
|
+
import {EMPTY_HASH} from '@webex/plugin-meetings/src/hashTree/constants';
|
|
3
|
+
|
|
4
|
+
import {expect} from '@webex/test-helper-chai';
|
|
5
|
+
|
|
6
|
+
// Define a type for the leaf data items used in tests
|
|
7
|
+
type LeafDataItem = {
|
|
8
|
+
type: string;
|
|
9
|
+
id: number;
|
|
10
|
+
version: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('HashTree', () => {
|
|
14
|
+
it('should initialize with empty leaves and hashes', () => {
|
|
15
|
+
const leafData: LeafDataItem[] = [];
|
|
16
|
+
const numLeaves = 4;
|
|
17
|
+
const hashTree = new HashTree(leafData, numLeaves);
|
|
18
|
+
|
|
19
|
+
expect(hashTree.leaves).to.deep.equal(new Array(numLeaves).fill(null).map(() => ({})));
|
|
20
|
+
expect(hashTree.leafHashes).to.deep.equal(new Array(numLeaves).fill(EMPTY_HASH));
|
|
21
|
+
expect(hashTree.getLeafCount()).to.equal(numLeaves);
|
|
22
|
+
expect(hashTree.getTotalItemCount()).to.equal(0);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('constructor should allow 0 leaves', () => {
|
|
26
|
+
const leafData: LeafDataItem[] = [];
|
|
27
|
+
const numLeaves = 0;
|
|
28
|
+
const hashTree = new HashTree(leafData, numLeaves);
|
|
29
|
+
expect(hashTree.getLeafCount()).to.equal(0);
|
|
30
|
+
expect(hashTree.getRootHash()).to.equal(EMPTY_HASH);
|
|
31
|
+
expect(hashTree.getHashes()).to.deep.equal([EMPTY_HASH]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('number of leaves must be 0 or a power of 2', () => {
|
|
35
|
+
const leafData: LeafDataItem[] = [];
|
|
36
|
+
const numLeaves = 3; // Not a power of 2
|
|
37
|
+
expect(() => new HashTree(leafData, numLeaves)).to.throw(
|
|
38
|
+
'Number of leaves must be a power of 2, saw 3'
|
|
39
|
+
);
|
|
40
|
+
const numLeavesNegative = -1;
|
|
41
|
+
expect(() => new HashTree(leafData, numLeavesNegative)).to.throw(
|
|
42
|
+
'Number of leaves must be a power of 2, saw -1'
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should have the correct hashes after putting ObjectIds using constructor', () => {
|
|
47
|
+
const oids: LeafDataItem[] = [
|
|
48
|
+
{type: 'participant', id: 1, version: 3}, // Hashes to bucket 1 % 4 = 1
|
|
49
|
+
];
|
|
50
|
+
// numLeaves is 4. Item id 1 % 4 = 1. So, leafHashes[1] will be updated.
|
|
51
|
+
// leafHashes[0], leafHashes[2], leafHashes[3] remain EMPTY_HASH.
|
|
52
|
+
const tree = new HashTree(oids, 4);
|
|
53
|
+
|
|
54
|
+
// These are the expected hash values from the Java reference for a similar structure.
|
|
55
|
+
// The actual values depend on the specific XXHash128 implementation and input serialization.
|
|
56
|
+
// For this test, we'll use the previously provided values, assuming they are correct for the TS implementation.
|
|
57
|
+
expect(tree.getHashes()).to.deep.equal([
|
|
58
|
+
'24a75d115a0a90ddb376a02b435c780f', // Root hash
|
|
59
|
+
'457eeb22808eadfcff92ee47d67acbbf', // Internal node (children: leaf 0, leaf 1)
|
|
60
|
+
'b113a76304e3a7121afecfe1606ee1c1', // Internal node (children: leaf 2, leaf 3)
|
|
61
|
+
EMPTY_HASH, // Leaf 0 hash (empty)
|
|
62
|
+
'42df811f5a902c5b6bfcf50c7004e275', // Leaf 1 hash (for item {type: 'participant', id: 1, version: 3})
|
|
63
|
+
EMPTY_HASH, // Leaf 2 hash (empty)
|
|
64
|
+
EMPTY_HASH, // Leaf 3 hash (empty)
|
|
65
|
+
]);
|
|
66
|
+
expect(tree.getRootHash()).to.equal('24a75d115a0a90ddb376a02b435c780f');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should have the correct hashes after putting multiple ObjectIds using constructor', () => {
|
|
70
|
+
const oids: LeafDataItem[] = [
|
|
71
|
+
{type: 'typeA', id: 1, version: 3}, // Leaf 1 (1 % 4 = 1)
|
|
72
|
+
{type: 'typeA', id: 6, version: 2}, // Leaf 2 (6 % 4 = 2)
|
|
73
|
+
{type: 'typeA', id: 7, version: 1}, // Leaf 3 (7 % 4 = 3)
|
|
74
|
+
{type: 'typeB', id: 11, version: 4}, // Leaf 3 (11 % 4 = 3)
|
|
75
|
+
];
|
|
76
|
+
const tree = new HashTree(oids, 4);
|
|
77
|
+
|
|
78
|
+
// Corrected expected hashes based on the test failure output
|
|
79
|
+
expect(tree.getHashes()).to.deep.equal([
|
|
80
|
+
'c8415198d4abca6f885fe974e9b3729d', // Root
|
|
81
|
+
'457eeb22808eadfcff92ee47d67acbbf', // Internal node (L0, L1)
|
|
82
|
+
'5c9ba182a069c16a77a1928fce52dad8', // Internal node (L2, L3)
|
|
83
|
+
EMPTY_HASH, // Leaf 0 (empty)
|
|
84
|
+
'42df811f5a902c5b6bfcf50c7004e275', // Leaf 1 (item id 1)
|
|
85
|
+
'feb384d8ac6374ffdbee92a9f48f2b40', // Leaf 2 (item id 6)
|
|
86
|
+
'ebfa4f7e104e1e30fbb6b8857ccb685d', // Leaf 3 (items id 7, 11)
|
|
87
|
+
]);
|
|
88
|
+
expect(tree.getRootHash()).to.equal('c8415198d4abca6f885fe974e9b3729d');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should putItems and compute hashes correctly', () => {
|
|
92
|
+
const initialLeafData: LeafDataItem[] = [];
|
|
93
|
+
const numLeaves = 4;
|
|
94
|
+
const hashTree = new HashTree(initialLeafData, numLeaves);
|
|
95
|
+
|
|
96
|
+
const itemsToPut: LeafDataItem[] = [
|
|
97
|
+
{type: 'participant', id: 1, version: 1}, // bucket 1
|
|
98
|
+
{type: 'participant', id: 2, version: 1}, // bucket 2
|
|
99
|
+
];
|
|
100
|
+
const results = hashTree.putItems(itemsToPut);
|
|
101
|
+
|
|
102
|
+
expect(results).to.deep.equal([true, true]);
|
|
103
|
+
expect(hashTree.leaves[1]['participant'][1]).to.deep.equal({
|
|
104
|
+
type: 'participant',
|
|
105
|
+
id: 1,
|
|
106
|
+
version: 1,
|
|
107
|
+
});
|
|
108
|
+
expect(hashTree.leaves[2]['participant'][2]).to.deep.equal({
|
|
109
|
+
type: 'participant',
|
|
110
|
+
id: 2,
|
|
111
|
+
version: 1,
|
|
112
|
+
});
|
|
113
|
+
expect(hashTree.leafHashes[0]).to.equal(EMPTY_HASH);
|
|
114
|
+
expect(hashTree.leafHashes[1]).to.not.equal(EMPTY_HASH);
|
|
115
|
+
expect(hashTree.leafHashes[2]).to.not.equal(EMPTY_HASH);
|
|
116
|
+
expect(hashTree.leafHashes[3]).to.equal(EMPTY_HASH);
|
|
117
|
+
expect(hashTree.getTotalItemCount()).to.equal(2);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('putItem should add a single item and update hash', () => {
|
|
121
|
+
const hashTree = new HashTree([], 2);
|
|
122
|
+
const item: LeafDataItem = {type: 'data', id: 3, version: 1}; // bucket 1
|
|
123
|
+
|
|
124
|
+
const result = hashTree.putItem(item);
|
|
125
|
+
expect(result).to.be.true;
|
|
126
|
+
expect(hashTree.leaves[1]['data'][3]).to.deep.equal(item);
|
|
127
|
+
expect(hashTree.leafHashes[1]).to.not.equal(EMPTY_HASH);
|
|
128
|
+
expect(hashTree.getTotalItemCount()).to.equal(1);
|
|
129
|
+
|
|
130
|
+
const itemSameVersion = {type: 'data', id: 3, version: 1};
|
|
131
|
+
const resultSame = hashTree.putItem(itemSameVersion);
|
|
132
|
+
expect(resultSame).to.be.false; // Not updated as version is not newer
|
|
133
|
+
|
|
134
|
+
const itemNewerVersion = {type: 'data', id: 3, version: 2};
|
|
135
|
+
const resultNewer = hashTree.putItem(itemNewerVersion);
|
|
136
|
+
expect(resultNewer).to.be.true;
|
|
137
|
+
expect(hashTree.leaves[1]['data'][3].version).to.equal(2);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('putItem should return false for tree with 0 leaves', () => {
|
|
141
|
+
const hashTree = new HashTree([], 0);
|
|
142
|
+
const item: LeafDataItem = {type: 'data', id: 1, version: 1};
|
|
143
|
+
expect(hashTree.putItem(item)).to.be.false;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('putItems should return array of false for tree with 0 leaves if items are provided', () => {
|
|
147
|
+
const hashTree = new HashTree([], 0);
|
|
148
|
+
const items: LeafDataItem[] = [{type: 'data', id: 1, version: 1}];
|
|
149
|
+
expect(hashTree.putItems(items)).to.deep.equal([false]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should have correct root hash after putting one item', () => {
|
|
153
|
+
const leafData: LeafDataItem[] = [{type: 'participant', id: 1, version: 10}]; // bucket 1 (1 % 2 = 1)
|
|
154
|
+
const numLeaves = 2;
|
|
155
|
+
const hashTree = new HashTree(leafData, numLeaves);
|
|
156
|
+
|
|
157
|
+
expect(hashTree.leaves[1]['participant'][1]).to.deep.equal({
|
|
158
|
+
type: 'participant',
|
|
159
|
+
id: 1,
|
|
160
|
+
version: 10,
|
|
161
|
+
});
|
|
162
|
+
// This hash is from the original test.
|
|
163
|
+
expect(hashTree.getRootHash()).to.equal('e1cb70c75b488d87cbc8f74934a4290b');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('removeItem should remove an item and update hash', () => {
|
|
167
|
+
const items: LeafDataItem[] = [{type: 'p', id: 1, version: 1}];
|
|
168
|
+
const hashTree = new HashTree(items, 2); // item in bucket 1
|
|
169
|
+
expect(hashTree.getTotalItemCount()).to.equal(1);
|
|
170
|
+
const oldRootHash = hashTree.getRootHash();
|
|
171
|
+
|
|
172
|
+
const result = hashTree.removeItem({type: 'p', id: 1, version: 1});
|
|
173
|
+
expect(result).to.be.true;
|
|
174
|
+
expect(hashTree.getTotalItemCount()).to.equal(0);
|
|
175
|
+
expect(hashTree.leaves[1]['p']).to.be.undefined;
|
|
176
|
+
expect(hashTree.leafHashes[1]).to.equal(EMPTY_HASH);
|
|
177
|
+
expect(hashTree.getRootHash()).to.not.equal(oldRootHash);
|
|
178
|
+
// After removing the only item, it should be like an empty tree with 2 leaves
|
|
179
|
+
const emptyTree = new HashTree([], 2);
|
|
180
|
+
expect(hashTree.getRootHash()).to.equal(emptyTree.getRootHash());
|
|
181
|
+
|
|
182
|
+
const resultNotFound = hashTree.removeItem({type: 'p', id: 1, version: 1});
|
|
183
|
+
expect(resultNotFound).to.be.false;
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('removeItem should only remove if version is <= existing (as per new logic)', () => {
|
|
187
|
+
const itemV1 = {type: 'test', id: 5, version: 1}; // bucket 1 (5%2=1)
|
|
188
|
+
const hashTree = new HashTree([itemV1], 2);
|
|
189
|
+
|
|
190
|
+
// Try to remove with older version - should fail if strict "version must be >=" is used for removal item
|
|
191
|
+
// The current removeItem logic: existingItem.version <= item.version for removal
|
|
192
|
+
let removed = hashTree.removeItem({type: 'test', id: 5, version: 0});
|
|
193
|
+
expect(removed).to.be.false;
|
|
194
|
+
|
|
195
|
+
removed = hashTree.removeItem({type: 'test', id: 5, version: 1}); // same version
|
|
196
|
+
expect(removed).to.be.true;
|
|
197
|
+
expect(hashTree.getTotalItemCount()).to.equal(0);
|
|
198
|
+
|
|
199
|
+
hashTree.putItem(itemV1); // re-add
|
|
200
|
+
expect(hashTree.getTotalItemCount()).to.equal(1);
|
|
201
|
+
removed = hashTree.removeItem({type: 'test', id: 5, version: 2}); // newer version in request
|
|
202
|
+
expect(removed).to.be.true;
|
|
203
|
+
expect(hashTree.getTotalItemCount()).to.equal(0);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('removeItem should return false for tree with 0 leaves', () => {
|
|
207
|
+
const hashTree = new HashTree([], 0);
|
|
208
|
+
const item: LeafDataItem = {type: 'data', id: 1, version: 1};
|
|
209
|
+
expect(hashTree.removeItem(item)).to.be.false;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('removeItems should process multiple items', () => {
|
|
213
|
+
const items: LeafDataItem[] = [
|
|
214
|
+
{type: 'a', id: 1, version: 2}, // bucket 1
|
|
215
|
+
{type: 'b', id: 2, version: 2}, // bucket 0
|
|
216
|
+
];
|
|
217
|
+
const hashTree = new HashTree(items, 2);
|
|
218
|
+
expect(hashTree.getTotalItemCount()).to.equal(2);
|
|
219
|
+
|
|
220
|
+
const itemsToRemove: LeafDataItem[] = [
|
|
221
|
+
{type: 'a', id: 1, version: 3}, // remove with newer version (original logic)
|
|
222
|
+
{type: 'b', id: 2, version: 1}, // attempt remove with older version (should fail by original logic)
|
|
223
|
+
{type: 'c', id: 3, version: 1}, // item not present
|
|
224
|
+
];
|
|
225
|
+
const results = hashTree.removeItems(itemsToRemove);
|
|
226
|
+
expect(results).to.deep.equal([true, false, false]);
|
|
227
|
+
expect(hashTree.getTotalItemCount()).to.equal(1); // item 'b' should remain
|
|
228
|
+
expect(hashTree.leaves[1]['a']).to.be.undefined;
|
|
229
|
+
expect(hashTree.leaves[0]['b'][2]).to.deep.equal({type: 'b', id: 2, version: 2});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('removeItems should return array of false for tree with 0 leaves if items are provided', () => {
|
|
233
|
+
const hashTree = new HashTree([], 0);
|
|
234
|
+
const items: LeafDataItem[] = [{type: 'data', id: 1, version: 1}];
|
|
235
|
+
expect(hashTree.removeItems(items)).to.deep.equal([false]);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('updateItems should update and remove multiple items correctly', () => {
|
|
239
|
+
const initialItems: LeafDataItem[] = [
|
|
240
|
+
{type: 'participant', id: 1, version: 1}, // bucket 1
|
|
241
|
+
{type: 'self', id: 2, version: 1}, // bucket 0
|
|
242
|
+
{type: 'locus', id: 3, version: 1}, // bucket 1
|
|
243
|
+
];
|
|
244
|
+
const hashTree = new HashTree(initialItems, 2);
|
|
245
|
+
expect(hashTree.getTotalItemCount()).to.equal(3);
|
|
246
|
+
|
|
247
|
+
const updates: any = [
|
|
248
|
+
{operation: 'update', item: {type: 'participant', id: 1, version: 2}}, // update existing
|
|
249
|
+
{operation: 'remove', item: {type: 'locus', id: 3, version: 1}}, // remove existing
|
|
250
|
+
{operation: 'update', item: {type: 'mediashare', id: 4, version: 1}}, // add new (bucket 0)
|
|
251
|
+
{operation: 'remove', item: {type: 'participant', id: 99, version: 1}}, // remove non-existent
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
const results = hashTree.updateItems(updates);
|
|
255
|
+
|
|
256
|
+
expect(results).to.deep.equal([true, true, true, false]);
|
|
257
|
+
expect(hashTree.getTotalItemCount()).to.equal(3); // participant (updated), self (unchanged), mediashare (added); locus removed
|
|
258
|
+
|
|
259
|
+
// Verify the updates
|
|
260
|
+
expect(hashTree.leaves[1]['participant'][1].version).to.equal(2); // updated
|
|
261
|
+
expect(hashTree.leaves[0]['self'][2]).to.deep.equal({type: 'self', id: 2, version: 1}); // unchanged
|
|
262
|
+
expect(hashTree.leaves[0]['mediashare'][4]).to.deep.equal({
|
|
263
|
+
type: 'mediashare',
|
|
264
|
+
id: 4,
|
|
265
|
+
version: 1,
|
|
266
|
+
}); // added
|
|
267
|
+
expect(hashTree.leaves[1]['locus']).to.be.undefined; // removed
|
|
268
|
+
|
|
269
|
+
// Verify hashes were updated
|
|
270
|
+
expect(hashTree.leafHashes[0]).to.not.equal(EMPTY_HASH);
|
|
271
|
+
expect(hashTree.leafHashes[1]).to.not.equal(EMPTY_HASH);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('updateItems should return array of false for tree with 0 leaves', () => {
|
|
275
|
+
const hashTree = new HashTree([], 0);
|
|
276
|
+
const updates: any = [
|
|
277
|
+
{operation: 'update', item: {type: 'participant', id: 1, version: 1}},
|
|
278
|
+
{operation: 'remove', item: {type: 'self', id: 2, version: 1}},
|
|
279
|
+
];
|
|
280
|
+
expect(hashTree.updateItems(updates)).to.deep.equal([false, false]);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('updateItems should handle empty updates array', () => {
|
|
284
|
+
const hashTree = new HashTree([{type: 'participant', id: 1, version: 1}], 2);
|
|
285
|
+
const results = hashTree.updateItems([]);
|
|
286
|
+
expect(results).to.deep.equal([]);
|
|
287
|
+
expect(hashTree.getTotalItemCount()).to.equal(1);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('updateItems should only update hashes for affected leaves', () => {
|
|
291
|
+
const hashTree = new HashTree([], 4);
|
|
292
|
+
const initialHash0 = hashTree.leafHashes[0];
|
|
293
|
+
const initialHash1 = hashTree.leafHashes[1];
|
|
294
|
+
const initialHash2 = hashTree.leafHashes[2];
|
|
295
|
+
const initialHash3 = hashTree.leafHashes[3];
|
|
296
|
+
|
|
297
|
+
const updates: any = [
|
|
298
|
+
{operation: 'update', item: {type: 'participant', id: 1, version: 1}}, // bucket 1
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
hashTree.updateItems(updates);
|
|
302
|
+
|
|
303
|
+
// Only leaf 1 should have changed hash
|
|
304
|
+
expect(hashTree.leafHashes[0]).to.equal(initialHash0);
|
|
305
|
+
expect(hashTree.leafHashes[1]).to.not.equal(initialHash1);
|
|
306
|
+
expect(hashTree.leafHashes[2]).to.equal(initialHash2);
|
|
307
|
+
expect(hashTree.leafHashes[3]).to.equal(initialHash3);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('updateItems should respect version control for updates', () => {
|
|
311
|
+
const hashTree = new HashTree([{type: 'participant', id: 1, version: 5}], 2);
|
|
312
|
+
|
|
313
|
+
const updates: any = [
|
|
314
|
+
{operation: 'update', item: {type: 'participant', id: 1, version: 4}}, // older version
|
|
315
|
+
{operation: 'update', item: {type: 'participant', id: 1, version: 5}}, // same version
|
|
316
|
+
{operation: 'update', item: {type: 'participant', id: 1, version: 6}}, // newer version
|
|
317
|
+
];
|
|
318
|
+
|
|
319
|
+
const results = hashTree.updateItems(updates);
|
|
320
|
+
expect(results).to.deep.equal([false, false, true]);
|
|
321
|
+
expect(hashTree.leaves[1]['participant'][1].version).to.equal(6);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('updateItems should respect version control for removes', () => {
|
|
325
|
+
const hashTree = new HashTree([{type: 'participant', id: 1, version: 5}], 2);
|
|
326
|
+
|
|
327
|
+
// Try to remove with older version - should fail
|
|
328
|
+
let updates: any = [{operation: 'remove', item: {type: 'participant', id: 1, version: 4}}];
|
|
329
|
+
let results = hashTree.updateItems(updates);
|
|
330
|
+
expect(results).to.deep.equal([false]);
|
|
331
|
+
expect(hashTree.getTotalItemCount()).to.equal(1); // still there
|
|
332
|
+
|
|
333
|
+
// Try with same version - should succeed
|
|
334
|
+
updates = [{operation: 'remove', item: {type: 'participant', id: 1, version: 5}}];
|
|
335
|
+
results = hashTree.updateItems(updates);
|
|
336
|
+
expect(results).to.deep.equal([true]);
|
|
337
|
+
expect(hashTree.getTotalItemCount()).to.equal(0);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('updateItems should handle multiple operations on same leaf efficiently', () => {
|
|
341
|
+
const hashTree = new HashTree([], 2);
|
|
342
|
+
|
|
343
|
+
const updates: any = [
|
|
344
|
+
{operation: 'update', item: {type: 'participant', id: 1, version: 1}}, // bucket 1
|
|
345
|
+
{operation: 'update', item: {type: 'self', id: 3, version: 1}}, // bucket 1
|
|
346
|
+
{operation: 'update', item: {type: 'locus', id: 5, version: 1}}, // bucket 1
|
|
347
|
+
];
|
|
348
|
+
|
|
349
|
+
const results = hashTree.updateItems(updates);
|
|
350
|
+
expect(results).to.deep.equal([true, true, true]);
|
|
351
|
+
expect(hashTree.getTotalItemCount()).to.equal(3);
|
|
352
|
+
|
|
353
|
+
// All items should be in leaf 1
|
|
354
|
+
expect(hashTree.leaves[1]['participant'][1]).to.exist;
|
|
355
|
+
expect(hashTree.leaves[1]['self'][3]).to.exist;
|
|
356
|
+
expect(hashTree.leaves[1]['locus'][5]).to.exist;
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('returns the correct root hash for an empty tree (0 leaves)', () => {
|
|
360
|
+
const hashTree = new HashTree([], 0);
|
|
361
|
+
expect(hashTree.getRootHash()).to.equal(EMPTY_HASH);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('returns the correct root hash for an empty tree with 2 leaves', () => {
|
|
365
|
+
const hashTree = new HashTree([], 2);
|
|
366
|
+
// This hash is from the original test.
|
|
367
|
+
expect(hashTree.getRootHash()).to.equal('b113a76304e3a7121afecfe1606ee1c1');
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('returns the correct root hash for an empty tree with 4 leaves', () => {
|
|
371
|
+
const hashTree = new HashTree([], 4);
|
|
372
|
+
// This hash is from the original test.
|
|
373
|
+
expect(hashTree.getRootHash()).to.equal('b5df9b92242752424d87053a14e6222d');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
describe('getTotalItemCount', () => {
|
|
377
|
+
it('should return 0 for an empty tree', () => {
|
|
378
|
+
const tree = new HashTree([], 4);
|
|
379
|
+
expect(tree.getTotalItemCount()).to.equal(0);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should return 0 for a tree with 0 leaves', () => {
|
|
383
|
+
const tree = new HashTree([], 0);
|
|
384
|
+
expect(tree.getTotalItemCount()).to.equal(0);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('should return correct count for a tree with single item', () => {
|
|
388
|
+
const tree = new HashTree([{type: 'participant', id: 1, version: 1}], 2);
|
|
389
|
+
expect(tree.getTotalItemCount()).to.equal(1);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('should return correct count for multiple items in different leaves', () => {
|
|
393
|
+
const items: LeafDataItem[] = [
|
|
394
|
+
{type: 'participant', id: 0, version: 1}, // leaf 0
|
|
395
|
+
{type: 'self', id: 1, version: 1}, // leaf 1
|
|
396
|
+
{type: 'locus', id: 2, version: 1}, // leaf 0
|
|
397
|
+
{type: 'mediashare', id: 3, version: 1}, // leaf 1
|
|
398
|
+
];
|
|
399
|
+
const tree = new HashTree(items, 2);
|
|
400
|
+
expect(tree.getTotalItemCount()).to.equal(4);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should return correct count for multiple items of different types in same leaf', () => {
|
|
404
|
+
const items: LeafDataItem[] = [
|
|
405
|
+
{type: 'participant', id: 1, version: 1}, // leaf 1
|
|
406
|
+
{type: 'self', id: 3, version: 1}, // leaf 1
|
|
407
|
+
{type: 'locus', id: 5, version: 1}, // leaf 1
|
|
408
|
+
];
|
|
409
|
+
const tree = new HashTree(items, 2);
|
|
410
|
+
expect(tree.getTotalItemCount()).to.equal(3);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should maintain correct count after resize', () => {
|
|
414
|
+
const items: LeafDataItem[] = [
|
|
415
|
+
{type: 'participant', id: 0, version: 1},
|
|
416
|
+
{type: 'self', id: 1, version: 1},
|
|
417
|
+
{type: 'locus', id: 2, version: 1},
|
|
418
|
+
];
|
|
419
|
+
const tree = new HashTree(items, 2);
|
|
420
|
+
expect(tree.getTotalItemCount()).to.equal(3);
|
|
421
|
+
|
|
422
|
+
tree.resize(4);
|
|
423
|
+
expect(tree.getTotalItemCount()).to.equal(3); // Count should remain same after resize
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('should return 0 after resizing to 0 leaves', () => {
|
|
427
|
+
const items: LeafDataItem[] = [
|
|
428
|
+
{type: 'participant', id: 1, version: 1},
|
|
429
|
+
{type: 'self', id: 2, version: 1},
|
|
430
|
+
];
|
|
431
|
+
const tree = new HashTree(items, 2);
|
|
432
|
+
expect(tree.getTotalItemCount()).to.equal(2);
|
|
433
|
+
|
|
434
|
+
tree.resize(0);
|
|
435
|
+
expect(tree.getTotalItemCount()).to.equal(0);
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
describe('getLeafData', () => {
|
|
440
|
+
it('should return items from a specific leaf', () => {
|
|
441
|
+
const items: LeafDataItem[] = [
|
|
442
|
+
{type: 't1', id: 0, version: 1}, // leaf 0
|
|
443
|
+
{type: 't2', id: 1, version: 1}, // leaf 1
|
|
444
|
+
{type: 't1', id: 2, version: 1}, // leaf 0
|
|
445
|
+
];
|
|
446
|
+
const tree = new HashTree(items, 2);
|
|
447
|
+
const leaf0Data = tree.getLeafData(0);
|
|
448
|
+
expect(leaf0Data).to.have.deep.members([
|
|
449
|
+
{type: 't1', id: 0, version: 1},
|
|
450
|
+
{type: 't1', id: 2, version: 1},
|
|
451
|
+
]);
|
|
452
|
+
expect(leaf0Data.length).to.equal(2);
|
|
453
|
+
|
|
454
|
+
const leaf1Data = tree.getLeafData(1);
|
|
455
|
+
expect(leaf1Data).to.have.deep.members([{type: 't2', id: 1, version: 1}]);
|
|
456
|
+
expect(leaf1Data.length).to.equal(1);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should return empty array for invalid leaf index or empty leaf', () => {
|
|
460
|
+
const tree = new HashTree([{type: 't', id: 0, version: 1}], 2); // item in leaf 0
|
|
461
|
+
expect(tree.getLeafData(1)).to.deep.equal([]); // leaf 1 is empty
|
|
462
|
+
expect(tree.getLeafData(2)).to.deep.equal([]); // invalid index
|
|
463
|
+
expect(tree.getLeafData(-1)).to.deep.equal([]); // invalid index
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('should return empty array for tree with 0 leaves', () => {
|
|
467
|
+
const tree = new HashTree([], 0);
|
|
468
|
+
expect(tree.getLeafData(0)).to.deep.equal([]);
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
describe('resize', () => {
|
|
473
|
+
it('should resize the tree and redistribute items', () => {
|
|
474
|
+
const items: LeafDataItem[] = [
|
|
475
|
+
{type: 'a', id: 0, version: 1}, // old leaf 0 (0%2=0)
|
|
476
|
+
{type: 'b', id: 1, version: 1}, // old leaf 1 (1%2=1)
|
|
477
|
+
{type: 'c', id: 2, version: 1}, // old leaf 0 (2%2=0)
|
|
478
|
+
{type: 'd', id: 3, version: 1}, // old leaf 1 (3%2=1)
|
|
479
|
+
];
|
|
480
|
+
const tree = new HashTree(items, 2);
|
|
481
|
+
expect(tree.getLeafCount()).to.equal(2);
|
|
482
|
+
expect(tree.getTotalItemCount()).to.equal(4);
|
|
483
|
+
const originalRootHash = tree.getRootHash();
|
|
484
|
+
|
|
485
|
+
const resized = tree.resize(4);
|
|
486
|
+
expect(resized).to.be.true;
|
|
487
|
+
expect(tree.getLeafCount()).to.equal(4);
|
|
488
|
+
expect(tree.getTotalItemCount()).to.equal(4); // count should remain same
|
|
489
|
+
|
|
490
|
+
// Check redistribution
|
|
491
|
+
// id:0 -> 0%4 = 0
|
|
492
|
+
// id:1 -> 1%4 = 1
|
|
493
|
+
// id:2 -> 2%4 = 2
|
|
494
|
+
// id:3 -> 3%4 = 3
|
|
495
|
+
expect(tree.getLeafData(0)).to.deep.include({type: 'a', id: 0, version: 1});
|
|
496
|
+
expect(tree.getLeafData(1)).to.deep.include({type: 'b', id: 1, version: 1});
|
|
497
|
+
expect(tree.getLeafData(2)).to.deep.include({type: 'c', id: 2, version: 1});
|
|
498
|
+
expect(tree.getLeafData(3)).to.deep.include({type: 'd', id: 3, version: 1});
|
|
499
|
+
expect(tree.getRootHash()).to.not.equal(originalRootHash); // Hash should change
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('should return false if size does not change', () => {
|
|
503
|
+
const tree = new HashTree([], 4);
|
|
504
|
+
expect(tree.resize(4)).to.be.false;
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('should throw error for invalid new number of leaves', () => {
|
|
508
|
+
const tree = new HashTree([], 4);
|
|
509
|
+
expect(() => tree.resize(3)).to.throw('New number of leaves must be 0 or a power of 2');
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('should handle resize to 0 leaves', () => {
|
|
513
|
+
const items: LeafDataItem[] = [{type: 'a', id: 0, version: 1}];
|
|
514
|
+
const tree = new HashTree(items, 2);
|
|
515
|
+
expect(tree.getTotalItemCount()).to.equal(1);
|
|
516
|
+
tree.resize(0);
|
|
517
|
+
expect(tree.getLeafCount()).to.equal(0);
|
|
518
|
+
expect(tree.getTotalItemCount()).to.equal(0);
|
|
519
|
+
expect(tree.getRootHash()).to.equal(EMPTY_HASH);
|
|
520
|
+
expect(tree.leaves.length).to.equal(0);
|
|
521
|
+
expect(tree.leafHashes.length).to.equal(0);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('should handle resize from 0 leaves', () => {
|
|
525
|
+
const tree = new HashTree([], 0);
|
|
526
|
+
tree.resize(2);
|
|
527
|
+
expect(tree.getLeafCount()).to.equal(2);
|
|
528
|
+
expect(tree.getTotalItemCount()).to.equal(0);
|
|
529
|
+
const emptyTree = new HashTree([], 2);
|
|
530
|
+
expect(tree.getRootHash()).to.equal(emptyTree.getRootHash());
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
describe('diffHashes', () => {
|
|
535
|
+
it('should return empty array if hashes are identical', () => {
|
|
536
|
+
const items: LeafDataItem[] = [{type: 'x', id: 1, version: 1}];
|
|
537
|
+
const tree1 = new HashTree(items, 2);
|
|
538
|
+
const tree2 = new HashTree(items, 2);
|
|
539
|
+
expect(tree1.diffHashes(tree2.getHashes())).to.deep.equal([]);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('should return differing leaf indices', () => {
|
|
543
|
+
const tree1 = new HashTree([{type: 'x', id: 0, version: 1}], 4); // item in leaf 0
|
|
544
|
+
const tree2 = new HashTree([{type: 'y', id: 1, version: 1}], 4); // item in leaf 1
|
|
545
|
+
// tree1: leaf 0 has item, leaves 1,2,3 empty
|
|
546
|
+
// tree2: leaf 1 has item, leaves 0,2,3 empty
|
|
547
|
+
// Expected diffs: leaf 0 (present in 1, not in 2), leaf 1 (present in 2, not in 1)
|
|
548
|
+
const diff = tree1.diffHashes(tree2.getHashes());
|
|
549
|
+
expect(diff).to.include.members([0, 1]);
|
|
550
|
+
// If one leaf's hash is EMPTY_HASH and the other's is a computed hash, they are different.
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('should return all leaf indices if externalHashes is for a different structure (e.g. too short)', () => {
|
|
554
|
+
const tree = new HashTree([{type: 'x', id: 0, version: 1}], 4);
|
|
555
|
+
const externalHashesShort = [EMPTY_HASH, EMPTY_HASH]; // Too short for 4 leaves + internal nodes
|
|
556
|
+
expect(tree.diffHashes(externalHashesShort)).to.deep.equal([0, 1, 2, 3]);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('should handle diff for 0-leaf trees', () => {
|
|
560
|
+
const tree0 = new HashTree([], 0);
|
|
561
|
+
expect(tree0.diffHashes([EMPTY_HASH])).to.deep.equal([]);
|
|
562
|
+
expect(tree0.diffHashes(['some_other_hash'])).to.deep.equal([]); // No leaves to differ
|
|
563
|
+
const tree2 = new HashTree([], 2);
|
|
564
|
+
// Comparing a 0-leaf tree with a 2-leaf tree's hashes
|
|
565
|
+
expect(tree0.diffHashes(tree2.getHashes())).to.deep.equal([]);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('should correctly identify differences when one leaf changes', () => {
|
|
569
|
+
const initialItems: LeafDataItem[] = [
|
|
570
|
+
{type: 'a', id: 0, version: 1}, // leaf 0
|
|
571
|
+
{type: 'b', id: 1, version: 1}, // leaf 1
|
|
572
|
+
];
|
|
573
|
+
const tree1 = new HashTree(initialItems, 2);
|
|
574
|
+
const tree1Hashes = tree1.getHashes();
|
|
575
|
+
|
|
576
|
+
const modifiedItems: LeafDataItem[] = [
|
|
577
|
+
{type: 'a', id: 0, version: 1}, // leaf 0 (same)
|
|
578
|
+
{type: 'b', id: 1, version: 2}, // leaf 1 (changed version)
|
|
579
|
+
];
|
|
580
|
+
const tree2 = new HashTree(modifiedItems, 2);
|
|
581
|
+
|
|
582
|
+
const diff1_2 = tree1.diffHashes(tree2.getHashes());
|
|
583
|
+
expect(diff1_2).to.deep.equal([1]); // Leaf 1 should differ
|
|
584
|
+
|
|
585
|
+
const diff2_1 = tree2.diffHashes(tree1Hashes);
|
|
586
|
+
expect(diff2_1).to.deep.equal([1]);
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
describe('computeTreeHashes', () => {
|
|
591
|
+
it('should return [EMPTY_HASH] for tree with 0 leaves', () => {
|
|
592
|
+
const hashTree = new HashTree([], 0);
|
|
593
|
+
const hashes = hashTree.computeTreeHashes();
|
|
594
|
+
expect(hashes).to.deep.equal([EMPTY_HASH]);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('should compute correct hashes for a known set of leaf hashes', () => {
|
|
598
|
+
const leafHashes = [
|
|
599
|
+
'aefa055a9b82c4c4ae10ac8ed1f61a30',
|
|
600
|
+
'99aa06d3014798d86001c324468d497f',
|
|
601
|
+
'99aa06d3014798d86001c324468d497f',
|
|
602
|
+
'c770ab632efcea7569a6e35c2f7cf5da',
|
|
603
|
+
'eedafc8238926775cee1fbb5cee030ff',
|
|
604
|
+
'dae3c2ec206d7d5967bfae01913c4a76',
|
|
605
|
+
'5301845214af2e8b70c7b54a72565dcf',
|
|
606
|
+
'99aa06d3014798d86001c324468d497f',
|
|
607
|
+
];
|
|
608
|
+
|
|
609
|
+
const expectedAllHashes = [
|
|
610
|
+
'ba1be9f757eae740753f887d76b7c405',
|
|
611
|
+
'0e027dc86d522c9cb61e3e20b33e0cb7',
|
|
612
|
+
'f6df8f800fac269c448b7725021a9dbb',
|
|
613
|
+
'803dde85957d497718837fb7e36342f8',
|
|
614
|
+
'55ed9b63802480f79698432a84a4e5f8',
|
|
615
|
+
'fa25dc2d64096c3b92f6701572060569',
|
|
616
|
+
'def77631d182a9b74523b218f0de771f',
|
|
617
|
+
'aefa055a9b82c4c4ae10ac8ed1f61a30', // - leaves start here
|
|
618
|
+
'99aa06d3014798d86001c324468d497f',
|
|
619
|
+
'99aa06d3014798d86001c324468d497f',
|
|
620
|
+
'c770ab632efcea7569a6e35c2f7cf5da',
|
|
621
|
+
'eedafc8238926775cee1fbb5cee030ff',
|
|
622
|
+
'dae3c2ec206d7d5967bfae01913c4a76',
|
|
623
|
+
'5301845214af2e8b70c7b54a72565dcf',
|
|
624
|
+
'99aa06d3014798d86001c324468d497f',
|
|
625
|
+
];
|
|
626
|
+
|
|
627
|
+
const hashTree = new HashTree([], leafHashes.length);
|
|
628
|
+
|
|
629
|
+
hashTree.leafHashes = leafHashes;
|
|
630
|
+
|
|
631
|
+
const computedHashes = hashTree.computeTreeHashes();
|
|
632
|
+
expect(computedHashes).to.deep.equal(expectedAllHashes);
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
describe('computeLeafHash', () => {
|
|
637
|
+
it('should compute the correct hash when the hash starts with a zero', () => {
|
|
638
|
+
const item: LeafDataItem = {type: 'self', id: 74, version: 1}; // Chosen to produce a hash starting with zero
|
|
639
|
+
const hashTree = new HashTree([], 2);
|
|
640
|
+
|
|
641
|
+
hashTree.putItem(item);
|
|
642
|
+
hashTree.computeLeafHash(0);
|
|
643
|
+
|
|
644
|
+
const leafHash = hashTree.leafHashes[0];
|
|
645
|
+
|
|
646
|
+
expect(leafHash.startsWith('0')).to.be.true;
|
|
647
|
+
expect(leafHash).equal('0525d6616d0f20119293c0bf2c818e8a');
|
|
648
|
+
});
|
|
649
|
+
it('should not crash for invalid leaf index', () => {
|
|
650
|
+
const hashTree = new HashTree([], 2);
|
|
651
|
+
expect(() => hashTree.computeLeafHash(-1)).to.not.throw();
|
|
652
|
+
expect(() => hashTree.computeLeafHash(2)).to.not.throw();
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
});
|