diff --git a/tests/api.test.js b/tests/api.test.js new file mode 100644 index 0000000..46398cb --- /dev/null +++ b/tests/api.test.js @@ -0,0 +1,87 @@ +const request = require('supertest'); +const app = require('../src/app'); + +describe('API Endpoints', () => { + describe('GET /api/system', () => { + it('should return system information', async() => { + const response = await request(app) + .get('/api/system') + .expect(200); + + expect(response.body.success).toBe(true); + expect(response.body.data).toHaveProperty('hostname'); + expect(response.body.data).toHaveProperty('platform'); + expect(response.body.data).toHaveProperty('cpus'); + expect(response.body.data).toHaveProperty('totalMemory'); + expect(typeof response.body.data.cpus).toBe('number'); + }); + }); + + describe('GET /api/memory', () => { + it('should return memory information', async() => { + const response = await request(app) + .get('/api/memory') + .expect(200); + + expect(response.body.success).toBe(true); + expect(response.body.data).toHaveProperty('system'); + expect(response.body.data).toHaveProperty('process'); + expect(response.body.data.system).toHaveProperty('total'); + expect(response.body.data.system).toHaveProperty('used'); + expect(response.body.data.process).toHaveProperty('rss'); + }); + }); + + describe('GET /api/process', () => { + it('should return process information', async() => { + const response = await request(app) + .get('/api/process') + .expect(200); + + expect(response.body.success).toBe(true); + expect(response.body.data).toHaveProperty('pid'); + expect(response.body.data).toHaveProperty('uptime'); + expect(response.body.data).toHaveProperty('version'); + expect(typeof response.body.data.pid).toBe('number'); + }); + }); + + describe('GET /api/metrics', () => { + it('should return application metrics', async() => { + const response = await request(app) + .get('/api/metrics') + .expect(200); + + expect(response.body.success).toBe(true); + expect(response.body.data).toHaveProperty('counters'); + expect(response.body.data).toHaveProperty('gauges'); + expect(response.body.data).toHaveProperty('histograms'); + expect(response.body.data).toHaveProperty('runtime'); + expect(Array.isArray(response.body.data.counters)).toBe(true); + }); + }); + + describe('GET /api/test', () => { + it('should return test response', async() => { + const response = await request(app) + .get('/api/test') + .expect(200); + + expect(response.body.success).toBe(true); + expect(response.body.message).toBe('API is working correctly'); + expect(response.body).toHaveProperty('version'); + expect(response.body).toHaveProperty('timestamp'); + }); + }); + + describe('Error handling', () => { + it('should return 404 for unknown API endpoints', async() => { + const response = await request(app) + .get('/api/nonexistent') + .expect(404); + + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toBe('Not Found'); + }); + }); +}); diff --git a/tests/app.test.js b/tests/app.test.js new file mode 100644 index 0000000..e3d059f --- /dev/null +++ b/tests/app.test.js @@ -0,0 +1,93 @@ +const request = require('supertest'); +const app = require('../src/app'); + +describe('Main Application', () => { + describe('Static file serving', () => { + it('should serve the main dashboard page', async() => { + const response = await request(app) + .get('/') + .expect(200); + + expect(response.headers['content-type']).toMatch(/html/); + }); + + it('should serve static CSS files', async() => { + const response = await request(app) + .get('/style.css') + .expect(200); + + expect(response.headers['content-type']).toMatch(/css/); + }); + + it('should serve static JavaScript files', async() => { + const response = await request(app) + .get('/app.js') + .expect(200); + + expect(response.headers['content-type']).toMatch(/javascript/); + }); + }); + + describe('Debug endpoints', () => { + it('should return debug file information', async() => { + const response = await request(app) + .get('/debug/files') + .expect(200); + + expect(response.body).toHaveProperty('publicPath'); + expect(response.body).toHaveProperty('files'); + expect(Array.isArray(response.body.files)).toBe(true); + }); + + it('should serve test static page', async() => { + const response = await request(app) + .get('/test-static') + .expect(200); + + expect(response.text).toContain('Static File Test'); + expect(response.headers['content-type']).toMatch(/html/); + }); + }); + + describe('Error handling', () => { + it('should return 404 for unknown routes', async() => { + const response = await request(app) + .get('/nonexistent-route') + .expect(404); + + expect(response.body).toHaveProperty('error', 'Not Found'); + expect(response.body).toHaveProperty('url', '/nonexistent-route'); + }); + }); + + describe('CORS and Security', () => { + it('should include CORS headers', async() => { + const response = await request(app) + .get('/api/system') + .expect(200); + + expect(response.headers).toHaveProperty('access-control-allow-origin'); + }); + + it('should include basic Express headers', async() => { + const response = await request(app) + .get('/') + .expect(200); + + // Check for basic Express headers that should be present + expect(response.headers).toHaveProperty('x-powered-by', 'Express'); + expect(response.headers).toHaveProperty('content-type'); + }); + }); + + describe('Request logging', () => { + it('should log requests without errors', async() => { + // This test ensures the logging middleware doesn't crash + const response = await request(app) + .get('/health') + .expect(200); + + expect(response.body).toHaveProperty('status', 'healthy'); + }); + }); +}); diff --git a/tests/health.test.js b/tests/health.test.js new file mode 100644 index 0000000..c597d80 --- /dev/null +++ b/tests/health.test.js @@ -0,0 +1,103 @@ +const request = require('supertest'); +const app = require('../src/app'); + +describe('Health Check Endpoints', () => { + describe('GET /health', () => { + it('should return basic health status', async() => { + const response = await request(app) + .get('/health') + .expect(200); + + expect(response.body).toHaveProperty('status', 'healthy'); + expect(response.body).toHaveProperty('timestamp'); + expect(response.body).toHaveProperty('uptime'); + expect(response.body).toHaveProperty('service', 'harbor-ci-cd-demo'); + expect(typeof response.body.uptime).toBe('number'); + }); + }); + + describe('GET /health/detailed', () => { + it('should return detailed health information', async() => { + const response = await request(app) + .get('/health/detailed') + .expect(200); + + expect(response.body).toHaveProperty('status'); + expect(response.body).toHaveProperty('timestamp'); + expect(response.body).toHaveProperty('service', 'harbor-ci-cd-demo'); + expect(response.body).toHaveProperty('uptime'); + expect(response.body).toHaveProperty('memory'); + expect(response.body).toHaveProperty('loadAverage'); + expect(response.body).toHaveProperty('environment'); + + // Check memory structure + expect(response.body.memory).toHaveProperty('status'); + expect(response.body.memory).toHaveProperty('system'); + expect(response.body.memory).toHaveProperty('process'); + expect(response.body.memory.system).toHaveProperty('total'); + expect(response.body.memory.system).toHaveProperty('used'); + expect(response.body.memory.system).toHaveProperty('percentage'); + + // Check uptime structure + expect(response.body.uptime).toHaveProperty('process'); + expect(response.body.uptime).toHaveProperty('system'); + expect(typeof response.body.uptime.process).toBe('number'); + expect(typeof response.body.uptime.system).toBe('number'); + + // Check load average + expect(Array.isArray(response.body.loadAverage)).toBe(true); + expect(response.body.loadAverage).toHaveLength(3); + }); + + it('should include version information', async() => { + const response = await request(app) + .get('/health/detailed') + .expect(200); + + expect(response.body).toHaveProperty('version'); + }); + }); + + describe('GET /health/ready', () => { + it('should return readiness status', async() => { + const response = await request(app) + .get('/health/ready') + .expect(200); + + expect(response.body).toHaveProperty('status', 'ready'); + expect(response.body).toHaveProperty('timestamp'); + }); + }); + + describe('GET /health/live', () => { + it('should return liveness status', async() => { + const response = await request(app) + .get('/health/live') + .expect(200); + + expect(response.body).toHaveProperty('status', 'alive'); + expect(response.body).toHaveProperty('timestamp'); + expect(response.body).toHaveProperty('uptime'); + expect(typeof response.body.uptime).toBe('number'); + }); + }); + + describe('Health status validation', () => { + it('should return valid timestamp format', async() => { + const response = await request(app) + .get('/health') + .expect(200); + + const timestamp = new Date(response.body.timestamp); + expect(timestamp).toBeInstanceOf(Date); + expect(timestamp.getTime()).not.toBeNaN(); + }); + + it('should return consistent service name across endpoints', async() => { + const basicHealth = await request(app).get('/health'); + const detailedHealth = await request(app).get('/health/detailed'); + + expect(basicHealth.body.service).toBe(detailedHealth.body.service); + }); + }); +}); diff --git a/tests/metrics.test.js b/tests/metrics.test.js new file mode 100644 index 0000000..522ab62 --- /dev/null +++ b/tests/metrics.test.js @@ -0,0 +1,121 @@ +const { + incrementCounter, + setGauge, + recordHistogram, + getMetrics, + resetMetrics +} = require('../src/utils/metrics'); + +describe('Metrics Utility', () => { + beforeEach(() => { + resetMetrics(); + }); + + describe('incrementCounter', () => { + it('should increment a counter', () => { + incrementCounter('test_counter'); + incrementCounter('test_counter'); + + const metrics = getMetrics(); + const counter = metrics.counters.find(c => c.name === 'test_counter'); + + expect(counter).toBeDefined(); + expect(counter.value).toBe(2); + }); + + it('should handle counters with labels', () => { + incrementCounter('http_requests', { method: 'GET' }); + incrementCounter('http_requests', { method: 'POST' }); + incrementCounter('http_requests', { method: 'GET' }); + + const metrics = getMetrics(); + const getCounter = metrics.counters.find(c => + c.name === 'http_requests' && c.labels.method === 'GET' + ); + const postCounter = metrics.counters.find(c => + c.name === 'http_requests' && c.labels.method === 'POST' + ); + + expect(getCounter.value).toBe(2); + expect(postCounter.value).toBe(1); + }); + }); + + describe('setGauge', () => { + it('should set a gauge value', () => { + setGauge('memory_usage', 85.5); + + const metrics = getMetrics(); + const gauge = metrics.gauges.find(g => g.name === 'memory_usage'); + + expect(gauge).toBeDefined(); + expect(gauge.value).toBe(85.5); + }); + + it('should update existing gauge values', () => { + setGauge('cpu_usage', 50); + setGauge('cpu_usage', 75); + + const metrics = getMetrics(); + const gauge = metrics.gauges.find(g => g.name === 'cpu_usage'); + + expect(gauge.value).toBe(75); + }); + }); + + describe('recordHistogram', () => { + it('should record histogram values', () => { + recordHistogram('response_time', 100); + recordHistogram('response_time', 150); + recordHistogram('response_time', 200); + + const metrics = getMetrics(); + const histogram = metrics.histograms.find(h => h.name === 'response_time'); + + expect(histogram).toBeDefined(); + expect(histogram.count).toBe(3); + expect(histogram.sum).toBe(450); + expect(histogram.average).toBe(150); + expect(histogram.min).toBe(100); + expect(histogram.max).toBe(200); + }); + }); + + describe('getMetrics', () => { + it('should return all metrics with runtime info', () => { + incrementCounter('test_counter'); + setGauge('test_gauge', 42); + recordHistogram('test_histogram', 100); + + const metrics = getMetrics(); + + expect(metrics).toHaveProperty('counters'); + expect(metrics).toHaveProperty('gauges'); + expect(metrics).toHaveProperty('histograms'); + expect(metrics).toHaveProperty('runtime'); + expect(metrics).toHaveProperty('timestamp'); + + expect(metrics.counters).toHaveLength(1); + expect(metrics.gauges).toHaveLength(1); + expect(metrics.histograms).toHaveLength(1); + + expect(metrics.runtime).toHaveProperty('uptime_seconds'); + expect(metrics.runtime).toHaveProperty('memory_usage_bytes'); + }); + }); + + describe('resetMetrics', () => { + it('should clear all metrics', () => { + incrementCounter('test_counter'); + setGauge('test_gauge', 42); + recordHistogram('test_histogram', 100); + + resetMetrics(); + + const metrics = getMetrics(); + expect(metrics.counters).toHaveLength(0); + expect(metrics.gauges).toHaveLength(0); + expect(metrics.histograms).toHaveLength(0); + }); + }); +});