Skip to content

Commit

Permalink
Add support for "patch"
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonas Svalin committed Mar 8, 2019
1 parent aeb904d commit f4bac4a
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 1 deletion.
25 changes: 25 additions & 0 deletions src/Navigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ class Navigator {
return this.postUrl(href, body, config)
}

async patch (rel, body, params = {}, config = {}) {
const { href } = this.resolveLink(rel, params)
return this.patchUrl(href, body, config)
}

async getUrl (url, params, config) {
const {
status,
Expand Down Expand Up @@ -117,6 +122,26 @@ class Navigator {
return this
}

async patchUrl (url, body, config) {
const {
status,
location,
body: responseBody,
response
} = await this.options.patch(url, body, config)

this._status = status
this._location = location
this._response = response
this._resource = Resource.fromObject(responseBody)

if (this.options.followRedirects && status === 204) {
return this.followRedirect(config)
}

return this
}

async followRedirect (config) {
const fullLocation = makeAbsolute(
this._location, this.getHeader('location'))
Expand Down
12 changes: 11 additions & 1 deletion src/axiosOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,17 @@ const axiosPost = (url, body, config) =>
response
}))

const axiosPatch = (url, body, config) =>
axios.patch(url, body, { ...config, validateStatus: () => true })
.then((response) => ({
status: response.status,
body: response.data,
location: response.config.url,
response
}))

module.exports = {
get: axiosGet,
post: axiosPost
post: axiosPost,
patch: axiosPatch
}
212 changes: 212 additions & 0 deletions test/navigator/patch.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import faker from 'faker'
import nock from 'nock'
import { expect } from 'chai'
import Resource from '../../src/Resource'
import Navigator from '../../src/Navigator'
import * as api from '../support/api'

const baseUrl = faker.internet.url()

describe('Navigator', () => {
beforeEach(() => {
nock.cleanAll()
})

it('updates resources in an API', async () => {
api.onDiscover(baseUrl, {}, {
users: {href: '/users'}
})

api.onPatchRedirect(baseUrl, '/users', {
name: 'Thomas'
}, `${baseUrl}/users/thomas`)

api.onGet(baseUrl, '/users/thomas',
new Resource()
.addProperty('name', 'Thomas'))

const discoveryResult = await Navigator.discover(baseUrl)
const result = await discoveryResult.patch('users', {
name: 'Thomas'
})

expect(result.status()).to.equal(200)

expect(result.resource().getProperty('name'))
.to.deep.equal('Thomas')
})

it('uses template params when updating resources', async () => {
api.onDiscover(baseUrl, {}, {
useritems: {href: '/users/{id}/items', templated: true}
})

api.onPatchRedirect(baseUrl, '/users/thomas/items', {
name: 'Sponge'
}, `${baseUrl}/users/thomas/items/1`)

api.onGet(baseUrl, '/users/thomas/items/1',
new Resource()
.addProperty('name', 'Sponge'))

const discoveryResult = await Navigator.discover(baseUrl)
const result = await discoveryResult.patch('useritems', {
name: 'Sponge'
}, {
id: 'thomas'
})

expect(result.status()).to.equal(200)

expect(result.resource().getProperty('name'))
.to.deep.equal('Sponge')
})

it('uses absolute url when location is relative', async () => {
api.onDiscover(baseUrl, {}, {
users: {href: '/users'}
})

api.onPatchRedirect(baseUrl, '/users', {
name: 'Thomas'
}, `/users/thomas`)

api.onGet(baseUrl, '/users/thomas',
new Resource()
.addProperty('name', 'Thomas'))

const discoveryResult = await Navigator.discover(baseUrl)
const result = await discoveryResult.patch('users', {
name: 'Thomas'
})

expect(result.status()).to.equal(200)

expect(result.resource().getProperty('name'))
.to.deep.equal('Thomas')
})

it('uses same configuration as provided on patch when following redirect',
async () => {
api.onDiscover(baseUrl, {}, {
users: {href: '/users'}
})

api.onPatchRedirect(baseUrl, '/users', {
name: 'Thomas'
},
`${baseUrl}/users/thomas`, {
headers: { authorization: 'Bearer 1a2b3c4d' }
})

api.onGet(baseUrl, '/users/thomas',
new Resource().addProperty('name', 'Thomas'), {
headers: { authorization: 'Bearer 1a2b3c4d' }
})

const discoveryResult = await Navigator.discover(baseUrl)
const result = await discoveryResult.patch('users', {
name: 'Thomas'
}, {}, {
headers: {
authorization: 'Bearer 1a2b3c4d'
}
})

expect(result.status()).to.equal(200)

expect(result.resource().getProperty('name'))
.to.deep.equal('Thomas')
})

it('does not follow location headers when the status is not 204', async () => {
api.onDiscover(baseUrl, {}, {
users: {href: '/users{?admin}', templated: true}
})

nock(baseUrl)
.patch('/users', {name: 'Thomas'})
.reply(400)

const discoveryResult = await Navigator.discover(baseUrl)
const result = await discoveryResult.patch('users', {
name: 'Thomas'
})

expect(result.status()).to.equal(400)
})

it('does not follow location headers when the options say not to', async () => {
api.onDiscover(baseUrl, {}, {
users: {href: '/users'}
})

api.onPatchRedirect(baseUrl, '/users', {
name: 'Thomas'
}, `${baseUrl}/users/thomas`)

const discoveryResult = await Navigator.discover(baseUrl, {followRedirects: false})
const result = await discoveryResult.patch('users', {
name: 'Thomas'
})

expect(result.status()).to.equal(204)

expect(result.getHeader('location'))
.to.deep.equal(`${baseUrl}/users/thomas`)
})

it('continues the conversation even if we do not follow redirects', async () => {
api.onDiscover(baseUrl, {}, {
users: {href: '/users'}
})

api.onPatchRedirect(baseUrl, '/users', {
name: 'Thomas'
}, `${baseUrl}/users/thomas`)

api.onGet(baseUrl, '/users/thomas',
new Resource()
.addProperty('name', 'Thomas'))

const discoveryResult = await Navigator.discover(baseUrl, {followRedirects: false})
const patchResult = await discoveryResult.patch('users', {
name: 'Thomas'
})
const result = await patchResult.followRedirect()

expect(result.status()).to.equal(200)

expect(result.resource().getProperty('name'))
.to.deep.equal('Thomas')
})

it('adds header options for navigation', async () => {
api.onDiscover(baseUrl, {}, {
users: {href: '/users'}
})

const headers = {
authorization: 'some-token'
}

api.onPatchRedirect(baseUrl, '/users',
{name: 'Thomas'},
`${baseUrl}/users/thomas`,
{headers})

api.onGet(baseUrl, '/users/thomas',
new Resource()
.addProperty('name', 'Thomas'))

const discoveryResult = await Navigator.discover(baseUrl)
const result = await discoveryResult.patch('users', {
name: 'Thomas'
}, {}, {headers})

expect(result.status()).to.equal(200)

expect(result.resource().getProperty('name'))
.to.deep.equal('Thomas')
})
})
7 changes: 7 additions & 0 deletions test/support/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ export const onPostRedirect = (url, path, body, location, { headers } = {}) =>
.reply(201, undefined, {
Location: location
})

export const onPatchRedirect = (url, path, body, location, { headers } = {}) =>
nock(url, { reqheaders: headers })
.patch(path, body)
.reply(204, undefined, {
Location: location
})

0 comments on commit f4bac4a

Please sign in to comment.