import { describe, test, expect } from 'bun:test';
describe('Reddit MCP Server', () => {
describe('Tool Schemas', () => {
test('get_subreddit_hot requires subreddit parameter', () => {
const requiredFields = ['subreddit'];
const params = { subreddit: 'programming', limit: 10 };
for (const field of requiredFields) {
expect(params).toHaveProperty(field);
}
});
test('get_subreddit_new requires subreddit parameter', () => {
const params = { subreddit: 'news', limit: 25 };
expect(params).toHaveProperty('subreddit');
});
test('get_subreddit_top supports time filter', () => {
const validTimeFilters = ['hour', 'day', 'week', 'month', 'year', 'all'];
const params = { subreddit: 'askreddit', time: 'week', limit: 10 };
expect(validTimeFilters).toContain(params.time);
});
test('get_post_content requires post_id', () => {
const params = { post_id: 'abc123', comment_limit: 20, comment_depth: 3 };
expect(params).toHaveProperty('post_id');
expect(params.post_id).toBeTruthy();
});
test('get_post_content supports comment sort options', () => {
const validSorts = ['confidence', 'top', 'new', 'controversial', 'old', 'qa'];
expect(validSorts).toContain('confidence');
expect(validSorts).toContain('top');
expect(validSorts).toContain('new');
});
test('search_posts requires query parameter', () => {
const params = { query: 'typescript', subreddit: 'programming', sort: 'relevance' };
expect(params).toHaveProperty('query');
expect(params.query).toBeTruthy();
});
test('search_posts supports sort options', () => {
const validSorts = ['relevance', 'hot', 'top', 'new', 'comments'];
const params = { query: 'test', sort: 'top' };
expect(validSorts).toContain(params.sort);
});
test('get_user_info requires username', () => {
const params = { username: 'spez' };
expect(params).toHaveProperty('username');
expect(params.username).toBeTruthy();
});
test('get_user_posts requires username', () => {
const params = { username: 'spez', sort: 'new', limit: 10 };
expect(params).toHaveProperty('username');
});
test('get_user_posts supports sort options', () => {
const validSorts = ['hot', 'new', 'top', 'controversial'];
expect(validSorts).toContain('new');
expect(validSorts).toContain('top');
});
test('get_user_comments requires username', () => {
const params = { username: 'automoderator', sort: 'new' };
expect(params).toHaveProperty('username');
});
test('get_subreddit_info requires subreddit', () => {
const params = { subreddit: 'technology' };
expect(params).toHaveProperty('subreddit');
expect(params.subreddit).toBeTruthy();
});
});
describe('API Endpoint Paths', () => {
test('subreddit hot endpoint is correct', () => {
const subreddit = 'programming';
const endpoint = `/r/${subreddit}/hot.json`;
expect(endpoint).toBe('/r/programming/hot.json');
});
test('subreddit new endpoint is correct', () => {
const subreddit = 'news';
const endpoint = `/r/${subreddit}/new.json`;
expect(endpoint).toBe('/r/news/new.json');
});
test('subreddit top endpoint is correct', () => {
const subreddit = 'askreddit';
const endpoint = `/r/${subreddit}/top.json`;
expect(endpoint).toBe('/r/askreddit/top.json');
});
test('post comments endpoint is correct', () => {
const postId = 'abc123';
const endpoint = `/comments/${postId}.json`;
expect(endpoint).toBe('/comments/abc123.json');
});
test('post comments with subreddit endpoint is correct', () => {
const subreddit = 'programming';
const postId = 'abc123';
const endpoint = `/r/${subreddit}/comments/${postId}.json`;
expect(endpoint).toBe('/r/programming/comments/abc123.json');
});
test('search endpoint is correct', () => {
const endpoint = '/search.json';
expect(endpoint).toBe('/search.json');
});
test('search in subreddit endpoint is correct', () => {
const subreddit = 'python';
const endpoint = `/r/${subreddit}/search.json`;
expect(endpoint).toBe('/r/python/search.json');
});
test('user about endpoint is correct', () => {
const username = 'spez';
const endpoint = `/user/${username}/about.json`;
expect(endpoint).toBe('/user/spez/about.json');
});
test('user posts endpoint is correct', () => {
const username = 'spez';
const endpoint = `/user/${username}/submitted.json`;
expect(endpoint).toBe('/user/spez/submitted.json');
});
test('user comments endpoint is correct', () => {
const username = 'spez';
const endpoint = `/user/${username}/comments.json`;
expect(endpoint).toBe('/user/spez/comments.json');
});
test('subreddit about endpoint is correct', () => {
const subreddit = 'technology';
const endpoint = `/r/${subreddit}/about.json`;
expect(endpoint).toBe('/r/technology/about.json');
});
});
describe('Parameter Validation', () => {
test('limit should default to 10', () => {
const defaultLimit = 10;
const params: { limit?: number } = {};
const actualLimit = params.limit ?? defaultLimit;
expect(actualLimit).toBe(10);
});
test('limit should not exceed 100', () => {
const maxLimit = 100;
const requestedLimit = 150;
const actualLimit = Math.min(requestedLimit, maxLimit);
expect(actualLimit).toBe(100);
});
test('time filter should be valid enum value', () => {
const validValues = ['hour', 'day', 'week', 'month', 'year', 'all'];
const testValue = 'week';
expect(validValues).toContain(testValue);
});
test('comment_depth should default to 3', () => {
const defaultDepth = 3;
const params: { comment_depth?: number } = {};
const actualDepth = params.comment_depth ?? defaultDepth;
expect(actualDepth).toBe(3);
});
test('comment_limit should default to 20', () => {
const defaultLimit = 20;
const params: { comment_limit?: number } = {};
const actualLimit = params.comment_limit ?? defaultLimit;
expect(actualLimit).toBe(20);
});
test('max_items should default to 10', () => {
const defaultMaxItems = 10;
const params: { max_items?: number } = {};
const actualMaxItems = params.max_items ?? defaultMaxItems;
expect(actualMaxItems).toBe(10);
});
});
describe('Response Format Validation', () => {
test('post response has expected fields', () => {
const mockPost = {
id: 'abc123',
title: 'Test Post',
subreddit: 'test',
author: 'testuser',
score: 100,
upvoteRatio: 0.95,
comments: 50,
created: '2024-01-15T00:00:00.000Z',
url: 'https://example.com',
permalink: 'https://reddit.com/r/test/comments/abc123/test_post/',
isVideo: false,
isSelf: true,
};
expect(mockPost).toHaveProperty('id');
expect(mockPost).toHaveProperty('title');
expect(mockPost).toHaveProperty('subreddit');
expect(mockPost).toHaveProperty('author');
expect(mockPost).toHaveProperty('score');
expect(mockPost).toHaveProperty('comments');
});
test('comment response has expected fields', () => {
const mockComment = {
id: 'comment123',
author: 'commenter',
body: 'This is a comment',
score: 25,
created: '2024-01-15T00:00:00.000Z',
depth: 0,
edited: false,
};
expect(mockComment).toHaveProperty('id');
expect(mockComment).toHaveProperty('author');
expect(mockComment).toHaveProperty('body');
expect(mockComment).toHaveProperty('score');
expect(mockComment).toHaveProperty('depth');
});
test('user response has expected fields', () => {
const mockUser = {
id: 'user123',
name: 'testuser',
created: '2020-01-01T00:00:00.000Z',
linkKarma: 1000,
commentKarma: 5000,
totalKarma: 6000,
verified: true,
premium: false,
};
expect(mockUser).toHaveProperty('id');
expect(mockUser).toHaveProperty('name');
expect(mockUser).toHaveProperty('linkKarma');
expect(mockUser).toHaveProperty('commentKarma');
});
test('subreddit response has expected fields', () => {
const mockSubreddit = {
id: 'sub123',
name: 'programming',
title: 'Programming',
description: 'A subreddit about programming',
subscribers: 5000000,
activeUsers: 10000,
created: '2008-01-01T00:00:00.000Z',
over18: false,
url: 'https://reddit.com/r/programming/',
};
expect(mockSubreddit).toHaveProperty('id');
expect(mockSubreddit).toHaveProperty('name');
expect(mockSubreddit).toHaveProperty('subscribers');
});
test('pagination response includes nextCursor when available', () => {
const mockResponse = {
data: [{ id: '1' }, { id: '2' }],
pagination: {
nextCursor: 't3_xyz789',
},
};
expect(mockResponse).toHaveProperty('pagination');
expect(mockResponse.pagination).toHaveProperty('nextCursor');
});
});
describe('Error Handling', () => {
test('missing subreddit for get_subreddit_hot should throw error', () => {
const hasSubreddit = false;
if (!hasSubreddit) {
expect(() => {
throw new Error('subreddit is required');
}).toThrow('subreddit is required');
}
});
test('missing post_id for get_post_content should throw error', () => {
const hasPostId = false;
if (!hasPostId) {
expect(() => {
throw new Error('post_id is required');
}).toThrow('post_id is required');
}
});
test('missing query for search_posts should throw error', () => {
const hasQuery = false;
if (!hasQuery) {
expect(() => {
throw new Error('query is required');
}).toThrow('query is required');
}
});
test('missing username for get_user_info should throw error', () => {
const hasUsername = false;
if (!hasUsername) {
expect(() => {
throw new Error('username is required');
}).toThrow('username is required');
}
});
});
describe('Data Cleaners', () => {
test('cleanPost handles null input', () => {
const result = null;
expect(result).toBeNull();
});
test('cleanPost extracts correct fields from raw data', () => {
const rawPost = {
data: {
id: 'test123',
title: 'Test Title',
subreddit: 'test',
author: 'testauthor',
score: 100,
upvote_ratio: 0.95,
num_comments: 50,
created_utc: 1705276800,
url: 'https://example.com',
permalink: '/r/test/comments/test123/test_title/',
selftext: 'Test content',
is_video: false,
is_self: true,
total_awards_received: 2,
over_18: false,
},
};
const expected = {
id: 'test123',
title: 'Test Title',
subreddit: 'test',
author: 'testauthor',
score: 100,
};
expect(rawPost.data.id).toBe(expected.id);
expect(rawPost.data.title).toBe(expected.title);
});
test('cleanComment handles nested replies', () => {
const mockComment = {
kind: 't1',
data: {
id: 'comment1',
author: 'user1',
body: 'Parent comment',
score: 10,
created_utc: 1705276800,
replies: {
data: {
children: [
{
kind: 't1',
data: {
id: 'comment2',
author: 'user2',
body: 'Reply comment',
score: 5,
created_utc: 1705276900,
},
},
],
},
},
},
};
expect(mockComment.data.replies.data.children.length).toBe(1);
expect(mockComment.data.replies.data.children[0].data.body).toBe('Reply comment');
});
test('cleanUser extracts karma fields', () => {
const rawUser = {
data: {
id: 'user123',
name: 'testuser',
link_karma: 1000,
comment_karma: 5000,
total_karma: 6000,
created_utc: 1577836800,
},
};
expect(rawUser.data.link_karma).toBe(1000);
expect(rawUser.data.comment_karma).toBe(5000);
expect(rawUser.data.total_karma).toBe(6000);
});
});
});
describe('MCP Protocol Compliance', () => {
test('tools/list returns valid tool array', () => {
const toolsResponse = {
tools: [
{ name: 'get_subreddit_hot', description: 'Get hot posts from a subreddit' },
{ name: 'get_subreddit_new', description: 'Get new posts from a subreddit' },
{ name: 'get_subreddit_top', description: 'Get top posts from a subreddit' },
{ name: 'get_post_content', description: 'Get post details with comments' },
{ name: 'search_posts', description: 'Search Reddit posts' },
{ name: 'get_user_info', description: 'Get user profile information' },
{ name: 'get_user_posts', description: 'Get posts by a user' },
{ name: 'get_user_comments', description: 'Get comments by a user' },
{ name: 'get_subreddit_info', description: 'Get subreddit information' },
],
};
expect(toolsResponse.tools).toBeInstanceOf(Array);
expect(toolsResponse.tools.length).toBe(9);
expect(toolsResponse.tools[0]).toHaveProperty('name');
expect(toolsResponse.tools[0]).toHaveProperty('description');
});
test('tool count should be 9', () => {
const expectedTools = [
'get_subreddit_hot',
'get_subreddit_new',
'get_subreddit_top',
'get_post_content',
'search_posts',
'get_user_info',
'get_user_posts',
'get_user_comments',
'get_subreddit_info',
];
expect(expectedTools.length).toBe(9);
});
test('tool call returns CallToolResult format', () => {
const result = {
content: [
{
type: 'text',
text: 'data:[]',
},
],
};
expect(result).toHaveProperty('content');
expect(result.content[0]).toHaveProperty('type');
expect(result.content[0]).toHaveProperty('text');
expect(result.content[0].type).toBe('text');
});
test('error response uses McpError format', () => {
const errorResponse = {
code: -32602,
message: 'Missing arguments',
};
expect(errorResponse).toHaveProperty('code');
expect(errorResponse).toHaveProperty('message');
});
test('error codes are correct', () => {
const ErrorCodes = {
ParseError: -32700,
InvalidRequest: -32600,
MethodNotFound: -32601,
InvalidParams: -32602,
InternalError: -32603,
};
expect(ErrorCodes.InvalidParams).toBe(-32602);
expect(ErrorCodes.MethodNotFound).toBe(-32601);
expect(ErrorCodes.InternalError).toBe(-32603);
});
});
describe('Base URL Configuration', () => {
test('base URL is correct', () => {
const baseURL = 'https://www.reddit.com';
expect(baseURL).toBe('https://www.reddit.com');
});
test('User-Agent header is set', () => {
const headers = {
'User-Agent': 'Reddit-MCP-Server/1.0.0 (by /u/mcp-bot)',
'Accept': 'application/json',
};
expect(headers).toHaveProperty('User-Agent');
expect(headers['User-Agent']).toContain('Reddit-MCP-Server');
});
test('proxy support is available', () => {
const proxyEnvVars = ['PROXY_URL', 'HTTP_PROXY', 'HTTPS_PROXY'];
for (const envVar of proxyEnvVars) {
const value = process.env[envVar];
expect(value === undefined || typeof value === 'string').toBe(true);
}
});
});