api-integration.test.ts•6.12 kB
/**
* Integration Tests for News API
*
* These tests verify the complete request-response cycle
* while mocking external API calls to ensure reliable tests.
*/
import { describe, test, expect, beforeAll, afterAll, beforeEach, jest } from '@jest/globals';
import supertest from 'supertest';
import { app, server } from '../../server';
import axios from 'axios';
// Mock axios to prevent real external API calls
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
// Sample test data
const mockArticles = [
{
uuid: '123e4567-e89b-12d3-a456-426614174000', // Using a properly formatted UUID
title: 'Test Article 1',
description: 'Test description 1',
content: 'Test content 1',
url: 'https://example.com/test1',
image_url: 'https://example.com/test1.jpg',
source: 'Test Source',
categories: ['technology'],
published_at: '2023-01-01T12:00:00Z',
created_at: '2023-01-01T12:00:00Z',
updated_at: '2023-01-01T12:00:00Z'
},
{
uuid: '223e4567-e89b-12d3-a456-426614174001', // Using another properly formatted UUID
title: 'Test Article 2',
description: 'Test description 2',
content: 'Test content 2',
url: 'https://example.com/test2',
image_url: 'https://example.com/test2.jpg',
source: 'Test Source',
categories: ['business'],
published_at: '2023-01-02T12:00:00Z',
created_at: '2023-01-02T12:00:00Z',
updated_at: '2023-01-02T12:00:00Z'
}
];
describe('API Integration Tests', () => {
// Using any type to avoid TypeScript errors with supertest
let request: any;
let port: number;
beforeAll(() => {
// Use a random port to avoid conflicts
port = Math.floor(Math.random() * 10000) + 10000;
server.listen(port);
request = supertest(app);
});
afterAll(async () => {
return new Promise<void>((resolve) => {
// Properly close the server
server.close(() => {
resolve();
});
});
});
beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
});
describe('GET /api/news/top', () => {
test('returns top news articles', async () => {
// Arrange - mock axios response
mockedAxios.get.mockResolvedValueOnce({
data: {
data: mockArticles,
meta: {
found: 2,
returned: 2
}
}
});
// Act
const response = await request.get('/api/news/top');
// Assert
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data.data).toHaveLength(2);
expect(response.body.data.data[0].uuid).toBe('123e4567-e89b-12d3-a456-426614174000');
expect(response.body.data.meta.found).toBe(2);
});
test('filters by category', async () => {
// Arrange - mock axios response
mockedAxios.get.mockResolvedValueOnce({
data: {
data: [mockArticles[0]], // Only the tech article
meta: {
found: 1,
returned: 1
}
}
});
// Act
const response = await request.get('/api/news/top?categories=technology');
// Assert
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data.data).toHaveLength(1);
expect(response.body.data.data[0].categories).toContain('technology');
// Verify that axios was called with the right parameters
expect(mockedAxios.get).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
params: expect.objectContaining({
categories: 'technology'
})
})
);
});
});
describe('GET /api/news/uuid/:uuid', () => {
test('returns a specific news article by UUID', async () => {
// Arrange
mockedAxios.get.mockResolvedValueOnce({
data: {
data: mockArticles[0]
}
});
// Act
const response = await request.get('/api/news/uuid/123e4567-e89b-12d3-a456-426614174000');
// Assert
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data.data.uuid).toBe('123e4567-e89b-12d3-a456-426614174000');
expect(response.body.data.data.title).toBe('Test Article 1');
});
test('returns 404 when article is not found', async () => {
// Arrange - mock a 404 error response from the API
// Using a properly formatted UUID that doesn't exist
const nonExistentUuid = '00000000-0000-0000-0000-000000000000';
// Create an error with a response property to simulate Axios error structure
// Note: The controller checks for the string "404" in the error message to set status code to 404
const errorResponse = new Error('Article not found - 404');
(errorResponse as any).response = {
status: 404,
statusText: 'Not Found',
data: { message: 'Article not found' }
};
// Mock axios to return this error for this specific UUID
mockedAxios.get.mockImplementationOnce((url, config) => {
if (url.includes(nonExistentUuid)) {
return Promise.reject(errorResponse);
}
return Promise.resolve({ data: {} } as any); // Should never reach this
});
// Act
const response = await request.get(`/api/news/uuid/${nonExistentUuid}`);
// Assert
expect(response.status).toBe(404);
expect(response.body.success).toBe(false);
expect(response.body.error).toBeDefined();
});
});
describe('GET /api/cache/stats', () => {
test('returns cache statistics', async () => {
// Act
const response = await request.get('/api/cache/stats');
// Assert
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('hits');
expect(response.body.data).toHaveProperty('misses');
expect(response.body.data).toHaveProperty('keys');
});
});
});