blob: 292b51af9cbc3ccfb40f42322076aec7fdf69642 [file]
// Licensed under the Apache License, Version 2.0 (the 'License'); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
const Nano = require('..')
const COUCH_URL = 'http://localhost:5984'
const nano = Nano(COUCH_URL)
const nock = require('nock')
const DBNAME = 'db'
afterEach(() => {
nock.cleanAll()
})
test('should be able to follow changes feed - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: 'now', limit: 100, include_docs: false })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start()
return new Promise((resolve, reject) => {
cr.on('seq', function (seq) {
// after our initial call with since=now, we should get a reply with last_seq=0-1
expect(seq).toBe('1-0')
db.changesReader.stop()
resolve()
})
})
})
test('should respect the fastChanges flag - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: 'now', limit: 100, include_docs: false, seq_interval: 100 })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start({ fastChanges: true })
return new Promise((resolve, reject) => {
cr.on('seq', function (seq) {
// after our initial call with since=now, we should get a reply with last_seq=0-1
expect(seq).toBe('1-0')
db.changesReader.stop()
resolve()
})
})
})
test('should respect the selector parameter - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
nock(COUCH_URL)
.post(changeURL, { selector: { name: 'fred' } })
.query({ feed: 'longpoll', timeout: 60000, since: 'now', limit: 100, include_docs: false, filter: '_selector' })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start({ selector: { name: 'fred' } })
return new Promise((resolve, reject) => {
cr.on('seq', function (seq) {
// after our initial call with since=now, we should get a reply with last_seq=0-1
expect(seq).toBe('1-0')
db.changesReader.stop()
resolve()
})
})
})
test('should respect the selector parameter - db.changesReader.spool', async () => {
const changeURL = `/${DBNAME}/_changes`
nock(COUCH_URL)
.post(changeURL, { selector: { name: 'fred' } })
.query({ since: 'now', seq_interval: 100, include_docs: false, filter: '_selector' })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
const db = nano.db.use(DBNAME)
const cr = db.changesReader.spool({ since: 'now', selector: { name: 'fred' } })
return new Promise((resolve, reject) => {
cr.on('end', function (data) {
resolve()
})
})
})
test('should emit change and batch events - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
const changes = [{ seq: null, id: '1', changes: ['1-1'] },
{ seq: null, id: '2', changes: ['1-1'] },
{ seq: null, id: '3', changes: ['1-1'] },
{ seq: null, id: '4', changes: ['1-1'] },
{ seq: null, id: '5', changes: ['1-1'] }]
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: 'now', limit: 100, include_docs: false })
.reply(200, { results: changes, last_seq: '1-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start()
let i = 0
return new Promise((resolve, reject) => {
cr.on('change', function (c) {
expect(c).toStrictEqual(changes[i++])
}).on('batch', function (b) {
expect(b).toStrictEqual(changes)
}).on('seq', function (seq) {
// after our initial call with since=now, we should get a reply with last_seq=0-1
expect(seq).toBe('1-0')
db.changesReader.stop()
resolve()
})
})
})
it('should keep polling the changes feed - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
const change = { seq: null, id: 'a', changes: ['1-1'] }
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: 'now', limit: 100, include_docs: false })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: '1-0', limit: 100, include_docs: false })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: '1-0', limit: 100, include_docs: false })
.reply(200, { results: [change], last_seq: '2-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start({ timeout: 1000 })
return new Promise((resolve, reject) => {
cr.on('change', function (c) {
// ensure we get a change on the third poll
expect(c).toStrictEqual(change)
db.changesReader.stop()
resolve()
})
})
}, 10000)
it('should keep polling the changes feed (wait: true) - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
const change = { seq: null, id: 'a', changes: ['1-1'] }
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: 'now', limit: 100, include_docs: false })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: '1-0', limit: 100, include_docs: false })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: '1-0', limit: 100, include_docs: false })
.reply(200, { results: [change], last_seq: '2-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start({ timeout: 1000, wait: true })
return new Promise((resolve, reject) => {
cr.on('change', function (c) {
// ensure we get a change on the third poll
expect(c).toStrictEqual(change)
db.changesReader.stop()
resolve()
})
.on('batch', function (data) {
db.changesReader.resume()
})
})
}, 10000)
test('spooling changes - db.changesReader.spool', async () => {
const changeURL = `/${DBNAME}/_changes`
const fs = require('fs')
const reply = fs.readFileSync('./test/changes.json')
const replyObj = JSON.parse(reply)
nock(COUCH_URL)
.post(changeURL)
.query({ since: '0', include_docs: false, seq_interval: 100 })
.reply(200, reply)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.spool({ since: 0 })
return new Promise((resolve, reject) => {
cr.on('batch', function (batch) {
expect(JSON.stringify(batch)).toBe(JSON.stringify(replyObj.results))
}).on('end', (lastSeq) => {
expect(lastSeq).toBe(replyObj.last_seq)
resolve()
})
})
})
test('spooling changes - numeric seq - db.changesReader.spool', async () => {
const changeURL = `/${DBNAME}/_changes`
const fs = require('fs')
const reply = fs.readFileSync('./test/changes_numeric.json')
const replyObj = JSON.parse(reply)
nock(COUCH_URL)
.post(changeURL)
.query({ since: 0, include_docs: false, seq_interval: 100 })
.reply(200, reply)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.spool({ since: 0 })
return new Promise((resolve, reject) => {
cr.on('batch', function (batch) {
expect(JSON.stringify(batch)).toBe(JSON.stringify(replyObj.results))
}).on('end', (lastSeq) => {
expect(lastSeq).toBe(replyObj.last_seq)
resolve()
})
})
})
test('should handle the batchSize parameter - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
const limit = 44
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: 'now', limit: limit, include_docs: false })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start({ batchSize: limit })
return new Promise((resolve, reject) => {
cr.on('seq', function (seq) {
// after our initial call with since=now, we should get a reply with last_seq=0-1
expect(seq).toBe('1-0')
db.changesReader.stop()
resolve()
})
})
})
test('should respect the since parameter db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
const limit = 44
const since = 'thedawnoftime'
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: since, limit: limit, include_docs: false })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start({ batchSize: limit, since: since })
return new Promise((resolve, reject) => {
cr.on('seq', function (seq) {
// after our initial call with since=thedawnoftime, we should get a reply with last_seq=0-1
expect(seq).toBe('1-0')
db.changesReader.stop()
resolve()
})
})
})
test('should stop on no changes - db.changesReader.get', async () => {
const changeURL = `/${DBNAME}/_changes`
const since = 'thedawnoftime'
const batchSize = 45
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: since, limit: batchSize, include_docs: false })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.get({ batchSize: batchSize, since: since })
return new Promise((resolve, reject) => {
cr.on('seq', function (seq) {
// after our initial call with since=now, we should get a reply with last_seq=0-1
expect(seq).toBe('1-0')
}).on('end', function () {
resolve()
})
})
})
test('stop after multiple batches - small batch stop - db.changesReader.get', async () => {
const changeURL = `/${DBNAME}/_changes`
const since = 'now'
const batchSize = 45
const batch1 = []
const batch2 = []
for (let i = 0; i < batchSize; i++) {
batch1.push({ seq: (i + 1) + '-0', id: 'a' + i, changes: ['1-1'] })
}
for (let i = 0; i < 5; i++) {
batch2.push({ seq: (45 + i + 1) + '-0', id: 'b' + i, changes: ['1-1'] })
}
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: since, limit: batchSize, include_docs: false })
.reply(200, { results: batch1, last_seq: '45-0', pending: 2 })
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: '45-0', limit: batchSize, include_docs: false })
.reply(200, { results: batch2, last_seq: '50-0', pending: 0 })
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: '50-0', limit: batchSize, include_docs: false })
.reply(200, { results: [], last_seq: '50-0', pending: 0 })
const db = nano.db.use(DBNAME)
const cr = db.changesReader.get({ batchSize: batchSize, since: since })
let batchCount = 0
return new Promise((resolve, reject) => {
cr.on('seq', function (seq) {
switch (batchCount) {
case 0: expect(seq).toBe('45-0'); break
case 1: expect(seq).toBe('50-0'); break
case 2: expect(seq).toBe('50-0'); break
}
batchCount++
}).on('end', function (lastSeq) {
expect(lastSeq).toBe('50-0')
resolve()
})
})
})
test('stop after multiple batches - zero stop - db.changesReader.get', async () => {
const changeURL = `/${DBNAME}/_changes`
const since = 'now'
const batchSize = 45
const batch1 = []
const batch2 = []
for (let i = 0; i < batchSize; i++) {
batch1.push({ seq: null, id: 'a' + i, changes: ['1-1'] })
}
for (let i = 0; i < 5; i++) {
batch2.push({ seq: null, id: 'b' + i, changes: ['1-1'] })
}
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: since, limit: batchSize, include_docs: false })
.reply(200, { results: batch1, last_seq: '45-0', pending: 2 })
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: '45-0', limit: batchSize, include_docs: false })
.reply(200, { results: batch2, last_seq: '90-0', pending: 0 })
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: '90-0', limit: batchSize, include_docs: false })
.reply(200, { results: [], last_seq: '90-0', pending: 0 })
const db = nano.db.use(DBNAME)
const cr = db.changesReader.get({ batchSize: batchSize, since: since })
let batchCount = 0
return new Promise((resolve, reject) => {
cr.on('seq', function (seq) {
if (batchCount === 0) {
expect(seq).toBe('45-0')
batchCount++
} else {
expect(seq).toBe('90-0')
}
}).on('end', function () {
resolve()
})
})
})
test('on bad credentials - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: 'now', limit: 100, include_docs: false })
.reply(401)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start()
return new Promise((resolve, reject) => {
cr.on('error', function (err) {
expect(err.statusCode).toBe(401)
resolve()
})
})
})
test('on bad since value - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 60000, since: 'badtoken', limit: 100, include_docs: false })
.reply(400, { error: 'bad_request', reason: 'Malformed sequence supplied in \'since\' parameter.' })
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start({ since: 'badtoken' })
return new Promise((resolve, reject) => {
cr.on('error', function (err) {
expect(err.statusCode).toBe(400)
resolve()
})
})
})
test('should survive a HTTP 500 response - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
const change = { seq: null, id: 'a', changes: ['1-1'] }
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: 'now', limit: 100, include_docs: false })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: '1-0', limit: 100, include_docs: false })
.reply(500)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: '1-0', limit: 100, include_docs: false })
.reply(200, { results: [change], last_seq: '2-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start({ timeout: 1000 })
return new Promise((resolve, reject) => {
cr.on('change', function (c) {
// ensure we get a change on the third poll
expect(c).toStrictEqual(change)
db.changesReader.stop()
resolve()
}).on('error', function (err) {
expect(err.statusCode).toBe(500)
})
})
}, 10000)
test('should survive HTTP 429 response - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
const change = { seq: null, id: 'a', changes: ['1-1'] }
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: 'now', limit: 100, include_docs: false })
.reply(200, { results: [], last_seq: '1-0', pending: 0 })
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: '1-0', limit: 100, include_docs: false })
.reply(429, { error: 'too_many_requests', reason: 'You\'ve exceeded your current limit of x requests per second for x class. Please try later.', class: 'x', rate: 1 })
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: '1-0', limit: 100, include_docs: false })
.reply(200, { results: [change], last_seq: '2-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start({ timeout: 1000 })
return new Promise((resolve, reject) => {
cr.on('change', function (c) {
// ensure we get a change on the third poll
expect(c).toStrictEqual(change)
db.changesReader.stop()
resolve()
}).on('error', function (err) {
expect(err.statusCode).toBe(429)
})
})
}, 10000)
test('should survive malformed JSON - db.changesReader.start', async () => {
const changeURL = `/${DBNAME}/_changes`
const change = { seq: null, id: 'a', changes: ['1-1'] }
nock(COUCH_URL)
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: 'now', limit: 100, include_docs: false })
.reply(200, '{ results: [], last_seq: "1-0", pending: 0') // missing bracket } - malformed JSON
.post(changeURL)
.query({ feed: 'longpoll', timeout: 1000, since: 'now', limit: 100, include_docs: false })
.reply(200, { results: [change], last_seq: '1-0', pending: 0 })
.post(changeURL)
.delay(2000)
.reply(500)
const db = nano.db.use(DBNAME)
const cr = db.changesReader.start({ timeout: 1000 })
return new Promise((resolve, reject) => {
cr.on('change', function (c) {
expect(c).toStrictEqual(change)
db.changesReader.stop()
resolve()
}).on('error', function (err) {
if (err) {
// shouldn't get here
expect(true).toBe(false)
}
})
})
}, 10000)