Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CI

on:
push:
branches: [ main ]
pull_request:

jobs:
backend-test:
runs-on: ubuntu-latest
services:
mongo:
image: mongo:7
ports:
- 27017:27017
env:
MONGO_URI: mongodb://localhost:27017/taskflow_test
JWT_SECRET: test_secret
NODE_ENV: test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: npm
cache-dependency-path: backend/package-lock.json
- name: Install backend deps
run: npm ci
working-directory: backend
- name: Run backend tests
run: npm test
working-directory: backend

docker-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build backend image
run: docker build -t taskflow-backend ./backend
- name: Build frontend image
run: docker build -t taskflow-frontend ./frontend
8 changes: 1 addition & 7 deletions backend/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ app.use(cors());
app.use(express.json());

app.use('/api/auth', authRoutes);
app.use('/api/test', testRoutes);
app.use('/api', testRoutes);
app.use('/api/projects', projectRoutes);
app.use('/api/tasks', taskRoutes);
app.use('/api/habits', habitRoutes);
Expand All @@ -23,12 +23,6 @@ app.get('/', (req, res) => {
res.send('Hello from TaskFlow');
});

app.get('/api/health', (req, res) => {
res.json({
status: 'ok',
message: 'Backend is running'
});
});


module.exports = app;
33 changes: 15 additions & 18 deletions backend/src/middleware/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const jwt = require("jsonwebtoken");
const User = require("../models/User");

