Add tests

This commit is contained in:
Chris Veleris 2025-10-22 15:56:48 +03:00
parent bbc4615ee3
commit 02633da704
5 changed files with 389 additions and 6 deletions

View 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();
});
});
});

View file

@ -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',
});
});

View file

@ -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
View file

@ -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",

View file

@ -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",