From dd9c2980896245ca7977145745b95677eb62f56d Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 25 Apr 2026 01:16:19 +0300 Subject: [PATCH] test: add comprehensive user isolation tests for tasks (#1067) * test: add comprehensive user isolation tests for tasks Add integration tests to verify that users cannot see each other's tasks. Tests cover: - Task creation without projects - Inbox items - Direct task access by UID - Search functionality - Today view filtering All tests pass, confirming that user isolation is working correctly in the current codebase. Related to #1063 * style: fix linting errors in task-user-isolation test --- .../integration/task-user-isolation.test.js | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 backend/tests/integration/task-user-isolation.test.js diff --git a/backend/tests/integration/task-user-isolation.test.js b/backend/tests/integration/task-user-isolation.test.js new file mode 100644 index 0000000..c1236e2 --- /dev/null +++ b/backend/tests/integration/task-user-isolation.test.js @@ -0,0 +1,174 @@ +const request = require('supertest'); +const app = require('../../app'); +const { sequelize } = require('../../models'); +const { createTestUser } = require('../helpers/testUtils'); + +describe('Task User Isolation', () => { + let agentA; + let agentB; + let userA; + let userB; + + beforeAll(async () => { + await sequelize.sync({ force: true }); + }); + + afterAll(async () => { + await sequelize.close(); + }); + + beforeEach(async () => { + // Create User A and authenticate + userA = await createTestUser({ + email: 'usera@test.com', + }); + + agentA = request.agent(app); + await agentA.post('/api/login').send({ + email: 'usera@test.com', + password: 'password123', + }); + + // Create User B and authenticate + userB = await createTestUser({ + email: 'userb@test.com', + }); + + agentB = request.agent(app); + await agentB.post('/api/login').send({ + email: 'userb@test.com', + password: 'password123', + }); + }); + + describe('Task creation and visibility', () => { + it('should NOT allow User B to see User A tasks without a project', async () => { + // User A creates a task without a project + const taskResponse = await agentA.post('/api/task').send({ + name: 'User A Private Task', + status: 'not_started', + }); + + expect(taskResponse.status).toBe(201); + const taskA = taskResponse.body; + + // User B tries to fetch all tasks + const tasksResponse = await agentB.get('/api/tasks'); + + expect(tasksResponse.status).toBe(200); + const tasks = tasksResponse.body.tasks; + + // User B should NOT see User A's task + const userATask = tasks.find((t) => t.uid === taskA.uid); + expect(userATask).toBeUndefined(); + }); + + it('should NOT allow User B to see User A tasks created via inbox', async () => { + // User A creates an inbox item + const inboxResponse = await agentA.post('/api/inbox').send({ + content: 'User A Inbox Item', + }); + + expect(inboxResponse.status).toBe(201); + + // User B tries to fetch inbox items + const inboxListResponse = await agentB.get('/api/inbox'); + + expect(inboxListResponse.status).toBe(200); + const inboxItems = + inboxListResponse.body.items || inboxListResponse.body; + + // User B should NOT see User A's inbox item + const userAInboxItem = Array.isArray(inboxItems) + ? inboxItems.find((i) => i.content === 'User A Inbox Item') + : undefined; + expect(userAInboxItem).toBeUndefined(); + }); + + it('should NOT allow User B to access User A task by UID', async () => { + // User A creates a task + const taskResponse = await agentA.post('/api/task').send({ + name: 'User A Secret Task', + status: 'not_started', + }); + + expect(taskResponse.status).toBe(201); + const taskA = taskResponse.body; + + // User B tries to access the task by UID + const getTaskResponse = await agentB.get(`/api/task/${taskA.uid}`); + + // Should return 403 Forbidden or 404 Not Found + expect([403, 404]).toContain(getTaskResponse.status); + }); + + it('should allow User A to see their own tasks', async () => { + // User A creates a task + const taskResponse = await agentA.post('/api/task').send({ + name: 'User A Own Task', + status: 'not_started', + }); + + expect(taskResponse.status).toBe(201); + const taskA = taskResponse.body; + + // User A fetches all tasks + const tasksResponse = await agentA.get('/api/tasks'); + + expect(tasksResponse.status).toBe(200); + const tasks = tasksResponse.body.tasks; + + // User A should see their own task + const ownTask = tasks.find((t) => t.uid === taskA.uid); + expect(ownTask).toBeDefined(); + expect(ownTask.name).toBe('User A Own Task'); + }); + + it('should NOT allow User B to see User A tasks in search', async () => { + // User A creates a unique task + const taskResponse = await agentA.post('/api/task').send({ + name: 'UniqueTaskNameXYZ123', + status: 'not_started', + }); + + expect(taskResponse.status).toBe(201); + + // User B searches for the task + const searchResponse = await agentB.get( + '/api/search?q=UniqueTaskNameXYZ123' + ); + + expect(searchResponse.status).toBe(200); + const results = searchResponse.body.results; + + // User B should NOT see User A's task in search results + const foundTask = results.find( + (r) => r.name === 'UniqueTaskNameXYZ123' + ); + expect(foundTask).toBeUndefined(); + }); + + it('should NOT allow User B to see User A tasks in today view', async () => { + // User A creates a task with today's date + const today = new Date().toISOString().split('T')[0]; + const taskResponse = await agentA.post('/api/task').send({ + name: 'User A Today Task', + status: 'planned', + due_date: today, + }); + + expect(taskResponse.status).toBe(201); + const taskA = taskResponse.body; + + // User B fetches today tasks + const todayResponse = await agentB.get('/api/tasks?type=today'); + + expect(todayResponse.status).toBe(200); + const tasks = todayResponse.body.tasks; + + // User B should NOT see User A's task + const userATask = tasks.find((t) => t.uid === taskA.uid); + expect(userATask).toBeUndefined(); + }); + }); +});