// TODO: This file was created by bulk-decaffeinate. // Sanity-check the conversion and remove this comment. import _ from "lodash" import chai from "chai" const { assert } = chai import sinon from "sinon" import lolex from "lolex" import MemoryDb from "../src/MemoryDb" import HybridDb from "../src/HybridDb" import db_queries from "./db_queries" // Note: Assumes local db is synchronous! function fail() { throw new Error("failed") } describe("HybridDb", function () { before(function ( this: any, done: any ) { this.reset = (done: any) => { this.local = new MemoryDb() this.remote = new MemoryDb() this.hybrid = new HybridDb(this.local, this.remote) this.local.addCollection("scratch") this.lc = this.local.scratch this.remote.addCollection("scratch") this.rc = this.remote.scratch this.hybrid.addCollection("scratch") this.hc = this.hybrid.scratch this.col = this.hc done() } return this.reset(done) }) describe("passes queries", function (this: any) { beforeEach(function (done: any) { return this.reset(done) }) return db_queries.call(this) }) context("resets each time", function () { beforeEach(function (done: any) { return this.reset(done) }) describe("interim:true (default)", function () { it("find gives only one result if data unchanged", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 1 }) this.rc.seed({ _id: "2", a: 2 }) let calls = 0 return this.hc.find({}).fetch(function (data: any) { calls += 1 assert.equal(data.length, 2) assert.equal(calls, 1) done() }, fail) }) it("find gives results twice if remote gives different answer", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) this.rc.seed({ _id: "2", a: 4 }) let calls = 0 return this.hc.find({}).fetch(function (data: any) { assert.equal(data.length, 2) calls = calls + 1 if (calls >= 2) { done() } }, fail) }) it("find gives results once if remote gives same answer with sort differences", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.find = () => ({ fetch(success: any) { return success([ { _id: "2", a: 2 }, { _id: "1", a: 1 } ]) } }) return this.hc.find({}).fetch(function (data: any) { assert.equal(data.length, 2) done() }, fail) }) it("local upserts are respected", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.upsert({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 1 }) this.rc.seed({ _id: "2", a: 4 }) return this.hc.findOne( { _id: "2" }, function (doc: any) { assert.deepEqual(doc, { _id: "2", a: 2 }) done() }, fail ) }) }) describe("cacheFind: true (default)", function () { it("find performs full field remote queries", function (done: any) { this.rc.seed({ _id: "1", a: 1, b: 11 }) this.rc.seed({ _id: "2", a: 2, b: 12 }) return this.hc.find({}, { fields: { b: 0 } }).fetch((data: any) => { if (data.length === 0) { return } assert.isUndefined(data[0].b) return this.lc.findOne({ _id: "1" }, function (doc: any) { assert.equal(doc.b, 11) done() }) }) }) it("caches remote data", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) this.rc.seed({ _id: "2", a: 2 }) let calls = 0 return this.hc.find({}).fetch((data: any) => { assert.equal(data.length, 2) calls = calls + 1 // After second call, check that local collection has latest if (calls === 2) { return this.lc.find({}).fetch(function (data: any) { assert.equal(data.length, 2) assert.deepEqual(_.map(data, "a"), [3, 2]) done() }) } }) }) it("snapshots local upserts/removes to prevent race condition", function (done: any) { // If the server receives the upsert/remove *after* the query and returns *before* the // query does, a newly upserted item may be removed from cache this.lc.upsert({ _id: "1", a: 1 }) const oldRcFind = this.rc.find this.rc.find = () => { return { fetch: (success: any) => { // Simulate separate process having performed and resolved upsert this.lc.pendingUpserts((us: any) => { return this.lc.resolveUpserts(us) }) return success([]) } } } return this.hc.find({}, { interim: false }).fetch((data: any) => { this.rc.find = oldRcFind assert.equal(data.length, 1) done() }) }) }) describe("cacheFindOne: true (default)", function () { it("findOne performs full field remote queries", function (done: any) { this.rc.seed({ _id: "1", a: 1, b: 11 }) this.rc.seed({ _id: "2", a: 2, b: 12 }) return this.hc.findOne({ _id: "1" }, { fields: { b: 0 } }, (doc: any) => { assert.isUndefined(doc.b) return this.lc.findOne({ _id: "1" }, function (doc: any) { assert.equal(doc.b, 11) done() }) }) }) it("findOne gives results twice if remote gives different answer", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) this.rc.seed({ _id: "2", a: 4 }) let calls = 0 return this.hc.findOne( { _id: "1" }, function (data: any) { calls = calls + 1 if (calls === 1) { assert.deepEqual(data, { _id: "1", a: 1 }) } if (calls >= 2) { assert.deepEqual(data, { _id: "1", a: 3 }) done() } }, fail ) }) it("findOne gives local results once if remote fails", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.rc.findOne = (selector: any, options = {}, success: any, error: any) => error(new Error("fail")) this.rc.find = (selector: any, options: any) => ({ fetch(success: any, error: any) { return error() } }) return this.hc.findOne( { _id: "1" }, function (data: any) { assert.equal(data.a, 1) done() }, fail ) }) it("findOne gives local results selected not by _id once if remote fails", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.rc.findOne = (selector: any, options = {}, success: any, error: any) => error(new Error("fail")) this.rc.find = (selector: any, options: any) => ({ fetch(success: any, error: any) { return error() } }) return this.hc.findOne( { a: 1 }, function (data: any) { assert.equal(data.a, 1) done() }, fail ) }) it("findOne gives local results once if remote fails", function (done: any) { let called = 0 this.rc.findOne = function (selector: any, options = {}, success: any, error: any) { called = called + 1 return error(new Error("fail")) } this.rc.find = (selector: any, options: any) => ({ fetch(success: any, error: any) { called = called + 1 return error() } }) return this.hc.findOne( { _id: "xyz" }, function (data: any) { assert.equal(data, null) assert.equal(called, 1) done() }, fail ) }) it("findOne keeps local cache updated on remote change", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) this.rc.seed({ _id: "2", a: 4 }) let calls = 0 return this.hc.findOne( { _id: "1" }, (data: any) => { calls = calls + 1 if (calls === 1) { assert.deepEqual(data, { _id: "1", a: 1 }) } if (calls >= 2) { assert.deepEqual(data, { _id: "1", a: 3 }) this.lc.find({}, {}).fetch((data: any) => assert.deepEqual(_.map(data, "a"), [3, 2])) done() } }, fail ) }) }) describe("interim: false", () => it("find gives final results only", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) this.rc.seed({ _id: "2", a: 4 }) const calls = 0 return this.hc.find({}, { interim: false }).fetch(function (data: any) { assert.equal(data.length, 2) assert.equal(data[0].a, 1) assert.equal(data[1].a, 4) done() }, fail) })) describe("interim: false with timeout", function () { beforeEach(function (this: any) { return (this.clock = lolex.install()) }) afterEach(function (this: any) { return this.clock.uninstall() }) it("find gives final results if in time", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) const oldFind = this.rc.find this.rc.find = (where: any, params: any) => { return { fetch: (success: any, error: any) => { // Wait a bit this.clock.tick(500) success([ { _id: "1", a: 3 }, { _id: "2", a: 4 } ]) return this.clock.tick(1) } } } this.hc.find({}, { interim: false, timeout: 1000 }).fetch(function (data: any) { assert.equal(data.length, 2) assert.equal(data[0].a, 1) assert.equal(data[1].a, 4) done() }, fail) return this.clock.tick(1) }) // Tick for setTimeout 0 it("find gives local results if out of time", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) const oldFind = this.rc.find this.rc.find = (where: any, params: any) => { return { fetch: (success: any, error: any) => { // Wait a bit too long this.clock.tick(1500) success([ { _id: "1", a: 3 }, { _id: "2", a: 4 } ]) return this.clock.tick(1) } } } this.hc.find({}, { interim: false, timeout: 1000 }).fetch(function (data: any) { assert.equal(data.length, 2) assert.equal(data[0].a, 1) assert.equal(data[1].a, 2) done() }, fail) return this.clock.tick(1) }) // Tick for setTimeout 0 it("find gives local results but still caches if out of time", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) const oldFind = this.rc.find this.rc.find = (where: any, params: any) => { return { fetch: (success: any, error: any) => { // Wait a bit too long this.clock.tick(1500) success([ { _id: "1", a: 3 }, { _id: "2", a: 4 } ]) return this.clock.tick(2000) } } } this.hc.find({}, { interim: false, timeout: 1000 }).fetch((data: any) => { assert.equal(data.length, 2) assert.equal(data[0].a, 1) assert.equal(data[1].a, 2) // Wait longer for remote to complete return setTimeout(() => { return this.lc.find({}, {}).fetch((data: any) => { assert.equal(data.length, 2) assert.equal(data[0].a, 1, "Should not change since upsert") assert.equal(data[1].a, 4) done() }) }, 1000) }, fail) return this.clock.tick(1) }) // Tick for setTimeout 0 it("find gives local results once if remote fails then out of time", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) const oldFind = this.rc.find this.rc.find = (where: any, params: any) => { return { fetch: (success: any, error: any) => { error(new Error("Fail")) return this.clock.tick(1) } } } let called = 0 this.hc.find({}, { interim: false, timeout: 1000 }).fetch((data: any) => { assert.equal(data.length, 2) assert.equal(data[0].a, 1) assert.equal(data[1].a, 2) called += 1 // Wait a bit too long this.clock.tick(1500) if (called > 1) { console.error("Fail! Called twice") } assert.equal(called, 1) done() }, fail) return this.clock.tick(1) }) // Tick for setTimeout 0 it("find gives local results once if out of time then remote fails", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) const oldFind = this.rc.find this.rc.find = (where: any, params: any) => { return { fetch: (success: any, error: any) => { this.clock.tick(1500) return error(new Error("Fail")) } } } let called = 0 this.hc.find({}, { interim: false, timeout: 1000 }).fetch((data: any) => { assert.equal(data.length, 2) assert.equal(data[0].a, 1) assert.equal(data[1].a, 2) called += 1 if (called > 1) { console.error("Fail! Called twice") } assert.equal(called, 1) done() }, fail) return this.clock.tick(1) }) }) // Tick for setTimeout 0 describe("cacheFind: false", function () { it("find performs partial field remote queries", function (done: any) { sinon.spy(this.rc, "find") this.rc.seed({ _id: "1", a: 1, b: 11 }) this.rc.seed({ _id: "2", a: 2, b: 12 }) return this.hc.find({}, { fields: { b: 0 }, cacheFind: false }).fetch((data: any) => { if (data.length === 0) { return } assert.isUndefined(data[0].b) assert.deepEqual(this.rc.find.firstCall.args[1].fields, { b: 0 }) this.rc.find.restore() done() }) }) it("does not cache remote data", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) this.rc.seed({ _id: "2", a: 2 }) let calls = 0 return this.hc.find({}, { cacheFind: false }).fetch((data: any) => { assert.equal(data.length, 2) calls = calls + 1 // After second call, check that local collection is unchanged if (calls === 2) { return this.lc.find({}).fetch(function (data: any) { assert.equal(data.length, 2) assert.deepEqual(_.map(data, "a"), [1, 2]) done() }) } }) }) }) describe("cacheFindOne: false", () => it("findOne performs partial field remote queries", function (done: any) { sinon.spy(this.rc, "find") this.rc.seed({ _id: "1", a: 1, b: 11 }) this.rc.seed({ _id: "2", a: 2, b: 12 }) return this.hc.findOne({ _id: "1" }, { fields: { b: 0 }, cacheFindOne: false }, (data: any) => { if (data === null) { return } assert.isUndefined(data.b) assert.deepEqual(this.rc.find.getCall(0).args[1].fields, { b: 0 }) this.rc.find.restore() done() }) })) context("shortcut: false (default)", function () { it("findOne calls both local and remote", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) this.rc.seed({ _id: "2", a: 4 }) let calls = 0 return this.hc.findOne( { _id: "1" }, function (data: any) { calls += 1 if (calls === 1) { return assert.deepEqual(data, { _id: "1", a: 1 }) } else { assert.deepEqual(data, { _id: "1", a: 3 }) done() } }, fail ) }) context("interim: false", () => it("findOne calls both local and remote", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) this.rc.seed({ _id: "2", a: 4 }) return this.hc.findOne( { _id: "1" }, { interim: false }, function (data: any) { assert.deepEqual(data, { _id: "1", a: 3 }) done() }, fail ) }) ) it("findOne calls remote if not found", function (done: any) { this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) this.rc.seed({ _id: "2", a: 4 }) const calls = 0 return this.hc.findOne( { _id: "1" }, { shortcut: true }, function (data: any) { assert.deepEqual(data, { _id: "1", a: 3 }) done() }, fail ) }) }) context("shortcut: true", function () { it("findOne only calls local if found", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) this.rc.seed({ _id: "2", a: 4 }) const calls = 0 return this.hc.findOne( { _id: "1" }, { shortcut: true }, function (data: any) { assert.deepEqual(data, { _id: "1", a: 1 }) done() }, fail ) }) it("findOne calls remote if not found", function (done: any) { this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) this.rc.seed({ _id: "2", a: 4 }) const calls = 0 return this.hc.findOne( { _id: "1" }, { shortcut: true }, function (data: any) { assert.deepEqual(data, { _id: "1", a: 3 }) done() }, fail ) }) }) context("cacheFind: false, interim: false", function () { beforeEach(function (this: any) { this.lc.seed({ _id: "1", a: 1 }) this.lc.seed({ _id: "2", a: 2 }) this.rc.seed({ _id: "1", a: 3 }) return this.rc.seed({ _id: "2", a: 4 }) }) it("find only calls remote", function (done: any) { return this.hc.find({}, { cacheFind: false, interim: false }).fetch(function (data: any) { assert.deepEqual(_.map(data, "a"), [3, 4]) done() }) }) it("find does not cache results", function (done: any) { return this.hc.find({}, { cacheFind: false, interim: false }).fetch((data: any) => { return this.lc.find({}).fetch((data: any) => { assert.deepEqual(_.map(data, "a"), [1, 2]) done() }) }) }) it("find falls back to local if remote fails", function (done: any) { this.rc.find = (selector: any, options: any) => ({ fetch(success: any, error: any) { return error() } }) return this.hc.find({}, { cacheFind: false, interim: false }).fetch(function (data: any) { assert.deepEqual(_.map(data, "a"), [1, 2]) done() }) }) it("findOne falls back to local if remote fails", function (done: any) { this.rc.find = (selector: any, options: any) => ({ fetch(success: any, error: any) { return error() } }) this.hc.findOne({ _id: "1" }, { cacheFind: false, interim: false }, function (data: any) { assert.deepEqual(data, { _id: "1", a: 1 }) done() }) }) it("find errors if useLocalOnRemoteError:false if remote fails", function (done: any) { this.rc.find = (selector: any, options: any) => { return { fetch(success: any, error: any) { return error() } } } return this.hc.find({}, { cacheFind: false, interim: false, useLocalOnRemoteError: false }).fetch( (data: any) => { return assert.fail() }, (err: any) => done() ) }) it("find respects local upserts", function (done: any) { this.lc.upsert({ _id: "1", a: 9 }) return this.hc.find({}, { cacheFind: false, interim: false, sort: ["_id"] }).fetch((data: any) => { assert.deepEqual(_.map(data, "a"), [9, 4]) done() }) }) it("find respects local removes", function (done: any) { this.lc.remove("1") return this.hc.find({}, { cacheFind: false, interim: false }).fetch(function (data: any) { assert.deepEqual(_.map(data, "a"), [4]) done() }) }) }) it("upload applies pending upserts", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) this.lc.upsert({ _id: "2", a: 2 }) return this.hybrid.upload(() => { return this.lc.pendingUpserts((data: any) => { assert.equal(data.length, 0) return this.rc.pendingUpserts(function (data: any) { assert.deepEqual(_.map(_.map(data, "doc"), "a"), [1, 2]) done() }) }) }, fail) }) it("upload sorts pending upserts", function (done: any) { this.lc.upsert({ _id: "1", a: 1, b: 2 }) this.lc.upsert({ _id: "2", a: 2, b: 1 }) const hybrid = new HybridDb(this.local, this.remote) hybrid.addCollection("scratch", { sortUpserts(u1: any, u2: any) { if (u1.b < u2.b) { return -1 } else { return 1 } } }) const upserts: any = [] this.rc.upsert = (doc: any, base: any, success: any, error: any) => { upserts.push(doc) return success() } return hybrid.upload(() => { return this.lc.pendingUpserts((data: any) => { assert.equal(data.length, 0) assert.deepEqual(_.map(upserts, "a"), [2, 1]) done() }) }, fail) }) it("does not resolve upsert if data changed, but changes base", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) // Override pending upserts to change doc right before returning const oldPendingUpserts = this.lc.pendingUpserts this.lc.pendingUpserts = (success: any) => { return oldPendingUpserts.call(this.lc, (upserts: any) => { // Alter row this.lc.upsert({ _id: "1", a: 2 }) return success(upserts) }) } return this.hybrid.upload(() => { return this.lc.pendingUpserts((data: any) => { assert.equal(data.length, 1) assert.deepEqual(data[0].doc, { _id: "1", a: 2 }) assert.deepEqual(data[0].base, { _id: "1", a: 1 }) return this.rc.pendingUpserts(function (data: any) { assert.deepEqual(data[0].doc, { _id: "1", a: 1 }) assert.isNull(data[0].base) done() }) }) }, fail) }) it("caches new upserted value", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) // Override remote upsert to change returned doc this.rc.upsert = (docs: any, bases: any, success: any) => success({ _id: "1", a: 2 }) return this.hybrid.upload(() => { return this.lc.pendingUpserts((data: any) => { assert.equal(data.length, 0) return this.lc.findOne({ _id: "1" }, {}, function (data: any) { assert.deepEqual(data, { _id: "1", a: 2 }) done() }) }) }, fail) }) it("upload applies pending removes", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.rc.seed({ _id: "1", a: 1 }) this.hc.remove("1") return this.hybrid.upload(() => { return this.lc.pendingRemoves((data: any) => { assert.equal(data.length, 0) return this.rc.pendingRemoves(function (data: any) { assert.deepEqual(data, ["1"]) done() }) }) }, fail) }) it("keeps upserts and deletes if failed to apply", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) this.lc.upsert({ _id: "2", a: 2 }) this.lc.seed({ _id: "3", a: 3 }) this.rc.seed({ _id: "3", a: 3 }) this.hc.remove("3") this.rc.upsert = (docs: any, bases: any, success: any, error: any) => error(new Error("fail")) this.rc.remove = (id: any, success: any, error: any) => error(new Error("fail")) return this.hybrid.upload( () => assert.fail(), () => { return this.lc.pendingUpserts((data: any) => { assert.equal(data.length, 2) this.lc.pendingRemoves(function (data: any) { assert.equal(data.length, 1) return assert.equal(data[0], "3") }) done() }) } ) }) it("removes upsert if fails with 410 (gone) and continue", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) this.rc.upsert = (docs: any, bases: any, success: any, error: any) => error({ status: 410 }) return this.hybrid.upload(() => { return this.lc.pendingUpserts((data: any) => { assert.equal(data.length, 0) return this.lc.pendingRemoves((data: any) => { assert.equal(data.length, 0) return this.lc.findOne( { _id: "1" }, function (data: any) { assert.isNull(data) done() }, fail ) }, fail) }, fail) }, fail) }) it("removes upsert if fails with 403 (permission) and fail", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) this.rc.upsert = (docs: any, bases: any, success: any, error: any) => error({ status: 403 }) return this.hybrid.upload(fail, () => { return this.lc.pendingUpserts((data: any) => { assert.equal(data.length, 0) return this.lc.pendingRemoves((data: any) => { assert.equal(data.length, 0) return this.lc.findOne( { _id: "1" }, function (data: any) { assert.isNull(data) done() }, fail ) }, fail) }, fail) }) }) it("removes document if remove fails with 403 (permission) and fail", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.hc.remove("3") this.rc.remove = (id: any, success: any, error: any) => error({ status: 403 }) return this.hybrid.upload( () => assert.fail(), () => { return this.lc.pendingUpserts((data: any) => { assert.equal(data.length, 0, "Should have zero upserts") return this.lc.pendingRemoves((data: any) => { assert.equal(data.length, 0, "Should have zero removes") return this.lc.findOne({ _id: "1" }, function (data: any) { assert.equal(data.a, 1) done() }) }) }) } ) }) it("removes upsert if returns null", function (done: any) { this.lc.upsert({ _id: "1", a: 1 }) this.rc.upsert = (docs: any, bases: any, success: any, error: any) => success(null) return this.hybrid.upload(() => { return this.lc.pendingUpserts((data: any) => { assert.equal(data.length, 0) return this.lc.pendingRemoves((data: any) => { assert.equal(data.length, 0) return this.lc.findOne( { _id: "1" }, function (data: any) { assert.isNull(data) done() }, fail ) }, fail) }, fail) }, fail) }) it("upserts to local db", function (done: any) { this.hc.upsert({ _id: "1", a: 1 }) return this.lc.pendingUpserts(function (data: any) { assert.equal(data.length, 1) done() }) }) it("passes up error from local db", function (done: any) { const oldUpsert = this.lc.upsert try { this.lc.upsert = function (docs: any, bases: any, success: any, error: any) { if (_.isFunction(bases)) { error = success success = bases } return error("FAIL") } } catch (error) {} return this.hc.upsert( { _id: "1", a: 1 }, () => done(new Error("Should not call success")), (err: any) => done() ) }) it("upserts to local db with base version", function (done: any) { this.hc.upsert({ _id: "1", a: 2 }, { _id: "1", a: 1 }) return this.lc.pendingUpserts(function (data: any) { assert.equal(data.length, 1) assert.equal(data[0].doc.a, 2) assert.equal(data[0].base.a, 1) done() }) }) it("removes to local db", function (done: any) { this.lc.seed({ _id: "1", a: 1 }) this.hc.remove("1") return this.lc.pendingRemoves(function (data: any) { assert.equal(data.length, 1) done() }) }) }) context("cacheFind: false, interim: false", function () { beforeEach(function ( this: any, ) { this.local = new MemoryDb() this.remote = new MemoryDb() this.hybrid = new HybridDb(this.local, this.remote) this.local.addCollection("scratch") this.lc = this.local.scratch this.remote.addCollection("scratch") this.rc = this.remote.scratch this.hybrid.addCollection("scratch") this.hc = this.hybrid.scratch // Seed some remote data this.rc.seed({ _id: "1", a: 3 }) return this.rc.seed({ _id: "2", a: 4 }) }) it("find uses remote", function (done: any) { return this.hc.find({}, { cacheFind: false, interim: false }).fetch((data: any) => { assert.deepEqual(_.map(data, "a"), [3, 4]) done() }) }) it("find does not cache results", function (done: any) { return this.hc.find({}, { cacheFind: false, interim: false }).fetch((data: any) => { return this.lc.find({}).fetch((data: any) => { assert.equal(data.length, 0) done() }) }) }) it("find respects local upserts", function (done: any) { this.lc.upsert({ _id: "1", a: 9 }) return this.hc.find({}, { cacheFind: false, interim: false, sort: ["_id"] }).fetch((data: any) => { assert.deepEqual(_.map(data, "a"), [9, 4]) done() }) }) it("find respects local removes", function (done: any) { this.lc.remove("1") return this.hc.find({}, { cacheFind: false, interim: false }).fetch((data: any) => { assert.deepEqual(_.map(data, "a"), [4]) done() }) }) it("findOne without _id selector uses remote", function (done: any) { return this.hc.findOne({}, { cacheFindOne: false, interim: false, sort: ["_id"] }, (data: any) => { assert.deepEqual(data, { _id: "1", a: 3 }) done() }) }) it("findOne without _id selector respects local upsert", function (done: any) { this.lc.upsert({ _id: "1", a: 9 }) return this.hc.findOne({}, { cacheFindOne: false, interim: false, sort: ["_id"] }, (data: any) => { assert.deepEqual(data, { _id: "1", a: 9 }) done() }) }) it("findOne without _id selector respects local remove", function (done: any) { this.lc.remove("1") return this.hc.findOne({}, { cacheFindOne: false, sort: ["_id"] }, (data: any) => { assert.deepEqual(data, { _id: "2", a: 4 }) done() }) }) it("findOne with _id selector uses remote", function (done: any) { return this.hc.findOne({ _id: "1" }, { cacheFindOne: false, sort: ["_id"] }, (data: any) => { assert.deepEqual(data, { _id: "1", a: 3 }) done() }) }) it("findOne with _id selector respects local upsert", function (done: any) { this.lc.upsert({ _id: "1", a: 9 }) return this.hc.findOne({ _id: "1" }, { cacheFindOne: false, interim: false, sort: ["_id"] }, (data: any) => { assert.deepEqual(data, { _id: "1", a: 9 }) done() }) }) it("findOne with _id selector respects local remove", function (done: any) { this.lc.remove("1") return this.hc.findOne({ _id: "1" }, { cacheFindOne: false, interim: false, sort: ["_id"] }, (data: any) => { assert.isNull(data) done() }) }) }) }) // Only use this test if cacheUpsert is used in the future // it "upload success removes from local", (done) -> // @lc.upsert({ _id:"1", a:9 }) // @hybrid.upload => // # Not pending locally // @lc.pendingRemoves (data) => // assert.equal data.length, 0 // # Pending remotely // @rc.pendingUpserts (data) => // assert.deepEqual _.pluck(_.pluck(data, 'doc'), "a"), [9] // # Not cached locally // @lc.find({}).fetch (data) => // assert.equal data.length, 0 // done() // , fail // , fail // , fail