Skip to content

Commit 84e354e

Browse files
cubapthehabes
andauthored
Handle 409 conflict error in overwrite function (#101)
* Handle 409 conflict error in overwrite function Added specific handling for HTTP 409 conflict errors in the overwrite function to trigger a custom event with conflict details. This improves error reporting when attempting to overwrite an object that has a version conflict. * Improve overwrite route error handling and header support Enhanced the overwrite route to better handle errors, including 409 version conflicts, and to support the If-Overwritten-Version header from both request headers and the request body. Also improved validation for record IDs and refactored response handling for clarity. * fixing tests * changes during review and testing * changes during review and testing * changes during review and testing --------- Co-authored-by: Bryan Haberberger <bryan.j.haberberger@slu.edu>
1 parent 3daf734 commit 84e354e

File tree

7 files changed

+65
-29
lines changed

7 files changed

+65
-29
lines changed

app.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ if(process.env.OPEN_API_CORS !== "false") {
3939
'X-HTTP-Method-Override',
4040
'Origin',
4141
'Referrer',
42-
'User-Agent'
42+
'User-Agent',
43+
'If-Overwritten-Version'
4344
],
4445
"exposedHeaders" : "*",
4546
"origin" : "*",

jest.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const config = {
2424
name: 'TinyNode',
2525
color: 'cyan'
2626
},
27+
// extensionsToTreatAsEsm: [".js"],
28+
testEnvironment: "node",
2729

2830
// Indicates whether the coverage information should be collected while executing the test
2931
collectCoverage: true,
@@ -206,4 +208,4 @@ const config = {
206208
// watchman: true,
207209
}
208210

209-
export default config
211+
export default config

public/scripts/api.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ function overwrite(form, objIn) {
208208
_customEvent("rerum-result", `URI ${uri} overwritten. See resulting object below:`, resultObj)
209209
})
210210
.catch(err => {
211+
if (err?.status === 409) {
212+
_customEvent("rerum-error", "Conflict detected while trying to overwrite object at " + uri, err.currentVersion, err)
213+
return
214+
}
211215
_customEvent("rerum-error", "There was an error trying to overwrite object at " + uri, {}, err)
212216
})
213217
}

routes/__tests__/overwrite.test.js

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
2+
import { jest } from "@jest/globals"
13
import express from "express"
24
import request from "supertest"
3-
import { jest } from "@jest/globals"
45
import overwriteRoute from "../overwrite.js"
56
//import app from "../../app.js"
67

@@ -13,22 +14,17 @@ routeTester.use("/app/overwrite", overwriteRoute)
1314
const rerum_tiny_test_obj_id = `${process.env.RERUM_ID_PATTERN}tiny_tester`
1415

1516
beforeEach(() => {
16-
/**
17-
* Request/Response Mock Using manual fetch replacement
18-
* This is overruling the fetch(store.rerum.io/v1/api/create) call in create.js
19-
*/
2017
global.fetch = jest.fn(() =>
2118
Promise.resolve({
19+
status: 200,
20+
ok: true,
2221
json: () => Promise.resolve({ "@id": rerum_tiny_test_obj_id, "testing": "item", "__rerum": { "stuff": "here" } })
2322
})
2423
)
2524
})
2625

2726
afterEach(() => {
28-
/**
29-
* Food for thought: delete data generated by tests?
30-
* Make a test.store available that uses the same annotationStoreTesting as RERUM tests?
31-
*/
27+
3228
})
3329

