import MediumClient from '../src/client';
import MediumAuth from '../src/auth';
import axios from 'axios';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
// Mock environment variables
process.env.MEDIUM_CLIENT_ID = 'test_client_id';
process.env.MEDIUM_CLIENT_SECRET = 'test_client_secret';
process.env.MEDIUM_ACCESS_TOKEN = 'test_access_token';
describe('MediumClient', () => {
let client: MediumClient;
let auth: MediumAuth;
beforeEach(async () => {
auth = new MediumAuth();
await auth.authenticate();
client = new MediumClient(auth);
// Reset all mocks
jest.clearAllMocks();
});
describe('publishArticle', () => {
it('should publish an article successfully', async () => {
const mockUserResponse = {
data: { data: { id: 'user123' } },
headers: {},
status: 200
};
const mockPublishResponse = {
data: {
data: {
id: 'post123',
title: 'Test Article',
url: 'https://medium.com/@user/test-article'
}
},
headers: {},
status: 201
};
mockedAxios.mockResolvedValueOnce(mockUserResponse as any);
mockedAxios.mockResolvedValueOnce(mockPublishResponse as any);
const result = await client.publishArticle({
title: 'Test Article',
content: '# Test Content',
tags: ['test', 'jest']
});
expect(result.data.id).toBe('post123');
expect(mockedAxios).toHaveBeenCalledTimes(2);
});
it('should handle API errors gracefully', async () => {
const mockUserResponse = {
data: { data: { id: 'user123' } },
headers: {},
status: 200
};
const mockPublishError = {
response: {
status: 400,
statusText: 'Bad Request',
data: { errors: [{ message: 'Invalid title' }] },
headers: {}
}
};
// getUserProfile succeeds, but publishArticle fails with 400 (not retryable)
mockedAxios
.mockResolvedValueOnce(mockUserResponse as any)
.mockRejectedValueOnce(mockPublishError);
await expect(
client.publishArticle({
title: '',
content: 'Content'
})
).rejects.toBeTruthy();
});
});
describe('getUserProfile', () => {
it('should retrieve user profile', async () => {
const mockResponse = {
data: {
data: {
id: 'user123',
username: 'testuser',
name: 'Test User'
}
},
headers: {},
status: 200
};
mockedAxios.mockResolvedValueOnce(mockResponse as any);
const result = await client.getUserProfile();
expect(result.data.username).toBe('testuser');
expect(mockedAxios).toHaveBeenCalledWith(
expect.objectContaining({
method: 'get',
url: expect.stringContaining('/me')
})
);
});
});
describe('retry logic', () => {
it('should retry on rate limit error', async () => {
const rateLimitError = {
response: {
status: 429,
statusText: 'Too Many Requests',
data: { message: 'Rate limit exceeded' }
}
};
const successResponse = {
data: { data: { id: 'user123' } },
headers: {},
status: 200
};
mockedAxios
.mockRejectedValueOnce(rateLimitError)
.mockResolvedValueOnce(successResponse as any);
const result = await client.getUserProfile();
expect(result.data.id).toBe('user123');
expect(mockedAxios).toHaveBeenCalledTimes(2);
});
});
describe('rate limit info', () => {
it('should track rate limit information', async () => {
const mockResponse = {
data: { data: { id: 'user123' } },
headers: {
'x-ratelimit-remaining': '99',
'x-ratelimit-reset': '1699999999'
},
status: 200
};
mockedAxios.mockResolvedValueOnce(mockResponse as any);
await client.getUserProfile();
const rateLimitInfo = client.getRateLimitInfo();
expect(rateLimitInfo.remaining).toBe(99);
expect(rateLimitInfo.resetAt).toBeInstanceOf(Date);
});
});
});