import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as opendata from '../ts/index.js'; const TEST_USER_AGENT = 'fin.cx test@fin.cx'; tap.test('FundamentalsService - Provider Registration', async () => { const service = new opendata.FundamentalsService(); const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); await tap.test('should register provider', async () => { service.register(provider); const registered = service.getProvider('SEC EDGAR'); expect(registered).toBeDefined(); expect(registered?.name).toEqual('SEC EDGAR'); }); await tap.test('should get all providers', async () => { const providers = service.getAllProviders(); expect(providers.length).toBeGreaterThan(0); expect(providers[0].name).toEqual('SEC EDGAR'); }); await tap.test('should get enabled providers', async () => { const providers = service.getEnabledProviders(); expect(providers.length).toBeGreaterThan(0); }); await tap.test('should unregister provider', async () => { service.unregister('SEC EDGAR'); const registered = service.getProvider('SEC EDGAR'); expect(registered).toBeUndefined(); // Re-register for other tests service.register(provider); }); }); tap.test('FundamentalsService - Fetch Fundamentals', async () => { const service = new opendata.FundamentalsService(); const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); service.register(provider); await tap.test('should fetch fundamentals for single ticker', async () => { const fundamentals = await service.getFundamentals('AAPL'); expect(fundamentals).toBeDefined(); expect(fundamentals.ticker).toEqual('AAPL'); expect(fundamentals.companyName).toEqual('Apple Inc.'); expect(fundamentals.provider).toEqual('SEC EDGAR'); expect(fundamentals.earningsPerShareDiluted).toBeGreaterThan(0); expect(fundamentals.sharesOutstanding).toBeGreaterThan(0); console.log('\nšŸ“Š Fetched via Service:'); console.log(` ${fundamentals.ticker}: ${fundamentals.companyName}`); console.log(` EPS: $${fundamentals.earningsPerShareDiluted?.toFixed(2)}`); console.log(` Shares: ${(fundamentals.sharesOutstanding! / 1_000_000_000).toFixed(2)}B`); }); await tap.test('should fetch fundamentals for multiple tickers', async () => { const fundamentalsList = await service.getBatchFundamentals(['AAPL', 'MSFT']); expect(fundamentalsList).toBeInstanceOf(Array); expect(fundamentalsList.length).toEqual(2); const apple = fundamentalsList.find(f => f.ticker === 'AAPL'); const msft = fundamentalsList.find(f => f.ticker === 'MSFT'); expect(apple).toBeDefined(); expect(msft).toBeDefined(); expect(apple!.companyName).toEqual('Apple Inc.'); expect(msft!.companyName).toContain('Microsoft'); console.log('\nšŸ“Š Batch Fetch via Service:'); fundamentalsList.forEach(f => { console.log(` ${f.ticker}: ${f.companyName} - EPS: $${f.earningsPerShareDiluted?.toFixed(2)}`); }); }); }); tap.test('FundamentalsService - Caching', async () => { const service = new opendata.FundamentalsService({ ttl: 60000, // 60 seconds for testing maxEntries: 100 }); const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); service.register(provider); await tap.test('should cache fundamentals data', async () => { // Clear cache first service.clearCache(); let stats = service.getCacheStats(); expect(stats.size).toEqual(0); // First fetch (should hit API) const start1 = Date.now(); await service.getFundamentals('AAPL'); const duration1 = Date.now() - start1; stats = service.getCacheStats(); expect(stats.size).toEqual(1); // Second fetch (should hit cache - much faster) const start2 = Date.now(); await service.getFundamentals('AAPL'); const duration2 = Date.now() - start2; expect(duration2).toBeLessThan(duration1); console.log('\n⚔ Cache Performance:'); console.log(` First fetch: ${duration1}ms`); console.log(` Cached fetch: ${duration2}ms`); console.log(` Speedup: ${Math.round(duration1 / duration2)}x`); }); await tap.test('should respect cache TTL', async () => { // Set very short TTL service.setCacheTTL(100); // 100ms // Fetch and cache await service.getFundamentals('MSFT'); // Wait for TTL to expire await new Promise(resolve => setTimeout(resolve, 150)); // This should fetch again (cache expired) const stats = service.getCacheStats(); console.log(`\nā±ļø Cache TTL: ${stats.ttl}ms`); }); await tap.test('should clear cache', async () => { service.clearCache(); const stats = service.getCacheStats(); expect(stats.size).toEqual(0); }); }); tap.test('FundamentalsService - Price Enrichment', async () => { const service = new opendata.FundamentalsService(); const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); service.register(provider); await tap.test('should enrich fundamentals with price to calculate market cap', async () => { const fundamentals = await service.getFundamentals('AAPL'); // Simulate current price const currentPrice = 270.37; const enriched = await service.enrichWithPrice(fundamentals, currentPrice); expect(enriched.marketCap).toBeDefined(); expect(enriched.priceToEarnings).toBeDefined(); expect(enriched.priceToBook).toBeDefined(); expect(enriched.marketCap).toBeGreaterThan(0); expect(enriched.priceToEarnings).toBeGreaterThan(0); console.log('\nšŸ’° Enriched with Price ($270.37):'); console.log(` Market Cap: $${(enriched.marketCap! / 1_000_000_000_000).toFixed(2)}T`); console.log(` P/E Ratio: ${enriched.priceToEarnings!.toFixed(2)}`); console.log(` P/B Ratio: ${enriched.priceToBook?.toFixed(2) || 'N/A'}`); // Verify calculations const expectedMarketCap = fundamentals.sharesOutstanding! * currentPrice; expect(Math.abs(enriched.marketCap! - expectedMarketCap)).toBeLessThan(1); // Allow for rounding const expectedPE = currentPrice / fundamentals.earningsPerShareDiluted!; expect(Math.abs(enriched.priceToEarnings! - expectedPE)).toBeLessThan(0.01); }); await tap.test('should enrich batch fundamentals with prices', async () => { const fundamentalsList = await service.getBatchFundamentals(['AAPL', 'MSFT']); const priceMap = new Map([ ['AAPL', 270.37], ['MSFT', 425.50] ]); const enriched = await service.enrichBatchWithPrices(fundamentalsList, priceMap); expect(enriched.length).toEqual(2); const apple = enriched.find(f => f.ticker === 'AAPL')!; const msft = enriched.find(f => f.ticker === 'MSFT')!; expect(apple.marketCap).toBeGreaterThan(0); expect(msft.marketCap).toBeGreaterThan(0); console.log('\nšŸ’° Batch Enrichment:'); console.log(` AAPL: Market Cap $${(apple.marketCap! / 1_000_000_000_000).toFixed(2)}T, P/E ${apple.priceToEarnings!.toFixed(2)}`); console.log(` MSFT: Market Cap $${(msft.marketCap! / 1_000_000_000_000).toFixed(2)}T, P/E ${msft.priceToEarnings!.toFixed(2)}`); }); }); tap.test('FundamentalsService - Provider Health', async () => { const service = new opendata.FundamentalsService(); const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); service.register(provider); await tap.test('should check provider health', async () => { const health = await service.checkProvidersHealth(); expect(health.size).toEqual(1); expect(health.get('SEC EDGAR')).toBe(true); console.log('\nšŸ’š Provider Health:'); health.forEach((isHealthy, name) => { console.log(` ${name}: ${isHealthy ? 'āœ… Healthy' : 'āŒ Unhealthy'}`); }); }); }); tap.test('FundamentalsService - Provider Statistics', async () => { const service = new opendata.FundamentalsService(); const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); service.register(provider); await tap.test('should track provider statistics', async () => { // Make some requests await service.getFundamentals('AAPL'); await service.getFundamentals('MSFT'); const stats = service.getProviderStats(); expect(stats.size).toEqual(1); const secStats = stats.get('SEC EDGAR'); expect(secStats).toBeDefined(); expect(secStats!.successCount).toBeGreaterThan(0); console.log('\nšŸ“ˆ Provider Stats:'); console.log(` Success Count: ${secStats!.successCount}`); console.log(` Error Count: ${secStats!.errorCount}`); }); }); tap.test('FundamentalsService - Error Handling', async () => { const service = new opendata.FundamentalsService(); const provider = new opendata.SecEdgarProvider({ userAgent: TEST_USER_AGENT }); service.register(provider); await tap.test('should throw error for invalid ticker', async () => { try { await service.getFundamentals('INVALIDTICKER123456'); throw new Error('Should have thrown error'); } catch (error) { expect(error.message).toContain('CIK not found'); } }); await tap.test('should throw error when no providers available', async () => { const emptyService = new opendata.FundamentalsService(); try { await emptyService.getFundamentals('AAPL'); throw new Error('Should have thrown error'); } catch (error) { expect(error.message).toContain('No fundamentals providers available'); } }); }); export default tap.start();