Skip to content

Commit

Permalink
Merge pull request #4 from DevanshMistry890/godwin/nodejs-rest-api
Browse files Browse the repository at this point in the history
Updates
  • Loading branch information
DevanshMistry890 authored Nov 26, 2024
2 parents e20a9ce + 41cccf0 commit b03f354
Show file tree
Hide file tree
Showing 13 changed files with 6,552 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
165 changes: 165 additions & 0 deletions __tests__/api.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
const request = require('supertest');
const app = require('../index'); // Make sure to export app from index.js

const sampleTask = {
description: 'Test task',
assignedTo: 'John Doe',
dueDate: '2024-12-31',
priority: 'high',
status: 'in progress',
};

describe('Task API Tests', () => {
let server;
let createdTask;

beforeAll((done) => {
server = app.listen(3001, () => {
console.log('Test server started on port 3001');
done();
});
});

afterAll((done) => {
server.close(done);
});

// Test POST /api/tasks
test('POST /api/tasks should create a new task', async () => {
const response = await request(app)
.post('/api/tasks')
.send(sampleTask)
.expect('Content-Type', /json/)
.expect(201);

expect(response.body).toMatchObject(sampleTask);
expect(response.body.id).toBeDefined();
expect(response.body.createdAt).toBeDefined();

createdTask = response.body; // Store for other tests
});

// Test GET /api/tasks
test('GET /api/tasks should return all tasks', async () => {
const response = await request(app)
.get('/api/tasks')
.expect('Content-Type', /json/)
.expect(200);

expect(Array.isArray(response.body)).toBeTruthy();
expect(response.body.length).toBe(1);
});

// Test GET /api/tasks/:id
test('GET /api/tasks/:id should return a specific task', async () => {
const response = await request(app)
.get(`/api/tasks/${createdTask.id}`)
.expect('Content-Type', /json/)
.expect(200);

expect(response.body).toMatchObject(sampleTask);
});

// Test PUT /api/tasks/:id
test('PUT /api/tasks/:id should update a task', async () => {
const updatedTask = {
...sampleTask,
description: 'Updated test task',
status: 'in progress',
};

const response = await request(app)
.put(`/api/tasks/${createdTask.id}`)
.send(updatedTask)
.expect('Content-Type', /json/)
.expect(200);

expect(response.body).toMatchObject(updatedTask);
expect(response.body.updatedAt).toBeDefined();
});

// Test DELETE /api/tasks/:id
test('DELETE /api/tasks/:id should delete a task', async () => {
await request(app).delete(`/api/tasks/${createdTask.id}`).expect(204);

// Verify task is deleted
await request(app).get(`/api/tasks/${createdTask.id}`).expect(404);
});

// Test GET /api/tasks/search
test('GET /api/tasks/search should return filtered tasks', async () => {
await request(app).post('/api/tasks').send(sampleTask).expect(201);

// Test search by description
const response = await request(app)
.get('/api/tasks/search?query=Test')
.expect('Content-Type', /json/)
.expect(200);

expect(Array.isArray(response.body)).toBeTruthy();
expect(response.body.length).toBeGreaterThan(0);
expect(response.body[0].description).toContain('Test');

// Test empty query returns all tasks
const allTasksResponse = await request(app)
.get('/api/tasks/search')
.expect('Content-Type', /json/)
.expect(200);

expect(Array.isArray(allTasksResponse.body)).toBeTruthy();
});

// Test error cases for GET /api/tasks/:id
test('GET /api/tasks/:id should return 404 for non-existent task', async () => {
await request(app).get('/api/tasks/999999').expect(404);
});

// Test error cases for PUT /api/tasks/:id
test('PUT /api/tasks/:id should return 404 for non-existent task', async () => {
await request(app).put('/api/tasks/999999').send(sampleTask).expect(404);
});

// Test error cases for DELETE /api/tasks/:id
test('DELETE /api/tasks/:id should return 404 for non-existent task', async () => {
await request(app).delete('/api/tasks/999999').expect(404);
});

// Test validation for POST /api/tasks
test('POST /api/tasks should validate required fields', async () => {
const invalidTask = {
description: 'Missing required fields',
};

await request(app).post('/api/tasks').send(invalidTask).expect(400);
});
});

// Test validation for POST /api/tasks
describe('Task Validation Tests', () => {
const invalidPriorityTask = {
...sampleTask,
priority: 'invalid_priority',
};

const invalidStatusTask = {
...sampleTask,
status: 'invalid_status',
};

const invalidDateTask = {
...sampleTask,
dueDate: 'invalid-date',
};

test('should reject task with invalid priority', async () => {
await request(app).post('/api/tasks').send(invalidPriorityTask).expect(400);
});

test('should reject task with invalid status', async () => {
await request(app).post('/api/tasks').send(invalidStatusTask).expect(400);
});

test('should reject task with invalid date format', async () => {
await request(app).post('/api/tasks').send(invalidDateTask).expect(400);
});
});
33 changes: 33 additions & 0 deletions __tests__/setup/testSetup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const fs = require('fs').promises;
const path = require('path');

const TEST_TASKS_FILE = path.join(__dirname, '../../data/test_tasks.json');

// Important: Add a dummy test to satisfy Jest
test('dummy test', () => {
expect(true).toBe(true);
});

beforeEach(async () => {
// Initialize with empty array instead of resetting
if (!(await fileExists(TEST_TASKS_FILE))) {
await fs.writeFile(TEST_TASKS_FILE, '[]');
}
});

afterAll(async () => {
try {
await fs.unlink(TEST_TASKS_FILE);
} catch (error) {
// Ignore if file doesn't exist
}
});

async function fileExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
86 changes: 86 additions & 0 deletions controllers/taskController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
const Task = require('../models/Task');

class TaskController {
static async getAllTasks(req, res) {
try {
const tasks = await Task.findAll();
res.json(tasks);
} catch (error) {
console.error('Error getting all tasks:', error);
res.status(500).json({ error: 'Failed to read tasks' });
}
}

static async getTaskById(req, res) {
try {
const task = await Task.findById(req.params.id);
if (!task) {
return res.status(404).json({ error: 'Task not found' });
}
res.json(task);
} catch (error) {
console.error('Error getting task by id:', error);
res.status(500).json({ error: 'Failed to read task' });
}
}

static async createTask(req, res) {
try {
const newTask = await Task.create(req.body);
res.status(201).json(newTask);
} catch (error) {
console.error('Error creating task:', error);
res.status(500).json({ error: 'Failed to create task' });
}
}

static async updateTask(req, res) {
try {
const updatedTask = await Task.update(req.params.id, req.body);
if (!updatedTask) {
return res.status(404).json({ error: 'Task not found' });
}
res.json(updatedTask);
} catch (error) {
console.error('Error updating task:', error);
res.status(500).json({ error: 'Failed to update task' });
}
}

static async deleteTask(req, res) {
try {
const deleted = await Task.delete(req.params.id);
if (!deleted) {
return res.status(404).json({ error: 'Task not found' });
}
res.status(204).send();
} catch (error) {
console.error('Error deleting task:', error);
res.status(500).json({ error: 'Failed to delete task' });
}
}

static async searchTasks(req, res) {
try {
const { query } = req.query;
if (!query) {
return res.json(await Task.findAll());
}

const tasks = await Task.findAll();

const filteredTasks = tasks.filter(
(task) =>
task.description.toLowerCase().includes(query.toLowerCase()) ||
task.assignedTo.toLowerCase().includes(query.toLowerCase())
);

res.json(filteredTasks);
} catch (error) {
console.error('Error searching tasks:', error);
res.status(500).json({ error: 'Failed to search tasks' });
}
}
}

module.exports = TaskController;
11 changes: 11 additions & 0 deletions data/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
{
"id": "1732645059698",
"description": "Test the API endpoints",
"assignedTo": "Jane Doe",
"dueDate": "2024-10-31",
"priority": "low",
"status": "completed",
"createdAt": "2024-11-26T18:17:39.698Z"
}
]
30 changes: 30 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const taskRoutes = require('./routes/taskRoutes');

const app = express();
const PORT = process.env.NODE_ENV === 'test' ? 3001 : 3000;

// Middleware
app.use(cors());
app.use(bodyParser.json());

// Routes
app.use('/api/tasks', taskRoutes);

// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});

// Only start the server if not in test environment
if (process.env.NODE_ENV !== 'test') {
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
}

// Needed for testing
module.exports = app;
27 changes: 27 additions & 0 deletions middleware/validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const validateTask = (req, res, next) => {
const { description, assignedTo, dueDate, priority, status } = req.body;

if (!description || !assignedTo || !dueDate || !priority || !status) {
return res.status(400).json({ error: 'All fields are required' });
}

const validPriorities = ['low', 'medium', 'high'];
const validStatuses = ['not started', 'in progress', 'completed'];

if (!validPriorities.includes(priority.toLowerCase())) {
return res.status(400).json({ error: 'Invalid priority value' });
}

if (!validStatuses.includes(status.toLowerCase())) {
return res.status(400).json({ error: 'Invalid status value' });
}

const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(dueDate)) {
return res.status(400).json({ error: 'Invalid date format. Use YYYY-MM-DD' });
}

next();
};

module.exports = { validateTask };
Loading

0 comments on commit b03f354

Please sign in to comment.