Skip to content

Commit ed47758

Browse files
emilygilbertsjanl
authored andcommitted
Add unit tests for checkpointer module
1 parent 6156a40 commit ed47758

File tree

1 file changed

+354
-0
lines changed

1 file changed

+354
-0
lines changed

tests/unit/test.checkpointer.js

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
"use strict";
2+
3+
require("chai").should();
4+
5+
var PouchDB = require("../../packages/node_modules/pouchdb-for-coverage");
6+
var Checkpointer = require("../../packages/node_modules/pouchdb-checkpointer");
7+
var memdown = require("memdown");
8+
9+
var genReplicationId = PouchDB.utils.generateReplicationId;
10+
var sourceDb = new PouchDB({ name: "local_test_db", db: memdown });
11+
var targetDb = new PouchDB({ name: "target_test_db", db: memdown });
12+
13+
describe("test.checkpointer.js", () => {
14+
it("create checkpointer instance", async () => {
15+
const checkpointer = await createCheckpointer();
16+
17+
checkpointer.src.name.should.equal("local_test_db");
18+
checkpointer.target.name.should.equal("target_test_db");
19+
checkpointer.opts.writeSourceCheckpoint.should.be.true;
20+
checkpointer.opts.writeTargetCheckpoint.should.be.true;
21+
checkpointer.id.startsWith("_local/").should.be.true;
22+
});
23+
24+
it("write and retrieve checkpoint doc to/from src and target db", async () => {
25+
const checkpointer = await createCheckpointer();
26+
27+
const result = await checkpointer.writeCheckpoint(1, "session-1");
28+
// { ok: true, id: '_local/1DB6QfM3RDEOFoOwE65CpQ==', rev: '0-1' }
29+
result.ok.should.equal(true);
30+
result.rev.should.equal("0-1");
31+
32+
//get checkpoint doc from source and target db by id
33+
let { srcDoc, tgtDoc } = await getCheckpointDocs(checkpointer.id);
34+
35+
srcDoc._rev.should.equal("0-1");
36+
tgtDoc._rev.should.equal("0-1");
37+
srcDoc._id.should.equal(checkpointer.id);
38+
tgtDoc._id.should.equal(checkpointer.id);
39+
srcDoc.history.length.should.equal(1);
40+
srcDoc.last_seq.should.equal(srcDoc.history[0].last_seq);
41+
});
42+
43+
it("update: new checkpoint with next replication session", async () => {
44+
const checkpointer = await createCheckpointer();
45+
await checkpointer.writeCheckpoint(1, "session-1");
46+
47+
const update = await checkpointer.writeCheckpoint(2, "session-2");
48+
update.ok.should.equal(true);
49+
update.rev.should.equal("0-2");
50+
51+
let { srcDoc, tgtDoc } = await getCheckpointDocs(checkpointer.id);
52+
53+
srcDoc.last_seq.should.equal(2);
54+
tgtDoc.last_seq.should.equal(2);
55+
srcDoc.history.length.should.equal(2);
56+
srcDoc.history[0].session_id.should.equal("session-2");
57+
});
58+
59+
it("update: don't update if checkpoint hasn't changed", async () => {
60+
const checkpointer = await createCheckpointer();
61+
await checkpointer.writeCheckpoint(1, "session-1");
62+
63+
// attempt to update with the same checkpoint number
64+
// -> returns undefined
65+
await checkpointer.writeCheckpoint(1, "session-2");
66+
let { srcDoc, tgtDoc } = await getCheckpointDocs(checkpointer.id);
67+
68+
srcDoc.last_seq.should.equal(1);
69+
srcDoc.history.length.should.equal(1);
70+
tgtDoc.last_seq.should.equal(1);
71+
tgtDoc.history.length.should.equal(1);
72+
srcDoc.history[0].session_id.should.equal("session-1");
73+
tgtDoc.history[0].session_id.should.equal("session-1");
74+
});
75+
76+
it("update: only keep one history entry per replication session", async () => {
77+
const checkpointer = await createCheckpointer();
78+
await checkpointer.writeCheckpoint(1, "session-1");
79+
80+
let { srcDoc, tgtDoc } = await getCheckpointDocs(checkpointer.id);
81+
srcDoc.last_seq.should.equal(1);
82+
srcDoc.history.length.should.equal(1);
83+
tgtDoc.last_seq.should.equal(1);
84+
tgtDoc.history.length.should.equal(1);
85+
86+
// update changed checkpoint with the same sessionId as before
87+
const update = await checkpointer.writeCheckpoint(2, "session-1");
88+
update.ok.should.equal(true);
89+
update.rev.should.equal("0-2");
90+
91+
// update should have replaced the history entry with that sessionId
92+
({ srcDoc, tgtDoc } = await getCheckpointDocs(checkpointer.id));
93+
94+
srcDoc.history.length.should.equal(1);
95+
srcDoc.last_seq.should.equal(2);
96+
srcDoc.session_id.should.equal("session-1");
97+
tgtDoc.history.length.should.equal(1);
98+
tgtDoc.last_seq.should.equal(2);
99+
tgtDoc.session_id.should.equal("session-1");
100+
});
101+
102+
it("update: history should store max five latest updates", async () => {
103+
const checkpointer = await createCheckpointer();
104+
await checkpointer.writeCheckpoint(1, "session-1");
105+
106+
for (let i = 2; i < 8; i++) {
107+
await checkpointer.writeCheckpoint(i, `session-${i}`);
108+
}
109+
110+
let { srcDoc, tgtDoc } = await getCheckpointDocs(checkpointer.id);
111+
srcDoc.history.length.should.equal(5);
112+
srcDoc._rev.should.equal("0-7");
113+
srcDoc.last_seq.should.equal(7);
114+
srcDoc.history[srcDoc.history.length - 1].session_id.should.equal(
115+
"session-3"
116+
);
117+
tgtDoc.history.length.should.equal(5);
118+
tgtDoc._rev.should.equal("0-7");
119+
tgtDoc.last_seq.should.equal(7);
120+
tgtDoc.history[tgtDoc.history.length - 1].session_id.should.equal(
121+
"session-3"
122+
);
123+
});
124+
125+
it("update: only update source checkpoint", async () => {
126+
//create checkpointer with options to only write to the source db
127+
const checkpointer = await createCheckpointer({
128+
writeSourceCheckpoint: true,
129+
writeTargetCheckpoint: false,
130+
});
131+
132+
await checkpointer.writeCheckpoint(1, "session-1");
133+
// getCheckpointDocIfExists returns either the document or null
134+
let { srcDoc, tgtDoc } = await getCheckpointDocs(checkpointer.id);
135+
136+
srcDoc.last_seq.should.equal(1);
137+
(tgtDoc === null).should.be.true;
138+
});
139+
140+
it("update: only update target checkpoint", async () => {
141+
//create checkpointer with option to only write to the target db
142+
const checkpointer = await createCheckpointer({
143+
writeSourceCheckpoint: false,
144+
writeTargetCheckpoint: true,
145+
});
146+
147+
await checkpointer.writeCheckpoint(1, "session-1");
148+
// getCheckpointDocIfExists returns either the doc or null
149+
let { srcDoc, tgtDoc } = await getCheckpointDocs(checkpointer.id);
150+
151+
tgtDoc.last_seq.should.equal(1);
152+
(srcDoc === null).should.be.true;
153+
});
154+
155+
it("get: return lowest_seq if no checkpoint doc exists", async () => {
156+
const checkpointer = await createCheckpointer();
157+
158+
let result = await checkpointer.getCheckpoint();
159+
160+
result.should.equal(0);
161+
});
162+
163+
it("get: return lowest_seq when write src and tgt opts are both set to false", async () => {
164+
const checkpointer = await createCheckpointer({
165+
writeSourceCheckpoint: false,
166+
writeTargetCheckpoint: false,
167+
});
168+
await checkpointer.writeCheckpoint(5, "session-5");
169+
170+
let result = await checkpointer.getCheckpoint();
171+
172+
result.should.equal(0);
173+
});
174+
175+
it("get: return last_seq from source if only written to source", async () => {
176+
const checkpointer = await createCheckpointer({
177+
writeSourceCheckpoint: true,
178+
writeTargetCheckpoint: false,
179+
});
180+
await checkpointer.writeCheckpoint(4, "session-5");
181+
182+
let result = await checkpointer.getCheckpoint();
183+
184+
result.should.equal(4);
185+
});
186+
187+
it("get: return lowest_seq if only written to source but no doc extists", async () => {
188+
const checkpointer = await createCheckpointer({
189+
writeSourceCheckpoint: true,
190+
writeTargetCheckpoint: false,
191+
});
192+
193+
let result = await checkpointer.getCheckpoint();
194+
195+
result.should.equal(0);
196+
});
197+
198+
it("get: return last_seq from target if only written to target", async () => {
199+
const checkpointer = await createCheckpointer({
200+
writeSourceCheckpoint: false,
201+
writeTargetCheckpoint: true,
202+
});
203+
await checkpointer.writeCheckpoint(8, "session-8");
204+
205+
let result = await checkpointer.getCheckpoint();
206+
207+
result.should.equal(8);
208+
});
209+
210+
it("get: return latest matching seq from divergent state src ahead", async () => {
211+
const checkpointer = await createCheckpointer();
212+
213+
await sourceDb.put({
214+
session_id: "session-3",
215+
_id: checkpointer.id,
216+
history: [
217+
{ session_id: "session-3", last_seq: 3 },
218+
{ session_id: "session-2", last_seq: 2 },
219+
{ session_id: "session-1", last_seq: 1 },
220+
],
221+
replicator: "pouchdb",
222+
version: 1,
223+
last_seq: 3,
224+
});
225+
await targetDb.put({
226+
session_id: "session-2",
227+
_id: checkpointer.id,
228+
history: [
229+
{ session_id: "session-2", last_seq: 2 },
230+
{ session_id: "session-1", last_seq: 1 },
231+
],
232+
replicator: "pouchdb",
233+
version: 1,
234+
last_seq: 2,
235+
});
236+
237+
const result = await checkpointer.getCheckpoint();
238+
239+
result.should.equal(2);
240+
});
241+
242+
it("get: return last matching seq from divergent state tgt ahead", async () => {
243+
const checkpointer = await createCheckpointer({
244+
writeSourceCheckpoint: true,
245+
writeTargetCheckpoint: true,
246+
});
247+
248+
await sourceDb.put({
249+
session_id: "session-6",
250+
_id: checkpointer.id,
251+
history: [
252+
{ session_id: "session-6", last_seq: 6 },
253+
{ session_id: "session-5", last_seq: 5 },
254+
{ session_id: "session-4", last_seq: 4 },
255+
{ session_id: "session-3", last_seq: 3 },
256+
{ session_id: "session-2", last_seq: 2 },
257+
],
258+
replicator: "pouchdb",
259+
version: 1,
260+
last_seq: 6,
261+
});
262+
263+
await targetDb.put({
264+
session_id: "session-9",
265+
_id: checkpointer.id,
266+
history: [
267+
{ session_id: "session-9", last_seq: 9 },
268+
{ session_id: "session-8", last_seq: 8 },
269+
{ session_id: "session-7", last_seq: 7 },
270+
{ session_id: "session-6", last_seq: 6 },
271+
{ session_id: "session-5", last_seq: 5 },
272+
],
273+
replicator: "pouchdb",
274+
version: 1,
275+
last_seq: 9,
276+
});
277+
278+
const result = await checkpointer.getCheckpoint();
279+
280+
result.should.equal(6);
281+
});
282+
283+
// target is always written first on update, so always ahead of source
284+
it("get: return source.last_seq if different last_seq, but same session_id", async () => {
285+
const checkpointer = await createCheckpointer();
286+
287+
await sourceDb.put({
288+
session_id: "session-3",
289+
_id: checkpointer.id,
290+
history: [
291+
{ session_id: "session-3", last_seq: 6 },
292+
{ session_id: "session-2", last_seq: 5 },
293+
{ session_id: "session-1", last_seq: 2 },
294+
],
295+
replicator: "pouchdb",
296+
version: 1,
297+
last_seq: 6,
298+
});
299+
300+
await targetDb.put({
301+
session_id: "session-3",
302+
_id: checkpointer.id,
303+
history: [
304+
{ session_id: "session-3", last_seq: 7 },
305+
{ session_id: "session-2", last_seq: 5 },
306+
{ session_id: "session-1", last_seq: 2 },
307+
],
308+
replicator: "pouchdb",
309+
version: 1,
310+
last_seq: 7,
311+
});
312+
313+
const result = await checkpointer.getCheckpoint();
314+
315+
result.should.equal(6);
316+
});
317+
});
318+
319+
// opts writeSourceCheckpoint and writeTargetCheckpoint can be set to only write to one db
320+
const createCheckpointer = async (opts = {}) => {
321+
//replicationId is created with a random docId
322+
const randomDocId = `${Date.now()}-${Math.random()}`;
323+
const replicationId = await genReplicationId(sourceDb, targetDb, {
324+
doc_ids: [randomDocId],
325+
});
326+
327+
const checkpointer = new Checkpointer(
328+
sourceDb,
329+
targetDb,
330+
replicationId,
331+
{},
332+
opts
333+
);
334+
return checkpointer;
335+
};
336+
337+
const getCheckpointDocs = async (id) => {
338+
const [srcDoc, tgtDoc] = await Promise.all([
339+
getCheckpointDocIfExists(sourceDb, id),
340+
getCheckpointDocIfExists(targetDb, id),
341+
]);
342+
return { srcDoc, tgtDoc };
343+
};
344+
345+
const getCheckpointDocIfExists = async (db, id) => {
346+
try {
347+
return await db.get(id);
348+
} catch (e) {
349+
if (e.status === 404) {
350+
return null;
351+
}
352+
throw new Error("Unexpected error", e.message);
353+
}
354+
};

0 commit comments

Comments
 (0)