const request = require('supertest'); const { expect } = require('chai'); const app = require('../src/server'); const { getHistoricalCandles, validateCandles, generateTestCandles } = require('../services/candlestickService'); describe('Candlestick API Tests', () => { const testSymbol = 'EURUSD'; const testTimeframe = 'H1'; describe('GET /api/prices/validate-candles/:symbol', () => { it('should validate candles for a valid symbol', async () => { const response = await request(app) .get(`/api/prices/validate-candles/${testSymbol}`) .query({ timeframe: testTimeframe, limit: 50 }) .expect(200); expect(response.body.status).to.equal('success'); expect(response.body.data).to.have.property('total'); expect(response.body.data).to.have.property('valid'); expect(response.body.data).to.have.property('invalid'); expect(response.body.data.total).to.be.greaterThan(0); }); it('should return 400 for missing symbol', async () => { const response = await request(app) .get('/api/prices/validate-candles/') .expect(404); // Express will return 404 for missing route param }); it('should handle different timeframes', async () => { const timeframes = ['M1', 'M5', 'M15', 'H1', 'H4', 'D1']; for (const timeframe of timeframes) { const response = await request(app) .get(`/api/prices/validate-candles/${testSymbol}`) .query({ timeframe, limit: 10 }) .expect(200); expect(response.body.status).to.equal('success'); expect(response.body.timeframe).to.equal(timeframe); } }); }); describe('GET /api/prices/generate-test-candles/:symbol', () => { it('should generate test candles for a valid symbol', async () => { const response = await request(app) .get(`/api/prices/generate-test-candles/${testSymbol}`) .query({ timeframe: testTimeframe, count: 20 }) .expect(200); expect(response.body.status).to.equal('success'); expect(response.body.data).to.be.an('array'); expect(response.body.data.length).to.equal(20); expect(response.body.validation).to.have.property('total', 20); }); it('should validate generated test candles', async () => { const response = await request(app) .get(`/api/prices/generate-test-candles/${testSymbol}`) .query({ timeframe: testTimeframe, count: 10 }) .expect(200); expect(response.body.meta.validationPassed).to.equal(true); expect(response.body.meta.successRate).to.equal('100.00%'); }); }); describe('GET /api/prices/candle-info/:symbol', () => { it('should return candle statistics for a valid symbol', async () => { const response = await request(app) .get(`/api/prices/candle-info/${testSymbol}`) .query({ timeframe: testTimeframe }) .expect(200); expect(response.body.status).to.equal('success'); expect(response.body.data).to.have.property('count'); expect(response.body.data).to.have.property('priceRange'); expect(response.body.data).to.have.property('volume'); expect(response.body.data.symbol).to.equal(testSymbol.toUpperCase()); }); }); describe('Candlestick Service Unit Tests', () => { it('should generate valid test candles', () => { const candles = generateTestCandles(testSymbol, testTimeframe, 10); expect(candles).to.have.length(10); candles.forEach(candle => { expect(candle.symbol).to.equal(testSymbol); expect(candle.timeframe).to.equal(testTimeframe); expect(candle).to.have.property('open'); expect(candle).to.have.property('high'); expect(candle).to.have.property('low'); expect(candle).to.have.property('close'); expect(candle).to.have.property('volume'); expect(candle).to.have.property('timestamp'); }); }); it('should validate correct candles', () => { const validCandles = generateTestCandles(testSymbol, testTimeframe, 5); const results = validateCandles(validCandles); expect(results.total).to.equal(5); expect(results.valid).to.equal(5); expect(results.invalid).to.equal(0); expect(results.issues).to.have.length(0); }); it('should detect invalid candles', () => { const invalidCandles = [ { symbol: testSymbol, timeframe: testTimeframe, timestamp: new Date(), open: 'invalid', high: 1.1, low: 1.0, close: 1.05, volume: 1000 }, { symbol: testSymbol, timeframe: testTimeframe, timestamp: new Date(), open: 1.0, high: 0.9, // Invalid: high < open low: 1.0, close: 1.05, volume: 1000 } ]; const results = validateCandles(invalidCandles); expect(results.invalid).to.be.greaterThan(0); expect(results.issues.length).to.be.greaterThan(0); }); }); describe('GET /api/prices/historical/:symbol (Integration)', () => { it('should return historical data that can be validated', async () => { const response = await request(app) .get(`/api/prices/historical/${testSymbol}`) .query({ timeframe: testTimeframe, limit: 20 }) .expect(200); expect(response.body.status).to.equal('success'); expect(response.body.data).to.be.an('array'); if (response.body.data.length > 0) { // Validate the returned candles const validationResults = validateCandles(response.body.data); expect(validationResults.total).to.equal(response.body.data.length); // Log validation results for debugging console.log(`Historical data validation: ${validationResults.valid}/${validationResults.total} valid candles`); } }); }); describe('Chart Data Format Tests', () => { it('should return data in correct format for charting', async () => { const response = await request(app) .get(`/api/prices/historical/${testSymbol}`) .query({ timeframe: testTimeframe, limit: 10 }) .expect(200); expect(response.body.status).to.equal('success'); expect(response.body.data).to.be.an('array'); if (response.body.data.length > 0) { const candle = response.body.data[0]; // Check required fields for charting expect(candle).to.have.property('timestamp'); expect(candle).to.have.property('open'); expect(candle).to.have.property('high'); expect(candle).to.have.property('low'); expect(candle).to.have.property('close'); // Validate data types expect(typeof candle.open).to.equal('number'); expect(typeof candle.high).to.equal('number'); expect(typeof candle.low).to.equal('number'); expect(typeof candle.close).to.equal('number'); // Validate price logic expect(candle.high).to.be.at.least(Math.max(candle.open, candle.close)); expect(candle.low).to.be.at.most(Math.min(candle.open, candle.close)); } }); }); }); // Helper function to wait for price updates const waitForPriceUpdate = (ms = 2000) => { return new Promise(resolve => setTimeout(resolve, ms)); }; module.exports = { waitForPriceUpdate };