Skip to content

Commit 97383f7

Browse files
authored
Fix JS Server : snapshot missing, docID filters, getDocuments, and doc's flags (#299)
* Fix JS Server : snapshot missing, docID filters, getDocuments, and doc flags * Fixed snaphot missing as it is deleted after being verification (We have a test that calls to verify on the sampe snapshot more than once). * Fixed documentIDs filter due to an invalid type check. * Fixed getDocuments result not including body. * Fixed document replication’s flags value. * Add enableAutoPurge and resetCheckpoint support * Disable non-applicable tests for JS
1 parent 00ca1c4 commit 97383f7

File tree

6 files changed

+49
-24
lines changed

6 files changed

+49
-24
lines changed

servers/javascript/src/filters.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type * as cbl from "@couchbase/lite-js";
22
import type * as tdk from "./tdkSchema";
3-
import { check } from "./utils";
3+
import {check, isObject} from "./utils";
44

55

66
// https://github.com/couchbaselabs/couchbase-lite-tests/blob/main/spec/api/replication-filters.md
@@ -12,8 +12,15 @@ type PullFilterMaker = (spec: tdk.Filter) => PullReplicatorFilter;
1212

1313
function _documentIDs(spec: tdk.Filter, rev: cbl.RemoteRevisionInfo | cbl.RevisionInfo) : boolean {
1414
const documentIDs = spec.params?.documentIDs;
15-
check(Array.isArray(documentIDs), "invalid documentIDs in filter");
16-
const docSet = new Set(documentIDs);
15+
check(isObject(documentIDs), "documentIDs must be an object");
16+
// Currently JS API doesn't provide information regarding the collection
17+
// so validating that only one collection is allowed at least for now.
18+
check(Object.keys(documentIDs).length === 1, "documentIDs have more than one collection");
19+
20+
const ids = Object.values(documentIDs)[0];
21+
check(Array.isArray(ids), "documentIDs's value must be an array");
22+
23+
const docSet = new Set(ids);
1724
return docSet.has(rev.id as string);
1825
}
1926

servers/javascript/src/snapshot.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// the file licenses/APL2.txt.
1111
//
1212

13-
import { HTTPError, normalizeCollectionID } from "./utils";
13+
import { docToJSON, HTTPError, normalizeCollectionID } from "./utils";
1414
import { KeyPath, KeyPathCache } from "./keyPath";
1515
import type * as tdk from "./tdkSchema";
1616
import type * as cbl from "@couchbase/lite-js";
@@ -238,9 +238,3 @@ class DocumentMap<T> {
238238

239239
#map = new Map<string, Map<cbl.DocID, T>>();
240240
}
241-
242-
243-
/** Converts a CBLDocument to a regular JSON object (i.e. Blobs are replaced by their metadata.) */
244-
function docToJSON(doc: cbl.CBLDocument): cbl.JSONObject {
245-
return JSON.parse(JSON.stringify(doc)) as cbl.JSONObject;
246-
}

servers/javascript/src/tdk.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import { KeyPathCache } from "./keyPath";
1616
import { LogSlurpSender } from "./logSlurpSender";
17-
import { check, HTTPError, normalizeCollectionID } from "./utils";
17+
import { check, docToJSON, HTTPError, normalizeCollectionID } from "./utils";
1818
import { Snapshot } from "./snapshot";
1919
import type { TestRequest } from "./testServer";
2020
import * as tdk from "./tdkSchema";
@@ -143,7 +143,8 @@ export class TDKImpl implements tdk.TDK, AsyncDisposable {
143143
const doc = await coll.getDocument(rq.document.id);
144144
if (!doc) throw new HTTPError(404, `No document "${rq.document.id}"`);
145145
const m = cbl.meta(doc);
146-
return {_id: rq.document.id, _revs: m.revisionID!};
146+
const jsonBody = docToJSON(doc);
147+
return {_id: rq.document.id, _revs: m.revisionID!, ...jsonBody};
147148
}
148149

149150

@@ -169,7 +170,7 @@ export class TDKImpl implements tdk.TDK, AsyncDisposable {
169170
function updatePath(pathStr: string, value: cbl.CBLValue | undefined): void {
170171
if (!KeyPathCache.path(pathStr).write(doc, value))
171172
throw new HTTPError(400, `Invalid path ${pathStr} in doc ${update.documentID}`);
172-
};
173+
}
173174

