Skip to content

Commit

Permalink
refactor(listDocuments): use transducer and beef up tests #36
Browse files Browse the repository at this point in the history
  • Loading branch information
TillaTheHun0 committed Mar 13, 2023
1 parent 980c55d commit 705d9a8
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 18 deletions.
8 changes: 4 additions & 4 deletions adapter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { crocks, R } from './deps.js'
import { bulk } from './bulk.js'
import { handleHyperErr, HyperErr } from './err.js'
import { sanitizeRows } from './utils.js'

const { Async } = crocks

Expand All @@ -19,7 +20,7 @@ const {
ifElse,
reduce,
allPass,
pluck,
trim,
} = R

const isDefined = complement(isNil)
Expand Down Expand Up @@ -268,7 +269,7 @@ export function adapter({ config, asyncFetch, headers, handleResponse }) {
options = limit ? mergeRight({ limit: Number(limit) }, options) : options
options = startkey ? mergeRight({ startkey }, options) : options
options = endkey ? mergeRight({ endkey }, options) : options
options = keys ? mergeRight({ keys: keys.split(',') }, options) : options
options = keys ? mergeRight({ keys: keys.split(',').map(trim) }, options) : options
options = descending ? mergeRight({ descending }, options) : options

// https://docs.couchdb.org/en/stable/api/database/bulk-api.html#post--db-_all_docs
Expand All @@ -279,8 +280,7 @@ export function adapter({ config, asyncFetch, headers, handleResponse }) {
})
.chain(handleResponse(200))
.map(prop('rows'))
.map(pluck('doc'))
.map(foldDocs)
.map(sanitizeRows)
.map((docs) => ({ ok: true, docs }))
.bichain(
handleHyperErr,
Expand Down
116 changes: 103 additions & 13 deletions adapter_test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertEquals, assertObjectMatch } from './dev_deps.js'
import { assert, assertEquals, assertObjectMatch } from './dev_deps.js'
import { asyncFetch, createHeaders, handleResponse } from './async-fetch.js'
import { adapter } from './adapter.js'

Expand Down Expand Up @@ -130,6 +130,11 @@ const testFetch = (url, options) => {
id: '3',
value: { rev: '3' },
no_doc: { _id: '3', _rev: '3', hello: 'world' }, // should be filtered out because no 'doc' key
}, {
key: '4',
id: '4',
value: { rev: '4' },
doc: undefined,
}],
}),
})
Expand Down Expand Up @@ -369,29 +374,114 @@ test('adapter', async (t) => {
})

await t.step('listDocuments', async (t) => {
await t.step('should list the documents', async () => {
await t.step('should list the documents', async (t) => {
const results = await a.listDocuments({
db: 'hello',
limit: 1,
})

assertEquals(results.docs.length, 1)
assertObjectMatch(results.docs[0], {
_id: '1',
hello: 'world',
await t.step('should return valid documents', () => {
assertEquals(results.docs.length, 1)
assertObjectMatch(results.docs[0], {
_id: '1',
hello: 'world',
})
})
})

await t.step('should include optional parameters', async () => {
})
await t.step('should remove undefined docs', () => {
assertEquals(results.docs.length, 1)
assert(results.docs[0] !== undefined)
})

await t.step('should remove undefined docs', async () => {
})
await t.step('should remove design docs', () => {
assertEquals(results.docs.length, 1)
assert(results.docs[0]._id !== '_design_2')
})

await t.step('should remove design docs', async () => {
await t.step('should remove _rev from all docs', () => {
assertEquals(results.docs.length, 1)
assert(!results.docs[0]._rev)
})
})

await t.step('should remove _rev from all documents', async () => {
await t.step('should include optional parameters', async (t) => {
const a = adapter({
config: { origin: COUCH },
asyncFetch: asyncFetch((url, options) => {
// List Docs
if (
url === 'http://localhost:5984/hello/_all_docs' && options.method === 'POST'
) {
return Promise.resolve({
status: 200,
ok: true,
json: () =>
Promise.resolve({
ok: true,
rows: [{
key: '1',
id: '1',
value: { rev: '1' },
doc: { _id: '1', _rev: '1', options: JSON.parse(options.body) },
}],
}),
})
}
}),
headers: createHeaders('admin', 'password'),
handleResponse,
})

await t.step('should always add include_docs', async () => {
const { docs: [doc] } = await a.listDocuments({
db: 'hello',
limit: 3,
})
assertEquals(doc.options.include_docs, true)
})

await t.step('should include limit, parsing to a number', async () => {
const { docs: [withLimit] } = await a.listDocuments({
db: 'hello',
limit: 3,
})
assertEquals(withLimit.options.limit, 3)
})

await t.step('should include the startkey', async () => {
const { docs: [withStartkey] } = await a.listDocuments({
db: 'hello',
startkey: '123',
})
assertEquals(withStartkey.options.startkey, '123')
})

await t.step('should include the endkey', async () => {
const { docs: [withEndkey] } = await a.listDocuments({
db: 'hello',
endkey: '123',
})
assertEquals(withEndkey.options.endkey, '123')
})

await t.step('should include the split trimmed keys', async () => {
const { docs: [withKeys] } = await a.listDocuments({
db: 'hello',
keys: '123,456, 789',
})
assertEquals(withKeys.options.keys.length, 3)
assertEquals(withKeys.options.keys[0], '123')
assertEquals(withKeys.options.keys[1], '456')
assertEquals(withKeys.options.keys[2], '789')
})

await t.step('should include descending', async () => {
const { docs: [withDescending] } = await a.listDocuments({
db: 'hello',
descending: true,
})
assertEquals(withDescending.options.descending, true)
})
})
})
})
6 changes: 5 additions & 1 deletion dev_deps.js
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export { assertEquals, assertObjectMatch } from 'https://deno.land/[email protected]/testing/asserts.ts'
export {
assert,
assertEquals,
assertObjectMatch,
} from 'https://deno.land/[email protected]/testing/asserts.ts'
63 changes: 63 additions & 0 deletions utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { R } from './deps.js'

const {
allPass,
isNil,
complement,
transduce,
append,
omit,
pluck,
map,
filter,
compose,
} = R

export const isDefined = complement(isNil)

export const isDesignDoc = (doc) => (/^_design/.test(doc._id))
export const isNotDesignDoc = complement(isDesignDoc)

export const pluckDoc = pluck('doc')
export const omitRev = omit(['rev', '_rev'])

/**
* A transduce allows us to iterate the array only once,
* performing composed transformations inlined with reducing,
* -- hence "trans"-"duce".
*
* This prevents iterating the array multiple times to perform multiple
* transformations
*
* NOTE: compositions passed to transduce run top -> bottom instead of the usual
* bottom to top. This is becase we are composing transformers which are functions
* not values
*/
export const foldWith = (transformer) => (iter) =>
transduce(transformer, (acc, item) => append(item, acc), [], iter)

export const sanitizeDocs = foldWith(
compose(
filter(allPass([isDefined, isNotDesignDoc])),
map(omitRev),
),
)

/**
* Each row is like
* {
key: '1',
id: '1',
value: { rev: '1' },
doc: { _id: '1', _rev: '1', hello: 'world' },
}
So pluck the doc first to pass into sanitizing
*/
export const sanitizeRows = foldWith(
compose(
pluck('doc'),
filter(allPass([isDefined, isNotDesignDoc])),
map(omitRev),
),
)

0 comments on commit 705d9a8

Please sign in to comment.