news-routes-fixed.test.ts•8.4 kB
/**
* Improved tests for News API endpoints
* - Uses dynamic port assignment to avoid conflicts
* - Properly shuts down server after tests
* - Matches actual API response structure
* - Implements better error handling
*/
import request from 'supertest';
import { Express } from 'express';
import { describe, beforeAll, afterAll, beforeEach, afterEach, test, expect, jest } from '@jest/globals';
import { McpServer } from '../../server';
import { cacheService } from '../../utils/cache';
// Use a separate random port range for each test file
const getRandomPort = () => Math.floor(Math.random() * 10000) + 30000;
describe('News API Endpoints', () => {
let app: Express;
let server: McpServer;
let port: number;
// Setup before all tests
beforeAll(async () => {
// Generate a random port for this test suite
port = getRandomPort();
try {
console.log(`Starting news API test server on port ${port}`);
// Create and start server
server = new McpServer(port);
await server.start();
app = server.getApp();
console.log(`News API test server running on port ${port}`);
} catch (error) {
console.error('Failed to start news API test server:', error);
throw error;
}
});
// Cleanup after all tests
afterAll(async () => {
try {
console.log(`Shutting down news API test server on port ${port}`);
if (server) {
await server.shutdown();
// Add a delay to ensure all connections are properly closed
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('News API test server successfully shut down');
} catch (error) {
console.error('Error during server shutdown:', error);
}
});
// Reset cache before each test
beforeEach(async () => {
cacheService.clear();
});
// Test top news retrieval
test('GET /api/news/top - should return a list of top news articles', async () => {
try {
const response = await request(app)
.get('/api/news/top')
.timeout(5000) // Add timeout to avoid hanging
.expect('Content-Type', /json/);
// Check response status
expect(response.status).toBe(200);
// Verify response structure - data is nested in response.body.data.data
expect(response.body.success).toBe(true);
expect(response.body.data).toBeDefined();
expect(response.body.data.data).toBeDefined();
expect(Array.isArray(response.body.data.data)).toBe(true);
// Log response for debugging if needed
// console.log('Response data:', response.body.data);
// Skip further assertions if no data returned
if (response.body.data.data.length === 0) {
console.log('No articles returned from API');
return;
}
// Verify article properties if data is returned
const article = response.body.data.data[0];
expect(article).toHaveProperty('title');
expect(article).toHaveProperty('source');
expect(article).toHaveProperty('published_at');
} catch (error) {
console.error('Error in top news test:', error);
throw error;
}
});
// Test category filtering
test('GET /api/news/top with category - should filter articles by category', async () => {
// Use 'general' as it's a common category across news sources
const category = 'general';
try {
const response = await request(app)
.get(`/api/news/top?category=${category}`)
.timeout(5000)
.expect('Content-Type', /json/);
// Check response status
expect(response.status).toBe(200);
// Verify response structure
expect(response.body.success).toBe(true);
expect(response.body.data).toBeDefined();
expect(response.body.data.data).toBeDefined();
expect(Array.isArray(response.body.data.data)).toBe(true);
// Skip further assertions if no data returned
if (response.body.data.data.length === 0) {
console.log(`No articles returned for category: ${category}`);
return;
}
// Verify that at least one article has the requested category
const hasMatchingArticle = response.body.data.data.some(
(article: any) => article.categories &&
(Array.isArray(article.categories) ?
article.categories.includes(category) :
article.categories === category)
);
expect(hasMatchingArticle).toBe(true);
} catch (error) {
console.error('Error in category filter test:', error);
throw error;
}
});
// Test cache functionality
test('GET /api/news/top - should use cache for repeated requests', async () => {
try {
// Clear cache stats
const initialStats = cacheService.getStats() as { hits: number, misses: number, keys: number };
// First request should miss cache
await request(app)
.get('/api/news/top')
.timeout(5000)
.expect(200);
// Second request should hit cache
await request(app)
.get('/api/news/top')
.timeout(5000)
.expect(200);
// Get final cache stats
const finalStats = cacheService.getStats() as { hits: number, misses: number, keys: number };
// Verify cache hit occurred
expect(finalStats.hits).toBeGreaterThan(initialStats.hits);
} catch (error) {
console.error('Error in cache test:', error);
throw error;
}
});
// Test all news with pagination
test('GET /api/news/all - should return all news articles with pagination', async () => {
try {
const limit = 5;
const response = await request(app)
.get(`/api/news/all?limit=${limit}`)
.timeout(5000)
.expect('Content-Type', /json/);
// Check response status
expect(response.status).toBe(200);
// Verify response structure
expect(response.body.success).toBe(true);
expect(response.body.data).toBeDefined();
expect(response.body.data.data).toBeDefined();
expect(Array.isArray(response.body.data.data)).toBe(true);
// Verify pagination works
expect(response.body.data.data.length).toBeLessThanOrEqual(limit);
// Check for metadata about pagination
expect(response.body.data.meta).toBeDefined();
expect(response.body.data.meta).toHaveProperty('returned');
expect(response.body.data.meta).toHaveProperty('found');
} catch (error) {
console.error('Error in pagination test:', error);
throw error;
}
});
// Test UUID-based article retrieval
test('GET /api/news/uuid/:uuid - should return a specific article by UUID', async () => {
try {
// Use a known valid UUID format for testing
const testUuid = '123e4567-e89b-12d3-a456-426614174000';
// Request specific article by UUID
const response = await request(app)
.get(`/api/news/uuid/${testUuid}`)
.timeout(5000);
// Verify response status
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
// Check that the data structure is correct
expect(response.body.data).toBeDefined();
expect(response.body.data.data).toBeDefined();
// Verify specific article properties
expect(response.body.data.data).toHaveProperty('uuid', testUuid);
expect(response.body.data.data).toHaveProperty('title');
} catch (error) {
console.error('Error in UUID test:', error);
throw error;
}
});
// Test non-existent but valid format UUID
test('GET /api/news/uuid/:uuid - should return 404 for non-existent article', async () => {
try {
// Use a properly formatted UUID that doesn't exist
const nonExistentUuid = '00000000-0000-0000-0000-000000000000';
// Request non-existent article by UUID
const response = await request(app)
.get(`/api/news/uuid/${nonExistentUuid}`)
.timeout(5000);
// Verify 404 response
expect(response.status).toBe(404);
expect(response.body.success).toBe(false);
expect(response.body.error).toBeDefined();
} catch (error) {
console.error('Error in non-existent UUID test:', error);
throw error;
}
});
});