Add tests
This commit is contained in:
parent
bbc4615ee3
commit
02633da704
5 changed files with 389 additions and 6 deletions
366
backend/tests/integration/project-sharing.test.js
Normal file
366
backend/tests/integration/project-sharing.test.js
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
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
|
||||
const response = await sharedUserAgent
|
||||
.get('/api/tasks?type=today');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.tasks).toBeDefined();
|
||||
|
||||
// Check if the task appears in the main tasks or metrics
|
||||
const allTasks = [
|
||||
...response.body.tasks,
|
||||
...(response.body.metrics?.tasks_due_today || []),
|
||||
];
|
||||
|
||||
const foundTask = allTasks.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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -8,13 +8,13 @@ describe('Users Routes', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
user = await createTestUser({
|
||||
email: 'test@example.com',
|
||||
email: `test_${Date.now()}@example.com`,
|
||||
});
|
||||
|
||||
// Create authenticated agent
|
||||
agent = request.agent(app);
|
||||
await agent.post('/api/login').send({
|
||||
email: 'test@example.com',
|
||||
email: user.email,
|
||||
password: 'password123',
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -466,13 +466,13 @@ describe('Parent-Child Relationship Functionality', () => {
|
|||
const uniqueDueDates = [...new Set(dueDates)];
|
||||
expect(uniqueDueDates.length).toBe(dueDates.length);
|
||||
|
||||
// Verify children have sequential due dates (within tolerance for floating point)
|
||||
// Verify children have sequential due dates (within tolerance for DST transitions)
|
||||
const sortedDueDates = dueDates.sort();
|
||||
for (let i = 1; i < sortedDueDates.length; i++) {
|
||||
const dayDiff =
|
||||
(sortedDueDates[i] - sortedDueDates[i - 1]) /
|
||||
(24 * 60 * 60 * 1000);
|
||||
expect(Math.abs(dayDiff - 1)).toBeLessThan(0.001); // Each task should be ~1 day apart
|
||||
expect(Math.abs(dayDiff - 1)).toBeLessThan(0.05); // Each task should be ~1 day apart (allowing for DST)
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
20
package-lock.json
generated
20
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "tududi",
|
||||
"version": "v0.84.1-rc1",
|
||||
"version": "v0.84.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tududi",
|
||||
"version": "v0.84.1-rc1",
|
||||
"version": "v0.84.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.1.5",
|
||||
|
|
@ -89,6 +89,7 @@
|
|||
"sequelize-cli": "~6.6.2",
|
||||
"style-loader": "^4.0.0",
|
||||
"supertest": "~7.1.1",
|
||||
"supertest-session": "^5.0.1",
|
||||
"swc-loader": "^0.2.6",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"ts-jest": "^29.0.0",
|
||||
|
|
@ -17934,6 +17935,21 @@
|
|||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supertest-session": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/supertest-session/-/supertest-session-5.0.1.tgz",
|
||||
"integrity": "sha512-RpR8tGQZGreQsOCiW3YMSPKMwPlAB8lA0Jyat+8VUSJaYvLHTMqhMW6gooJ2htzjr3w/kgqJTQDnmuFenzA9JA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookiejar": "^2.1.2",
|
||||
"methods": "^1.1.2",
|
||||
"object-assign": "^4.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"supertest": ">= 3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@
|
|||
"sequelize-cli": "~6.6.2",
|
||||
"style-loader": "^4.0.0",
|
||||
"supertest": "~7.1.1",
|
||||
"supertest-session": "^5.0.1",
|
||||
"swc-loader": "^0.2.6",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"ts-jest": "^29.0.0",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue