From 8690228d9827ca6d55c9c83a0ed273cb7883e82e Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 29 Oct 2023 23:24:11 +0100 Subject: [PATCH] etapi test infrastructure plus a basic note creation test --- .gitignore | 3 +- package-lock.json | 18 +++++- package.json | 4 +- spec/etapi/notes.js | 27 +++++++++ spec/support/etapi.js | 125 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 spec/etapi/notes.js create mode 100644 spec/support/etapi.js diff --git a/.gitignore b/.gitignore index 6c7f73ae1..0e095b075 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,6 @@ cert.crt server-package.json .idea/httpRequests/ data/ +data-test/ tmp/ -.eslintcache \ No newline at end of file +.eslintcache diff --git a/package-lock.json b/package-lock.json index ec1f0d7b6..b9f0408fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trilium", - "version": "0.61.8-beta", + "version": "0.61.10-beta", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "trilium", - "version": "0.61.8-beta", + "version": "0.61.10-beta", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { @@ -68,6 +68,7 @@ "stream-throttle": "0.1.3", "striptags": "3.2.0", "tmp": "0.2.1", + "tree-kill": "^1.2.2", "turndown": "7.1.2", "unescape": "1.0.1", "ws": "8.14.2", @@ -12630,6 +12631,14 @@ "node": ">=14" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", @@ -23061,6 +23070,11 @@ "punycode": "^2.3.0" } }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" + }, "trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", diff --git a/package.json b/package.json index 936edacc6..8cbf94490 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,14 @@ "start-electron": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .", "start-electron-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .", "qstart-electron": "rm -r ./node_modules/better-sqlite3/bin ; mkdir -p ./node_modules/better-sqlite3/build ; cp ./bin/better-sqlite3/linux-desktop-better_sqlite3.node ./node_modules/better-sqlite3/build/better_sqlite3.node && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .", + "start-test-server": "rm -rf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 node ./src/www", "switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install", "switch-electron": "./node_modules/.bin/electron-rebuild", "build-backend-docs": "rm -rf ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js", "build-frontend-docs": "rm -rf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js", "build-docs": "npm run build-backend-docs && npm run build-frontend-docs", "webpack": "webpack -c webpack.config.js", - "test-jasmine": "jasmine", + "test-jasmine": "TRILIUM_DATA_DIR=~/trilium/data-test jasmine", "test-es6": "node -r esm spec-es6/attribute_parser.spec.js ", "test": "npm run test-jasmine && npm run test-es6", "postinstall": "rimraf ./node_modules/canvas", @@ -91,6 +92,7 @@ "stream-throttle": "0.1.3", "striptags": "3.2.0", "tmp": "0.2.1", + "tree-kill": "^1.2.2", "turndown": "7.1.2", "unescape": "1.0.1", "ws": "8.14.2", diff --git a/spec/etapi/notes.js b/spec/etapi/notes.js new file mode 100644 index 000000000..8e09dc313 --- /dev/null +++ b/spec/etapi/notes.js @@ -0,0 +1,27 @@ +const {describeEtapi, postEtapi, getEtapi, getEtapiContent} = require("../support/etapi"); + +describeEtapi("notes", () => { + it("create", async () => { + const {note, branch} = await postEtapi('create-note', { + parentNoteId: 'root', + type: 'text', + title: 'Hello World!', + content: 'Content', + prefix: 'Custom prefix' + }); + + expect(note.title).toEqual("Hello World!"); + expect(branch.parentNoteId).toEqual("root"); + expect(branch.prefix).toEqual("Custom prefix"); + + const rNote = await getEtapi(`notes/${note.noteId}`); + expect(rNote.title).toEqual("Hello World!"); + + const rContent = await getEtapiContent(`notes/${note.noteId}/content`); + expect(rContent).toEqual("Content"); + + const rBranch = await getEtapi(`branches/${branch.branchId}`); + expect(rBranch.parentNoteId).toEqual("root"); + expect(rBranch.prefix).toEqual("Custom prefix"); + }); +}); diff --git a/spec/support/etapi.js b/spec/support/etapi.js new file mode 100644 index 000000000..a089122a7 --- /dev/null +++ b/spec/support/etapi.js @@ -0,0 +1,125 @@ +const {spawn} = require("child_process"); +const kill = require('tree-kill'); + +let etapiAuthToken; + +const getEtapiAuthorizationHeader = () => "Basic " + Buffer.from(`etapi:${etapiAuthToken}`).toString('base64'); + +const PORT = '9999'; +const HOST = 'http://localhost:' + PORT; + +function describeEtapi(description, specDefinitions) { + describe(description, () => { + let appProcess; + + beforeAll(async () => { + appProcess = spawn('npm', ['run', 'start-test-server']); + + await new Promise(res => { + appProcess.stdout.on('data', data => { + console.log("Trilium: " + data.toString().trim()); + + if (data.toString().includes('Listening on port')) { + res(); + } + }); + }); + + await fetch(HOST + '/api/setup/new-document', { method: 'POST' }); + + const formData = new URLSearchParams(); + formData.append('password1', '1234'); + formData.append('password2', '1234'); + + await fetch(HOST + '/set-password', { method: 'POST', body: formData }); + + etapiAuthToken = (await (await fetch(HOST + '/etapi/auth/login', { + method: 'POST', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ password: '1234' }) + })).json()).authToken; + }); + + afterAll(() => { + console.log("Attempting to kill the Trilium process as part of the cleanup..."); + kill(appProcess.pid, 'SIGKILL', () => { console.log("Trilium process killed.") }); + }); + + specDefinitions(); + }); +} + +async function getEtapi(url) { + const response = await fetch(`${HOST}/etapi/${url}`, { + method: 'GET', + headers: { + Authorization: getEtapiAuthorizationHeader() + } + }); + return await processEtapiResponse(response); +} + +async function getEtapiContent(url) { + const response = await fetch(`${HOST}/etapi/${url}`, { + method: 'GET', + headers: { + Authorization: getEtapiAuthorizationHeader() + } + }); + return await response.text(); +} + +async function postEtapi(url, data = {}) { + const response = await fetch(`${HOST}/etapi/${url}`, { + method: 'POST', + headers: { + "Content-Type": "application/json", + Authorization: getEtapiAuthorizationHeader() + }, + body: JSON.stringify(data) + }); + return await processEtapiResponse(response); +} + +async function putEtapi(url, data = {}) { + const response = await fetch(`${HOST}/etapi/${url}`, { + method: 'PUT', + 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', + headers: { + Authorization: getEtapiAuthorizationHeader() + } + }); + return await processEtapiResponse(response); +} + +async function processEtapiResponse(response) { + const json = await response.json(); + + if (response.status < 200 || response.status >= 300) { + throw new Error("ETAPI error: " + JSON.stringify(json)); + } + + return json; +} + +module.exports = { + describeEtapi, + getEtapi, + getEtapiContent, + postEtapi, + putEtapi, + deleteEtapi +};