mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-30 09:56:36 +01:00 
			
		
		
		
	enable jasmine test runs
This commit is contained in:
		
							
								
								
									
										13
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -100,6 +100,7 @@ | |||||||
|         "@types/express-session": "^1.18.0", |         "@types/express-session": "^1.18.0", | ||||||
|         "@types/html": "^1.0.4", |         "@types/html": "^1.0.4", | ||||||
|         "@types/ini": "^4.1.0", |         "@types/ini": "^4.1.0", | ||||||
|  |         "@types/jasmine": "^5.1.4", | ||||||
|         "@types/jsdom": "^21.1.6", |         "@types/jsdom": "^21.1.6", | ||||||
|         "@types/mime-types": "^2.1.4", |         "@types/mime-types": "^2.1.4", | ||||||
|         "@types/multer": "^1.4.11", |         "@types/multer": "^1.4.11", | ||||||
| @@ -1408,6 +1409,12 @@ | |||||||
|       "integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==", |       "integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/@types/jasmine": { | ||||||
|  |       "version": "5.1.4", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.4.tgz", | ||||||
|  |       "integrity": "sha512-px7OMFO/ncXxixDe1zR13V1iycqWae0MxTaw62RpFlksUi5QuNWgQJFkTQjIOvrmutJbI7Fp2Y2N1F6D2R4G6w==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "node_modules/@types/jsdom": { |     "node_modules/@types/jsdom": { | ||||||
|       "version": "21.1.6", |       "version": "21.1.6", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.6.tgz", |       "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.6.tgz", | ||||||
| @@ -14458,6 +14465,12 @@ | |||||||
|       "integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==", |       "integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "@types/jasmine": { | ||||||
|  |       "version": "5.1.4", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.4.tgz", | ||||||
|  |       "integrity": "sha512-px7OMFO/ncXxixDe1zR13V1iycqWae0MxTaw62RpFlksUi5QuNWgQJFkTQjIOvrmutJbI7Fp2Y2N1F6D2R4G6w==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "@types/jsdom": { |     "@types/jsdom": { | ||||||
|       "version": "21.1.6", |       "version": "21.1.6", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.6.tgz", |       "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.6.tgz", | ||||||
|   | |||||||
| @@ -19,7 +19,8 @@ | |||||||
|     "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": "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 .", |     "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": "npm run qswitch-electron && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .", |     "qstart-electron": "npm run qswitch-electron && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .", | ||||||
|     "start-test-server": "npm run qswitch-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.js", |     "start-test-server": "npm run qswitch-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 ts-node src/www.ts", | ||||||
|  |     "rebuild": "electron-rebuild", | ||||||
|     "switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install", |     "switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install", | ||||||
|     "switch-electron": "./node_modules/.bin/electron-rebuild", |     "switch-electron": "./node_modules/.bin/electron-rebuild", | ||||||
|     "qswitch-server": "rm -rf ./node_modules/better-sqlite3/bin ; mkdir -p ./node_modules/better-sqlite3/build ; cp ./bin/better-sqlite3/linux-server-better_sqlite3.node ./node_modules/better-sqlite3/build/better_sqlite3.node", |     "qswitch-server": "rm -rf ./node_modules/better-sqlite3/bin ; mkdir -p ./node_modules/better-sqlite3/build ; cp ./bin/better-sqlite3/linux-server-better_sqlite3.node ./node_modules/better-sqlite3/build/better_sqlite3.node", | ||||||
| @@ -28,10 +29,10 @@ | |||||||
|     "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-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", |     "build-docs": "npm run build-backend-docs && npm run build-frontend-docs", | ||||||
|     "webpack": "webpack -c webpack.config.js", |     "webpack": "webpack -c webpack.config.js", | ||||||
|     "test-jasmine": "TRILIUM_DATA_DIR=~/trilium/data-test jasmine", |     "test-jasmine": "TRILIUM_DATA_DIR=~/trilium/data-test ts-node ./node_modules/.bin/jasmine", | ||||||
|     "test-es6": "node -r esm spec-es6/attribute_parser.spec.js ", |     "test-es6": "node -r esm spec-es6/attribute_parser.spec.js ", | ||||||
|     "test": "npm run test-jasmine && npm run test-es6", |     "test": "npm run test-jasmine && npm run test-es6", | ||||||
|     "postinstall": "rimraf ./node_modules/canvas" |     "postinstall": "rimraf ./node_modules/canvas && npm run rebuild" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@braintree/sanitize-url": "6.0.4", |     "@braintree/sanitize-url": "6.0.4", | ||||||
| @@ -121,6 +122,7 @@ | |||||||
|     "@types/express-session": "^1.18.0", |     "@types/express-session": "^1.18.0", | ||||||
|     "@types/html": "^1.0.4", |     "@types/html": "^1.0.4", | ||||||
|     "@types/ini": "^4.1.0", |     "@types/ini": "^4.1.0", | ||||||
|  |     "@types/jasmine": "^5.1.4", | ||||||
|     "@types/jsdom": "^21.1.6", |     "@types/jsdom": "^21.1.6", | ||||||
|     "@types/mime-types": "^2.1.4", |     "@types/mime-types": "^2.1.4", | ||||||
|     "@types/multer": "^1.4.11", |     "@types/multer": "^1.4.11", | ||||||
|   | |||||||
| @@ -1,12 +0,0 @@ | |||||||
| const { |  | ||||||
|     describeEtapi, postEtapi, |  | ||||||
|     putEtapiContent |  | ||||||
| } = require('../support/etapi.js'); |  | ||||||
| const {getEtapi} = require("../support/etapi.js"); |  | ||||||
|  |  | ||||||
| describeEtapi("app_info", () => { |  | ||||||
|     it("get", async () => { |  | ||||||
|         const appInfo = await getEtapi("app-info"); |  | ||||||
|         expect(appInfo.clipperProtocolVersion).toEqual("1.0"); |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
							
								
								
									
										8
									
								
								spec/etapi/app_info.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								spec/etapi/app_info.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | import etapi = require("../support/etapi"); | ||||||
|  |  | ||||||
|  | etapi.describeEtapi("app_info", () => { | ||||||
|  |   it("get", async () => { | ||||||
|  |     const appInfo = await etapi.getEtapi("app-info"); | ||||||
|  |     expect(appInfo.clipperProtocolVersion).toEqual("1.0"); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| const { |  | ||||||
|     describeEtapi, postEtapi, |  | ||||||
|     getEtapi, |  | ||||||
| } = require('../support/etapi.js'); |  | ||||||
| const {putEtapiContent} = require("../support/etapi.js"); |  | ||||||
|  |  | ||||||
| describeEtapi("backup", () => { |  | ||||||
|     it("create", async () => { |  | ||||||
|         const response = await putEtapiContent("backup/etapi_test"); |  | ||||||
|         expect(response.status).toEqual(204); |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
							
								
								
									
										8
									
								
								spec/etapi/backup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								spec/etapi/backup.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | import etapi = require("../support/etapi"); | ||||||
|  |  | ||||||
|  | etapi.describeEtapi("backup", () => { | ||||||
|  |   it("create", async () => { | ||||||
|  |     const response = await etapi.putEtapiContent("backup/etapi_test"); | ||||||
|  |     expect(response.status).toEqual(204); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| const { |  | ||||||
|     describeEtapi, postEtapi, |  | ||||||
|     postEtapiContent, |  | ||||||
| } = require('../support/etapi.js'); |  | ||||||
| const fs = require("fs"); |  | ||||||
| const path = require("path"); |  | ||||||
| const {getEtapiContent} = require("../support/etapi.js"); |  | ||||||
|  |  | ||||||
| describeEtapi("import", () => { |  | ||||||
|     it("import", async () => { |  | ||||||
|         const zipFileBuffer = fs.readFileSync(path.resolve(__dirname, 'test-export.zip')); |  | ||||||
|  |  | ||||||
|         const response = await postEtapiContent("notes/root/import", zipFileBuffer); |  | ||||||
|         expect(response.status).toEqual(201); |  | ||||||
|  |  | ||||||
|         const {note, branch} = await response.json(); |  | ||||||
|  |  | ||||||
|         expect(note.title).toEqual("test-export"); |  | ||||||
|         expect(branch.parentNoteId).toEqual("root"); |  | ||||||
|  |  | ||||||
|         const content = await (await getEtapiContent(`notes/${note.noteId}/content`)).text(); |  | ||||||
|         expect(content).toContain("test export content"); |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
							
								
								
									
										27
									
								
								spec/etapi/import.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								spec/etapi/import.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | import etapi = require("../support/etapi"); | ||||||
|  | import fs = require("fs"); | ||||||
|  | import path = require("path"); | ||||||
|  |  | ||||||
|  | etapi.describeEtapi("import", () => { | ||||||
|  |   it("import", async () => { | ||||||
|  |     const zipFileBuffer = fs.readFileSync( | ||||||
|  |       path.resolve(__dirname, "test-export.zip") | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     const response = await etapi.postEtapiContent( | ||||||
|  |       "notes/root/import", | ||||||
|  |       zipFileBuffer | ||||||
|  |     ); | ||||||
|  |     expect(response.status).toEqual(201); | ||||||
|  |  | ||||||
|  |     const { note, branch } = await response.json(); | ||||||
|  |  | ||||||
|  |     expect(note.title).toEqual("test-export"); | ||||||
|  |     expect(branch.parentNoteId).toEqual("root"); | ||||||
|  |  | ||||||
|  |     const content = await ( | ||||||
|  |       await etapi.getEtapiContent(`notes/${note.noteId}/content`) | ||||||
|  |     ).text(); | ||||||
|  |     expect(content).toContain("test export content"); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -1,109 +0,0 @@ | |||||||
| const crypto = require('crypto'); |  | ||||||
| const { |  | ||||||
|     deleteEtapi, |  | ||||||
|     getEtapiResponse, |  | ||||||
|     describeEtapi, postEtapi, |  | ||||||
|     getEtapi, |  | ||||||
|     getEtapiContent, |  | ||||||
|     patchEtapi, putEtapi, |  | ||||||
|     putEtapiContent |  | ||||||
| } = require('../support/etapi.js'); |  | ||||||
|  |  | ||||||
| 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 (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.`); |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
							
								
								
									
										107
									
								
								spec/etapi/notes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								spec/etapi/notes.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | import crypto = require("crypto"); | ||||||
|  | import etapi = require("../support/etapi"); | ||||||
|  |  | ||||||
|  | etapi.describeEtapi("notes", () => { | ||||||
|  |   it("create", async () => { | ||||||
|  |     const { note, branch } = await etapi.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 etapi.getEtapi(`notes/${note.noteId}`); | ||||||
|  |     expect(rNote.title).toEqual("Hello World!"); | ||||||
|  |  | ||||||
|  |     const rContent = await ( | ||||||
|  |       await etapi.getEtapiContent(`notes/${note.noteId}/content`) | ||||||
|  |     ).text(); | ||||||
|  |     expect(rContent).toEqual("Content"); | ||||||
|  |  | ||||||
|  |     const rBranch = await etapi.getEtapi(`branches/${branch.branchId}`); | ||||||
|  |     expect(rBranch.parentNoteId).toEqual("root"); | ||||||
|  |     expect(rBranch.prefix).toEqual("Custom prefix"); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it("patch", async () => { | ||||||
|  |     const { note } = await etapi.postEtapi("create-note", { | ||||||
|  |       parentNoteId: "root", | ||||||
|  |       type: "text", | ||||||
|  |       title: "Hello World!", | ||||||
|  |       content: "Content", | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await etapi.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 etapi.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 etapi.postEtapi("create-note", { | ||||||
|  |       parentNoteId: "root", | ||||||
|  |       type: "text", | ||||||
|  |       title: "Hello World!", | ||||||
|  |       content: "Content", | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await etapi.putEtapiContent(`notes/${note.noteId}/content`, "new content"); | ||||||
|  |  | ||||||
|  |     const rContent = await ( | ||||||
|  |       await etapi.getEtapiContent(`notes/${note.noteId}/content`) | ||||||
|  |     ).text(); | ||||||
|  |     expect(rContent).toEqual("new content"); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it("create / update binary content", async () => { | ||||||
|  |     const { note } = await etapi.postEtapi("create-note", { | ||||||
|  |       parentNoteId: "root", | ||||||
|  |       type: "file", | ||||||
|  |       title: "Hello World!", | ||||||
|  |       content: "ZZZ", | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const updatedContent = crypto.randomBytes(16); | ||||||
|  |  | ||||||
|  |     await etapi.putEtapiContent(`notes/${note.noteId}/content`, updatedContent); | ||||||
|  |  | ||||||
|  |     const rContent = await ( | ||||||
|  |       await etapi.getEtapiContent(`notes/${note.noteId}/content`) | ||||||
|  |     ).arrayBuffer(); | ||||||
|  |     expect(Buffer.from(new Uint8Array(rContent))).toEqual(updatedContent); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it("delete note", async () => { | ||||||
|  |     const { note } = await etapi.postEtapi("create-note", { | ||||||
|  |       parentNoteId: "root", | ||||||
|  |       type: "text", | ||||||
|  |       title: "Hello World!", | ||||||
|  |       content: "Content", | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await etapi.deleteEtapi(`notes/${note.noteId}`); | ||||||
|  |  | ||||||
|  |     const resp = await etapi.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.`); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -1,184 +0,0 @@ | |||||||
| 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 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); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getEtapiContent(url) { |  | ||||||
|     const response = await fetch(`${HOST}/etapi/${url}`, { |  | ||||||
|         method: 'GET', |  | ||||||
|         headers: { |  | ||||||
|             Authorization: getEtapiAuthorizationHeader() |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     checkStatus(response); |  | ||||||
|  |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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 postEtapiContent(url, data) { |  | ||||||
|     const response = await fetch(`${HOST}/etapi/${url}`, { |  | ||||||
|         method: 'POST', |  | ||||||
|         headers: { |  | ||||||
|             "Content-Type": "application/octet-stream", |  | ||||||
|             Authorization: getEtapiAuthorizationHeader() |  | ||||||
|         }, |  | ||||||
|         body: data |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     checkStatus(response); |  | ||||||
|  |  | ||||||
|     return 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 putEtapiContent(url, data) { |  | ||||||
|     const response = await fetch(`${HOST}/etapi/${url}`, { |  | ||||||
|         method: 'PUT', |  | ||||||
|         headers: { |  | ||||||
|             "Content-Type": "application/octet-stream", |  | ||||||
|             Authorization: getEtapiAuthorizationHeader() |  | ||||||
|         }, |  | ||||||
|         body: data |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     checkStatus(response); |  | ||||||
|  |  | ||||||
|     return 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', |  | ||||||
|         headers: { |  | ||||||
|             Authorization: getEtapiAuthorizationHeader() |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|     return await processEtapiResponse(response); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function processEtapiResponse(response) { |  | ||||||
|     const text = await response.text(); |  | ||||||
|  |  | ||||||
|     if (response.status < 200 || response.status >= 300) { |  | ||||||
|         throw new Error(`ETAPI error ${response.status}: ` + text); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     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, |  | ||||||
|     postEtapiContent, |  | ||||||
|     putEtapi, |  | ||||||
|     putEtapiContent, |  | ||||||
|     patchEtapi, |  | ||||||
|     deleteEtapi |  | ||||||
| }; |  | ||||||
							
								
								
									
										224
									
								
								spec/support/etapi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								spec/support/etapi.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,224 @@ | |||||||
|  | import child_process = require("child_process"); | ||||||
|  | import kill = require("tree-kill"); | ||||||
|  |  | ||||||
|  | let etapiAuthToken: string | undefined; | ||||||
|  |  | ||||||
|  | const getEtapiAuthorizationHeader = (): string => | ||||||
|  |   "Basic " + Buffer.from(`etapi:${etapiAuthToken}`).toString("base64"); | ||||||
|  |  | ||||||
|  | const PORT: string = "9999"; | ||||||
|  | const HOST: string = "http://localhost:" + PORT; | ||||||
|  |  | ||||||
|  | type SpecDefinitionsFunc = () => void; | ||||||
|  |  | ||||||
|  | function describeEtapi( | ||||||
|  |   description: string, | ||||||
|  |   specDefinitions: SpecDefinitionsFunc | ||||||
|  | ): void { | ||||||
|  |   describe(description, () => { | ||||||
|  |     let appProcess: ReturnType<typeof child_process.spawn>; | ||||||
|  |  | ||||||
|  |     beforeAll(async () => { | ||||||
|  |       appProcess = child_process.spawn("npm", ["run", "start-test-server"]); | ||||||
|  |       if (!appProcess) { | ||||||
|  |         throw new Error("Failed to start the Trilium process."); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       await new Promise<void>((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..." | ||||||
|  |       ); | ||||||
|  |       if (!appProcess.pid) { | ||||||
|  |         console.log("Trilium process not found. Cannot kill."); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       kill(appProcess.pid, "SIGKILL", (error) => { | ||||||
|  |         if (error) { | ||||||
|  |           console.error("Failed to kill the Trilium process.", error); | ||||||
|  |         } | ||||||
|  |         console.log("Trilium process killed."); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     specDefinitions(); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getEtapiResponse(url: string): Promise<Response> { | ||||||
|  |   return await fetch(`${HOST}/etapi/${url}`, { | ||||||
|  |     method: "GET", | ||||||
|  |     headers: { | ||||||
|  |       Authorization: getEtapiAuthorizationHeader(), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getEtapi(url: string): Promise<any> { | ||||||
|  |   const response = await getEtapiResponse(url); | ||||||
|  |   return await processEtapiResponse(response); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getEtapiContent(url: string): Promise<Response> { | ||||||
|  |   const response = await fetch(`${HOST}/etapi/${url}`, { | ||||||
|  |     method: "GET", | ||||||
|  |     headers: { | ||||||
|  |       Authorization: getEtapiAuthorizationHeader(), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   checkStatus(response); | ||||||
|  |  | ||||||
|  |   return response; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function postEtapi( | ||||||
|  |   url: string, | ||||||
|  |   data: Record<string, unknown> = {} | ||||||
|  | ): Promise<any> { | ||||||
|  |   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 postEtapiContent( | ||||||
|  |   url: string, | ||||||
|  |   data: BodyInit | ||||||
|  | ): Promise<Response> { | ||||||
|  |   const response = await fetch(`${HOST}/etapi/${url}`, { | ||||||
|  |     method: "POST", | ||||||
|  |     headers: { | ||||||
|  |       "Content-Type": "application/octet-stream", | ||||||
|  |       Authorization: getEtapiAuthorizationHeader(), | ||||||
|  |     }, | ||||||
|  |     body: data, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   checkStatus(response); | ||||||
|  |  | ||||||
|  |   return response; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function putEtapi( | ||||||
|  |   url: string, | ||||||
|  |   data: Record<string, unknown> = {} | ||||||
|  | ): Promise<any> { | ||||||
|  |   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 putEtapiContent( | ||||||
|  |   url: string, | ||||||
|  |   data?: BodyInit | ||||||
|  | ): Promise<Response> { | ||||||
|  |   const response = await fetch(`${HOST}/etapi/${url}`, { | ||||||
|  |     method: "PUT", | ||||||
|  |     headers: { | ||||||
|  |       "Content-Type": "application/octet-stream", | ||||||
|  |       Authorization: getEtapiAuthorizationHeader(), | ||||||
|  |     }, | ||||||
|  |     body: data, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   checkStatus(response); | ||||||
|  |  | ||||||
|  |   return response; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function patchEtapi( | ||||||
|  |   url: string, | ||||||
|  |   data: Record<string, unknown> = {} | ||||||
|  | ): Promise<any> { | ||||||
|  |   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: string): Promise<any> { | ||||||
|  |   const response = await fetch(`${HOST}/etapi/${url}`, { | ||||||
|  |     method: "DELETE", | ||||||
|  |     headers: { | ||||||
|  |       Authorization: getEtapiAuthorizationHeader(), | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  |   return await processEtapiResponse(response); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function processEtapiResponse(response: Response): Promise<any> { | ||||||
|  |   const text = await response.text(); | ||||||
|  |  | ||||||
|  |   if (response.status < 200 || response.status >= 300) { | ||||||
|  |     throw new Error(`ETAPI error ${response.status}: ${text}`); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return text?.trim() ? JSON.parse(text) : null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function checkStatus(response: Response): void { | ||||||
|  |   if (response.status < 200 || response.status >= 300) { | ||||||
|  |     throw new Error(`ETAPI error ${response.status}`); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export { | ||||||
|  |   describeEtapi, | ||||||
|  |   getEtapi, | ||||||
|  |   getEtapiResponse, | ||||||
|  |   getEtapiContent, | ||||||
|  |   postEtapi, | ||||||
|  |   postEtapiContent, | ||||||
|  |   putEtapi, | ||||||
|  |   putEtapiContent, | ||||||
|  |   patchEtapi, | ||||||
|  |   deleteEtapi, | ||||||
|  | }; | ||||||
| @@ -1,12 +1,7 @@ | |||||||
| { | { | ||||||
|   "spec_dir": "spec", |   "spec_dir": "spec", | ||||||
|   "spec_files": [ |   "spec_files": ["./etapi/*.ts"], | ||||||
|     "**/*[sS]pec.js", |   "helpers": ["helpers/**/*.js"], | ||||||
|     "**/*[sS]pec.mjs" |  | ||||||
|   ], |  | ||||||
|   "helpers": [ |  | ||||||
|     "helpers/**/*.js" |  | ||||||
|   ], |  | ||||||
|   "stopSpecOnExpectationFailure": false, |   "stopSpecOnExpectationFailure": false, | ||||||
|   "random": true |   "random": true | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,15 +10,10 @@ | |||||||
|     "lib": ["ES2022"], |     "lib": ["ES2022"], | ||||||
|     "downlevelIteration": true |     "downlevelIteration": true | ||||||
|   }, |   }, | ||||||
| 	"include": [ |   "include": ["./src/**/*.js", "./src/**/*.ts", "./spec/**/*.ts"], | ||||||
|       "./src/**/*.js", |  | ||||||
| 	  "./src/**/*.ts" |  | ||||||
| 	], |  | ||||||
|   "exclude": ["./node_modules/**/*"], |   "exclude": ["./node_modules/**/*"], | ||||||
|   "ts-node": { |   "ts-node": { | ||||||
|     "files": true |     "files": true | ||||||
|   }, |   }, | ||||||
| 	"files": [ |   "files": ["src/types.d.ts"] | ||||||
| 		"src/types.d.ts" |  | ||||||
| 	] |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user