Skip to content

Commit

Permalink
Merge 9dd163b into a61a7bc
Browse files Browse the repository at this point in the history
  • Loading branch information
kjellmorten committed Mar 29, 2018
2 parents a61a7bc + 9dd163b commit e3aee5e
Show file tree
Hide file tree
Showing 14 changed files with 2,259 additions and 5,062 deletions.
5 changes: 5 additions & 0 deletions .tidelift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Just warn about deprecated development dependencies
ci:
development:
tests:
deprecated: warn
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,12 @@ accepts the id of a source to remove.
See [mapping definition](#mapping-definition) for a description of the
relationship between sources and mappings, and the `mappings` property.

The `auth` property should normally be set to the id of an
[auth definition](#source-authentication) if the source requires authentication.
In cases where the source is authenticated by other means, e.g. by including
username and password in the uri, set the `auth` property to `true` to signal
that this is an authenticated source.

### Endpoint definition
```
{
Expand Down
59 changes: 48 additions & 11 deletions lib/actions/delete-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,33 @@ import deleteFn from './delete'

// Helpers

const datatypes = {entry: datatype({
id: 'entry',
attributes: {
type: 'string',
title: {default: 'A title'}
}
})}
const datatypes = {
entry: datatype({
id: 'entry',
attributes: {
type: 'string',
title: {default: 'A title'}
}
}),
account: datatype({
id: 'account',
attributes: {
name: 'string'
},
access: {identFromField: 'id'}
})
}

const mappings = [
createMapping({
type: 'entry',
source: 'entries',
attributes: {id: 'id', title: 'header'}
}, {datatypes}),
createMapping({
type: 'account',
source: 'accounts',
attributes: {id: 'id', name: 'name'}
}, {datatypes})
]
test.after.always(() => {
Expand All @@ -31,10 +45,6 @@ test.after.always(() => {

// Tests

test('should exist', (t) => {
t.is(typeof deleteFn, 'function')
})

test('should delete items from source', async (t) => {
const scope = nock('http://api1.test')
.post('/database/bulk_delete', {docs: [{id: 'ent1'}, {id: 'ent2'}]})
Expand Down Expand Up @@ -181,6 +191,33 @@ test('should skip null values in data array', async (t) => {
t.is(ret.status, 'noaction')
})

test('should delete items from source', async (t) => {
const scope = nock('http://api4.test')
.post('/database/bulk_delete', {docs: [{id: 'johnf'}]})
.reply(200, [{ok: true, id: 'ent1', rev: '2-000001'}, {ok: true, id: 'ent2', rev: '2-000001'}])
const endpoints = [createEndpoint({
action: 'DELETE',
uri: 'http://api4.test/database/bulk_delete',
path: 'docs[]',
method: 'POST'
})]
const src = source({id: 'accounts', adapter: json, endpoints}, {mappings})
const getSource = (type, source) => (source === 'accounts') ? src : null
const payload = {
data: [
{id: 'johnf', type: 'account'},
{id: 'betty', type: 'account'}
],
source: 'accounts'
}
const ident = {id: 'johnf'}

const ret = await deleteFn({payload, ident}, {getSource})

t.is(ret.status, 'ok', ret.error)
t.true(scope.isDone())
})

test('should return error if no getSource', async (t) => {
const payload = {id: 'ent1', type: 'entry'}

Expand Down
9 changes: 5 additions & 4 deletions lib/actions/delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ const prepareData = (payload) => {
}
}

const sendDeleteRequest = async (source, payload, data) => {
const sendDeleteRequest = async (source, payload, data, ident) => {
const {type, id, params, endpoint} = payload
const request = {
action: 'DELETE',
endpoint,
params: {type, id, ...params},
data
data,
access: {ident}
}
const {response} = await source.send(request)

Expand All @@ -34,7 +35,7 @@ const sendDeleteRequest = async (source, payload, data) => {
* @param {Object} resources - Object with getSource
* @returns {Object} Response object with any data returned from the source
*/
async function deleteFn ({payload}, {getSource} = {}) {
async function deleteFn ({payload, ident}, {getSource} = {}) {
debug('Action: DELETE')
if (!payload) {
debug('DELETE: No payload')
Expand All @@ -57,7 +58,7 @@ async function deleteFn ({payload}, {getSource} = {}) {
const endpointDebug = (endpoint) ? `endpoint '${endpoint}'` : `endpoint matching ${type} and ${id}`
debug('DELETE: Delete from source \'%s\' at %s.', source.id, endpointDebug)

return sendDeleteRequest(source, payload, data)
return sendDeleteRequest(source, payload, data, ident)
}

module.exports = deleteFn
23 changes: 23 additions & 0 deletions lib/actions/get-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,29 @@ test('should get items by id array from source from member endpoints', async (t)
t.is(ret.data[1].id, 'ent2')
})

test('should return undefined for items not found when getting by id array', async (t) => {
nock('http://api10.test')
.get('/entries/ent1')
.reply(200, {id: 'ent1', type: 'entry'})
.get('/entries/ent2')
.reply(404)
const payload = {
id: ['ent1', 'ent2'],
type: 'entry',
source: 'entries'
}
const src = createSource('http://api10.test/entries/{id}', {scope: 'member'})
const getSource = (type, source) => (source === 'entries') ? src : null

const ret = await get({payload}, {getSource, datatypes})

t.is(ret.status, 'ok', ret.error)
t.true(Array.isArray(ret.data))
t.is(ret.data.length, 2)
t.is(ret.data[0].id, 'ent1')
t.is(typeof ret.data[1], 'undefined')
})

test('should return error when one or more requests for individual ids fails', async (t) => {
nock('http://api8.test')
.get('/entries/ent1')
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const hasCollectionEndpoint = (endpoints) =>

const getIndividualItems = async (ids, payload, getSource) => {
const responses = await Promise.all(ids.map((id) => get({payload: {...payload, id}}, {getSource})))
if (responses.some((response) => response.status !== 'ok')) {
if (responses.some((response) => response.status !== 'ok' && response.status !== 'notfound')) {
return {status: 'error', error: `One or more of the requests for ids ${ids} failed.`}
}
return {status: 'ok', data: responses.map((response) => response.data && response.data[0])}
Expand Down
46 changes: 35 additions & 11 deletions lib/actions/setMeta-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,23 @@ import setMeta from './setMeta'

// Helpers

const datatypes = {meta: datatype({
id: 'meta',
source: 'store',
attributes: {lastSyncedAt: 'date', status: 'string'}
})}
const datatypes = {
meta: datatype({
id: 'meta',
source: 'store',
attributes: {lastSyncedAt: 'date', status: 'string'},
access: 'auth'
})
}

const mappings = [
setupMapping(
{type: 'meta', source: 'store'},
{datatypes})
]

const ident = {id: 'johnf'}

const createSource = (id, {meta, endpoints = []} = {}) => source({
id,
adapter: json,
Expand Down Expand Up @@ -60,7 +65,7 @@ test('should set metadata on source', async (t) => {
meta: {lastSyncedAt, status: 'busy'}
}

const ret = await setMeta({payload}, {getSource})
const ret = await setMeta({payload, ident}, {getSource})

t.truthy(ret)
t.is(ret.status, 'ok', ret.error)
Expand All @@ -79,7 +84,7 @@ test('should not set metadata on source when no meta type', async (t) => {
meta: {lastSyncedAt}
}

const ret = await setMeta({payload}, {getSource})
const ret = await setMeta({payload, ident}, {getSource})

t.truthy(ret)
t.is(ret.status, 'noaction')
Expand Down Expand Up @@ -108,7 +113,7 @@ test('should set metadata on other source', async (t) => {
endpoint: 'setMeta'
}

const ret = await setMeta({payload}, {getSource})
const ret = await setMeta({payload, ident}, {getSource})

t.truthy(ret)
t.is(ret.status, 'ok', ret.error)
Expand All @@ -123,20 +128,39 @@ test('should return status noaction when meta is set to an unknown datatype', as
meta: {lastSyncedAt}
}

const ret = await setMeta({payload}, {getSource})
const ret = await setMeta({payload, ident}, {getSource})

t.truthy(ret)
t.is(ret.status, 'noaction')
})

test('should refuse setting metadata on source when not authorized', async (t) => {
const scope = nock('http://api4.test')
.put('/database/meta%3Astore')
.reply(200, {okay: true, id: 'meta:store', rev: '000001'})
const endpoints = [createEndpoint({uri: 'http://api4.test/database/{id}'})]
const src = createSource('store', {meta: 'meta', endpoints})
const getSource = (type, source) => (source === 'store' || type === 'meta') ? src : null
const payload = {
source: 'store',
meta: {lastSyncedAt, status: 'busy'}
}

const ret = await setMeta({payload, ident: null}, {getSource})

t.truthy(ret)
t.is(ret.status, 'noaccess', ret.error)
t.false(scope.isDone())
})

test('should return error for unknown source', async (t) => {
const getSource = (type, source) => null
const payload = {
source: 'unknown',
meta: {lastSyncedAt}
}

const ret = await setMeta({payload}, {getSource})
const ret = await setMeta({payload, ident}, {getSource})

t.truthy(ret)
t.is(ret.status, 'error')
Expand All @@ -147,7 +171,7 @@ test('should return error when no payload', async (t) => {
const src = createSource('store')
const getSource = () => src

const ret = await setMeta({payload}, {getSource})
const ret = await setMeta({payload, ident}, {getSource})

t.truthy(ret)
t.is(ret.status, 'error')
Expand Down
5 changes: 3 additions & 2 deletions lib/actions/setMeta.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const createError = require('../utils/createError')
* @param {Object} resources - Object with getSource
* @returns {Promise} Promise that will be resolved when metadata is set
*/
async function setMeta ({payload}, {getSource}) {
async function setMeta ({payload, ident}, {getSource}) {
debug('Action: SET_META')

if (!payload) {
Expand Down Expand Up @@ -43,7 +43,8 @@ async function setMeta ({payload}, {getSource}) {
action: 'SET_META',
endpoint,
params: {keys: Object.keys(meta), type, id},
data: {id, type, attributes: meta}
data: {id, type, attributes: meta},
access: {ident}
}
const {response} = await metaSource.send(request, {useDefaults: false})
return response
Expand Down
12 changes: 12 additions & 0 deletions lib/adapters/json/send-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,18 @@ test('should retrieve with auth headers', async (t) => {
t.is(ret.status, 'ok')
})

test('should not throw when auth=true', async (t) => {
const auth = true
nock('http://json1.test')
.put('/entries/ent3', {})
.reply(200)
const request = {endpoint: prepareEndpoint({uri: 'http://json1.test/entries/ent3'}), data: {}, auth}

const ret = await send(request)

t.is(ret.status, 'ok')
})

test('should retrieve with given headers', async (t) => {
const auth = {
isAuthenticated: () => true,
Expand Down
3 changes: 2 additions & 1 deletion lib/adapters/json/sendToSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ const handleError = async (request, error, isAuthRetry) => {
}
}

const isAuthenticationNeeded = (auth, isAuthRetry) => auth && (isAuthRetry || !auth.isAuthenticated())
const isAuthenticationNeeded = (auth, isAuthRetry) =>
auth && typeof auth === 'object' && (isAuthRetry || !auth.isAuthenticated())

/**
* Send the prepared request to the source
Expand Down
Loading

0 comments on commit e3aee5e

Please sign in to comment.