const protect = async (req, res, next) =>{
let token;
const JWT_SECRET = process.env.JWT_SECRET || "secret123";

if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')){
try{
token = req.headers.authorization.split(' ')[1];
const protect = async (req, res, next) => {
let token;

const decoded = jwt.verify(token, 'secret123');
if (req.headers.authorization && req.headers.authorization.startsWith("Bearer")) {
try {
token = req.headers.authorization.split(" ")[1];

req.user = await User.findById(decoded.id).select('-password');

next();
} catch (error){
return res.status(401).json({ message: 'Not authorized, invalid token' });
const decoded = jwt.verify(token, JWT_SECRET);

}
}

if (!token){
return res.status(401).json({ message: 'Not authorized, no token' });
req.user = await User.findById(decoded.id).select("-password");
return next();
} catch (error) {
return res.status(401).json({ message: "Not authorized, invalid token" });
}
}

return res.status(401).json({ message: "Not authorized, no token" });
};

module.exports = { protect };
22 changes: 22 additions & 0 deletions backend/src/routes/testRoutes.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
const express = require('express');
const { protect } = require('../middleware/authMiddleware');
const mongoose = require('mongoose');

const router = express.Router();

// Alive check
router.get('/health', (req, res) => {
res.status(200).json({ status : "ok"});
});

// Ready check
router.get('/ready', async(req, res) => {
try{
if(!mongoose.connection.db){
return res.status(503).json({ status: "not ready", reason: "no db"});
}

await mongoose.connection.db.admin().ping();

return res.status(200).json({ status: "ready"});
}catch (err){
return res.status(503).json({ status: "not ready", reason: err.message});
}
});


// ✅ Route protected
router.get('/private', protect, (req, res) => {
res.json({
Expand Down
144 changes: 72 additions & 72 deletions backend/tests/api.test.js
Original file line number Diff line number Diff line change
@@ -1,145 +1,145 @@
const request = require('supertest');
const app = require('../src/app');
const mongoose = require('mongoose');
const request = require("supertest");
const app = require("../src/app");
const mongoose = require("mongoose");

let token = '';
let projectId = '';
let taskId = '';
let token = "";
let projectId = "";
let taskId = "";

jest.setTimeout(20000); // ✅ نزيد وقت التست لـ 20 ثانية
// make data unique per run (CI safe)
const email = `tester_${Date.now()}@test.com`;
const password = "123456";

describe('✅ FULL API TEST', () => {
jest.setTimeout(20000);

describe(" FULL API TEST", () => {
beforeAll(async () => {
await mongoose.connect('mongodb://127.0.0.1:27017/taskflow-test', {
useNewUrlParser: true,
useUnifiedTopology: true
});
});
const mongoUri =
process.env.MONGO_URI || "mongodb://127.0.0.1:27017/taskflow-test";

await mongoose.connect(mongoUri);
});

afterAll(async () => {
await mongoose.connection.close(); // ✅ إغلاق الاتصال
await mongoose.connection.close();
});

// ✅ Register
it('should register a new user', async () => {
const res = await request(app)
.post('/api/auth/register')
.send({
name: 'Tester1',
email: 'tester123@test.com',
password: '123456'
});
// Register
it("should register a new user", async () => {
const res = await request(app).post("/api/auth/register").send({
name: "Tester1",
email,
password,
});

console.log('REGISTER:', res.body);
console.log("REGISTER:", res.body);

expect(res.statusCode).toBe(201);
expect([200, 201]).toContain(res.statusCode);
expect(res.body.token).toBeDefined();

// keep token so the rest of the suite can run
token = res.body.token;
});

// ✅ Login
it('should login the user', async () => {
const res = await request(app)
.post('/api/auth/login')
.send({
email: 'tester@test.com',
password: '123456'
});
// Login
it("should login the user", async () => {
const res = await request(app).post("/api/auth/login").send({
email,
password,
});

console.log('LOGIN:', res.body);
console.log("LOGIN:", res.body);

expect(res.statusCode).toBe(200);
expect(res.body.token).toBeDefined();

token = res.body.token;
});

// ✅ Create Project
it('should create a new project', async () => {
// Create Project
it("should create a new project", async () => {
const res = await request(app)
.post('/api/projects')
.set('Authorization', `Bearer ${token}`)
.post("/api/projects")
.set("Authorization", `Bearer ${token}`)
.send({
title: 'Test Project',
description: 'Project from automated test'
title: "Test Project",
description: "Project from automated test",
});

console.log('CREATE PROJECT:', res.body);
console.log("CREATE PROJECT:", res.body);

expect(res.statusCode).toBe(201);
expect(res.body._id).toBeDefined();

projectId = res.body._id;
});

// ✅ Get Projects
it('should fetch user projects', async () => {
// Get Projects
it("should fetch user projects", async () => {
const res = await request(app)
.get('/api/projects')
.set('Authorization', `Bearer ${token}`);
.get("/api/projects")
.set("Authorization", `Bearer ${token}`);

console.log('GET PROJECTS:', res.body);
console.log("GET PROJECTS:", res.body);

expect(res.statusCode).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
expect(res.body.length).toBeGreaterThan(0);
});

// ✅ Create Task
it('should create a new task', async () => {
// Create Task
it("should create a new task", async () => {
const res = await request(app)
.post('/api/tasks')
.set('Authorization', `Bearer ${token}`)
.post("/api/tasks")
.set("Authorization", `Bearer ${token}`)
.send({
projectId,
title: 'First Test Task',
description: 'Task from automated test'
title: "First Test Task",
description: "Task from automated test",
});

console.log('CREATE TASK:', res.body);
console.log("CREATE TASK:", res.body);

expect(res.statusCode).toBe(201);
expect(res.body._id).toBeDefined();

taskId = res.body._id;
});

// ✅ Get Tasks
it('should fetch project tasks', async () => {
// Get Tasks
it("should fetch project tasks", async () => {
const res = await request(app)
.get(`/api/tasks/${projectId}`)
.set('Authorization', `Bearer ${token}`);
.set("Authorization", `Bearer ${token}`);

console.log('GET TASKS:', res.body);
console.log("GET TASKS:", res.body);

expect(res.statusCode).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
expect(res.body.length).toBeGreaterThan(0);
});

// ✅ Update Task
it('should update task status', async () => {
// Update Task
it("should update task status", async () => {
const res = await request(app)
.patch(`/api/tasks/${taskId}`)
.set('Authorization', `Bearer ${token}`)
.send({
status: 'done'
});
.set("Authorization", `Bearer ${token}`)
.send({ status: "done" });

console.log('UPDATE TASK:', res.body);
console.log("UPDATE TASK:", res.body);

expect(res.statusCode).toBe(200);
expect(res.body.status).toBe('done');
expect(res.body.status).toBe("done");
});

// ✅ Delete Task
it('should delete a task', async () => {
// Delete Task
it("should delete a task", async () => {
const res = await request(app)
.delete(`/api/tasks/${taskId}`)
.set('Authorization', `Bearer ${token}`);
.set("Authorization", `Bearer ${token}`);

console.log('DELETE TASK:', res.body);
console.log("DELETE TASK:", res.body);

expect(res.statusCode).toBe(204);
expect([200, 204]).toContain(res.statusCode);
});

});
});