346 lines
13 KiB
JavaScript
346 lines
13 KiB
JavaScript
const request = require('supertest');
|
|
const app = require('../../app');
|
|
const {
|
|
User,
|
|
Project,
|
|
Task,
|
|
Note,
|
|
Permission,
|
|
sequelize,
|
|
} = require('../../models');
|
|
const { createTestUser } = require('../helpers/testUtils');
|
|
|
|
describe('Project Sharing Integration Tests', () => {
|
|
let ownerUser, sharedUser, ownerAgent, sharedUserAgent, project;
|
|
|
|
beforeEach(async () => {
|
|
// Create test users using test helper
|
|
ownerUser = await createTestUser({
|
|
email: `owner_${Date.now()}@test.com`,
|
|
name: 'Owner',
|
|
timezone: 'UTC',
|
|
});
|
|
|
|
sharedUser = await createTestUser({
|
|
email: `shared_${Date.now()}@test.com`,
|
|
name: 'Shared User',
|
|
timezone: 'UTC',
|
|
});
|
|
|
|
// Create agents for both users (maintains sessions)
|
|
ownerAgent = request.agent(app);
|
|
sharedUserAgent = request.agent(app);
|
|
|
|
// Login as owner
|
|
await ownerAgent
|
|
.post('/api/login')
|
|
.send({ email: ownerUser.email, password: 'password123' });
|
|
|
|
// Login as shared user
|
|
await sharedUserAgent
|
|
.post('/api/login')
|
|
.send({ email: sharedUser.email, password: 'password123' });
|
|
|
|
// Create a project as owner
|
|
const projectResponse = await ownerAgent.post('/api/project').send({
|
|
name: 'Shared Test Project',
|
|
description: 'Project for sharing tests',
|
|
});
|
|
project = projectResponse.body;
|
|
|
|
// Share the project with read-write access
|
|
await ownerAgent.post('/api/shares').send({
|
|
resource_type: 'project',
|
|
resource_uid: project.uid,
|
|
target_user_email: sharedUser.email,
|
|
access_level: 'rw',
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await sequelize.close();
|
|
});
|
|
|
|
describe('Issue 1: Task and Note Visibility in Shared Projects', () => {
|
|
test('shared user should see tasks in shared project', async () => {
|
|
// Owner creates a task in the shared project
|
|
const taskResponse = await ownerAgent.post('/api/task').send({
|
|
name: 'Task by owner in shared project',
|
|
project_id: project.id,
|
|
priority: 1,
|
|
status: 0,
|
|
});
|
|
const taskInSharedProject = taskResponse.body;
|
|
|
|
// Shared user should see this task
|
|
const response = await sharedUserAgent.get('/api/tasks');
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.tasks).toBeDefined();
|
|
|
|
const foundTask = response.body.tasks.find(
|
|
(t) => t.id === taskInSharedProject.id
|
|
);
|
|
expect(foundTask).toBeDefined();
|
|
expect(foundTask.name).toBe('Task by owner in shared project');
|
|
});
|
|
|
|
test('shared user should see notes in shared project', async () => {
|
|
// Owner creates a note in the shared project
|
|
const noteResponse = await ownerAgent.post('/api/note').send({
|
|
title: 'Note by owner in shared project',
|
|
content: 'This note should be visible to shared user',
|
|
project_uid: project.uid,
|
|
});
|
|
const noteInSharedProject = noteResponse.body;
|
|
|
|
// Shared user should see this note
|
|
const response = await sharedUserAgent.get('/api/notes');
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toBeDefined();
|
|
|
|
const foundNote = response.body.find(
|
|
(n) => n.id === noteInSharedProject.id
|
|
);
|
|
expect(foundNote).toBeDefined();
|
|
expect(foundNote.title).toBe('Note by owner in shared project');
|
|
});
|
|
|
|
test('shared user should NOT see tasks in non-shared projects', async () => {
|
|
// Create another project (not shared)
|
|
const privateProjectResponse = await ownerAgent
|
|
.post('/api/project')
|
|
.send({
|
|
name: 'Private Project',
|
|
description: 'This should not be visible',
|
|
});
|
|
const privateProject = privateProjectResponse.body;
|
|
|
|
// Create task in private project
|
|
const privateTaskResponse = await ownerAgent
|
|
.post('/api/task')
|
|
.send({
|
|
name: 'Private task',
|
|
project_id: privateProject.id,
|
|
priority: 1,
|
|
status: 0,
|
|
});
|
|
const privateTask = privateTaskResponse.body;
|
|
|
|
// Shared user should NOT see this task
|
|
const response = await sharedUserAgent.get('/api/tasks');
|
|
|
|
expect(response.status).toBe(200);
|
|
const foundTask = response.body.tasks.find(
|
|
(t) => t.id === privateTask.id
|
|
);
|
|
expect(foundTask).toBeUndefined();
|
|
});
|
|
|
|
test('shared user should see tasks due today in shared projects', async () => {
|
|
// Create a task due today in shared project
|
|
const today = new Date().toISOString().split('T')[0];
|
|
const taskDueTodayResponse = await ownerAgent
|
|
.post('/api/task')
|
|
.send({
|
|
name: 'Task due today in shared project',
|
|
project_id: project.id,
|
|
due_date: today,
|
|
priority: 1,
|
|
status: 0,
|
|
});
|
|
const taskDueToday = taskDueTodayResponse.body;
|
|
|
|
// Fetch today's tasks as shared user with include_lists to get tasks_due_today
|
|
const response = await sharedUserAgent.get(
|
|
'/api/tasks?type=today&include_lists=true'
|
|
);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.tasks).toBeDefined();
|
|
|
|
// Task should appear in tasks_due_today since it has a due date but today=false
|
|
expect(response.body.tasks_due_today).toBeDefined();
|
|
const foundTask = response.body.tasks_due_today.find(
|
|
(t) => t.id === taskDueToday.id
|
|
);
|
|
expect(foundTask).toBeDefined();
|
|
expect(foundTask.name).toBe('Task due today in shared project');
|
|
});
|
|
});
|
|
|
|
describe('Issue 2: Task and Note Creation in Shared Projects', () => {
|
|
test('shared user with RW access can create tasks in shared project', async () => {
|
|
const response = await sharedUserAgent.post('/api/task').send({
|
|
name: 'Task created by shared user',
|
|
project_id: project.id,
|
|
priority: 1,
|
|
status: 0,
|
|
});
|
|
|
|
expect(response.status).toBe(201);
|
|
expect(response.body).toBeDefined();
|
|
expect(response.body.name).toBe('Task created by shared user');
|
|
expect(response.body.project_id).toBe(project.id);
|
|
});
|
|
|
|
test('shared user with RW access can create notes in shared project', async () => {
|
|
const response = await sharedUserAgent.post('/api/note').send({
|
|
title: 'Note created by shared user',
|
|
content: 'Content of the note',
|
|
project_uid: project.uid,
|
|
});
|
|
|
|
expect(response.status).toBe(201);
|
|
expect(response.body).toBeDefined();
|
|
expect(response.body.title).toBe('Note created by shared user');
|
|
});
|
|
|
|
test('shared user with RO access cannot create tasks', async () => {
|
|
// Change permission to read-only
|
|
await Permission.update(
|
|
{ access_level: 'ro' },
|
|
{
|
|
where: {
|
|
resource_uid: project.uid,
|
|
user_id: sharedUser.id,
|
|
},
|
|
}
|
|
);
|
|
|
|
const response = await sharedUserAgent.post('/api/task').send({
|
|
name: 'Task that should fail',
|
|
project_id: project.id,
|
|
priority: 1,
|
|
status: 0,
|
|
});
|
|
|
|
expect(response.status).toBe(403);
|
|
expect(response.body.error).toBe('Forbidden');
|
|
});
|
|
|
|
test('shared user with RO access cannot create notes', async () => {
|
|
// Change permission to read-only
|
|
await Permission.update(
|
|
{ access_level: 'ro' },
|
|
{
|
|
where: {
|
|
resource_uid: project.uid,
|
|
user_id: sharedUser.id,
|
|
},
|
|
}
|
|
);
|
|
|
|
const response = await sharedUserAgent.post('/api/note').send({
|
|
title: 'Note that should fail',
|
|
content: 'Content',
|
|
project_uid: project.uid,
|
|
});
|
|
|
|
expect(response.status).toBe(403);
|
|
expect(response.body.error).toBe('Forbidden');
|
|
});
|
|
});
|
|
|
|
describe('Task Timeline Access', () => {
|
|
test('shared user can access task timeline in shared project', async () => {
|
|
// Create a task in the shared project
|
|
const taskResponse = await ownerAgent.post('/api/task').send({
|
|
name: 'Task with timeline',
|
|
project_id: project.id,
|
|
priority: 1,
|
|
status: 0,
|
|
});
|
|
const taskInSharedProject = taskResponse.body;
|
|
|
|
// Shared user should be able to access the timeline
|
|
const response = await sharedUserAgent.get(
|
|
`/api/task/${taskInSharedProject.uid}/timeline`
|
|
);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(Array.isArray(response.body)).toBe(true);
|
|
});
|
|
|
|
test('shared user cannot access task timeline in non-shared project', async () => {
|
|
// Create a private project and task
|
|
const privateProjectResponse = await ownerAgent
|
|
.post('/api/project')
|
|
.send({
|
|
name: 'Private Project for Timeline',
|
|
});
|
|
const privateProject = privateProjectResponse.body;
|
|
|
|
const privateTaskResponse = await ownerAgent
|
|
.post('/api/task')
|
|
.send({
|
|
name: 'Private task',
|
|
project_id: privateProject.id,
|
|
priority: 1,
|
|
status: 0,
|
|
});
|
|
const privateTask = privateTaskResponse.body;
|
|
|
|
// Shared user should NOT access this timeline
|
|
const response = await sharedUserAgent.get(
|
|
`/api/task/${privateTask.uid}/timeline`
|
|
);
|
|
|
|
expect(response.status).toBe(404);
|
|
});
|
|
|
|
test('shared user can access completion time analytics', async () => {
|
|
// Create a task in the shared project
|
|
const taskResponse = await ownerAgent.post('/api/task').send({
|
|
name: 'Task for completion analytics',
|
|
project_id: project.id,
|
|
priority: 1,
|
|
status: 0,
|
|
});
|
|
const taskInSharedProject = taskResponse.body;
|
|
|
|
const response = await sharedUserAgent.get(
|
|
`/api/task/${taskInSharedProject.uid}/completion-time`
|
|
);
|
|
|
|
// Should return 404 if not completed, or 200 with data if completed
|
|
expect([200, 404]).toContain(response.status);
|
|
});
|
|
});
|
|
|
|
describe('Owner sees their own tasks correctly', () => {
|
|
test('owner should see all their tasks including those in shared projects', async () => {
|
|
// Create a task in the shared project
|
|
await ownerAgent.post('/api/task').send({
|
|
name: 'Owner task in shared project',
|
|
project_id: project.id,
|
|
priority: 1,
|
|
status: 0,
|
|
});
|
|
|
|
const response = await ownerAgent.get('/api/tasks');
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.tasks).toBeDefined();
|
|
expect(response.body.tasks.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('owner should see tasks due today including shared project tasks', async () => {
|
|
const today = new Date().toISOString().split('T')[0];
|
|
|
|
await ownerAgent.post('/api/task').send({
|
|
name: 'Owner task due today',
|
|
project_id: project.id,
|
|
due_date: today,
|
|
priority: 1,
|
|
status: 0,
|
|
});
|
|
|
|
const response = await ownerAgent.get('/api/tasks?type=today');
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.tasks).toBeDefined();
|
|
});
|
|
});
|
|
});
|