paper-search-mcp-nodejs
Version:
A Node.js MCP server for searching and downloading academic papers from multiple sources, including arXiv, PubMed, bioRxiv, Web of Science, and more.
657 lines (527 loc) โข 14.2 kB
Markdown
# AIPaper-assisant - Testing & Development Workflow
## ๐งช Testing Strategy
### Testing Philosophy
- **Test-Driven Development**: Write tests before implementation
- **Platform Isolation**: Each platform tested independently
- **Error Scenario Coverage**: Test failure modes extensively
- **Performance Validation**: Ensure rate limiting works correctly
- **Integration Confidence**: Cross-platform compatibility verified
### Test Categories
#### 1. Unit Tests
**Purpose**: Test individual components in isolation
**Coverage**:
- Model validation (`PaperFactory`)
- Rate limiting logic
- Error handling utilities
- Data transformation functions
**Example**:
```typescript
describe('PaperFactory', () => {
it('should validate required fields', () => {
const invalidData = { title: '', authors: [] };
expect(() => PaperFactory.create(invalidData)).toThrow();
});
it('should normalize DOI format', () => {
const paper = PaperFactory.create({ doi: '10.1000/abc' });
expect(paper.doi).toBe('10.1000/abc');
});
});
```
#### 2. Integration Tests
**Purpose**: Test platform API integrations
**Coverage**:
- Actual API calls to platforms
- Authentication validation
- Response parsing
- Error handling from APIs
**Example**:
```typescript
describe('ArxivSearcher Integration', () => {
it('should search for papers successfully', async () => {
const searcher = new ArxivSearcher();
const results = await searcher.search('machine learning');
expect(results).toBeInstanceOf(Array);
expect(results.length).toBeGreaterThan(0);
expect(results[0]).toHaveProperty('title');
expect(results[0]).toHaveProperty('authors');
});
it('should handle rate limiting', async () => {
const searcher = new ArxivSearcher();
// Make multiple rapid requests
const promises = Array(20).fill(null).map(() =>
searcher.search('test')
);
await expect(Promise.all(promises)).resolves.not.toThrow();
});
});
```
#### 3. End-to-End Tests
**Purpose**: Test complete user workflows
**Coverage**:
- MCP tool registration
- Full search workflows
- Download processes
- Multi-platform searches
**Example**:
```typescript
describe('E2E Search Workflow', () => {
it('should search across multiple platforms', async () => {
const client = await connectToMCP();
const results = await client.callTool('search_papers', {
query: 'quantum computing',
maxResults: 5
});
expect(results.papers).toBeDefined();
expect(results.papers.length).toBeLessThanOrEqual(5);
expect(results.metadata.totalResults).toBeGreaterThan(0);
});
});
```
#### 4. Performance Tests
**Purpose**: Validate system performance under load
**Coverage**:
- Rate limiting effectiveness
- Concurrent request handling
- Memory usage patterns
- Response time benchmarks
**Example**:
```typescript
describe('Performance Tests', () => {
it('should handle 100 concurrent searches', async () => {
const startTime = Date.now();
const searches = Array(100).fill(null).map(() =>
searchPapers('artificial intelligence', { maxResults: 10 })
);
const results = await Promise.all(searches);
const duration = Date.now() - startTime;
expect(duration).toBeLessThan(30000); // 30 seconds max
expect(results.every(r => r.success)).toBe(true);
});
});
```
#### 5. Security Tests
**Purpose**: Ensure secure operation
**Coverage**:
- Input sanitization
- API key protection
- Error message safety
- Rate limit enforcement
**Example**:
```typescript
describe('Security Tests', () => {
it('should sanitize malicious input', async () => {
const maliciousQuery = '<script>alert("xss")</script>';
const results = await searchPapers(maliciousQuery);
// Verify no script tags in results
const hasScript = results.papers.some(p =>
p.title.includes('<script>') || p.abstract.includes('<script>')
);
expect(hasScript).toBe(false);
});
it('should not expose API keys in errors', async () => {
process.env.WOS_API_KEY = 'secret-key-123';
try {
await searchPapers('test', { platform: 'wos' });
} catch (error) {
expect(error.message).not.toContain('secret-key-123');
}
});
});
```
### Test Structure
```
src/test/
โโโ unit/ # Unit tests
โ โโโ models/
โ โโโ utils/
โ โโโ platforms/
โโโ integration/ # Integration tests
โ โโโ platforms/
โ โโโ api/
โโโ e2e/ # End-to-end tests
โ โโโ mcp/
โ โโโ workflows/
โโโ fixtures/ # Test data
โ โโโ papers/
โ โโโ responses/
โโโ helpers/ # Test utilities
โโโ mock-server/
โโโ test-data/
```
## ๐ง Development Workflow
### 1. Local Development Setup
```bash
# 1. Clone and setup
git clone https://github.com/yourusername/paper-search-nodejs.git
cd paper-search-nodejs
# 2. Install dependencies
npm install
# 3. Setup environment
cp .env.example .env
# Edit .env with your configurations
# 4. Run in development mode
npm run dev
# 5. Run tests
npm test
```
### 2. Development Cycle
#### Feature Development
1. **Create feature branch**
```bash
git checkout -b feature/new-platform-integration
```
2. **Write tests first**
```typescript
// test/platforms/new-platform.test.ts
describe('NewPlatformSearcher', () => {
it('should implement search interface', async () => {
const searcher = new NewPlatformSearcher();
const results = await searcher.search('test query');
expect(results).toBeInstanceOf(Array);
});
});
```
3. **Implement feature**
```typescript
// src/platforms/NewPlatformSearcher.ts
export class NewPlatformSearcher extends PaperSource {
constructor() {
super('new-platform', {
rateLimit: { tokens: 10, refillRate: 1 }
});
}
async search(query: string): Promise<Paper[]> {
// Implementation
}
}
```
4. **Run tests continuously**
```bash
npm run test:watch
```
5. **Refactor and optimize**
```bash
npm run lint
npm run format
npm run type-check
```
#### Bug Fix Process
1. **Write failing test**
2. **Fix the bug**
3. **Verify test passes**
4. **Check for regressions**
### 3. Code Quality Tools
#### Linting
```bash
npm run lint # Run ESLint
npm run lint:fix # Auto-fix issues
```
#### Formatting
```bash
npm run format # Format with Prettier
npm run format:check # Check formatting
```
#### Type Checking
```bash
npm run type-check # TypeScript validation
```
#### Pre-commit Hooks
Automatic checks before commits:
- Lint validation
- Type checking
- Test execution
- Format verification
### 4. Testing Commands
```bash
# Run all tests
npm test
# Run specific test suite
npm run test:unit
npm run test:integration
npm run test:e2e
# Run with coverage
npm run test:coverage
# Run in watch mode
npm run test:watch
# Run specific test file
npm test -- --testNamePattern="ArxivSearcher"
# Debug tests
npm run test:debug
```
### 5. Platform-Specific Testing
#### Testing New Platform Integration
1. **Create test file**
```bash
touch src/test/platforms/your-platform.test.ts
```
2. **Write comprehensive tests**
```typescript
describe('YourPlatformSearcher', () => {
let searcher: YourPlatformSearcher;
beforeEach(() => {
searcher = new YourPlatformSearcher();
});
describe('search', () => {
it('should return valid paper structure', async () => {
const results = await searcher.search('machine learning');
expect(results).toBeInstanceOf(Array);
results.forEach(paper => {
expect(paper).toHaveProperty('title');
expect(paper).toHaveProperty('authors');
expect(paper).toHaveProperty('year');
expect(paper.source).toBe('your-platform');
});
});
it('should handle empty results', async () => {
const results = await searcher.search('xyzxyzxyz-noresults');
expect(results).toEqual([]);
});
it('should handle API errors gracefully', async () => {
// Mock API failure
jest.spyOn(searcher, 'performSearch').mockRejectedValue(
new Error('API Error')
);
await expect(searcher.search('test')).rejects.toThrow('API Error');
});
});
describe('rate limiting', () => {
it('should respect rate limits', async () => {
const startTime = Date.now();
// Make multiple requests
await Promise.all([
searcher.search('test1'),
searcher.search('test2'),
searcher.search('test3')
]);
const duration = Date.now() - startTime;
expect(duration).toBeGreaterThanOrEqual(2000); // Minimum delay
});
});
});
```
3. **Test with real API**
```typescript
describe('YourPlatform API Integration', () => {
// Skip if no API key
const describeIfKey = process.env.YOUR_PLATFORM_API_KEY ? describe : describe.skip;
describeIfKey('with API key', () => {
it('should authenticate successfully', async () => {
const searcher = new YourPlatformSearcher();
const results = await searcher.search('test query');
expect(results.length).toBeGreaterThan(0);
});
});
});
```
## ๐ Test Coverage Guidelines
### Coverage Targets
- **Statements**: > 85%
- **Branches**: > 80%
- **Functions**: > 90%
- **Lines**: > 85%
### Coverage Reports
```bash
# Generate coverage report
npm run test:coverage
# View HTML report
open coverage/index.html
# Check specific file coverage
npm run test:coverage -- --testPathPattern=ArxivSearcher
```
### What to Test
#### Must Test
- Core business logic
- Error handling paths
- Rate limiting behavior
- Data transformation
- Authentication flows
#### Should Test
- Edge cases
- Boundary conditions
- Concurrent operations
- Performance benchmarks
#### Optional
- Simple getters/setters
- Third-party integrations (mock)
- Configuration loading
## ๐ Continuous Integration
### CI/CD Pipeline
```yaml
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Type check
run: npm run type-check
- name: Run tests
run: npm run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
```
### Pre-release Checks
1. **Version bump**
```bash
npm version patch # or minor/major
```
2. **Update changelog**
```bash
npm run changelog
```
3. **Full test suite**
```bash
npm run test:all
```
4. **Security audit**
```bash
npm audit
npm audit fix
```
5. **Build verification**
```bash
npm run build
npm pack
```
## ๐๏ธ Test Data Management
### Mock Data Strategy
#### 1. Platform Responses
```typescript
// test/fixtures/responses/arxiv-response.xml
export const arxivResponse = `
xml version="1.0" encoding="UTF-8"
<feed xmlns="http://www.w3.org/2005/Atom">
<entry>
<title>Test Paper Title</title>
<author><name>John Doe</name></author>
<published>2024-01-01T00:00:00Z</published>
</entry>
</feed>`;
```
#### 2. Test Paper Objects
```typescript
// test/fixtures/papers/sample-papers.ts
export const samplePapers: Paper[] = [
{
title: 'Attention Is All You Need',
authors: ['Ashish Vaswani', 'Noam Shazeer'],
year: 2017,
doi: '10.5555/3295222.3295349',
source: 'arxiv',
pdfUrl: 'https://arxiv.org/pdf/1706.03762.pdf'
}
];
```
### Test Environment Setup
#### 1. Test Database (optional)
```typescript
// test/helpers/test-db.ts
export async function setupTestDB() {
// Setup in-memory database for tests
}
export async function teardownTestDB() {
// Cleanup
}
```
#### 2. Mock Servers
```typescript
// test/helpers/mock-server.ts
export function createMockPlatform() {
return http.createServer((req, res) => {
if (req.url?.includes('/search')) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(mockSearchResponse));
}
});
}
```
## ๐ Quality Metrics
### Code Quality Gates
#### Pre-commit
- Lint passes
- Type checks pass
- Unit tests > 90% pass
- No console.log statements
#### Pre-push
- All tests pass
- Coverage > 85%
- No security vulnerabilities
- Documentation updated
#### Pre-merge
- CI pipeline passes
- Code review approved
- Integration tests pass
- Performance benchmarks met
### Monitoring
#### Test Flakiness
```bash
# Run tests multiple times to detect flaky tests
for i in {1..10}; do
npm test -- --testNamePattern="unreliable test"
done
```
#### Performance Regression
```bash
# Benchmark current performance
npm run test:performance:baseline
# Compare with previous
npm run test:performance:compare
```
## ๐ง Debugging Tips
### Test Debugging
1. **Enable verbose output**
```bash
DEBUG=* npm test
```
2. **Run single test**
```bash
npm test -- --testNamePattern="specific test name"
```
3. **Debug in VS Code**
```json
{
"type": "node",
"request": "launch",
"name": "Debug Tests",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand", "--testNamePattern", "${input:testName}"]
}
```
### Platform Debugging
1. **Check API responses**
```typescript
// Add logging to platform searcher
async search(query: string): Promise<Paper[]> {
console.log('Search request:', { query, timestamp: Date.now() });
const response = await this.makeRequest(query);
console.log('Raw response:', response.data);
return this.transformResults(response.data);
}
```
2. **Mock network issues**
```typescript
// Test network failures
jest.spyOn(axios, 'get').mockRejectedValue(new Error('Network error'));
```
---
*This testing and development workflow ensures high-quality code and reliable platform integrations. Regular updates to testing strategies should be made as the project evolves.*