Thank you for considering contributing to the Substack API client library! This document provides guidelines for contributing to this project.
- Fork the repository
- Clone your fork locally
- Install dependencies:
npm install - Run tests to verify setup:
npm test
This project uses a three-layer testing architecture designed to provide comprehensive coverage while maintaining clear separation of concerns:
Location: tests/unit/
Purpose: Test pure functions, utilities, and individual components in isolation
Characteristics:
- Fast execution (< 1 second)
- No I/O operations
- No network calls
- Mock external dependencies
- High code coverage
Running unit tests:
npm run test:unit # Run once
npm run test:watch # Run in watch modeExample test structure:
describe('SubstackClient', () => {
let client: SubstackClient
beforeEach(() => {
client = new SubstackClient({ apiKey: 'test', hostname: 'test.com' })
})
test('should handle API responses correctly', () => {
// Test implementation
})
})Location: tests/integration/
Purpose: Test SDK behavior against known Substack API responses using sample data
Characteristics:
- Medium execution time (< 10 seconds)
- Uses local HTTP server with sample responses
- Tests data parsing and entity creation
- Validates sample data consistency
- No real network calls
Running integration tests:
npm run test:integration # Run once
npm run test:integration:watch # Run in watch modeSample data:
Integration tests use sample API responses stored in samples/api/v1/:
subscription- Single subscription datasubscriptions- Complete subscriptions list with publicationsuser/*/profile- User profile datauser/*/public_profile- Public profile data
Example integration test:
describe('Sample Data Integration', () => {
test('should parse subscription data correctly', () => {
const samplePath = join(samplesDir, 'subscription')
const sampleData = JSON.parse(readFileSync(samplePath, 'utf8'))
expect(sampleData.id).toBeDefined()
expect(sampleData.membership_state).toBe('subscribed')
})
})Location: tests/e2e/
Purpose: Live testing against the actual Substack API using real credentials
Characteristics:
- ✅ Read-only operations only (e.g., fetching profiles, subscriptions, posts)
- ❌ No create/update/delete operations allowed
- Mandatory - CI fails if credentials are missing
- Enforcement - Tests fail immediately without proper credentials
Running E2E tests:
# Setup credentials first
cp .env.example .env
# Edit .env and add your SUBSTACK_TOKEN
npm run test:e2e # Run once
npm run test:e2e:watch # Run in watch mode
npm run test:all # Run all test typesCredential requirements:
SUBSTACK_TOKEN: Required - Your Substack tokenSUBSTACK_PUBLICATION_URL: Optional - Your Substack publication URL
Example E2E test:
describe('SubstackClient E2E', () => {
beforeAll(() => {
if (!global.E2E_CONFIG.hasCredentials) {
throw new Error('E2E tests require credentials')
}
})
test('should fetch real profile data', async () => {
const profile = await client.profileForSlug('platformer')
expect(profile.name).toBeTruthy()
})
})The GitHub Actions workflow runs all three test layers:
- Unit tests - Always run, provide code coverage
- Integration tests - Always run, validate sample data
- E2E tests - Run with repository secrets, fail if credentials missing
- Create test file in
tests/unit/ - Use
.test.tssuffix - Mock external dependencies
- Test individual functions/methods
- Aim for high coverage
- Create test file in
tests/integration/ - Use
.integration.test.tssuffix - Test against sample data from
samples/api/v1/ - Validate data structures and parsing
- Ensure referential integrity
- Create test file in
tests/e2e/ - Use
.e2e.test.tssuffix - Only use read-only operations
- Handle API errors gracefully
- Add descriptive logging for debugging
When adding new sample data files:
- Place in appropriate
samples/api/v1/subdirectory - Ensure valid JSON format
- Remove sensitive information
- Use realistic, representative data
- Maintain consistency with existing samples
# Verify current state
npm run lint # Check code style
npm run build # Ensure compilation works
npm test # Run all tests- Write tests first (TDD approach recommended)
- Make minimal changes to pass tests
- Ensure all tests pass
- Update documentation if needed
# Final verification
npm run lint # Fix any linting issues
npm run format # Format code consistently
npm run build # Ensure clean build
npm test # All tests must pass- Use TypeScript strictly - avoid
anyunless necessary - Prefer named exports and immutable data
- Keep functions pure where possible
- Use
async/awaitand handle errors gracefully - Add JSDoc comments on all public-facing methods
- Follow existing patterns and conventions
- Keep changes focused and atomic
- Include tests for new functionality
- Update documentation as needed
- Follow conventional commits:
feat:,fix:,test:,docs: - Describe your changes in the PR description
- Ensure all CI checks pass
- Check mocks are properly configured
- Verify TypeScript types match expectations
- Use
console.logfor debugging (remove before commit)
- Validate sample data JSON is valid
- Check file paths in
samples/api/v1/ - Ensure data structures match expected interfaces
- Verify
.envfile has valid credentials - Check token permissions
- Review network connectivity
- Some operations may not be available for all account types
- Check existing tests for patterns and examples
- Review the codebase for similar implementations
- Open an issue for clarification on requirements
- Ask questions in pull request discussions
- Never commit your
.envfile or API credentials - Use repository secrets for CI/CD credentials
- Tokens should have minimal required permissions
- All E2E tests must be read-only operations
Thank you for contributing to make this library better! 🚀