Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP on Issue 57 extend line web api #64

Merged
merged 10 commits into from
Mar 19, 2024
69 changes: 27 additions & 42 deletions line/__tests__/end_to_end_unit.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,42 @@ import lineRouter from '../index.mjs'
import express from 'express'
import request from 'supertest'

const routeTester = new express()
routeTester.use("/line", lineRouter)
const app = express()
app.use('/line', lineRouter)

describe('Line endpoint end to end unit test (spinning up the endpoint and using it). #end2end_unit', () => {

it('POST instead of GET. That status should be 405 with a message.', async () => {
const res = await request(routeTester)
.post('/line/')
expect(res.statusCode).toBe(405)
expect(res.body).toBeTruthy()
it('should return 405 for POST request', async () => {
const res = await request(app).post('/line/').send()
expect(res.statusCode).toBe(405)
})

it('PUT instead of GET. That status should be 405 with a message.', async () => {
const res = await request(routeTester)
.put('/line/')
expect(res.statusCode).toBe(405)
expect(res.body).toBeTruthy()
it('should return 405 for PUT request', async () => {
const res = await request(app).put('/line/').send()
expect(res.statusCode).toBe(405)
})

it('PATCH instead of GET. That status should be 405 with a message.', async () => {
const res = await request(routeTester)
.patch('/line/')
expect(res.statusCode).toBe(405)
expect(res.body).toBeTruthy()
it('should return 405 for PATCH request', async () => {
const res = await request(app).patch('/line/').send()
expect(res.statusCode).toBe(405)
})

it('Call to /line without a TPEN3 line ID. The status should be 400 with a message.', async () => {
const res = await request(routeTester)
.get('/line/')
expect(res.statusCode).toBe(400)
expect(res.body).toBeTruthy()
it('should return 400 if no TPEN3 line ID provided', async () => {
const res = await request(app).get('/line/').send()
expect(res.statusCode).toBe(400)
})

it('Call to /line with a TPEN 3 line "${id}" does not exist. The status should be 404 with a message.', async () => {
try {
const res = await request(routeTester).get('/line/1257')
console.log('Response Body:', res.body)
expect(res.statusCode).toBe(404)
expect(res.body).toBeTruthy()
} catch (error) {
console.error('Error:', error)
}
it('should return 404 for non-existing TPEN 3 line ID', async () => {
const res = await request(app).get('/line/1257').send()
expect(res.statusCode).toBe(404)
})
it('Call to /line with a valid TPEN3 line ID. The status should be 200 with a JSON line in the body.', async () => {
try {
const res = await request(routeTester).get('/line/123')
console.log('Response Body:', res.body)
expect(res.statusCode).toBe(200)
expect(res.body).toBeTruthy()
} catch (error) {
console.error('Error:', error)
}

it('should return 200 with a JSON line in the body for valid TPEN3 line ID', async () => {
const res = await request(app).get('/line/123').send()

expect(res.statusCode).toBe(200)
expect(res.body).toEqual({
body: '{"@context":"http://t-pen.org/3/context.json","id":123,"@type":"Annotation","creator":"https://store.rerum.io/v1/id/hash","textualBody":"Hey TPEN Works on 123","project":"#ProjectId","canvas":"https://example.com/canvas.json","layer":"#AnnotationCollectionId","viewer":"https://static.t-pen.org/#ProjectId/#PageId/#LineId123","license":"CC-BY"}',
headers: { 'Content-Type': 'application/json' }
})
})
})
})
64 changes: 63 additions & 1 deletion line/__tests__/functionality_unit.test.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,69 @@
import { findLineById } from '../line.mjs'
import { validateID } from '../../utilities/shared.mjs'

describe('Line endpoint functionality unit test (just testing helper functions). #functions_unit', () => {
describe('Line endpoint functionality unit test', () => {
describe('findLineById function', () => {
it('should return null if no ID provided', async () => {
const line = await findLineById()
expect(line).toBeNull()
})

it('should return null for non-existing ID', async () => {
const line = await findLineById(-111)
expect(line).toBeNull()
})

it('should return a valid line object for existing ID', async () => {
const line = await findLineById(123)
expect(line).not.toBeNull()
})

it('should return blob content for ?text=blob', async () => {
const options = { text: 'blob' }
const lineBlob = await findLineById(123, options)
expect(lineBlob).toBeDefined()
expect(lineBlob.text).toBeDefined()
})

it('should return full page URL for ?image=full', async () => {
const options = { image: 'full' }
const lineFullImage = await findLineById(123, options)
expect(lineFullImage).toBeDefined()
expect(lineFullImage.image).toBeDefined()
})

it('should return line image fragment URL for ?image=line', async () => {
const options = { image: 'line' }
const lineLineImage = await findLineById(123, options)
expect(lineLineImage).toBeDefined()
expect(lineLineImage.image).toBeDefined()
})

it('should return project document for ?lookup=project', async () => {
const options = { lookup: 'project' }
const lineLookupProject = await findLineById(123, options)
expect(lineLookupProject).toBeDefined()
})

it('should return XML representation for ?view=xml', async () => {
const options = { view: 'xml' }
const lineXML = await findLineById(123, options)
expect(lineXML).toBeDefined()
})

it('should return HTML viewer document for ?view=html', async () => {
const options = { view: 'html' }
const lineHTML = await findLineById(123, options)
expect(lineHTML).toBeDefined()
})

it('should return expanded document for ?embed=true', async () => {
const options = { embed: true }
const lineEmbedded = await findLineById(123, options)
expect(lineEmbedded).toBeDefined()
})
})

it('No TPEN3 line ID provided. Line ID validation must be false.', () => {
expect(validateID()).toBe(false)
})
Expand Down
57 changes: 27 additions & 30 deletions line/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,29 @@ import * as utils from '../utilities/shared.mjs'
import cors from 'cors'
import { findLineById } from './line.mjs'

let router = express.Router()
const router = express.Router()

router.use(
cors({
methods: 'GET',
allowedHeaders: [
'Content-Type',
'Content-Length',
'Allow',
'Authorization',
'Location',
'ETag',
'Connection',
'Keep-Alive',
'Date',
'Cache-Control',
'Last-Modified',
'Link',
'X-HTTP-Method-Override'
],
exposedHeaders: '*',
origin: '*',
maxAge: '600'
})
)
router.use(cors({
methods: 'GET',
allowedHeaders: [
'Content-Type',
'Content-Length',
'Allow',
'Authorization',
'Location',
'ETag',
'Connection',
'Keep-Alive',
'Date',
'Cache-Control',
'Last-Modified',
'Link',
'X-HTTP-Method-Override'
],
exposedHeaders: '*',
origin: '*',
maxAge: '600'
}))

router.route('/:id')
.get(async (req, res, next) => {
Expand All @@ -42,11 +40,9 @@ router.route('/:id')

const lineObject = await findLineById(id)

if (lineObject !== null) {
respondWithLine(res, lineObject)
} else {
return utils.respondWithError(res, 404, `TPEN 3 line "${id}" does not exist.`)
}
if (lineObject.statusCode === 404) {
return utils.respondWithError(res, 404, lineObject.body)
}
} catch (error) {
console.error(error)
return utils.respondWithError(res, 500, 'Internal Server Error')
Expand All @@ -55,6 +51,7 @@ router.route('/:id')
.all((req, res, next) => {
return utils.respondWithError(res, 405, 'Improper request method, please use GET.')
})

router.route('/')
.get((req, res, next) => {
return utils.respondWithError(res, 400, 'Improper request. There was no line ID.')
Expand All @@ -68,4 +65,4 @@ function respondWithLine(res, lineObject) {
res.status(200).json(lineObject)
}

export default router
export default router
92 changes: 81 additions & 11 deletions line/line.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import * as utils from '../utilities/shared.mjs'

export async function findLineById(id = null) {
let line = null

if (!utils.validateID(id)) {
return line
export async function findLineById(id = null, options = {}) {
if (id === null || id === undefined || !utils.validateID(id)) {
return { statusCode: 400, headers: { 'Content-Type': 'application/json' }, body: null }
}

const mockPause = new Promise((resolve) => {
Expand All @@ -14,14 +12,86 @@ export async function findLineById(id = null) {
})

const linesArray = [
{ id: 123, text: "Hey TPEN Works on 123" },
{
id: 123,
text: 'Hey TPEN Works on 123',
'@context': 'http://t-pen.org/3/context.json',
'@type': 'Annotation',
creator: 'https://store.rerum.io/v1/id/hash',
project: '#ProjectId',
canvas: 'https://example.com/canvas.json',
layer: '#AnnotationCollectionId',
viewer: 'https://static.t-pen.org/#ProjectId/#PageId/#LineId123',
license: 'CC-BY',
},
]

line = linesArray.find((line) => line.id === id) || null
let line = linesArray.find((line) => line.id === id) || (await mockPause)

if (line === null || line.id !== id) {
return { statusCode: 404, body: `TPEN 3 line "${id}" does not exist.` }
}

if (options.text === 'blob') {
return { statusCode: 200, headers: { 'Content-Type': 'text/plain' }, body: line.text }
}


switch (options.image) {
case 'full':
return { statusCode: 200, headers: { 'Content-Type': 'image/jpeg' }, body: line.canvas }
case 'line':
return { statusCode: 200, headers: { 'Content-Type': 'image/jpeg' }, body: `some line image URL for id ${id}` }
default:
break
}

if (options.lookup) {
return { statusCode: 200, headers: { 'Content-Type': 'text/plain' }, body: `some ${options.lookup} document for id ${id}` }
}

if (line === null) {
line = await mockPause
const jsonResponse = {
'@context': line['@context'],
id: line.id,
'@type': line['@type'],
creator: line.creator,
textualBody: line.text,
project: line.project,
canvas: line.canvas,
layer: line.layer,
viewer: line.viewer,
license: line.license,
}
switch (options.view) {
case 'xml':
return { statusCode: 200, headers: { 'Content-Type': 'text/xml' }, body: generateXML(line) }
case 'html':
return { statusCode: 200, headers: { 'Content-Type': 'text/html' }, body: generateHTML(line) }
default:
break
}

if (options.embed === true) {
return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ expandedDocument: jsonResponse }) }
}

return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: jsonResponse }
}

function generateXML(lineData) {
// Generate XML representation of the line data
return `<line><id>${lineData.id}</id><text>${lineData.text}</text></line>`
}

return line
}
function generateHTML(lineData) {
// Generate HTML viewer document
return `
<html>
<head><title>Line Viewer</title></head>
<body>
<h1>Line ${lineData.id} Viewer</h1>
<p>${lineData.text}</p>
</body>
</html>
`
}