mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 19:26:08 +08:00 
			
		
		
		
	enable jasmine test runs
This commit is contained in:
		
							parent
							
								
									a68b75f069
								
							
						
					
					
						commit
						e7f11d6687
					
				
					 14 changed files with 413 additions and 375 deletions
				
			
		
							
								
								
									
										13
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -100,6 +100,7 @@ | |||
|         "@types/express-session": "^1.18.0", | ||||
|         "@types/html": "^1.0.4", | ||||
|         "@types/ini": "^4.1.0", | ||||
|         "@types/jasmine": "^5.1.4", | ||||
|         "@types/jsdom": "^21.1.6", | ||||
|         "@types/mime-types": "^2.1.4", | ||||
|         "@types/multer": "^1.4.11", | ||||
|  | @ -1408,6 +1409,12 @@ | |||
|       "integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==", | ||||
|       "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": { | ||||
|       "version": "21.1.6", | ||||
|       "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.6.tgz", | ||||
|  | @ -14458,6 +14465,12 @@ | |||
|       "integrity": "sha512-mTehMtc+xtnWBBvqizcqYCktKDBH2WChvx1GU3Sfe4PysFDXiNe+1YwtpVX1MDtCa4NQrSPw2+3HmvXHY3gt1w==", | ||||
|       "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": { | ||||
|       "version": "21.1.6", | ||||
|       "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.6.tgz", | ||||
|  |  | |||
							
								
								
									
										10
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
										
									
									
									
								
							|  | @ -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-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 .", | ||||
|     "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-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", | ||||
|  | @ -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-docs": "npm run build-backend-docs && npm run build-frontend-docs", | ||||
|     "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": "npm run test-jasmine && npm run test-es6", | ||||
|     "postinstall": "rimraf ./node_modules/canvas" | ||||
|     "postinstall": "rimraf ./node_modules/canvas && npm run rebuild" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@braintree/sanitize-url": "6.0.4", | ||||
|  | @ -121,6 +122,7 @@ | |||
|     "@types/express-session": "^1.18.0", | ||||
|     "@types/html": "^1.0.4", | ||||
|     "@types/ini": "^4.1.0", | ||||
|     "@types/jasmine": "^5.1.4", | ||||
|     "@types/jsdom": "^21.1.6", | ||||
|     "@types/mime-types": "^2.1.4", | ||||
|     "@types/multer": "^1.4.11", | ||||
|  | @ -155,4 +157,4 @@ | |||
|   "optionalDependencies": { | ||||
|     "electron-installer-debian": "3.2.0" | ||||
|   } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -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_files": [ | ||||
|     "**/*[sS]pec.js", | ||||
|     "**/*[sS]pec.mjs" | ||||
|   ], | ||||
|   "helpers": [ | ||||
|     "helpers/**/*.js" | ||||
|   ], | ||||
|   "spec_files": ["./etapi/*.ts"], | ||||
|   "helpers": ["helpers/**/*.js"], | ||||
|   "stopSpecOnExpectationFailure": false, | ||||
|   "random": true | ||||
| } | ||||
|  |  | |||
|  | @ -1,24 +1,19 @@ | |||
| { | ||||
| 	"compilerOptions": { | ||||
|       "moduleResolution": "Node", | ||||
| 	  "declaration": false, | ||||
| 	  "sourceMap": true, | ||||
| 	  "outDir": "./dist", | ||||
| 	  "strict": true, | ||||
| 	  "noImplicitAny": true, | ||||
| 	  "resolveJsonModule": true, | ||||
| 	  "lib": ["ES2022"], | ||||
| 	  "downlevelIteration": true | ||||
| 	}, | ||||
| 	"include": [ | ||||
|       "./src/**/*.js", | ||||
| 	  "./src/**/*.ts" | ||||
| 	], | ||||
| 	"exclude": ["./node_modules/**/*"], | ||||
| 	"ts-node": { | ||||
| 		"files": true | ||||
| 	}, | ||||
| 	"files": [ | ||||
| 		"src/types.d.ts" | ||||
| 	] | ||||
|   } | ||||
|   "compilerOptions": { | ||||
|     "moduleResolution": "Node", | ||||
|     "declaration": false, | ||||
|     "sourceMap": true, | ||||
|     "outDir": "./dist", | ||||
|     "strict": true, | ||||
|     "noImplicitAny": true, | ||||
|     "resolveJsonModule": true, | ||||
|     "lib": ["ES2022"], | ||||
|     "downlevelIteration": true | ||||
|   }, | ||||
|   "include": ["./src/**/*.js", "./src/**/*.ts", "./spec/**/*.ts"], | ||||
|   "exclude": ["./node_modules/**/*"], | ||||
|   "ts-node": { | ||||
|     "files": true | ||||
|   }, | ||||
|   "files": ["src/types.d.ts"] | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue