import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as opendata from '../ts/index.js'; // Test configuration const TEST_USER_AGENT = 'fin.cx test@fin.cx'; const TEST_TICKER = 'AAPL'; // Apple Inc - well-known test case const RATE_LIMIT_DELAY = 150; // 150ms between requests (< 10 req/sec) tap.test('SEC EDGAR Provider - Constructor', async () => { await tap.test('should create provider with valid User-Agent', async () => { const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); expect(provider.name).toEqual('SEC EDGAR'); expect(provider.priority).toEqual(100); expect(provider.requiresAuth).toBe(false); expect(provider.rateLimit?.requestsPerMinute).toEqual(600); }); await tap.test('should throw error if User-Agent is missing', async () => { expect(() => { new opendata.SecEdgarProvider({ userAgent: '' }); }).toThrow('User-Agent is required'); }); await tap.test('should throw error if User-Agent format is invalid', async () => { expect(() => { new opendata.SecEdgarProvider({ userAgent: 'InvalidFormat' }); }).toThrow('Invalid User-Agent format'); }); await tap.test('should accept valid User-Agent with space and email', async () => { const provider = new opendata.SecEdgarProvider({ userAgent: 'MyCompany contact@example.com' }); expect(provider).toBeInstanceOf(opendata.SecEdgarProvider); }); }); tap.test('SEC EDGAR Provider - Availability', async () => { const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); await tap.test('should report as available', async () => { const isAvailable = await provider.isAvailable(); expect(isAvailable).toBe(true); }); }); tap.test('SEC EDGAR Provider - Fetch Fundamentals', async () => { const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT, timeout: 30000 }); await tap.test('should fetch fundamentals for Apple (AAPL)', async () => { const fundamentals = await provider.fetchData({ type: 'fundamentals-current', ticker: TEST_TICKER }); // Verify structure expect(fundamentals).toBeDefined(); expect(fundamentals).not.toBeInstanceOf(Array); const data = fundamentals as opendata.IStockFundamentals; // Basic fields expect(data.ticker).toEqual('AAPL'); expect(data.cik).toBeDefined(); expect(data.companyName).toEqual('Apple Inc.'); expect(data.provider).toEqual('SEC EDGAR'); expect(data.timestamp).toBeInstanceOf(Date); expect(data.fetchedAt).toBeInstanceOf(Date); // Financial metrics (Apple should have all of these) expect(data.earningsPerShareDiluted).toBeDefined(); expect(data.earningsPerShareDiluted).toBeGreaterThan(0); expect(data.sharesOutstanding).toBeDefined(); expect(data.sharesOutstanding).toBeGreaterThan(0); expect(data.revenue).toBeDefined(); expect(data.revenue).toBeGreaterThan(0); expect(data.netIncome).toBeDefined(); expect(data.assets).toBeDefined(); expect(data.assets).toBeGreaterThan(0); expect(data.liabilities).toBeDefined(); expect(data.stockholdersEquity).toBeDefined(); // Metadata expect(data.fiscalYear).toBeDefined(); console.log('\n📊 Sample Apple Fundamentals:'); console.log(` Company: ${data.companyName} (CIK: ${data.cik})`); console.log(` EPS (Diluted): $${data.earningsPerShareDiluted?.toFixed(2)}`); console.log(` Shares Outstanding: ${(data.sharesOutstanding! / 1_000_000_000).toFixed(2)}B`); console.log(` Revenue: $${(data.revenue! / 1_000_000_000).toFixed(2)}B`); console.log(` Net Income: $${(data.netIncome! / 1_000_000_000).toFixed(2)}B`); console.log(` Assets: $${(data.assets! / 1_000_000_000).toFixed(2)}B`); console.log(` Fiscal Year: ${data.fiscalYear}`); }); await tap.test('should throw error for invalid ticker', async () => { try { await provider.fetchData({ type: 'fundamentals-current', ticker: 'INVALIDTICKER123456' }); throw new Error('Should have thrown error'); } catch (error) { expect(error.message).toContain('CIK not found'); } }); }); tap.test('SEC EDGAR Provider - Batch Fetch', async () => { const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT, timeout: 30000 }); await tap.test('should fetch fundamentals for multiple tickers', async () => { const result = await provider.fetchData({ type: 'fundamentals-batch', tickers: ['AAPL', 'MSFT'] }); expect(result).toBeInstanceOf(Array); const fundamentalsList = result as opendata.IStockFundamentals[]; expect(fundamentalsList.length).toEqual(2); // Check Apple const apple = fundamentalsList.find(f => f.ticker === 'AAPL'); expect(apple).toBeDefined(); expect(apple!.companyName).toEqual('Apple Inc.'); expect(apple!.earningsPerShareDiluted).toBeGreaterThan(0); // Check Microsoft const microsoft = fundamentalsList.find(f => f.ticker === 'MSFT'); expect(microsoft).toBeDefined(); expect(microsoft!.companyName).toContain('Microsoft'); expect(microsoft!.earningsPerShareDiluted).toBeGreaterThan(0); console.log('\n📊 Batch Fetch Results:'); fundamentalsList.forEach(f => { console.log(` ${f.ticker}: ${f.companyName} - EPS: $${f.earningsPerShareDiluted?.toFixed(2)}`); }); }); }); tap.test('SEC EDGAR Provider - CIK Caching', async () => { const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); await tap.test('should cache CIK lookups', async () => { // Clear cache first provider.clearCache(); let stats = provider.getCacheStats(); expect(stats.cikCacheSize).toEqual(0); // First fetch (should populate cache) await provider.fetchData({ type: 'fundamentals-current', ticker: 'AAPL' }); stats = provider.getCacheStats(); expect(stats.cikCacheSize).toBeGreaterThan(0); console.log(`\n💾 CIK Cache: ${stats.cikCacheSize} entries`); }); await tap.test('should clear cache', async () => { provider.clearCache(); const stats = provider.getCacheStats(); expect(stats.cikCacheSize).toEqual(0); expect(stats.hasTickerList).toBe(false); }); }); tap.test('SEC EDGAR Provider - Rate Limiting', async () => { const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); await tap.test('should handle multiple rapid requests without exceeding rate limit', async () => { // Make 5 requests in succession // Rate limiter should ensure we don't exceed 10 req/sec const tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META']; const startTime = Date.now(); const promises = tickers.map(ticker => provider.fetchData({ type: 'fundamentals-current', ticker }) ); const results = await Promise.all(promises); const duration = Date.now() - startTime; expect(results.length).toEqual(5); console.log(`\n⏱️ 5 requests completed in ${duration}ms (avg: ${Math.round(duration / 5)}ms/request)`); // Verify all results are valid results.forEach((result, index) => { expect(result).toBeDefined(); const data = result as opendata.IStockFundamentals; expect(data.ticker).toEqual(tickers[index]); expect(data.earningsPerShareDiluted).toBeGreaterThan(0); }); }); }); tap.test('SEC EDGAR Provider - Market Cap Calculation', async () => { const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); await tap.test('should provide data needed for market cap calculation', async () => { const fundamentals = await provider.fetchData({ type: 'fundamentals-current', ticker: 'AAPL' }) as opendata.IStockFundamentals; expect(fundamentals.sharesOutstanding).toBeDefined(); expect(fundamentals.earningsPerShareDiluted).toBeDefined(); // Simulate current price (in real usage, this comes from price provider) const simulatedPrice = 270.37; // Calculate market cap const marketCap = fundamentals.sharesOutstanding! * simulatedPrice; const pe = simulatedPrice / fundamentals.earningsPerShareDiluted!; console.log('\n💰 Calculated Metrics (with simulated price $270.37):'); console.log(` Shares Outstanding: ${(fundamentals.sharesOutstanding! / 1_000_000_000).toFixed(2)}B`); console.log(` Market Cap: $${(marketCap / 1_000_000_000_000).toFixed(2)}T`); console.log(` EPS: $${fundamentals.earningsPerShareDiluted!.toFixed(2)}`); console.log(` P/E Ratio: ${pe.toFixed(2)}`); expect(marketCap).toBeGreaterThan(0); expect(pe).toBeGreaterThan(0); }); }); export default tap.start();