Skip to content

Commit f04b5ef

Browse files
committed
Syntactically and structurally validate patches.
1 parent 4d1e746 commit f04b5ef

File tree

5 files changed

+75
-6
lines changed

5 files changed

+75
-6
lines changed

lib/handlers/patch.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function patchHandler (req, res, next) {
4343
// Find the appropriate patcher for the given content type
4444
const patchGraph = PATCHERS[patch.contentType]
4545
if (!patchGraph) {
46-
return next(error(415, 'Unknown patch content type: ' + patch.contentType))
46+
return next(error(415, 'Unsupported patch content type: ' + patch.contentType))
4747
}
4848

4949
// Read the RDF graph to be patched from the file

lib/handlers/patch/n3-patcher.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,26 @@ function parsePatchDocument (targetURI, patchURI, patchText, patchKB) {
2828
$rdf.parse(patchText, patchGraph, patchURI, 'text/n3')
2929
resolve(patchGraph)
3030
})
31+
.catch(err => { throw error(400, `Invalid patch document: ${err}`) })
32+
3133
// Query the N3 document for insertions and deletions
3234
.then(patchGraph => queryForFirstResult(patchGraph, `${PREFIXES}
3335
SELECT ?insert ?delete WHERE {
3436
?patch p:patches <${targetURI}>.
3537
OPTIONAL { ?patch p:insert ?insert. }
3638
OPTIONAL { ?patch p:delete ?delete. }
3739
}`)
40+
.catch(err => { throw error(400, `No patch for ${targetURI} found.`, err) })
3841
)
42+
3943
// Return the insertions and deletions as an rdflib patch document
4044
.then(result => {
41-
return {
42-
insert: result['?insert'],
43-
delete: result['?delete']
45+
const inserts = result['?insert']
46+
const deletes = result['?delete']
47+
if (!inserts && !deletes) {
48+
throw error(400, 'Patch should at least contain inserts or deletes.')
4449
}
50+
return {insert: inserts, delete: deletes}
4551
})
4652
}
4753

@@ -63,6 +69,6 @@ function applyPatch (patchObject, target, targetKB) {
6369
function queryForFirstResult (store, sparql) {
6470
return new Promise((resolve, reject) => {
6571
const query = $rdf.SPARQLToQuery(sparql, false, store)
66-
store.query(query, resolve)
72+
store.query(query, resolve, null, () => reject(new Error('No results.')))
6773
})
6874
}

test/integration/patch.js

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Integration tests for PATCH
1+
// Integration tests for PATCH with text/n3
22
const assert = require('chai').assert
33
const ldnode = require('../../index')
44
const path = require('path')
@@ -43,6 +43,62 @@ describe('PATCH', () => {
4343
})
4444
)
4545
})
46+
47+
describe('with an unsupported request content type', () => {
48+
it('returns a 415', () =>
49+
request.patch(`/read-write.ttl`)
50+
.set('Authorization', `Bearer ${userCredentials}`)
51+
.set('Content-Type', 'text/other')
52+
.send('other content type')
53+
.expect(415)
54+
.then(response => {
55+
assert.include(response.text, 'Unsupported patch content type: text/other')
56+
})
57+
)
58+
})
59+
60+
describe('with a patch document containing invalid syntax', () => {
61+
it('returns a 400', () =>
62+
request.patch(`/read-write.ttl`)
63+
.set('Authorization', `Bearer ${userCredentials}`)
64+
.set('Content-Type', 'text/n3')
65+
.send('invalid')
66+
.expect(400)
67+
.then(response => {
68+
assert.include(response.text, 'Invalid patch document')
69+
})
70+
)
71+
})
72+
73+
describe('with a patch document without relevant patch element', () => {
74+
it('returns a 400', () =>
75+
request.patch(`/read-write.ttl`)
76+
.set('Authorization', `Bearer ${userCredentials}`)
77+
.set('Content-Type', 'text/n3')
78+
.send(n3Patch(`
79+
<> a p:Patch.`
80+
))
81+
.expect(400)
82+
.then(response => {
83+
assert.include(response.text, 'No patch for https://tim.localhost:7777/read-write.ttl found')
84+
})
85+
)
86+
})
87+
88+
describe('with a patch document without insert and without deletes', () => {
89+
it('returns a 400', () =>
90+
request.patch(`/read-write.ttl`)
91+
.set('Authorization', `Bearer ${userCredentials}`)
92+
.set('Content-Type', 'text/n3')
93+
.send(n3Patch(`
94+
<> p:patches <https://tim.localhost:7777/read-write.ttl>.`
95+
))
96+
.expect(400)
97+
.then(response => {
98+
assert.include(response.text, 'Patch should at least contain inserts or deletes')
99+
})
100+
)
101+
})
46102
})
47103

48104
function n3Patch (contents) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<a> <b> <c>.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
2+
3+
<#Owner> a acl:Authorization;
4+
acl:accessTo <./read-write.ttl>;
5+
acl:agent <https://tim.localhost:7777/profile/card#me>;
6+
acl:mode acl:Read, acl:Write.

0 commit comments

Comments
 (0)