Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 91 additions & 63 deletions __tests__/routes_mounted.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,111 +6,139 @@
*/

import request from "supertest"
import api_routes from "../routes/api-routes.js"
import app from "../app.js"
import fs from "fs"

let app_stack = app.router.stack
let api_stack = api_routes.stack

/**
* Check if a route exists in the Express app
* @param {Array} stack - The router stack to search
* @param {string} testPath - The path to test for
* @returns {boolean} - True if the route exists
*/
function routeExists(stack, testPath) {
for (const layer of stack) {
// Check if layer has matchers (Express 5)
if (layer.matchers && layer.matchers.length > 0) {
const matcher = layer.matchers[0]
const match = matcher(testPath)
if (match && match.path) return true
}
// Also check route.path directly if it exists
if (layer.route && layer.route.path) {
if (layer.route.path === testPath || layer.route.path.includes(testPath)) return true
}
}
return false
}

describe('Check to see that all expected top level route patterns exist.', () => {

it('/v1 -- mounted ', () => {
expect(routeExists(app_stack, '/v1')).toBe(true)
it('/v1 -- mounted ', async () => {
const response = await request(app).get('/v1')
expect(response.statusCode).not.toBe(404)
})

it('/client -- mounted ', () => {
expect(routeExists(app_stack, '/client')).toBe(true)
it('/client -- mounted ', async () => {
const response = await request(app).get('/client/register')
expect(response.statusCode).not.toBe(404)
})

it('/v1/id/{_id} -- mounted', () => {
expect(routeExists(api_stack, '/id')).toBe(true)
it('/v1/id/{_id} -- mounted', async () => {
const response = await request(app).get('/v1/id/test-mounted-id')
// Mounted route with unknown id should 404 (not an unmapped endpoint 404)
expect(response.statusCode).toBe(404)
})

it('/v1/since/{_id} -- mounted', () => {
expect(routeExists(api_stack, '/since')).toBe(true)
it('/v1/since/{_id} -- mounted', async () => {
const response = await request(app).get('/v1/since/test-mounted-id')
// Mounted route with unknown id should 404
expect(response.statusCode).toBe(404)
})

it('/v1/history/{_id} -- mounted', () => {
expect(routeExists(api_stack, '/history')).toBe(true)
it('/v1/history/{_id} -- mounted', async () => {
const response = await request(app).get('/v1/history/test-mounted-id')
// Mounted route with unknown id should 404
expect(response.statusCode).toBe(404)
})

})

describe('Check to see that all /v1/api/ route patterns exist.', () => {

it('/v1/api/query -- mounted ', () => {
expect(routeExists(api_stack, '/api/query')).toBe(true)
it('/v1/api/query -- mounted ', async () => {
const response = await request(app)
.post('/v1/api/query')
.set('Content-Type', 'application/json')
.send({ mounted: true })
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/create -- mounted ', () => {
expect(routeExists(api_stack, '/api/create')).toBe(true)
it('/v1/api/create -- mounted ', async () => {
const response = await request(app)
.post('/v1/api/create')
.set('Content-Type', 'application/json')
.send({ mounted: true })
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/bulkCreate -- mounted ', () => {
expect(routeExists(api_stack, '/api/bulkCreate')).toBe(true)
it('/v1/api/bulkCreate -- mounted ', async () => {
const response = await request(app)
.post('/v1/api/bulkCreate')
.set('Content-Type', 'application/json')
.send([{ mounted: true }])
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/update -- mounted ', () => {
expect(routeExists(api_stack, '/api/update')).toBe(true)
it('/v1/api/update -- mounted ', async () => {
const response = await request(app)
.put('/v1/api/update')
.set('Content-Type', 'application/json')
.send({ mounted: true })
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/bulkUpdate -- mounted ', () => {
expect(routeExists(api_stack, '/api/bulkUpdate')).toBe(true)
it('/v1/api/bulkUpdate -- mounted ', async () => {
const response = await request(app)
.put('/v1/api/bulkUpdate')
.set('Content-Type', 'application/json')
.send([{ mounted: true }])
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/overwrite -- mounted ', () => {
expect(routeExists(api_stack, '/api/overwrite')).toBe(true)
it('/v1/api/overwrite -- mounted ', async () => {
const response = await request(app)
.post('/v1/api/overwrite')
.set('Content-Type', 'application/json')
.send({ mounted: true })
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/patch -- mounted ', () => {
expect(routeExists(api_stack, '/api/patch')).toBe(true)
it('/v1/api/patch -- mounted ', async () => {
const response = await request(app)
.patch('/v1/api/patch')
.set('Content-Type', 'application/json')
.send({ mounted: true })
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/set -- mounted ', () => {
expect(routeExists(api_stack, '/api/set')).toBe(true)
it('/v1/api/set -- mounted ', async () => {
const response = await request(app)
.patch('/v1/api/set')
.set('Content-Type', 'application/json')
.send({ mounted: true })
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/unset -- mounted ', () => {
expect(routeExists(api_stack, '/api/unset')).toBe(true)
it('/v1/api/unset -- mounted ', async () => {
const response = await request(app)
.patch('/v1/api/unset')
.set('Content-Type', 'application/json')
.send({ mounted: true })
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/delete/{id} -- mounted ', () => {
expect(routeExists(api_stack, '/api/delete')).toBe(true)
it('/v1/api/delete/{id} -- mounted ', async () => {
const response = await request(app).delete('/v1/api/delete/test-mounted-id')
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/release/{id} -- mounted ', () => {
expect(routeExists(api_stack, '/api/release')).toBe(true)
it('/v1/api/release/{id} -- mounted ', async () => {
const response = await request(app).patch('/v1/api/release/test-mounted-id')
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/search -- mounted ', () => {
expect(routeExists(api_stack, '/api/search')).toBe(true)
it('/v1/api/search -- mounted ', async () => {
const response = await request(app)
.post('/v1/api/search')
.set('Content-Type', 'text/plain')
.send('mounted search')
expect(response.statusCode).not.toBe(404)
})

it('/v1/api/search/phrase -- mounted ', () => {
expect(routeExists(api_stack, '/api/search/phrase')).toBe(true)
it('/v1/api/search/phrase -- mounted ', async () => {
const response = await request(app)
.post('/v1/api/search/phrase')
.set('Content-Type', 'text/plain')
.send('mounted phrase search')
expect(response.statusCode).not.toBe(404)
})

})
Expand Down Expand Up @@ -142,4 +170,4 @@ describe('Check to see that critical repo files are present', () => {
expect(fs.existsSync(filePath+"jest.config.js")).toBeTruthy()
expect(fs.existsSync(filePath+"package.json")).toBeTruthy()
})
})
})
6 changes: 4 additions & 2 deletions controllers/bulk.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ const bulkCreate = async function (req, res, next) {
// Each item must be valid JSON, but can't be an array.
if(Array.isArray(d) || typeof d !== "object") return d
try {
JSON.parse(JSON.stringify(d))
// Validate that the entry is structured-cloneable; result is intentionally discarded.
structuredClone(d)
} catch (err) {
return d
}
Expand Down Expand Up @@ -120,7 +121,8 @@ const bulkUpdate = async function (req, res, next) {
// Each item must be valid JSON, but can't be an array.
if(Array.isArray(d) || typeof d !== "object") return d
try {
JSON.parse(JSON.stringify(d))
// Validate that the entry is structured-cloneable; result is intentionally discarded.
structuredClone(d)
} catch (err) {
return d
}
Expand Down
9 changes: 4 additions & 5 deletions controllers/crud.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
import { newID, isValidID, db } from '../database/index.js'
import utils from '../utils.js'
import { _contextid, idNegotiation, generateSlugId, ObjectID, getAgentClaim, parseDocumentID } from './utils.js'
import { _contextid, idNegotiation, getPagination, generateSlugId, ObjectID, getAgentClaim, parseDocumentID } from './utils.js'

/**
* Create a new Linked Open Data object in RERUM v1.
Expand Down Expand Up @@ -37,7 +37,7 @@ const create = async function (req, res, next) {
let generatorAgent = getAgentClaim(req, next)
if (!generatorAgent) return
let context = req.body["@context"] ? { "@context": req.body["@context"] } : {}
let provided = JSON.parse(JSON.stringify(req.body))
let provided = structuredClone(req.body)
let rerumProp = { "__rerum": utils.configureRerumOptions(generatorAgent, provided, false, false)["__rerum"] }
if(slug){
rerumProp.__rerum.slug = slug
Expand All @@ -55,7 +55,7 @@ const create = async function (req, res, next) {
let result = await db.insertOne(newObject)
res.set(utils.configureWebAnnoHeadersFor(newObject))
newObject = idNegotiation(newObject)
newObject.new_obj_state = JSON.parse(JSON.stringify(newObject))
newObject.new_obj_state = structuredClone(newObject)
res.location(newObject[_contextid(newObject["@context"]) ? "id":"@id"])
res.status(201)
res.json(newObject)
Expand All @@ -74,8 +74,7 @@ const create = async function (req, res, next) {
const query = async function (req, res, next) {
res.set("Content-Type", "application/json; charset=utf-8")
let props = req.body
const limit = parseInt(req.query.limit ?? 100)
const skip = parseInt(req.query.skip ?? 0)
const { limit, skip } = getPagination(req.query, 100)
if (!props || Object.keys(props).length === 0) {
//Hey now, don't ask for everything...this can happen by accident. Don't allow it.
let err = {
Expand Down
10 changes: 5 additions & 5 deletions controllers/delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const deleteObj = async function(req, res, next) {
return next(utils.createExpressError(error))
}
if (null !== originalObject) {
let safe_original = JSON.parse(JSON.stringify(originalObject))
let safe_original = structuredClone(originalObject)
if (utils.isDeleted(safe_original)) {
err = Object.assign(err, {
message: `The object you are trying to delete is already deleted. ${err.message}`,
Expand All @@ -57,7 +57,7 @@ const deleteObj = async function(req, res, next) {
}
let preserveID = safe_original["@id"]
let deletedFlag = {} //The __deleted flag is a JSONObject
deletedFlag["object"] = JSON.parse(JSON.stringify(originalObject))
deletedFlag["object"] = structuredClone(originalObject)
deletedFlag["deletor"] = agentRequestingDelete
deletedFlag["time"] = new Date(Date.now()).toISOString().replace("Z", "")
let deletedObject = {
Expand Down Expand Up @@ -121,7 +121,7 @@ async function healHistoryTree(obj) {
const nextIdForQuery = parseDocumentID(nextID)
const objToUpdate = await db.findOne({"$or":[{"_id": nextIdForQuery}, {"__rerum.slug": nextIdForQuery}]})
if (null !== objToUpdate) {
let fixHistory = JSON.parse(JSON.stringify(objToUpdate))
let fixHistory = structuredClone(objToUpdate)
if (objToDeleteisRoot) {
//This means this next object must become root.
//Strictly, all history trees must have num(root) > 0.
Expand Down Expand Up @@ -154,7 +154,7 @@ async function healHistoryTree(obj) {
let previousIdForQuery = parseDocumentID(previous_id)
const objToUpdate2 = await db.findOne({"$or":[{"_id": previousIdForQuery}, {"__rerum.slug": previousIdForQuery}]})
if (null !== objToUpdate2) {
let fixHistory2 = JSON.parse(JSON.stringify(objToUpdate2))
let fixHistory2 = structuredClone(objToUpdate2)
let origNextArray = fixHistory2["__rerum"]["history"]["next"]
let newNextArray = [...origNextArray]
newNextArray = newNextArray.filter(id => id !== obj["@id"])
Expand Down Expand Up @@ -193,7 +193,7 @@ async function newTreePrime(obj) {
// fail silently
}
for (const d of descendants) {
let objWithUpdate = JSON.parse(JSON.stringify(d))
let objWithUpdate = structuredClone(d)
objWithUpdate["__rerum"]["history"]["prime"] = primeID
let result = await db.replaceOne({ "_id": d["_id"] }, objWithUpdate)
if (result.modifiedCount === 0) {
Expand Down
10 changes: 4 additions & 6 deletions controllers/gog.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import { newID, isValidID, db } from '../database/index.js'
import utils from '../utils.js'
import { _contextid, ObjectID, getAgentClaim, parseDocumentID, idNegotiation } from './utils.js'
import { _contextid, ObjectID, getAgentClaim, getPagination, parseDocumentID, idNegotiation } from './utils.js'

/**
* THIS IS SPECIFICALLY FOR 'Gallery of Glosses'
Expand All @@ -27,8 +27,7 @@ const _gog_fragments_from_manuscript = async function (req, res, next) {
if (!agent) return
const agentID = agent.split("/").pop()
const manID = req.body["ManuscriptWitness"]
const limit = parseInt(req.query.limit ?? 50)
const skip = parseInt(req.query.skip ?? 0)
const { limit, skip } = getPagination(req.query, 50)
let err = { message: `` }
// This request can only be made my Gallery of Glosses production apps.
if (agentID !== "61043ad4ffce846a83e700dd") {
Expand Down Expand Up @@ -159,8 +158,7 @@ const _gog_glosses_from_manuscript = async function (req, res, next) {
if (!agent) return
const agentID = agent.split("/").pop()
const manID = req.body["ManuscriptWitness"]
const limit = parseInt(req.query.limit ?? 50)
const skip = parseInt(req.query.skip ?? 0)
const { limit, skip } = getPagination(req.query, 50)
let err = { message: `` }
// This request can only be made my Gallery of Glosses production apps.
if (agentID !== "61043ad4ffce846a83e700dd") {
Expand Down Expand Up @@ -389,7 +387,7 @@ const expand = async function(primitiveEntity, GENERATOR=undefined, CREATOR=unde
})

// Combine the Annotation bodies with the primitive object
let expandedEntity = JSON.parse(JSON.stringify(primitiveEntity))
let expandedEntity = structuredClone(primitiveEntity)
for(const anno of matches){
const body = anno.body
let keys = Object.keys(body)
Expand Down
Loading