Files
overleaf-cep/services/web/test/unit/src/Notifications/NotificationsHandler.test.mjs
T
Antoine Clausse 039491f866 [web] Promisify NotificationsHandler (#28805)
* Replace request by fetch

* Promisify NotificationsHandler.js

* Replace to `fetchNothing` when we don't consume the body.

I also checked that these endpoints don't return anything: https://github.com/overleaf/internal/blob/de2d0ed8cab15c4347ddecf227073a6d4e06120f/services/notifications/app.js

* Make URLs safer

* Ignore (but log) failing `fetchNothing`s on DELETE endpoints

* Defensively add `userId.toString()` to prevent bugs (!)

* Update NotificationsHandler unit tests

* Throw fetch errors only if we did not get a response

GitOrigin-RevId: 03700ad29658c0e3af4e476b25a964030e9c97f1
2025-12-17 09:05:38 +00:00

154 lines
4.6 KiB
JavaScript

import { vi, assert } from 'vitest'
import sinon from 'sinon'
import path from 'node:path'
const modulePath = path.join(
import.meta.dirname,
'../../../../app/src/Features/Notifications/NotificationsHandler.mjs'
)
describe('NotificationsHandler', function () {
const userId = '123nd3ijdks'
const notificationId = '123njdskj9jlk'
const notificationUrl = 'http://notification.overleaf.testing'
beforeEach(async function (ctx) {
ctx.FetchUtils = {
fetchJson: sinon.stub().resolves(),
fetchNothing: sinon.stub().resolves(),
}
vi.doMock('@overleaf/settings', () => ({
default: {
apis: { notifications: { url: notificationUrl } },
},
}))
vi.doMock('@overleaf/fetch-utils', () => ctx.FetchUtils)
ctx.handler = (await import(modulePath)).default
})
describe('getUserNotifications', function () {
it('should get unread notifications', async function (ctx) {
const stubbedNotifications = [{ _id: notificationId, user_id: userId }]
ctx.FetchUtils.fetchJson.resolves(stubbedNotifications)
const unreadNotifications =
await ctx.handler.promises.getUserNotifications(userId)
stubbedNotifications.should.deep.equal(unreadNotifications)
ctx.FetchUtils.fetchJson
.calledWith(
sinon.match(u => u.href === `${notificationUrl}/user/${userId}`)
)
.should.equal(true)
})
it('should return empty arrays if there are no notifications', async function (ctx) {
ctx.FetchUtils.fetchJson.resolves(null)
const unreadNotifications =
await ctx.handler.promises.getUserNotifications(userId)
unreadNotifications.length.should.equal(0)
})
})
describe('markAsRead', function () {
beforeEach(function (ctx) {
ctx.key = 'some key here'
})
it('should send a delete request when a delete has been received to mark a notification', async function (ctx) {
await ctx.handler.promises.markAsReadWithKey(userId, ctx.key)
ctx.FetchUtils.fetchNothing
.calledWith(
sinon.match(u => u.href === `${notificationUrl}/user/${userId}`),
sinon.match({
method: 'DELETE',
json: { key: ctx.key },
})
)
.should.equal(true)
})
})
describe('createNotification', function () {
beforeEach(function (ctx) {
ctx.key = 'some key here'
ctx.messageOpts = { value: 12344 }
ctx.templateKey = 'renderThisHtml'
ctx.expiry = null
})
it('should post the message over', async function (ctx) {
await ctx.handler.promises.createNotification(
userId,
ctx.key,
ctx.templateKey,
ctx.messageOpts,
ctx.expiry
)
const [url, opts] = ctx.FetchUtils.fetchNothing.getCall(0).args
url.href.should.equal(`${notificationUrl}/user/${userId}`)
opts.method.should.equal('POST')
const expectedJson = {
key: ctx.key,
templateKey: ctx.templateKey,
messageOpts: ctx.messageOpts,
forceCreate: true,
}
assert.deepEqual(opts.json, expectedJson)
})
describe('when expiry date is supplied', function () {
beforeEach(function (ctx) {
ctx.key = 'some key here'
ctx.messageOpts = { value: 12344 }
ctx.templateKey = 'renderThisHtml'
ctx.expiry = new Date()
})
it('should post the message over with expiry field', async function (ctx) {
await ctx.handler.promises.createNotification(
userId,
ctx.key,
ctx.templateKey,
ctx.messageOpts,
ctx.expiry
)
const [url, args] = ctx.FetchUtils.fetchNothing.getCall(0).args
url.href.should.equal(`${notificationUrl}/user/${userId}`)
args.method.should.equal('POST')
const expectedJson = {
key: ctx.key,
templateKey: ctx.templateKey,
messageOpts: ctx.messageOpts,
expires: ctx.expiry,
forceCreate: true,
}
assert.deepEqual(args.json, expectedJson)
})
})
})
describe('markAsReadByKeyOnly', function () {
beforeEach(function (ctx) {
ctx.key = 'some key here'
})
it('should send a delete request when a delete has been received to mark a notification', async function (ctx) {
await ctx.handler.promises.markAsReadByKeyOnly(ctx.key)
ctx.FetchUtils.fetchNothing
.calledWith(
sinon.match(
u => u.href === `${notificationUrl}/key/some%20key%20here`
),
sinon.match({
method: 'DELETE',
})
)
.should.equal(true)
})
})
})