/**
* Direct API Integration Tests for Reddit JSON API
*
* These tests make real API calls to verify the Reddit API works correctly.
* No API key required for public Reddit endpoints.
*
* Run with: bun test src/api.test.ts
*/
import { describe, test, expect, setDefaultTimeout, beforeAll } from 'bun:test';
import axios, { AxiosInstance } from 'axios';
setDefaultTimeout(30000);
describe('Reddit Direct API Integration Tests', () => {
let apiClient: AxiosInstance;
beforeAll(() => {
apiClient = axios.create({
baseURL: 'https://www.reddit.com',
timeout: 30000,
headers: {
'User-Agent': 'Reddit-MCP-Server-Test/1.0.0 (by /u/mcp-bot)',
'Accept': 'application/json',
},
});
});
describe('Subreddit Endpoints', () => {
test('get hot posts from r/programming', async () => {
const response = await apiClient.get('/r/programming/hot.json', {
params: { limit: 5, raw_json: 1 },
});
expect(response.status).toBe(200);
expect(response.data).toHaveProperty('data');
expect(response.data.data).toHaveProperty('children');
expect(response.data.data.children).toBeInstanceOf(Array);
expect(response.data.data.children.length).toBeGreaterThan(0);
const firstPost = response.data.data.children[0].data;
expect(firstPost).toHaveProperty('id');
expect(firstPost).toHaveProperty('title');
expect(firstPost).toHaveProperty('subreddit');
console.log('Hot post:', firstPost.title?.substring(0, 50) + '...');
});
test('get new posts from r/technology', async () => {
const response = await apiClient.get('/r/technology/new.json', {
params: { limit: 5, raw_json: 1 },
});
expect(response.status).toBe(200);
expect(response.data.data.children).toBeInstanceOf(Array);
console.log('New posts count:', response.data.data.children.length);
});
test('get top posts from r/askreddit with time filter', async () => {
const response = await apiClient.get('/r/askreddit/top.json', {
params: { t: 'week', limit: 5, raw_json: 1 },
});
expect(response.status).toBe(200);
expect(response.data.data.children).toBeInstanceOf(Array);
const firstPost = response.data.data.children[0]?.data;
if (firstPost) {
expect(firstPost.score).toBeGreaterThan(0);
console.log('Top post score:', firstPost.score);
}
});
test('get subreddit info for r/news', async () => {
const response = await apiClient.get('/r/news/about.json', {
params: { raw_json: 1 },
});
expect(response.status).toBe(200);
expect(response.data).toHaveProperty('data');
expect(response.data.data).toHaveProperty('display_name');
expect(response.data.data).toHaveProperty('subscribers');
expect(response.data.data.display_name.toLowerCase()).toBe('news');
console.log('r/news subscribers:', response.data.data.subscribers);
});
});
describe('Post Content Endpoints', () => {
test('get post with comments', async () => {
// First get a post ID from hot posts
const hotResponse = await apiClient.get('/r/programming/hot.json', {
params: { limit: 1, raw_json: 1 },
});
const postId = hotResponse.data.data.children[0]?.data?.id;
expect(postId).toBeDefined();
// Then get the post content with comments
const response = await apiClient.get(`/comments/${postId}.json`, {
params: { limit: 10, depth: 2, sort: 'top', raw_json: 1 },
});
expect(response.status).toBe(200);
expect(response.data).toBeInstanceOf(Array);
expect(response.data.length).toBe(2); // [post, comments]
const postData = response.data[0].data.children[0]?.data;
expect(postData).toHaveProperty('title');
expect(postData).toHaveProperty('id');
console.log('Post title:', postData.title?.substring(0, 50) + '...');
const commentsData = response.data[1].data.children;
console.log('Comments count:', commentsData.filter((c: any) => c.kind === 't1').length);
});
test('get post with different comment sorts', async () => {
const hotResponse = await apiClient.get('/r/askreddit/hot.json', {
params: { limit: 1, raw_json: 1 },
});
const postId = hotResponse.data.data.children[0]?.data?.id;
for (const sort of ['confidence', 'top', 'new']) {
const response = await apiClient.get(`/comments/${postId}.json`, {
params: { limit: 5, sort, raw_json: 1 },
});
expect(response.status).toBe(200);
console.log(`Comments with sort=${sort}: OK`);
}
});
});
describe('Search Endpoints', () => {
test('search posts by query', async () => {
const response = await apiClient.get('/search.json', {
params: {
q: 'typescript',
sort: 'relevance',
t: 'month',
limit: 5,
type: 'link',
raw_json: 1,
},
});
expect(response.status).toBe(200);
expect(response.data.data.children).toBeInstanceOf(Array);
console.log('Search results:', response.data.data.children.length);
});
test('search posts within a subreddit', async () => {
const response = await apiClient.get('/r/programming/search.json', {
params: {
q: 'rust',
restrict_sr: 'on',
sort: 'top',
t: 'week',
limit: 5,
raw_json: 1,
},
});
expect(response.status).toBe(200);
expect(response.data.data.children).toBeInstanceOf(Array);
console.log('Subreddit search results:', response.data.data.children.length);
});
test('search with different sort options', async () => {
for (const sort of ['relevance', 'hot', 'top', 'new', 'comments']) {
const response = await apiClient.get('/search.json', {
params: { q: 'python', sort, limit: 3, raw_json: 1 },
});
expect(response.status).toBe(200);
console.log(`Search with sort=${sort}: OK`);
}
});
});
describe('User Endpoints', () => {
test('get user info', async () => {
const response = await apiClient.get('/user/spez/about.json', {
params: { raw_json: 1 },
});
expect(response.status).toBe(200);
expect(response.data).toHaveProperty('data');
expect(response.data.data).toHaveProperty('name');
expect(response.data.data.name.toLowerCase()).toBe('spez');
expect(response.data.data).toHaveProperty('link_karma');
expect(response.data.data).toHaveProperty('comment_karma');
console.log('User spez karma:', response.data.data.total_karma);
});
test('get user submitted posts', async () => {
const response = await apiClient.get('/user/AutoModerator/submitted.json', {
params: { sort: 'new', limit: 5, raw_json: 1 },
});
expect(response.status).toBe(200);
expect(response.data.data.children).toBeInstanceOf(Array);
console.log('User posts:', response.data.data.children.length);
});
test('get user comments', async () => {
const response = await apiClient.get('/user/AutoModerator/comments.json', {
params: { sort: 'new', limit: 5, raw_json: 1 },
});
expect(response.status).toBe(200);
expect(response.data.data.children).toBeInstanceOf(Array);
if (response.data.data.children.length > 0) {
const comment = response.data.data.children[0].data;
expect(comment).toHaveProperty('body');
expect(comment).toHaveProperty('subreddit');
}
console.log('User comments:', response.data.data.children.length);
});
});
describe('Pagination', () => {
test('pagination with after cursor', async () => {
// First page
const page1 = await apiClient.get('/r/programming/hot.json', {
params: { limit: 5, raw_json: 1 },
});
expect(page1.status).toBe(200);
const afterCursor = page1.data.data.after;
expect(afterCursor).toBeDefined();
console.log('Page 1 after cursor:', afterCursor);
// Second page
const page2 = await apiClient.get('/r/programming/hot.json', {
params: { limit: 5, after: afterCursor, raw_json: 1 },
});
expect(page2.status).toBe(200);
expect(page2.data.data.children.length).toBeGreaterThan(0);
// Verify different posts
const page1Ids = page1.data.data.children.map((c: any) => c.data.id);
const page2Ids = page2.data.data.children.map((c: any) => c.data.id);
const overlap = page1Ids.filter((id: string) => page2Ids.includes(id));
expect(overlap.length).toBe(0);
console.log('Page 2 posts are different from page 1: OK');
});
});
describe('Time Filters', () => {
test('top posts with different time filters', async () => {
for (const time of ['hour', 'day', 'week', 'month', 'year', 'all']) {
const response = await apiClient.get('/r/all/top.json', {
params: { t: time, limit: 1, raw_json: 1 },
});
expect(response.status).toBe(200);
console.log(`Top posts t=${time}: OK`);
}
});
});
});
describe('API Response Format Validation', () => {
let apiClient: AxiosInstance;
beforeAll(() => {
apiClient = axios.create({
baseURL: 'https://www.reddit.com',
timeout: 30000,
headers: {
'User-Agent': 'Reddit-MCP-Server-Test/1.0.0',
'Accept': 'application/json',
},
});
});
test('post response contains all expected fields', async () => {
const response = await apiClient.get('/r/programming/hot.json', {
params: { limit: 1, raw_json: 1 },
});
const post = response.data.data.children[0].data;
// Required fields for our data cleaner
expect(post).toHaveProperty('id');
expect(post).toHaveProperty('title');
expect(post).toHaveProperty('subreddit');
expect(post).toHaveProperty('author');
expect(post).toHaveProperty('score');
expect(post).toHaveProperty('upvote_ratio');
expect(post).toHaveProperty('num_comments');
expect(post).toHaveProperty('created_utc');
expect(post).toHaveProperty('url');
expect(post).toHaveProperty('permalink');
expect(post).toHaveProperty('is_video');
expect(post).toHaveProperty('is_self');
});
test('comment response contains all expected fields', async () => {
const hotResponse = await apiClient.get('/r/askreddit/hot.json', {
params: { limit: 1, raw_json: 1 },
});
const postId = hotResponse.data.data.children[0].data.id;
const response = await apiClient.get(`/comments/${postId}.json`, {
params: { limit: 5, raw_json: 1 },
});
const comments = response.data[1].data.children.filter((c: any) => c.kind === 't1');
if (comments.length > 0) {
const comment = comments[0].data;
expect(comment).toHaveProperty('id');
expect(comment).toHaveProperty('author');
expect(comment).toHaveProperty('body');
expect(comment).toHaveProperty('score');
expect(comment).toHaveProperty('created_utc');
}
});
test('user response contains all expected fields', async () => {
const response = await apiClient.get('/user/spez/about.json', {
params: { raw_json: 1 },
});
const user = response.data.data;
expect(user).toHaveProperty('id');
expect(user).toHaveProperty('name');
expect(user).toHaveProperty('created_utc');
expect(user).toHaveProperty('link_karma');
expect(user).toHaveProperty('comment_karma');
expect(user).toHaveProperty('total_karma');
});
test('subreddit response contains all expected fields', async () => {
const response = await apiClient.get('/r/programming/about.json', {
params: { raw_json: 1 },
});
const subreddit = response.data.data;
expect(subreddit).toHaveProperty('id');
expect(subreddit).toHaveProperty('display_name');
expect(subreddit).toHaveProperty('title');
expect(subreddit).toHaveProperty('public_description');
expect(subreddit).toHaveProperty('subscribers');
expect(subreddit).toHaveProperty('created_utc');
});
});
describe('API Test File Validation', () => {
test('Reddit public API does not require authentication', () => {
console.log('Reddit public JSON API does not require API key');
console.log('All tests use public endpoints (.json suffix)');
expect(true).toBe(true);
});
});