diff --git a/package-lock.json b/package-lock.json index 8929c472fd..1e3391d845 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22014,6 +22014,29 @@ "resolved": "https://registry.npmjs.org/events-listener/-/events-listener-1.1.0.tgz", "integrity": "sha512-Kd3EgYfODHueq6GzVfs/VUolh2EgJsS8hkO3KpnDrxVjU3eq63eXM2ujXkhPP+OkeUOhL8CxdfZbQXzryb5C4g==" }, + "node_modules/eventsource-client": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/eventsource-client/-/eventsource-client-1.1.4.tgz", + "integrity": "sha512-CKnqZTwXCnHN2EqrEB9eLSjMMRqHum09VOsikkgSPoa2Jr2XgQnX7P1Fxhnnj/UHxi3GQ2xVsXDKIktEes07bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-client/node_modules/eventsource-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz", + "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/eventsource-parser": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz", @@ -44791,6 +44814,7 @@ "eslint-plugin-testing-library": "^7.1.1", "eslint-plugin-unicorn": "^56.0.0", "events": "^3.3.0", + "eventsource-client": "^1.1.4", "fake-indexeddb": "^6.0.0", "fetch-mock": "^12.5.2", "formik": "^2.2.9", diff --git a/services/web/package.json b/services/web/package.json index 2c674ac6a8..7d83193c63 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -300,6 +300,7 @@ "eslint-plugin-testing-library": "^7.1.1", "eslint-plugin-unicorn": "^56.0.0", "events": "^3.3.0", + "eventsource-client": "^1.1.4", "fake-indexeddb": "^6.0.0", "fetch-mock": "^12.5.2", "formik": "^2.2.9", diff --git a/services/web/test/acceptance/src/DeletionTests.mjs b/services/web/test/acceptance/src/DeletionTests.mjs index e589be9474..d466a071f9 100644 --- a/services/web/test/acceptance/src/DeletionTests.mjs +++ b/services/web/test/acceptance/src/DeletionTests.mjs @@ -395,7 +395,8 @@ describe('Deleting a project', function () { expect(error).not.to.exist expect(res.statusCode).to.equal(200) - expect(MockChatApi.projects[this.projectId.toString()]).not.to.exist + expect(MockChatApi.projects.has(this.projectId.toString())).to.be + .false done() } ) diff --git a/services/web/test/acceptance/src/mocks/MockChatApi.mjs b/services/web/test/acceptance/src/mocks/MockChatApi.mjs index ee3346597d..0dc056a637 100644 --- a/services/web/test/acceptance/src/mocks/MockChatApi.mjs +++ b/services/web/test/acceptance/src/mocks/MockChatApi.mjs @@ -2,42 +2,69 @@ import AbstractMockApi from './AbstractMockApi.mjs' class MockChatApi extends AbstractMockApi { reset() { - this.projects = {} + this.projects = new Map() } - getGlobalMessages(req, res) { - res.json(this.projects[req.params.project_id] || []) + getThread(projectId, threadId) { + let threads = this.projects.get(projectId) + if (threads == null) { + threads = new Map() + this.projects.set(projectId, threads) + } + let thread = threads.get(threadId) + if (thread == null) { + thread = [] + threads.set(threadId, thread) + } + return thread } - sendGlobalMessage(req, res) { - const projectId = req.params.project_id + sendMessage(projectId, threadId, props) { const message = { id: Math.random().toString(), - content: req.body.content, + content: props.content, timestamp: Date.now(), - user_id: req.body.user_id, + user_id: props.user_id, } - this.projects[projectId] = this.projects[projectId] || [] - this.projects[projectId].push(message) - res.json(Object.assign({ room_id: projectId }, message)) + const thread = this.getThread(projectId, threadId) + thread.push(message) + return { room_id: projectId, ...message } } - destroyProject(req, res) { - const projectId = req.params.project_id - delete this.projects[projectId] - res.sendStatus(204) + destroyProject(projectId) { + this.projects.delete(projectId) } applyRoutes() { - this.app.get('/project/:project_id/messages', (req, res) => - this.getGlobalMessages(req, res) + this.app.get('/project/:project_id/messages', (req, res) => { + res.json(this.getThread(req.params.project_id, 'global')) + }) + this.app.post('/project/:project_id/messages', (req, res) => { + res.json(this.sendMessage(req.params.project_id, 'global', req.body)) + }) + this.app.get( + '/project/:project_id/thread/:thread_id/messages', + (req, res) => { + res.json(this.getThread(req.params.project_id, req.params.thread_id)) + } ) - this.app.post('/project/:project_id/messages', (req, res) => - this.sendGlobalMessage(req, res) - ) - this.app.delete('/project/:project_id', (req, res) => - this.destroyProject(req, res) + this.app.post( + '/project/:project_id/thread/:thread_id/messages', + (req, res) => { + res.json( + this.sendMessage( + req.params.project_id, + req.params.thread_id, + req.body + ) + ) + } ) + this.app.delete('/project/:project_id', (req, res) => { + const projectId = req.params.project_id + this.destroyProject(projectId) + res.sendStatus(204) + }) } }