mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-30 10:14:53 -05:00
201 lines
7.3 KiB
TypeScript
201 lines
7.3 KiB
TypeScript
import * as merkle from './merkle';
|
|
import { Timestamp } from './timestamp';
|
|
|
|
function message(timestampStr: string, hash: number) {
|
|
const timestamp = Timestamp.parse(timestampStr)!;
|
|
timestamp.hash = () => hash;
|
|
return { timestamp };
|
|
}
|
|
|
|
function insertMessages(
|
|
trie: merkle.TrieNode,
|
|
messages: ReturnType<typeof message>[],
|
|
) {
|
|
messages.forEach(msg => {
|
|
trie = merkle.insert(trie, msg.timestamp);
|
|
});
|
|
return trie;
|
|
}
|
|
|
|
describe('merkle trie', () => {
|
|
test('adding an item works', () => {
|
|
let trie = merkle.insert(
|
|
merkle.emptyTrie(),
|
|
Timestamp.parse('2018-11-12T13:21:40.122Z-0000-0123456789ABCDEF')!,
|
|
);
|
|
trie = merkle.insert(
|
|
trie,
|
|
Timestamp.parse('2018-11-13T13:21:40.122Z-0000-0123456789ABCDEF')!,
|
|
);
|
|
expect(trie).toMatchSnapshot();
|
|
});
|
|
|
|
test('diff returns the correct time difference', () => {
|
|
let trie1 = merkle.emptyTrie();
|
|
let trie2 = merkle.emptyTrie();
|
|
|
|
const messages = [
|
|
// First client messages
|
|
message('2018-11-13T13:20:40.122Z-0000-0123456789ABCDEF', 1000),
|
|
message('2018-11-14T13:05:35.122Z-0000-0123456789ABCDEF', 1100),
|
|
message('2018-11-15T22:19:00.122Z-0000-0123456789ABCDEF', 1200),
|
|
|
|
// Second client messages
|
|
message('2018-11-20T13:19:40.122Z-0000-0123456789ABCDEF', 1300),
|
|
message('2018-11-25T13:19:40.122Z-0000-0123456789ABCDEF', 1400),
|
|
];
|
|
|
|
trie1 = merkle.insert(trie1, messages[0].timestamp);
|
|
trie1 = merkle.insert(trie1, messages[1].timestamp);
|
|
trie1 = merkle.insert(trie1, messages[2].timestamp);
|
|
expect(trie1.hash).toBe(788);
|
|
|
|
trie2 = merkle.insert(trie2, messages[3].timestamp);
|
|
trie2 = merkle.insert(trie2, messages[4].timestamp);
|
|
expect(trie2.hash).toBe(108);
|
|
|
|
expect(new Date(merkle.diff(trie1, trie2)!).toISOString()).toBe(
|
|
'2018-11-02T17:15:00.000Z',
|
|
);
|
|
|
|
trie1 = merkle.insert(trie1, messages[3].timestamp);
|
|
trie1 = merkle.insert(trie1, messages[4].timestamp);
|
|
trie2 = merkle.insert(trie2, messages[0].timestamp);
|
|
trie2 = merkle.insert(trie2, messages[1].timestamp);
|
|
trie2 = merkle.insert(trie2, messages[2].timestamp);
|
|
expect(trie1.hash).toBe(888);
|
|
expect(trie1.hash).toBe(trie2.hash);
|
|
});
|
|
|
|
test('diffing works with empty tries', () => {
|
|
const trie1 = merkle.emptyTrie();
|
|
const trie2 = merkle.insert(
|
|
merkle.emptyTrie(),
|
|
Timestamp.parse('2009-01-02T10:17:37.789Z-0000-0000testinguuid1')!,
|
|
);
|
|
|
|
expect(merkle.diff(trie1, trie2)).toBe(0);
|
|
});
|
|
|
|
test('pruning works and keeps correct hashes', () => {
|
|
const messages = [
|
|
message('2018-11-01T01:00:00.000Z-0000-0123456789ABCDEF', 1000),
|
|
message('2018-11-01T01:09:00.000Z-0000-0123456789ABCDEF', 1100),
|
|
message('2018-11-01T01:18:00.000Z-0000-0123456789ABCDEF', 1200),
|
|
message('2018-11-01T01:27:00.000Z-0000-0123456789ABCDEF', 1300),
|
|
message('2018-11-01T01:36:00.000Z-0000-0123456789ABCDEF', 1400),
|
|
message('2018-11-01T01:45:00.000Z-0000-0123456789ABCDEF', 1500),
|
|
message('2018-11-01T01:54:00.000Z-0000-0123456789ABCDEF', 1600),
|
|
message('2018-11-01T02:03:00.000Z-0000-0123456789ABCDEF', 1700),
|
|
message('2018-11-01T02:10:00.000Z-0000-0123456789ABCDEF', 1800),
|
|
message('2018-11-01T02:19:00.000Z-0000-0123456789ABCDEF', 1900),
|
|
message('2018-11-01T02:28:00.000Z-0000-0123456789ABCDEF', 2000),
|
|
message('2018-11-01T02:37:00.000Z-0000-0123456789ABCDEF', 2100),
|
|
];
|
|
|
|
let trie = merkle.emptyTrie();
|
|
messages.forEach(msg => {
|
|
trie = merkle.insert(trie, msg.timestamp);
|
|
});
|
|
expect(trie.hash).toBe(2496);
|
|
expect(trie).toMatchSnapshot();
|
|
|
|
const pruned = merkle.prune(trie);
|
|
expect(pruned.hash).toBe(2496);
|
|
expect(pruned).toMatchSnapshot();
|
|
});
|
|
|
|
test('diffing differently shaped tries returns correct time', () => {
|
|
const messages = [
|
|
message('2018-11-01T01:00:00.000Z-0000-0123456789ABCDEF', 1000),
|
|
message('2018-11-01T01:09:00.000Z-0000-0123456789ABCDEF', 1100),
|
|
message('2018-11-01T01:18:00.000Z-0000-0123456789ABCDEF', 1200),
|
|
message('2018-11-01T01:27:00.000Z-0000-0123456789ABCDEF', 1300),
|
|
message('2018-11-01T01:36:00.000Z-0000-0123456789ABCDEF', 1400),
|
|
message('2018-11-01T01:45:00.000Z-0000-0123456789ABCDEF', 1500),
|
|
message('2018-11-01T01:54:00.000Z-0000-0123456789ABCDEF', 1600),
|
|
message('2018-11-01T02:03:00.000Z-0000-0123456789ABCDEF', 1700),
|
|
message('2018-11-01T02:10:00.000Z-0000-0123456789ABCDEF', 1800),
|
|
message('2018-11-01T02:19:00.000Z-0000-0123456789ABCDEF', 1900),
|
|
message('2018-11-01T02:28:00.000Z-0000-0123456789ABCDEF', 2000),
|
|
message('2018-11-01T02:37:00.000Z-0000-0123456789ABCDEF', 2100),
|
|
];
|
|
|
|
const trie = insertMessages({}, messages);
|
|
|
|
// Case 0: It always returns a base time when comparing with an
|
|
// empty trie
|
|
expect(new Date(merkle.diff(merkle.emptyTrie(), trie)!).toISOString()).toBe(
|
|
'1970-01-01T00:00:00.000Z',
|
|
);
|
|
expect(new Date(merkle.diff(trie, merkle.emptyTrie())!).toISOString()).toBe(
|
|
'1970-01-01T00:00:00.000Z',
|
|
);
|
|
|
|
// Case 1: Add an older message that modifies the trie in such a
|
|
// way that it modifies the 1st out of 3 branches (so it will be
|
|
// pruned away)
|
|
const trie1 = insertMessages(trie, [
|
|
message('2018-11-01T00:59:00.000Z-0000-0123456789ABCDEF', 900),
|
|
]);
|
|
|
|
// Normal comparison works
|
|
expect(new Date(merkle.diff(trie1, trie)!).toISOString()).toBe(
|
|
'2018-11-01T00:54:00.000Z',
|
|
);
|
|
|
|
// Comparing the pruned new trie is lossy, so it returns an even older time
|
|
expect(
|
|
new Date(merkle.diff(merkle.prune(trie1), trie)!).toISOString(),
|
|
).toBe('2018-11-01T00:45:00.000Z');
|
|
|
|
// Comparing the pruned original trie is just as lossy
|
|
expect(
|
|
new Date(merkle.diff(trie1, merkle.prune(trie))!).toISOString(),
|
|
).toBe('2018-11-01T00:45:00.000Z');
|
|
|
|
// Pruning both tries is just as lossy as well, since the changed
|
|
// key is pruned away in both cases and it won't find a changed
|
|
// key so it bails at the point
|
|
expect(
|
|
new Date(
|
|
merkle.diff(merkle.prune(trie1), merkle.prune(trie))!,
|
|
).toISOString(),
|
|
).toBe('2018-11-01T00:45:00.000Z');
|
|
|
|
// Case 2: Add two messages similar to the above case, but the
|
|
// second message modifies the 2nd key at the same level as the
|
|
// first message modifying the 1st key
|
|
const trie2 = insertMessages(trie, [
|
|
message('2018-11-01T00:59:00.000Z-0000-0123456789ABCDEF', 900),
|
|
message('2018-11-01T01:15:00.000Z-0000-0123456789ABCDEF', 1422),
|
|
]);
|
|
|
|
// Normal comparison works
|
|
expect(new Date(merkle.diff(trie2, trie)!).toISOString()).toBe(
|
|
'2018-11-01T00:54:00.000Z',
|
|
);
|
|
|
|
// Same as case 1
|
|
expect(
|
|
new Date(merkle.diff(merkle.prune(trie2), trie)!).toISOString(),
|
|
).toBe('2018-11-01T00:45:00.000Z');
|
|
|
|
// Same as case 1
|
|
expect(
|
|
new Date(merkle.diff(trie2, merkle.prune(trie))!).toISOString(),
|
|
).toBe('2018-11-01T00:45:00.000Z');
|
|
|
|
// Pruning both tries is very lossy and this ends up returning a
|
|
// time that only covers the second message. Syncing will need
|
|
// multiple passes to sync up. This happens because the second
|
|
// message provides a "changed path" that the diff takes which
|
|
// ignores the first message.
|
|
expect(
|
|
new Date(
|
|
merkle.diff(merkle.prune(trie2), merkle.prune(trie))!,
|
|
).toISOString(),
|
|
).toBe('2018-11-01T01:12:00.000Z');
|
|
});
|
|
});
|