feat: Implement Phase 2 cross-cutting services with email and notification enhancements
- Refactor email verification and password reset utilities to use new email service. - Introduce email delivery queue and notification delivery model for better tracking. - Enhance file validation and storage services for improved security and ownership management. - Add cron job for cleaning inactive notifications with retention policy. - Update document worker to handle document generation and storage more efficiently. - Implement logging improvements in activity and log workers. - Create comprehensive documentation for new API endpoints and services. - Add unit tests for file validation and notification policies to ensure robustness.
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
describe("Phase 2 cross-cutting policies", () => {
|
||||
test("email templates escape user-controlled HTML", () => {
|
||||
const { loadTemplate } = require("../../app/utils/mail.util");
|
||||
const html = loadTemplate("otp", { firstName: "<script>x</script>", otp: "123456" });
|
||||
expect(html).toContain("<script>x</script>"); expect(html).not.toContain("<script>x</script>");
|
||||
});
|
||||
test("security notifications bypass disabled marketing preference", () => {
|
||||
const { channelAllowed } = require("../../app/services/notification/notification-policy.service");
|
||||
expect(channelAllowed({ eventType: "LOGIN_OTP", channel: "EMAIL", profile: { notificationsEnabled: false } })).toBe(true);
|
||||
expect(channelAllowed({ eventType: "MARKETING", channel: "EMAIL", profile: { notificationsEnabled: false } })).toBe(false);
|
||||
});
|
||||
test("queue policies specify bounded retries and retention", () => {
|
||||
jest.resetModules();
|
||||
jest.doMock("../../app/config/redisClient", () => ({}));
|
||||
jest.doMock("bullmq", () => ({ Queue: jest.fn(function Queue(name, options) { this.name = name; this.opts = options; }) }));
|
||||
const emailQueue = require("../../app/queues/email.queue");
|
||||
expect(emailQueue.opts.defaultJobOptions.attempts).toBe(5);
|
||||
expect(emailQueue.opts.defaultJobOptions.removeOnComplete).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
describe("Phase 2 file validation", () => {
|
||||
beforeEach(() => jest.resetModules());
|
||||
test("detects a valid PNG and creates checksum/safe name", () => {
|
||||
const { validateUpload } = require("../../app/services/storage/file-validation.service");
|
||||
const buffer = Buffer.concat([Buffer.from([0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a]), Buffer.from("payload")]);
|
||||
const result = validateUpload({ buffer, mimetype: "image/png", originalname: "../unsafe name.svg" });
|
||||
expect(result.mimeType).toBe("image/png"); expect(result.safeName).toBe("unsafe_name.png"); expect(result.checksum).toMatch(/^[a-f0-9]{64}$/);
|
||||
});
|
||||
test("rejects claimed MIME that disagrees with magic bytes", () => {
|
||||
const { validateUpload } = require("../../app/services/storage/file-validation.service");
|
||||
expect(() => validateUpload({ buffer: Buffer.from("not an image"), mimetype: "image/png", originalname: "x.png" })).toThrow("does not match");
|
||||
});
|
||||
test("rejects empty files", () => {
|
||||
const { validateUpload } = require("../../app/services/storage/file-validation.service");
|
||||
expect(() => validateUpload({ buffer: Buffer.alloc(0), mimetype: "image/png", originalname: "x.png" })).toThrow("Empty");
|
||||
});
|
||||
test("builds controlled owner/date keys without original filenames", () => {
|
||||
const { createObjectKey } = require("../../app/services/storage/storage.service");
|
||||
expect(createObjectKey({ ownerId: "usr_1", mimeType: "application/pdf", purpose: "document", now: new Date("2026-09-03T00:00:00Z"), id: "fixed" })).toBe("documents/usr_1/2026/09/fixed.pdf");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user