minimongo
Version:
Client-side mongo database with server sync over http
1,141 lines (969 loc) • 34.5 kB
text/typescript
// 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("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