diff --git a/.eslintrc.js b/.eslintrc.js index 03ed3439b..c3b3404fc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -109,7 +109,9 @@ module.exports = { // src\public\app\services\utils.js logInfo: true, __non_webpack_require__: true, - // + describe: true, + it: true, + expect: true }, parserOptions: { ecmaVersion: 'latest', diff --git a/spec/etapi/notes.js b/spec/etapi/notes.js index 8e09dc313..a27d6ff69 100644 --- a/spec/etapi/notes.js +++ b/spec/etapi/notes.js @@ -1,4 +1,6 @@ -const {describeEtapi, postEtapi, getEtapi, getEtapiContent} = require("../support/etapi"); +const {describeEtapi, postEtapi, getEtapi, getEtapiContent, patchEtapi, putEtapi, putEtapiContent} = require("../support/etapi"); +const crypto = require('crypto'); +const {deleteEtapi, getEtapiResponse} = require("../support/etapi.js"); describeEtapi("notes", () => { it("create", async () => { @@ -17,11 +19,84 @@ describeEtapi("notes", () => { const rNote = await getEtapi(`notes/${note.noteId}`); expect(rNote.title).toEqual("Hello World!"); - const rContent = await getEtapiContent(`notes/${note.noteId}/content`); + const rContent = await (await getEtapiContent(`notes/${note.noteId}/content`)).text(); expect(rContent).toEqual("Content"); const rBranch = await getEtapi(`branches/${branch.branchId}`); expect(rBranch.parentNoteId).toEqual("root"); expect(rBranch.prefix).toEqual("Custom prefix"); }); + + it("patch", async () => { + const {note} = await postEtapi('create-note', { + parentNoteId: 'root', + type: 'text', + title: 'Hello World!', + content: 'Content' + }); + + await patchEtapi(`notes/${note.noteId}`, { + title: 'new title', + type: 'code', + mime: 'text/apl', + dateCreated: '2000-01-01 12:34:56.999+0200', + utcDateCreated: '2000-01-01 10:34:56.999Z', + }); + + const rNote = await getEtapi(`notes/${note.noteId}`); + expect(rNote.title).toEqual("new title"); + expect(rNote.type).toEqual("code"); + expect(rNote.mime).toEqual("text/apl"); + expect(rNote.dateCreated).toEqual("2000-01-01 12:34:56.999+0200"); + expect(rNote.utcDateCreated).toEqual("2000-01-01 10:34:56.999Z"); + }); + + it("update content", async () => { + const {note} = await postEtapi('create-note', { + parentNoteId: 'root', + type: 'text', + title: 'Hello World!', + content: 'Content' + }); + + await putEtapiContent(`notes/${note.noteId}/content`, "new content"); + + const rContent = await (await getEtapiContent(`notes/${note.noteId}/content`)).text(); + expect(rContent).toEqual("new content"); + }); + + it("create / update binary content", async () => { + const {note} = await postEtapi('create-note', { + parentNoteId: 'root', + type: 'file', + title: 'Hello World!', + content: 'ZZZ' + }); + + const updatedContent = crypto.randomBytes(16); + + await putEtapiContent(`notes/${note.noteId}/content`, updatedContent); + + const rContent = await (await getEtapiContent(`notes/${note.noteId}/content`)).arrayBuffer(); + expect(Buffer.from(new Uint8Array(rContent))).toEqual(updatedContent); + }); + + it("delete note", async () => { + const {note} = await postEtapi('create-note', { + parentNoteId: 'root', + type: 'text', + title: 'Hello World!', + content: 'Content' + }); + + await deleteEtapi(`notes/${note.noteId}`); + + const resp = await getEtapiResponse(`notes/${note.noteId}`); + expect(resp.status).toEqual(404); + + const error = await resp.json(); + expect(error.status).toEqual(404); + expect(error.code).toEqual("NOTE_NOT_FOUND"); + expect(error.message).toEqual(`Note '${note.noteId}' not found.`); + }); }); diff --git a/spec/support/etapi.js b/spec/support/etapi.js index a089122a7..0675d630b 100644 --- a/spec/support/etapi.js +++ b/spec/support/etapi.js @@ -51,13 +51,17 @@ function describeEtapi(description, specDefinitions) { }); } -async function getEtapi(url) { - const response = await fetch(`${HOST}/etapi/${url}`, { +async function getEtapiResponse(url) { + return await fetch(`${HOST}/etapi/${url}`, { method: 'GET', headers: { Authorization: getEtapiAuthorizationHeader() } }); +} + +async function getEtapi(url) { + const response = await getEtapiResponse(url); return await processEtapiResponse(response); } @@ -68,7 +72,10 @@ async function getEtapiContent(url) { Authorization: getEtapiAuthorizationHeader() } }); - return await response.text(); + + checkStatus(response); + + return response; } async function postEtapi(url, data = {}) { @@ -95,6 +102,31 @@ async function putEtapi(url, data = {}) { return await processEtapiResponse(response); } +async function putEtapiContent(url, data) { + const response = await fetch(`${HOST}/etapi/${url}`, { + method: 'PUT', + headers: { + "Content-Type": "application/octet-stream", + Authorization: getEtapiAuthorizationHeader() + }, + body: data + }); + + checkStatus(response); +} + +async function patchEtapi(url, data = {}) { + const response = await fetch(`${HOST}/etapi/${url}`, { + method: 'PATCH', + headers: { + "Content-Type": "application/json", + Authorization: getEtapiAuthorizationHeader() + }, + body: JSON.stringify(data) + }); + return await processEtapiResponse(response); +} + async function deleteEtapi(url) { const response = await fetch(`${HOST}/etapi/${url}`, { method: 'DELETE', @@ -106,20 +138,29 @@ async function deleteEtapi(url) { } async function processEtapiResponse(response) { - const json = await response.json(); + const text = await response.text(); if (response.status < 200 || response.status >= 300) { - throw new Error("ETAPI error: " + JSON.stringify(json)); + throw new Error(`ETAPI error ${response.status}: ` + text); } - return json; + return text?.trim() ? JSON.parse(text) : null; +} + +function checkStatus(response) { + if (response.status < 200 || response.status >= 300) { + throw new Error(`ETAPI error ${response.status}`); + } } module.exports = { describeEtapi, getEtapi, + getEtapiResponse, getEtapiContent, postEtapi, putEtapi, + putEtapiContent, + patchEtapi, deleteEtapi }; diff --git a/src/routes/error_handlers.js b/src/routes/error_handlers.js index 52ea33da7..1b945ea86 100644 --- a/src/routes/error_handlers.js +++ b/src/routes/error_handlers.js @@ -15,7 +15,7 @@ function register(app) { // catch 404 and forward to error handler app.use((req, res, next) => { - const err = new Error(`Router not found for request ${req.url}`); + const err = new Error(`Router not found for request ${req.method} ${req.url}`); err.status = 404; next(err); }); diff --git a/src/services/notes.js b/src/services/notes.js index 0183aff98..b89755393 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -412,7 +412,6 @@ function checkImageAttachments(note, content) { }; } - function findImageLinks(content, foundLinks) { const re = /src="[^"]*api\/images\/([a-zA-Z0-9_]+)\//g; let match;