import { vi, describe, it, expect, beforeEach } from 'vitest'; const mockStorage = vi.hoisted(() => ({ getUserWithOrganization: vi.fn(), createBookmark: vi.fn(), createBookmarkFolder: vi.fn(), getBookmarkFolder: vi.fn(), getBookmarksByOrganization: vi.fn(), getBookmarksByFolder: vi.fn(), getUser: vi.fn(), getFormStatuses: vi.fn(), })); vi.mock('../server/db', () => ({ db: {}, pool: { query: vi.fn().mockResolvedValue({ rows: [] }) }, withTenant: (_orgId: number, fn: () => unknown) => fn(), openTenantCtx: vi.fn().mockResolvedValue({ run: (fn: () => void) => fn(), release: vi.fn(), }), openSuperAdminCtx: vi.fn().mockResolvedValue(undefined), _tenantCtx: { getStore: vi.fn().mockReturnValue(null) }, })); vi.mock('../server/storage', () => ({ storage: mockStorage })); vi.mock('../server/services/notification.service', () => ({ notificationService: { emit: vi.fn(), on: vi.fn(), sendNotification: vi.fn().mockResolvedValue(undefined), processEvent: vi.fn().mockResolvedValue(undefined), }, EVENT_TYPES: {}, })); vi.mock('../server/services/webhook.service', () => ({ webhookService: { dispatchEvent: vi.fn().mockResolvedValue(undefined), processWebhook: vi.fn().mockResolvedValue(undefined), }, })); vi.mock('../server/utils/webhook', () => ({ sendWebhook: vi.fn().mockResolvedValue(undefined), })); vi.mock('../server/utils/s3', () => ({ isS3Enabled: false, ensureS3Bucket: vi.fn().mockResolvedValue(undefined), streamFromS3: vi.fn().mockResolvedValue(null), deleteFromS3: vi.fn().mockResolvedValue(undefined), })); vi.mock('../server/utils/audit', () => ({ logAudit: vi.fn().mockResolvedValue(undefined), getClientIp: vi.fn().mockReturnValue('127.0.0.1'), })); import express from 'express'; import request from 'supertest'; import jwt from 'jsonwebtoken'; import chatRouter from '../server/routes/chat.routes'; const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET as string; function makeValidToken(userId: number, organizationId: number): string { return jwt.sign( { userId, organizationId, role: 'user' }, ACCESS_SECRET, { issuer: 'workflow-system', audience: 'workflow-users', expiresIn: '1h' }, ); } function buildApp() { const app = express(); app.use(express.json()); app.use(chatRouter); return app; } const TEST_USER = { id: 42, email: 'test@example.com', firstName: 'Test', lastName: 'User', role: 'user', isActive: true, organizationId: 7, organization: { id: 7, isActive: true, billingBlocked: false }, }; describe('POST /api/bookmarks — auth guard', () => { const app = buildApp(); const validBookmarkBody = { name: 'My Bookmark', type: 'dashboard', }; it('returns 401 when Authorization header is absent', async () => { const res = await request(app) .post('/api/bookmarks') .send(validBookmarkBody); expect(res.status).toBe(401); expect(res.body).toMatchObject({ error: expect.any(String) }); }); it('returns 403 when token is invalid / tampered', async () => { const res = await request(app) .post('/api/bookmarks') .set('Authorization', 'Bearer not-a-real-token') .send(validBookmarkBody); expect(res.status).toBe(403); }); it('returns 403 when token is signed with the wrong secret', async () => { const badToken = jwt.sign( { userId: 1, organizationId: 1, role: 'user' }, 'completely-wrong-secret', ); const res = await request(app) .post('/api/bookmarks') .set('Authorization', `Bearer ${badToken}`) .send(validBookmarkBody); expect(res.status).toBe(403); }); it('returns 401 and does not write to storage when token is valid but user does not exist', async () => { mockStorage.getUserWithOrganization.mockResolvedValue(undefined); mockStorage.createBookmark.mockClear(); const token = makeValidToken(99, 1); const res = await request(app) .post('/api/bookmarks') .set('Authorization', `Bearer ${token}`) .send(validBookmarkBody); expect(res.status).toBe(401); expect(mockStorage.createBookmark).not.toHaveBeenCalled(); }); it('returns 401 and does not write to storage when user account is inactive', async () => { mockStorage.getUserWithOrganization.mockResolvedValue({ ...TEST_USER, isActive: false, }); mockStorage.createBookmark.mockClear(); const token = makeValidToken(TEST_USER.id, TEST_USER.organizationId); const res = await request(app) .post('/api/bookmarks') .set('Authorization', `Bearer ${token}`) .send(validBookmarkBody); expect(res.status).toBe(401); expect(mockStorage.createBookmark).not.toHaveBeenCalled(); }); describe('with a valid token', () => { beforeEach(() => { mockStorage.getUserWithOrganization.mockResolvedValue(TEST_USER); mockStorage.createBookmark.mockResolvedValue({ id: 1, ...validBookmarkBody, userId: TEST_USER.id, organizationId: TEST_USER.organizationId, createdBy: TEST_USER.id, position: 0, folderId: null, targetId: null, formId: null, url: null, icon: null, viewConfig: null, createdAt: new Date(), updatedAt: new Date(), }); }); it('returns 201 and the created bookmark contains the authenticated user\'s id', async () => { const token = makeValidToken(TEST_USER.id, TEST_USER.organizationId); const res = await request(app) .post('/api/bookmarks') .set('Authorization', `Bearer ${token}`) .send(validBookmarkBody); expect(res.status).toBe(201); expect(res.body.success).toBe(true); expect(res.body.bookmark.userId).toBe(TEST_USER.id); }); it('passes the authenticated user\'s id to storage.createBookmark', async () => { const token = makeValidToken(TEST_USER.id, TEST_USER.organizationId); await request(app) .post('/api/bookmarks') .set('Authorization', `Bearer ${token}`) .send(validBookmarkBody); expect(mockStorage.createBookmark).toHaveBeenCalledWith( expect.objectContaining({ userId: TEST_USER.id }), ); }); }); }); describe('POST /api/bookmark-folders — auth guard', () => { const app = buildApp(); const validFolderBody = { name: 'My Folder', }; it('returns 401 when Authorization header is absent', async () => { const res = await request(app) .post('/api/bookmark-folders') .send(validFolderBody); expect(res.status).toBe(401); expect(res.body).toMatchObject({ error: expect.any(String) }); }); it('returns 403 when token is invalid / tampered', async () => { const res = await request(app) .post('/api/bookmark-folders') .set('Authorization', 'Bearer garbage-token') .send(validFolderBody); expect(res.status).toBe(403); }); it('returns 403 when token is signed with the wrong secret', async () => { const badToken = jwt.sign( { userId: 1, organizationId: 1, role: 'user' }, 'wrong-secret', ); const res = await request(app) .post('/api/bookmark-folders') .set('Authorization', `Bearer ${badToken}`) .send(validFolderBody); expect(res.status).toBe(403); }); it('returns 401 and does not write to storage when token is valid but user does not exist', async () => { mockStorage.getUserWithOrganization.mockResolvedValue(undefined); mockStorage.createBookmarkFolder.mockClear(); const token = makeValidToken(99, 1); const res = await request(app) .post('/api/bookmark-folders') .set('Authorization', `Bearer ${token}`) .send(validFolderBody); expect(res.status).toBe(401); expect(mockStorage.createBookmarkFolder).not.toHaveBeenCalled(); }); it('returns 401 and does not write to storage when user account is inactive', async () => { mockStorage.getUserWithOrganization.mockResolvedValue({ ...TEST_USER, isActive: false, }); mockStorage.createBookmarkFolder.mockClear(); const token = makeValidToken(TEST_USER.id, TEST_USER.organizationId); const res = await request(app) .post('/api/bookmark-folders') .set('Authorization', `Bearer ${token}`) .send(validFolderBody); expect(res.status).toBe(401); expect(mockStorage.createBookmarkFolder).not.toHaveBeenCalled(); }); describe('with a valid token', () => { beforeEach(() => { mockStorage.getUserWithOrganization.mockResolvedValue(TEST_USER); mockStorage.createBookmarkFolder.mockResolvedValue({ id: 10, ...validFolderBody, userId: TEST_USER.id, organizationId: TEST_USER.organizationId, createdBy: TEST_USER.id, parentId: null, position: 0, isExpanded: true, createdAt: new Date(), updatedAt: new Date(), }); }); it('returns 201 and the created folder contains the authenticated user\'s id', async () => { const token = makeValidToken(TEST_USER.id, TEST_USER.organizationId); const res = await request(app) .post('/api/bookmark-folders') .set('Authorization', `Bearer ${token}`) .send(validFolderBody); expect(res.status).toBe(201); expect(res.body.success).toBe(true); expect(res.body.folder.userId).toBe(TEST_USER.id); }); it('passes the authenticated user\'s id to storage.createBookmarkFolder', async () => { const token = makeValidToken(TEST_USER.id, TEST_USER.organizationId); await request(app) .post('/api/bookmark-folders') .set('Authorization', `Bearer ${token}`) .send(validFolderBody); expect(mockStorage.createBookmarkFolder).toHaveBeenCalledWith( expect.objectContaining({ userId: TEST_USER.id }), ); }); }); });