Skip to content

Commit

Permalink
feat: Add Tests for PREP
Browse files Browse the repository at this point in the history
  • Loading branch information
CxRes committed Oct 8, 2024
1 parent beaaa03 commit bda034a
Show file tree
Hide file tree
Showing 3 changed files with 302 additions and 1 deletion.
69 changes: 69 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
"vhost": "^3.0.2"
},
"devDependencies": {
"@cxres/structured-headers": "^2.0.0-alpha.1-nesting.0",
"@solid/solid-auth-oidc": "0.3.0",
"c8": "^10.1.2",
"chai": "^4.4.1",
Expand All @@ -129,6 +130,7 @@
"nock": "^13.5.4",
"node-mocks-http": "^1.14.1",
"pre-commit": "1.2.2",
"prep-fetch": "^0.1.0",
"randombytes": "2.1.0",
"sinon": "12.0.1",
"sinon-chai": "3.7.0",
Expand Down Expand Up @@ -177,7 +179,9 @@
"before",
"beforeEach",
"describe",
"it"
"it",
"fetch",
"AbortController"
]
},
"bin": {
Expand Down
228 changes: 228 additions & 0 deletions test/integration/prep-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
const fs = require('fs')
const path = require('path')
const { expect } = require('chai')
const { parseDictionary } = require('structured-headers')
const prepFetch = require('prep-fetch').default
const { createServer } = require('../utils')

const samplePath = path.join(__dirname, '../resources', 'sampleContainer')
const sampleFile = fs.readFileSync(path.join(samplePath, 'example1.ttl'))

describe('Per Resource Events Protocol', function () {
let server

before((done) => {
server = createServer({
live: true,
dataBrowserPath: 'default',
root: path.join(__dirname, '../resources'),
auth: 'oidc',
webid: false
})
server.listen(8443, done)
})

after(() => {
server.close()
})

it('should set `Accept-Events` header on a GET response with "prep"',
async function () {
const response = await fetch('http://localhost:8443/sampleContainer/example1.ttl')
expect(response.headers.get('Accept-Events')).to.match(/^"prep"/)
expect(response.status).to.equal(200)
}
)

it('should send an ordinary response, if `Accept-Events` header is not specified',
async function () {
const response = await fetch('http://localhost:8443/sampleContainer/example1.ttl')
expect(response.headers.get('Content-Type')).to.match(/text\/turtle/)
expect(response.headers.has('Events')).to.equal(false)
expect(response.status).to.equal(200)
})

describe('with prep response on container', async function () {
let response
let prepResponse
const controller = new AbortController()
const { signal } = controller

it('should set headers correctly', async function () {
response = await fetch('http://localhost:8443/sampleContainer/', {
headers: {
'Accept-Events': '"prep";accept=application/ld+json',
Accept: 'text/turtle'
},
signal
})
expect(response.status).to.equal(200)
expect(response.headers.get('Vary')).to.match(/Accept-Events/)
const eventsHeader = parseDictionary(response.headers.get('Events'))
expect(eventsHeader.get('protocol')?.[0]).to.equal('prep')
expect(eventsHeader.get('status')?.[0]).to.equal(200)
expect(eventsHeader.get('expires')?.[0]).to.be.a('string')
expect(response.headers.get('Content-Type')).to.match(/^multipart\/mixed/)
})

it('should send a representation as the first part, matching the content size on disk',
async function () {
prepResponse = prepFetch(response)
const representation = await prepResponse.getRepresentation()
expect(representation.headers.get('Content-Type')).to.match(/text\/turtle/)
await representation.text()
})

describe('should send notifications in the second part', async function () {
let notifications
let notificationsIterator

it('when a contained resource is created', async function () {
notifications = await prepResponse.getNotifications()
notificationsIterator = notifications.notifications()
await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', {
method: 'PUT',
headers: {
'Content-Type': 'text/turtle'
},
body: sampleFile
})
const { value } = await notificationsIterator.next()
expect(value.headers.get('content-type')).to.match(/application\/ld\+json/)
const notification = await value.json()
expect(notification).to.haveOwnProperty('published')
expect(notification.type).to.equal('Add')
expect(notification.target).to.match(/sampleContainer\/example-prep\.ttl$/)
expect(notification.object).to.match(/sampleContainer\/$/)
})

it('when contained resource is modified', async function () {
await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', {
method: 'PATCH',
headers: {
'Content-Type': 'text/n3'
},
body: `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
<> a solid:InsertDeletePatch;
solid:inserts { <u> <v> <z>. }.`
})
const { value } = await notificationsIterator.next()
expect(value.headers.get('content-type')).to.match(/application\/ld\+json/)
const notification = await value.json()
expect(notification).to.haveOwnProperty('published')
expect(notification.type).to.equal('Update')
expect(notification.object).to.match(/sampleContainer\/$/)
})

it('when contained resource is deleted',
async function () {
await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', {
method: 'DELETE'
})
const { value } = await notificationsIterator.next()
expect(value.headers.get('content-type')).to.match(/application\/ld\+json/)
const notification = await value.json()
expect(notification).to.haveOwnProperty('published')
expect(notification.type).to.equal('Remove')
expect(notification.object).to.match(/sampleContainer\/$/)
})

it('when resource is created by POST',
async function () {
await fetch('http://localhost:8443/sampleContainer/', {
method: 'POST',
headers: {
slug: 'example-prep.ttl',
'content-type': 'text/turtle'
},
body: sampleFile
})
const { value } = await notificationsIterator.next()
expect(value.headers.get('content-type')).to.match(/application\/ld\+json/)
const notification = await value.json()
expect(notification).to.haveOwnProperty('published')
expect(notification.type).to.equal('Update')
expect(notification.object).to.match(/sampleContainer\/$/)
controller.abort()
})
})
})

