feat(laws,opendata): add local law storage and migrate OpenData persistence to smartdb-backed local storage
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as opendata from '../ts/index.js';
|
||||
import * as plugins from '../ts/plugins.js';
|
||||
|
||||
// Test data
|
||||
const testCryptos = ['BTC', 'ETH', 'USDT'];
|
||||
@@ -8,6 +9,28 @@ const invalidCrypto = 'INVALID_CRYPTO_XYZ_12345';
|
||||
|
||||
let stockService: opendata.StockPriceService;
|
||||
let coingeckoProvider: opendata.CoinGeckoProvider;
|
||||
let coingeckoAvailable = false;
|
||||
let coingeckoSkipReason = 'CoinGecko integration requirements are unavailable.';
|
||||
|
||||
type TSkipTools = {
|
||||
skip: (reason?: string) => never;
|
||||
};
|
||||
|
||||
const runCoinGeckoRequest = async <T>(toolsArg: TSkipTools, operation: () => Promise<T>): Promise<T> => {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
const errorMessage = plugins.getErrorMessage(error);
|
||||
|
||||
if (errorMessage.includes('Rate limit exceeded')) {
|
||||
coingeckoAvailable = false;
|
||||
coingeckoSkipReason = `Skipping CoinGecko integration tests: ${errorMessage}.`;
|
||||
toolsArg.skip(coingeckoSkipReason);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
tap.test('should create StockPriceService instance', async () => {
|
||||
stockService = new opendata.StockPriceService({
|
||||
@@ -23,22 +46,29 @@ tap.test('should create CoinGeckoProvider instance without API key', async () =>
|
||||
expect(coingeckoProvider.name).toEqual('CoinGecko');
|
||||
expect(coingeckoProvider.requiresAuth).toEqual(false);
|
||||
expect(coingeckoProvider.priority).toEqual(90);
|
||||
coingeckoAvailable = await coingeckoProvider.isAvailable();
|
||||
if (!coingeckoAvailable) {
|
||||
coingeckoSkipReason = 'Skipping CoinGecko integration tests: provider is not reachable.';
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('should register CoinGecko provider with the service', async () => {
|
||||
tap.test('should register CoinGecko provider with the service', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
stockService.register(coingeckoProvider);
|
||||
const providers = stockService.getAllProviders();
|
||||
expect(providers).toContainEqual(coingeckoProvider);
|
||||
expect(stockService.getProvider('CoinGecko')).toEqual(coingeckoProvider);
|
||||
});
|
||||
|
||||
tap.test('should check CoinGecko provider health', async () => {
|
||||
const health = await stockService.checkProvidersHealth();
|
||||
tap.test('should check CoinGecko provider health', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
const health = await runCoinGeckoRequest(toolsArg, async () => stockService.checkProvidersHealth());
|
||||
expect(health.get('CoinGecko')).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should fetch single crypto price using ticker symbol (BTC)', async () => {
|
||||
const price = await stockService.getPrice({ ticker: 'BTC' });
|
||||
tap.test('should fetch single crypto price using ticker symbol (BTC)', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
const price = await runCoinGeckoRequest(toolsArg, async () => stockService.getPrice({ ticker: 'BTC' }));
|
||||
|
||||
expect(price).toHaveProperty('ticker');
|
||||
expect(price).toHaveProperty('price');
|
||||
@@ -62,11 +92,12 @@ tap.test('should fetch single crypto price using ticker symbol (BTC)', async ()
|
||||
console.log(` Change: ${price.changePercent >= 0 ? '+' : ''}${price.changePercent.toFixed(2)}%`);
|
||||
});
|
||||
|
||||
tap.test('should fetch single crypto price using CoinGecko ID (bitcoin)', async () => {
|
||||
tap.test('should fetch single crypto price using CoinGecko ID (bitcoin)', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
// Clear cache to ensure fresh fetch
|
||||
stockService.clearCache();
|
||||
|
||||
const price = await stockService.getPrice({ ticker: 'bitcoin' });
|
||||
const price = await runCoinGeckoRequest(toolsArg, async () => stockService.getPrice({ ticker: 'bitcoin' }));
|
||||
|
||||
expect(price.ticker).toEqual('BITCOIN');
|
||||
expect(price.price).toBeGreaterThan(0);
|
||||
@@ -74,12 +105,13 @@ tap.test('should fetch single crypto price using CoinGecko ID (bitcoin)', async
|
||||
expect(price.companyName).toInclude('Bitcoin');
|
||||
});
|
||||
|
||||
tap.test('should fetch multiple crypto prices (batch)', async () => {
|
||||
tap.test('should fetch multiple crypto prices (batch)', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
stockService.clearCache();
|
||||
|
||||
const prices = await stockService.getPrices({
|
||||
const prices = await runCoinGeckoRequest(toolsArg, async () => stockService.getPrices({
|
||||
tickers: testCryptos
|
||||
});
|
||||
}));
|
||||
|
||||
expect(prices).toBeArray();
|
||||
expect(prices.length).toEqual(testCryptos.length);
|
||||
@@ -98,19 +130,20 @@ tap.test('should fetch multiple crypto prices (batch)', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('should fetch historical crypto prices', async () => {
|
||||
tap.test('should fetch historical crypto prices', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
// Add delay to avoid rate limiting
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
const to = new Date();
|
||||
const from = new Date(to.getTime() - 7 * 24 * 60 * 60 * 1000); // 7 days ago
|
||||
|
||||
const prices = await stockService.getData({
|
||||
const prices = await runCoinGeckoRequest(toolsArg, async () => stockService.getData({
|
||||
type: 'historical',
|
||||
ticker: 'BTC',
|
||||
from: from,
|
||||
to: to
|
||||
});
|
||||
}));
|
||||
|
||||
expect(prices).toBeArray();
|
||||
expect((prices as opendata.IStockPrice[]).length).toBeGreaterThan(0);
|
||||
@@ -143,16 +176,17 @@ tap.test('should fetch historical crypto prices', async () => {
|
||||
expect(firstPrice.provider).toEqual('CoinGecko');
|
||||
});
|
||||
|
||||
tap.test('should fetch intraday crypto prices (hourly)', async () => {
|
||||
tap.test('should fetch intraday crypto prices (hourly)', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
// Add delay to avoid rate limiting
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
const prices = await stockService.getData({
|
||||
const prices = await runCoinGeckoRequest(toolsArg, async () => stockService.getData({
|
||||
type: 'intraday',
|
||||
ticker: 'ETH',
|
||||
interval: '1hour',
|
||||
limit: 12 // Last 12 hours
|
||||
});
|
||||
}));
|
||||
|
||||
expect(prices).toBeArray();
|
||||
expect((prices as opendata.IStockPrice[]).length).toBeGreaterThan(0);
|
||||
@@ -175,9 +209,10 @@ tap.test('should fetch intraday crypto prices (hourly)', async () => {
|
||||
expect(firstPrice.provider).toEqual('CoinGecko');
|
||||
});
|
||||
|
||||
tap.test('should serve cached prices on subsequent requests', async () => {
|
||||
tap.test('should serve cached prices on subsequent requests', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
// First request - should hit the API
|
||||
const firstRequest = await stockService.getPrice({ ticker: 'BTC' });
|
||||
const firstRequest = await runCoinGeckoRequest(toolsArg, async () => stockService.getPrice({ ticker: 'BTC' }));
|
||||
|
||||
// Second request - should be served from cache
|
||||
const secondRequest = await stockService.getPrice({ ticker: 'BTC' });
|
||||
@@ -188,23 +223,26 @@ tap.test('should serve cached prices on subsequent requests', async () => {
|
||||
expect(secondRequest.fetchedAt).toEqual(firstRequest.fetchedAt);
|
||||
});
|
||||
|
||||
tap.test('should handle invalid crypto ticker gracefully', async () => {
|
||||
tap.test('should handle invalid crypto ticker gracefully', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
try {
|
||||
await stockService.getPrice({ ticker: invalidCrypto });
|
||||
await runCoinGeckoRequest(toolsArg, async () => stockService.getPrice({ ticker: invalidCrypto }));
|
||||
throw new Error('Should have thrown an error for invalid ticker');
|
||||
} catch (error) {
|
||||
expect(error.message).toInclude('Failed to fetch');
|
||||
expect(plugins.getErrorMessage(error)).toInclude('Failed to fetch');
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('should support market checking', async () => {
|
||||
tap.test('should support market checking', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
expect(coingeckoProvider.supportsMarket('CRYPTO')).toEqual(true);
|
||||
expect(coingeckoProvider.supportsMarket('BTC')).toEqual(true);
|
||||
expect(coingeckoProvider.supportsMarket('ETH')).toEqual(true);
|
||||
expect(coingeckoProvider.supportsMarket('NASDAQ')).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should support ticker validation', async () => {
|
||||
tap.test('should support ticker validation', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
expect(coingeckoProvider.supportsTicker('BTC')).toEqual(true);
|
||||
expect(coingeckoProvider.supportsTicker('bitcoin')).toEqual(true);
|
||||
expect(coingeckoProvider.supportsTicker('wrapped-bitcoin')).toEqual(true);
|
||||
@@ -212,11 +250,15 @@ tap.test('should support ticker validation', async () => {
|
||||
expect(coingeckoProvider.supportsTicker('BTC@USD')).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should display provider statistics', async () => {
|
||||
tap.test('should display provider statistics', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
const stats = stockService.getProviderStats();
|
||||
const coingeckoStats = stats.get('CoinGecko');
|
||||
|
||||
expect(coingeckoStats).toBeTruthy();
|
||||
if (!coingeckoStats) {
|
||||
throw new Error('Missing CoinGecko stats');
|
||||
}
|
||||
expect(coingeckoStats.successCount).toBeGreaterThan(0);
|
||||
|
||||
console.log('\n📊 CoinGecko Provider Statistics:');
|
||||
@@ -227,14 +269,15 @@ tap.test('should display provider statistics', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('should display crypto price dashboard', async () => {
|
||||
tap.test('should display crypto price dashboard', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
// Add delay to avoid rate limiting
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
stockService.clearCache();
|
||||
|
||||
const cryptos = ['BTC', 'ETH', 'BNB', 'SOL', 'ADA'];
|
||||
const prices = await stockService.getPrices({ tickers: cryptos });
|
||||
const prices = await runCoinGeckoRequest(toolsArg, async () => stockService.getPrices({ tickers: cryptos }));
|
||||
|
||||
console.log('\n╔═══════════════════════════════════════════════════════════╗');
|
||||
console.log('║ 🌐 CRYPTOCURRENCY PRICE DASHBOARD ║');
|
||||
@@ -253,7 +296,8 @@ tap.test('should display crypto price dashboard', async () => {
|
||||
console.log(`Fetched at: ${prices[0].fetchedAt.toISOString()}`);
|
||||
});
|
||||
|
||||
tap.test('should clear cache', async () => {
|
||||
tap.test('should clear cache', async (toolsArg) => {
|
||||
toolsArg.skipIf(!coingeckoAvailable, coingeckoSkipReason);
|
||||
stockService.clearCache();
|
||||
// Cache is cleared, no assertions needed
|
||||
});
|
||||
@@ -12,6 +12,8 @@ const testGermanBusinessDataDir = plugins.path.join(testNogitDir, 'germanbusines
|
||||
const testOutputDir = plugins.path.join(testNogitDir, 'testoutput');
|
||||
|
||||
let testOpenDataInstance: opendata.OpenData;
|
||||
let handelsregisterStarted = false;
|
||||
let handelsregisterSkipReason = 'Handelsregister integration requirements are unavailable.';
|
||||
|
||||
tap.test('first test', async () => {
|
||||
testOpenDataInstance = new opendata.OpenData({
|
||||
@@ -23,32 +25,44 @@ tap.test('first test', async () => {
|
||||
});
|
||||
|
||||
tap.test('should start the instance', async () => {
|
||||
await testOpenDataInstance.start();
|
||||
try {
|
||||
await testOpenDataInstance.start();
|
||||
handelsregisterStarted = true;
|
||||
} catch (error) {
|
||||
handelsregisterSkipReason = `Skipping Handelsregister integration tests: ${plugins.getErrorMessage(error)}`;
|
||||
console.warn(handelsregisterSkipReason);
|
||||
}
|
||||
});
|
||||
|
||||
const resultsSearch = tap.test('should get the data for a company', async () => {
|
||||
const resultsSearch = tap.test('should get the data for a company', async (toolsArg) => {
|
||||
toolsArg.skipIf(!handelsregisterStarted, handelsregisterSkipReason);
|
||||
const result = await testOpenDataInstance.handelsregister.searchCompany('LADR', 20);
|
||||
console.log(result);
|
||||
return result;
|
||||
});
|
||||
|
||||
tap.test('should get the data for a specific company', async () => {
|
||||
let testCompany: BusinessRecord['data']['germanParsedRegistration'] = (await resultsSearch.testResultPromise)[0]['germanParsedRegistration'];
|
||||
tap.test('should get the data for a specific company', async (toolsArg) => {
|
||||
toolsArg.skipIf(!handelsregisterStarted, handelsregisterSkipReason);
|
||||
const searchResults = await resultsSearch.testResultPromise as BusinessRecord['data'][];
|
||||
let testCompany: BusinessRecord['data']['germanParsedRegistration'] = searchResults[0].germanParsedRegistration;
|
||||
console.log(`trying to find specific company with:`);
|
||||
console.log(testCompany);
|
||||
const result = await testOpenDataInstance.handelsregister.getSpecificCompany(testCompany);
|
||||
console.log(result);
|
||||
|
||||
await plugins.smartfs.directory(testOutputDir).create();
|
||||
await Promise.all(result.files.map(async (file) => {
|
||||
await file.writeToDir(testOutputDir);
|
||||
await file.writeToDiskAtPath(
|
||||
plugins.path.join(testOutputDir, plugins.path.basename(file.path))
|
||||
);
|
||||
}));
|
||||
|
||||
|
||||
});
|
||||
|
||||
tap.test('should stop the instance', async (toolsArg) => {
|
||||
toolsArg.skipIf(!handelsregisterStarted, handelsregisterSkipReason);
|
||||
await testOpenDataInstance.stop();
|
||||
});
|
||||
|
||||
|
||||
tap.start()
|
||||
export default tap.start()
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as opendata from '../ts/index.js';
|
||||
import * as paths from '../ts/paths.js';
|
||||
import * as plugins from '../ts/plugins.js';
|
||||
|
||||
const testDbFolder = plugins.path.join(paths.packageDir, '.nogit', 'law-smartdb-test');
|
||||
|
||||
let lawService: opendata.LawService;
|
||||
|
||||
tap.test('LawService - setup local smartdb', async () => {
|
||||
await plugins.smartfs.directory(testDbFolder).recursive().delete().catch(() => {});
|
||||
|
||||
lawService = new opendata.LawService({
|
||||
dbFolderPath: testDbFolder,
|
||||
dbName: 'lawstest',
|
||||
});
|
||||
|
||||
await lawService.start();
|
||||
expect(lawService).toBeInstanceOf(opendata.LawService);
|
||||
});
|
||||
|
||||
tap.test('LawService - sync and search Germany law', async () => {
|
||||
const germanLaw = await lawService.syncLaw({
|
||||
jurisdiction: 'de',
|
||||
identifier: 'aeg',
|
||||
});
|
||||
|
||||
expect(germanLaw.identifier).toEqual('aeg');
|
||||
expect(germanLaw.title).toInclude('Eisenbahngesetz');
|
||||
expect(germanLaw.text).toInclude('Ausgleichspflicht');
|
||||
|
||||
const results = await lawService.searchLaws({
|
||||
jurisdiction: 'de',
|
||||
query: 'Eisenbahngesetz',
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].identifier).toEqual('aeg');
|
||||
});
|
||||
|
||||
tap.test('LawService - sync and search EU law', async () => {
|
||||
const euLaw = await lawService.syncLaw({
|
||||
jurisdiction: 'eu',
|
||||
identifier: '32024R1689',
|
||||
language: 'EN',
|
||||
});
|
||||
|
||||
expect(euLaw.identifier).toEqual('32024R1689');
|
||||
expect(euLaw.title).toInclude('Artificial Intelligence Act');
|
||||
expect(euLaw.text.toLowerCase()).toInclude('artificial intelligence');
|
||||
|
||||
const results = await lawService.searchLaws({
|
||||
jurisdiction: 'eu',
|
||||
query: 'Artificial Intelligence Act',
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].identifier).toEqual('32024R1689');
|
||||
});
|
||||
|
||||
tap.test('LawService - sync and search USA law', async () => {
|
||||
const usLaw = await lawService.syncLaw({
|
||||
jurisdiction: 'us',
|
||||
identifier: 'PLAW-119publ1',
|
||||
usCollection: 'PLAW',
|
||||
});
|
||||
|
||||
expect(usLaw.identifier).toEqual('PLAW-119publ1');
|
||||
expect(usLaw.shortTitle).toInclude('Laken Riley Act');
|
||||
expect(usLaw.text).toInclude('To require the Secretary of Homeland Security');
|
||||
|
||||
const results = await lawService.searchLaws({
|
||||
jurisdiction: 'us',
|
||||
query: 'Laken Riley Act',
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].identifier).toEqual('PLAW-119publ1');
|
||||
});
|
||||
|
||||
tap.test('LawService - sync and search USA code citation', async () => {
|
||||
const usCodeSection = await lawService.syncLaw({
|
||||
jurisdiction: 'us',
|
||||
identifier: '8 U.S.C. § 1226',
|
||||
});
|
||||
|
||||
expect(usCodeSection.identifier).toEqual('8 USC 1226');
|
||||
expect(usCodeSection.citation).toEqual('8 USC 1226');
|
||||
expect(usCodeSection.title).toInclude('Apprehension and detention of aliens');
|
||||
expect(usCodeSection.text).toInclude('Detention of criminal aliens');
|
||||
|
||||
const results = await lawService.searchLaws({
|
||||
jurisdiction: 'us',
|
||||
query: 'Apprehension and detention of aliens',
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results.map((lawArg) => lawArg.identifier).includes('8 USC 1226')).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('LawService - local lookup returns synced law', async () => {
|
||||
const euLaw = await lawService.getLaw({
|
||||
jurisdiction: 'eu',
|
||||
identifier: '32024R1689',
|
||||
language: 'EN',
|
||||
});
|
||||
|
||||
expect(euLaw).toBeDefined();
|
||||
expect(euLaw?.title).toInclude('Artificial Intelligence Act');
|
||||
|
||||
const usCodeLaw = await lawService.getLaw({
|
||||
jurisdiction: 'us',
|
||||
identifier: '8 USC 1226(c)',
|
||||
});
|
||||
|
||||
expect(usCodeLaw).toBeDefined();
|
||||
expect(usCodeLaw?.identifier).toEqual('8 USC 1226');
|
||||
});
|
||||
|
||||
tap.test('LawService - teardown local smartdb', async () => {
|
||||
await lawService.stop();
|
||||
await plugins.smartfs.directory(testDbFolder).recursive().delete().catch(() => {});
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -13,6 +13,8 @@ const invalidTicker = 'INVALID_TICKER_XYZ';
|
||||
let stockService: opendata.StockPriceService;
|
||||
let marketstackProvider: opendata.MarketstackProvider;
|
||||
let testQenv: plugins.qenv.Qenv;
|
||||
let marketstackAvailable = false;
|
||||
let marketstackSkipReason = 'Marketstack integration requirements are unavailable.';
|
||||
|
||||
tap.test('should create StockPriceService instance', async () => {
|
||||
stockService = new opendata.StockPriceService({
|
||||
@@ -27,6 +29,10 @@ tap.test('should create MarketstackProvider instance', async () => {
|
||||
// Create qenv and get API key
|
||||
testQenv = new plugins.qenv.Qenv(paths.packageDir, testNogitDir);
|
||||
const apiKey = await testQenv.getEnvVarOnDemand('MARKETSTACK_COM_TOKEN');
|
||||
if (!apiKey) {
|
||||
marketstackSkipReason = 'Skipping Marketstack integration tests: MARKETSTACK_COM_TOKEN not set.';
|
||||
return;
|
||||
}
|
||||
|
||||
marketstackProvider = new opendata.MarketstackProvider(apiKey, {
|
||||
enabled: true,
|
||||
@@ -37,13 +43,17 @@ tap.test('should create MarketstackProvider instance', async () => {
|
||||
expect(marketstackProvider).toBeInstanceOf(opendata.MarketstackProvider);
|
||||
expect(marketstackProvider.name).toEqual('Marketstack');
|
||||
expect(marketstackProvider.requiresAuth).toEqual(true);
|
||||
expect(marketstackProvider.priority).toEqual(80);
|
||||
expect(marketstackProvider.priority).toEqual(90);
|
||||
|
||||
marketstackAvailable = await marketstackProvider.isAvailable();
|
||||
if (!marketstackAvailable) {
|
||||
marketstackSkipReason = 'Skipping Marketstack integration tests: provider is not reachable.';
|
||||
marketstackProvider = undefined as any;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.message.includes('MARKETSTACK_COM_TOKEN')) {
|
||||
if (plugins.getErrorMessage(error).includes('MARKETSTACK_COM_TOKEN')) {
|
||||
console.log('⚠️ MARKETSTACK_COM_TOKEN not set - skipping Marketstack tests');
|
||||
tap.test('Marketstack token not available', async () => {
|
||||
expect(true).toEqual(true); // Skip gracefully
|
||||
});
|
||||
marketstackSkipReason = 'Skipping Marketstack integration tests: MARKETSTACK_COM_TOKEN not set.';
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
@@ -64,7 +74,7 @@ tap.test('should register Marketstack provider with the service', async () => {
|
||||
|
||||
tap.test('should check provider health', async () => {
|
||||
if (!marketstackProvider) {
|
||||
console.log('⚠️ Skipping - Marketstack provider not initialized');
|
||||
console.log(`⚠️ ${marketstackSkipReason}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,7 +161,7 @@ tap.test('should handle invalid ticker gracefully', async () => {
|
||||
await stockService.getPrice({ ticker: invalidTicker });
|
||||
throw new Error('Should have thrown an error for invalid ticker');
|
||||
} catch (error) {
|
||||
expect(error.message).toInclude('Failed to fetch');
|
||||
expect(plugins.getErrorMessage(error)).toInclude('Failed to fetch');
|
||||
console.log('✓ Invalid ticker handled correctly');
|
||||
}
|
||||
});
|
||||
@@ -196,6 +206,9 @@ tap.test('should get provider statistics', async () => {
|
||||
const marketstackStats = stats.get('Marketstack');
|
||||
|
||||
expect(marketstackStats).not.toEqual(undefined);
|
||||
if (!marketstackStats) {
|
||||
throw new Error('Missing Marketstack stats');
|
||||
}
|
||||
expect(marketstackStats.successCount).toBeGreaterThan(0);
|
||||
expect(marketstackStats.errorCount).toBeGreaterThanOrEqual(0);
|
||||
|
||||
@@ -280,7 +293,7 @@ tap.test('should fetch sample EOD data', async () => {
|
||||
console.log(`Provider: Marketstack (EOD Data)`);
|
||||
console.log(`Last updated: ${new Date().toLocaleString()}\n`);
|
||||
} catch (error) {
|
||||
console.log('Error fetching sample data:', error.message);
|
||||
console.log('Error fetching sample data:', plugins.getErrorMessage(error));
|
||||
}
|
||||
|
||||
expect(true).toEqual(true);
|
||||
+35
-6
@@ -11,6 +11,8 @@ const testDownloadDir = plugins.path.join(testNogitDir, 'downloads');
|
||||
const testGermanBusinessDataDir = plugins.path.join(testNogitDir, 'germanbusinessdata');
|
||||
|
||||
let testOpenDataInstance: opendata.OpenData;
|
||||
let openDataStarted = false;
|
||||
let openDataSkipReason = 'OpenData integration requirements are unavailable.';
|
||||
|
||||
tap.test('first test', async () => {
|
||||
testOpenDataInstance = new opendata.OpenData({
|
||||
@@ -22,16 +24,43 @@ tap.test('first test', async () => {
|
||||
});
|
||||
|
||||
tap.test('should start the instance', async () => {
|
||||
await testOpenDataInstance.start();
|
||||
try {
|
||||
await testOpenDataInstance.start();
|
||||
openDataStarted = true;
|
||||
} catch (error) {
|
||||
openDataSkipReason = `Skipping OpenData integration tests: ${plugins.getErrorMessage(error)}`;
|
||||
console.warn(openDataSkipReason);
|
||||
}
|
||||
})
|
||||
|
||||
tap.test('should build initial data', async () => {
|
||||
await testOpenDataInstance.buildInitialDb();
|
||||
tap.test('should persist business records using local smartdb', async (toolsArg) => {
|
||||
toolsArg.skipIf(!openDataStarted, openDataSkipReason);
|
||||
|
||||
const businessRecord = new testOpenDataInstance.CBusinessRecord();
|
||||
businessRecord.id = await testOpenDataInstance.CBusinessRecord.getNewId();
|
||||
businessRecord.data.name = `Test Company ${plugins.smartunique.uniSimple()}`;
|
||||
businessRecord.data.germanParsedRegistration = {
|
||||
court: 'Bremen',
|
||||
type: 'HRB',
|
||||
number: `${Date.now()}`,
|
||||
};
|
||||
|
||||
await businessRecord.save();
|
||||
|
||||
const storedRecord = await BusinessRecord.getInstance({
|
||||
id: businessRecord.id,
|
||||
});
|
||||
|
||||
expect(storedRecord.id).toEqual(businessRecord.id);
|
||||
expect(storedRecord.data.name).toEqual(businessRecord.data.name);
|
||||
expect(storedRecord.data.germanParsedRegistration).toEqual(
|
||||
businessRecord.data.germanParsedRegistration
|
||||
);
|
||||
});
|
||||
|
||||
tap.test('should stop the instance', async () => {
|
||||
tap.test('should stop the instance', async (toolsArg) => {
|
||||
toolsArg.skipIf(!openDataStarted, openDataSkipReason);
|
||||
await testOpenDataInstance.stop();
|
||||
});
|
||||
|
||||
|
||||
tap.start()
|
||||
export default tap.start()
|
||||
|
||||
Reference in New Issue
Block a user