Compare commits

...

1 Commits

Author SHA1 Message Date
Tom Moor 2228df4054 fix: notifications.pixel requests hang
tests
2025-03-23 20:47:18 -04:00
2 changed files with 86 additions and 5 deletions
@@ -1,3 +1,5 @@
import queryString from "query-string";
import { v4 as uuidv4 } from "uuid";
import { randomElement } from "@shared/random";
import { NotificationEventType } from "@shared/types";
import {
@@ -273,6 +275,74 @@ describe("#notifications.list", () => {
});
});
describe("#notifications.pixel", () => {
it("should mark notification as viewed", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const actor = await buildUser({
teamId: team.id,
});
const notification = await buildNotification({
teamId: team.id,
userId: user.id,
actorId: actor.id,
event: NotificationEventType.UpdateDocument,
});
expect(notification.viewedAt).toBeNull();
const res = await server.get(
`/api/notifications.pixel?${queryString.stringify({
id: notification.id,
token: notification.pixelToken,
})}`
);
expect(res.status).toBe(200);
expect(res.headers.get("content-type")).toBe("image/gif");
const reloaded = await notification.reload();
expect(reloaded.viewedAt).not.toBeNull();
});
it("should not mark notification as viewed with invalid token", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const actor = await buildUser({
teamId: team.id,
});
const notification = await buildNotification({
teamId: team.id,
userId: user.id,
actorId: actor.id,
event: NotificationEventType.UpdateDocument,
});
const res = await server.get(
`/api/notifications.pixel?${queryString.stringify({
id: notification.id,
token: "invalid-token",
})}`
);
expect(res.status).toBe(401);
const reloaded = await notification.reload();
expect(reloaded.viewedAt).toBeNull();
});
it("should return 404 for notification that does not exist", async () => {
const res = await server.get(
`/api/notifications.pixel?${queryString.stringify({
id: uuidv4(),
token: "invalid-token",
})}`
);
expect(res.status).toBe(404);
});
});
describe("#notifications.update", () => {
it("should mark notification as viewed", async () => {
const team = await buildTeam();
@@ -5,6 +5,7 @@ import isNull from "lodash/isNull";
import isUndefined from "lodash/isUndefined";
import { WhereOptions, Op } from "sequelize";
import { NotificationEventType } from "@shared/types";
import { createContext } from "@server/context";
import env from "@server/env";
import { AuthenticationError } from "@server/errors";
import auth from "@server/middlewares/authentication";
@@ -85,7 +86,6 @@ router.post(
auth(),
pagination(),
validate(T.NotificationsListSchema),
transaction(),
async (ctx: APIContext<T.NotificationsListReq>) => {
const { eventType, archived } = ctx.input.body;
const user = ctx.state.auth.user;
@@ -152,17 +152,28 @@ router.get(
const notification = await Notification.unscoped().findByPk(id, {
lock: transaction.LOCK.UPDATE,
rejectOnEmpty: true,
transaction,
rejectOnEmpty: true,
});
if (!notification || !safeEqual(token, notification.pixelToken)) {
if (!safeEqual(token, notification.pixelToken)) {
throw AuthenticationError();
}
if (!notification.viewedAt) {
notification.viewedAt = new Date();
await notification.saveWithCtx(ctx);
const user = await notification.$get("user");
if (user) {
await notification.updateWithCtx(
createContext({
...ctx,
transaction,
user,
}),
{
viewedAt: new Date(),
}
);
}
}
ctx.response.set("Content-Type", "image/gif");