feat: add backend setup with Express and unit tests

Backend Foundation (Phase 1 - Step 1):

**Infrastructure:**
- Add backend service to docker-compose.yml
- Configure nginx to proxy /api/* to backend
- Node.js 20 Alpine Docker container

**Backend Setup:**
- Express.js REST API server
- CORS configuration
- Request logging middleware
- Error handling (404, 500)
- Graceful shutdown on SIGTERM/SIGINT
- Health check endpoint: GET /api/health

**Testing:**
- Jest + Supertest for unit tests
- 7 test cases covering:
  - Health check endpoint
  - 404 error handling
  - CORS headers
  - JSON body parsing
- Code coverage: 88.23%

**Project Structure:**
- backend/src/app.js - Express app setup
- backend/src/server.js - Server entry point
- backend/src/__tests__/ - Unit tests
- backend/README.md - Backend documentation

**Environment:**
- .env.example template
- Development configuration
- Ready for PostgreSQL integration

All tests passing 
This commit is contained in:
Radosław Gierwiało
2025-11-12 21:42:52 +01:00
parent a1357393e8
commit 320aaf1ce1
11 changed files with 5237 additions and 14 deletions

View File

@@ -0,0 +1,83 @@
const request = require('supertest');
const app = require('../app');
describe('Backend API Tests', () => {
describe('GET /api/health', () => {
it('should return 200 OK with health status', async () => {
const response = await request(app)
.get('/api/health')
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('status', 'ok');
expect(response.body).toHaveProperty('message', 'Backend is running');
expect(response.body).toHaveProperty('timestamp');
expect(response.body).toHaveProperty('environment');
});
it('should return valid timestamp', async () => {
const response = await request(app)
.get('/api/health')
.expect(200);
const timestamp = new Date(response.body.timestamp);
expect(timestamp).toBeInstanceOf(Date);
expect(timestamp.getTime()).not.toBeNaN();
});
it('should return development environment in test mode', async () => {
const response = await request(app)
.get('/api/health')
.expect(200);
expect(response.body.environment).toBe('development');
});
});
describe('404 Handler', () => {
it('should return 404 for non-existent route', async () => {
const response = await request(app)
.get('/api/nonexistent')
.expect('Content-Type', /json/)
.expect(404);
expect(response.body).toHaveProperty('error', 'Not Found');
expect(response.body).toHaveProperty('message');
expect(response.body.message).toContain('Cannot GET /api/nonexistent');
});
it('should return 404 for POST to non-existent route', async () => {
const response = await request(app)
.post('/api/nonexistent')
.expect('Content-Type', /json/)
.expect(404);
expect(response.body).toHaveProperty('error', 'Not Found');
expect(response.body.message).toContain('Cannot POST /api/nonexistent');
});
});
describe('CORS Configuration', () => {
it('should have CORS headers', async () => {
const response = await request(app)
.get('/api/health')
.expect(200);
expect(response.headers).toHaveProperty('access-control-allow-origin');
});
});
describe('JSON Body Parser', () => {
it('should parse JSON body (when routes are added)', async () => {
// This will be tested when we add POST routes
// For now, just verify the middleware is loaded
const response = await request(app)
.post('/api/health')
.send({ test: 'data' })
.set('Content-Type', 'application/json')
.expect(404); // 404 because POST /api/health doesn't exist
expect(response.body).toHaveProperty('error');
});
});
});