describe('with prep response on RDF resource', async function () {
let response
let prepResponse

it('should set headers correctly', async function () {
response = await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', {
headers: {
'Accept-Events': '"prep";accept=application/ld+json',
Accept: 'text/n3'
}
})
expect(response.status).to.equal(200)
expect(response.headers.get('Vary')).to.match(/Accept-Events/)
const eventsHeader = parseDictionary(response.headers.get('Events'))
expect(eventsHeader.get('protocol')?.[0]).to.equal('prep')
expect(eventsHeader.get('status')?.[0]).to.equal(200)
expect(eventsHeader.get('expires')?.[0]).to.be.a('string')
expect(response.headers.get('Content-Type')).to.match(/^multipart\/mixed/)
})

it('should send a representation as the first part, matching the content size on disk',
async function () {
prepResponse = prepFetch(response)
const representation = await prepResponse.getRepresentation()
expect(representation.headers.get('Content-Type')).to.match(/text\/n3/)
const blob = await representation.blob()
expect(function (done) {
const size = fs.statSync(path.join(__dirname,
'../resources/sampleContainer/example-prep.ttl')).size
if (blob.size !== size) {
return done(new Error('files are not of the same size'))
}
})
})

describe('should send notifications in the second part', async function () {
let notifications
let notificationsIterator

it('when modified with PATCH', async function () {
notifications = await prepResponse.getNotifications()
notificationsIterator = notifications.notifications()
await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', {
method: 'PATCH',
headers: {
'content-type': 'text/n3'
},
body: `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
<> a solid:InsertDeletePatch;
solid:inserts { <u> <v> <z>. }.`
})
const { value } = await notificationsIterator.next()
expect(value.headers.get('content-type')).to.match(/application\/ld\+json/)
const notification = await value.json()
expect(notification).to.haveOwnProperty('published')
expect(notification).to.haveOwnProperty('state')
expect(notification.type).to.equal('Update')
expect(notification.object).to.match(/sampleContainer\/example-prep\.ttl$/)
})

it('when removed with DELETE, it should also close the connection',
async function () {
await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', {
method: 'DELETE'
})
const { value } = await notificationsIterator.next()
expect(value.headers.get('content-type')).to.match(/application\/ld\+json/)
const notification = await value.json()
expect(notification).to.haveOwnProperty('published')
expect(notification).to.haveOwnProperty('state')
expect(notification.type).to.equal('Delete')
expect(notification.object).to.match(/sampleContainer\/example-prep\.ttl$/)
const { done } = await notificationsIterator.next()
expect(done).to.equal(true)
})
})
})
})

0 comments on commit bda034a

Please sign in to comment.