174175
switch (update.type) {
175176
case 'UPDATE': {
@@ -233,9 +234,11 @@ export class TDKImpl implements tdk.TDK, AsyncDisposable {
233234
filter: CreatePushFilter(colls.pushFilter),
234235
};
235236
}
237+
236238
if (rq.config.replicatorType !== 'push') {
237239
collCfg.pull = {
238240
continuous: rq.config.continuous,
241+
enableAutoPurge: rq.config.enableAutoPurge,
239242
channels: colls.channels,
240243
filter: CreatePullFilter(colls.pullFilter),
241244
};
@@ -246,8 +249,12 @@ export class TDKImpl implements tdk.TDK, AsyncDisposable {
246249
collCfg.pull.conflictResolver = resolverFn;
247250
}
248251
}
252+
249253
if (colls.documentIDs)
250254
collCfg.documentIDs = colls.documentIDs;
255+
256+
if (rq.reset)
257+
collCfg.resetCheckpoint = rq.reset;
251258

252259
for (const collName of colls.names)
253260
config.collections[normalizeCollectionID(collName)] = collCfg;
@@ -261,11 +268,15 @@ export class TDKImpl implements tdk.TDK, AsyncDisposable {
261268
if (info.documents === undefined)
262269
info.documents = [];
263270
for (const doc of documents) {
271+
const flags: ('deleted' | 'accessRemoved')[] = [];
272+
doc.deleted && flags.push('deleted');
273+
doc.lostAccess && flags.push('accessRemoved');
274+
264275
info.documents.push({
265276
collection: collectionIDWithScope(collection.name),
266277
documentID: doc.docID,
267278
isPush: (direction === 'push'),
268-
flags: (doc.deleted ? ["deleted"] : []),
279+
flags: flags,
269280
error: this.#mkErrorInfo(doc.error),
270281
});
271282
}
@@ -352,8 +363,6 @@ export class TDKImpl implements tdk.TDK, AsyncDisposable {
352363
throw new HTTPError(404, `No such snapshot ${rq.snapshot}`);
353364
if (snap.db !== db)
354365
throw new HTTPError(400, `Snapshot is of a different database, ${db.name}`);
355-
this.#snapshots.delete(rq.snapshot);
356-
357366
return await snap.verify(rq.changes, this.#downloadBlob.bind(this));
358367
}
359368

@@ -362,9 +371,9 @@ export class TDKImpl implements tdk.TDK, AsyncDisposable {
362371

363372

364373
#mkErrorInfo(error: Error | undefined): tdk.ErrorInfo | undefined {
365-
let code = -1
374+
let code = -1;
366375
if (error instanceof cbl.ReplicatorError) {
367-
code = (error as cbl.ReplicatorError).code ?? -1;
376+
code = error.code ?? -1;
368377
}
369378

370379
return error ? {domain: "CBL-JS", code: code, message: error.message} : undefined;

servers/javascript/src/utils.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// the file licenses/APL2.txt.
1111
//
1212

13-
import type { CBLDictionary, CBLValue, JSONObject, JSONValue } from "@couchbase/lite-js";
13+
import type { CBLDictionary, CBLDocument, CBLValue, JSONObject, JSONValue } from "@couchbase/lite-js";
1414
import { Blob } from "@couchbase/lite-js";
1515

1616

@@ -44,3 +44,9 @@ export function isObject(val: JSONValue | undefined) : val is JSONObject {
4444
export function isDict(val: CBLValue | undefined) : val is CBLDictionary {
4545
return typeof val === "object" && val !== null && !Array.isArray(val) && !(val instanceof Blob);
4646
}
47+
48+
49+
/** Converts a CBLDocument to a regular JSON object (i.e. Blobs are replaced by their metadata.) */
50+
export function docToJSON(doc: CBLDocument): JSONObject {
51+
return JSON.parse(JSON.stringify(doc)) as JSONObject;
52+
}

tests/dev_e2e/test_basic_replication.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
)
2020
from cbltest.api.syncgateway import DocumentUpdateEntry
2121
from cbltest.api.test_functions import compare_local_and_remote
22+
from cbltest.responses import ServerVariant
2223
from cbltest.utils import assert_not_null
2324

2425

@@ -64,11 +65,14 @@ async def test_replicate_non_existing_sg_collections(
6465
status = await replicator.wait_for(ReplicatorActivityLevel.STOPPED)
6566

6667
self.mark_test_step("Check that the replicator's error is CBL/10404")
67-
assert (
68-
status.error is not None
69-
and status.error.code == 10404
70-
and ErrorDomain.equal(status.error.domain, ErrorDomain.CBL)
71-
)
68+
if (await cblpytest.test_servers[0].get_info()).variant == ServerVariant.JS:
69+
assert status.error is not None and status.error.code == 404
70+
else:
71+
assert (
72+
status.error is not None
73+
and status.error.code == 10404
74+
and ErrorDomain.equal(status.error.domain, ErrorDomain.CBL)
75+
)
7276

7377
await cblpytest.test_servers[0].cleanup()
7478

tests/dev_e2e/test_replication_blob.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from cbltest.api.replicator_types import ReplicatorBasicAuthenticator
1717
from cbltest.api.syncgateway import DocumentUpdateEntry
1818
from cbltest.api.test_functions import compare_local_and_remote
19+
from cbltest.responses import ServerVariant
1920
from cbltest.utils import assert_not_null
2021

2122

@@ -28,6 +29,10 @@ class TestReplicationBlob(CBLTestClass):
2829
async def test_pull_non_blob_changes_with_delta_sync_and_compact(
2930
self, cblpytest: CBLPyTest, dataset_path: Path
3031
):
32+
await self.skip_if_not_platform(
33+
cblpytest.test_servers[0], ServerVariant.ALL & ~ServerVariant.JS
34+
)
35+
3136
self.mark_test_step(
3237
"Reset SG and load `travel` dataset with delta sync enabled."
3338
)

0 commit comments

Comments
 (0)