3430
/**
@@ -51,7 +47,9 @@ describe("Check that the request/response behavior of the TinyNode overwrite rou
5147
.set("Content-Type", "application/json")
5248
.then(resp => resp)
5349
.catch(err => err)
54-
expect(response.header.location).toBe(rerum_tiny_test_obj_id)
50+
if (response.header.location !== rerum_tiny_test_obj_id) {
51+
throw new Error(`Expected Location header to be '${rerum_tiny_test_obj_id}', but got '${response.header.location}'.\nAll headers: ${JSON.stringify(response.header)}\nResponse body: ${JSON.stringify(response.body)}`)
52+
}
5553
expect(response.statusCode).toBe(200)
5654
expect(response.body.testing).toBe("item")
5755
})
@@ -131,4 +129,4 @@ describe("Check that the properly used overwrite endpoints function and interact
131129
expect(response.statusCode).toBe(200)
132130
expect(response.body.testing).toBe("item")
133131
})
134-
})
132+
})

routes/delete.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ router.delete('/', checkAccessToken, async (req, res, next) => {
1010
// check for @id in body. Any value is valid. Lack of value is a bad request.
1111
if (!req?.body || !(req.body['@id'] ?? req.body.id)) {
1212
res.status(400).send("No record id to delete! (https://store.rerum.io/v1/API.html#delete)")
13+
return
1314
}
1415
const body = JSON.stringify(req.body)
1516
const deleteOptions = {

routes/overwrite.js

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,65 @@
11
import express from "express"
22
import checkAccessToken from "../tokens.js"
33
const router = express.Router()
4-
import rerumPropertiesWasher from "../preprocessor.js"
54

65
/* PUT an overwrite to the thing. */
7-
router.put('/', checkAccessToken, rerumPropertiesWasher, async (req, res, next) => {
6+
router.put('/', checkAccessToken, async (req, res, next) => {
87

98
try {
10-
// check for @id in body. Any value is valid. Lack of value is a bad request.
11-
if (!req?.body || !(req.body['@id'] ?? req.body.id)) {
12-
res.status(400).send("No record id to overwrite! (https://store.rerum.io/v1/API.html#overwrite)")
9+
10+
const overwriteBody = req.body
11+
// check for @id; any value is valid
12+
if (!(overwriteBody['@id'] ?? overwriteBody.id)) {
13+
res.status(400).send("No record id to overwrite! (https://store.rerum.io/API.html#overwrite)")
14+
return
1315
}
14-
// check body for JSON
15-
const body = JSON.stringify(req.body)
16+
1617
const overwriteOptions = {
1718
method: 'PUT',
18-
body,
19+
body: JSON.stringify(overwriteBody),
1920
headers: {
2021
'user-agent': 'Tiny-Things/1.0',
2122
'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
2223
'Content-Type' : "application/json;charset=utf-8"
2324
}
2425
}
26+
27+
// Pass through If-Overwritten-Version header if present
28+
const ifOverwrittenVersion = req.headers.hasOwnProperty('if-overwritten-version') ? req.headers['if-overwritten-version'] : null
29+
if (ifOverwrittenVersion !== null) {
30+
overwriteOptions.headers['If-Overwritten-Version'] = ifOverwrittenVersion
31+
}
32+
33+
// Check for __rerum.isOverwritten in body and use as If-Overwritten-Version header
34+
const isOverwrittenValue = req.body?.__rerum?.hasOwnProperty("isOverwritten") ? req.body.__rerum.isOverwritten : null
35+
if (isOverwrittenValue !== null) {
36+
overwriteOptions.headers['If-Overwritten-Version'] = isOverwrittenValue
37+
}
38+
2539
const overwriteURL = `${process.env.RERUM_API_ADDR}overwrite`
26-
const result = await fetch(overwriteURL, overwriteOptions).then(res=>res.json())
27-
.catch(err=>next(err))
28-
res.setHeader("Location", result["@id"] ?? result.id)
29-
res.status(200)
30-
res.send(result)
40+
const response = await fetch(overwriteURL, overwriteOptions)
41+
.then(resp=>{
42+
if (!resp.ok) throw resp
43+
return resp
44+
})
45+
.catch(async err => {
46+
// Handle 409 conflict error for version mismatch
47+
if (err.status === 409) {
48+
const currentVersion = await err.json()
49+
return res.status(409).json(currentVersion)
50+
}
51+
throw new Error(`Error in overwrite request: ${err.status} ${err.statusText}`)
52+
})
53+
if(res.headersSent) return
54+
const result = await response.json()
55+
const location = result?.["@id"] ?? result?.id
56+
if (location) {
57+
res.setHeader("Location", location)
58+
}
59+
res.status(response.status ?? 200)
60+
res.json(result)
3161
}
32-
catch (err) {
62+
catch (err) {
3363
next(err)
3464
}
3565
})

routes/update.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import express from "express"
22
import checkAccessToken from "../tokens.js"
33
const router = express.Router()
4-
import rerumPropertiesWasher from "../preprocessor.js"
54

65
/* PUT an update to the thing. */
7-
router.put('/', checkAccessToken, rerumPropertiesWasher, async (req, res, next) => {
6+
router.put('/', checkAccessToken, async (req, res, next) => {
87

98
try {
109
// check for @id in body. Any value is valid. Lack of value is a bad request.
1110
if (!req?.body || !(req.body['@id'] ?? req.body.id)) {
1211
res.status(400).send("No record id to update! (https://store.rerum.io/v1/API.html#update)")
12+
return
1313
}
1414
// check body for JSON
1515
const body = JSON.stringify(req.body)

0 commit comments

Comments
 (0)