258 lines
8 KiB
TypeScript
258 lines
8 KiB
TypeScript
/**
|
|
* QuestDB Client Integration Test
|
|
*
|
|
* This test validates that all components work together correctly
|
|
* without requiring an actual QuestDB instance.
|
|
*/
|
|
|
|
import { afterEach, describe, expect, it } from 'bun:test';
|
|
import {
|
|
QuestDBClient,
|
|
QuestDBHealthMonitor,
|
|
QuestDBInfluxWriter,
|
|
QuestDBQueryBuilder,
|
|
QuestDBSchemaManager,
|
|
} from '../src';
|
|
import { questdbTestHelpers } from './setup';
|
|
|
|
describe('QuestDB Client Integration', () => {
|
|
let client: QuestDBClient;
|
|
beforeEach(() => {
|
|
client = new QuestDBClient({
|
|
host: 'localhost',
|
|
httpPort: 9000,
|
|
pgPort: 8812,
|
|
influxPort: 9009,
|
|
database: 'questdb',
|
|
user: 'admin',
|
|
password: 'quest',
|
|
});
|
|
});
|
|
afterEach(async () => {
|
|
if (client && client.connected) {
|
|
try {
|
|
await client.disconnect();
|
|
} catch {
|
|
// Ignore cleanup errors in tests
|
|
}
|
|
}
|
|
});
|
|
|
|
describe('Client Initialization', () => {
|
|
it('should create client with constructor', () => {
|
|
const newClient = new QuestDBClient({
|
|
host: 'localhost',
|
|
httpPort: 9000,
|
|
pgPort: 8812,
|
|
influxPort: 9009,
|
|
database: 'questdb',
|
|
user: 'admin',
|
|
password: 'quest',
|
|
});
|
|
expect(newClient).toBeInstanceOf(QuestDBClient);
|
|
});
|
|
|
|
it('should initialize all supporting classes', () => {
|
|
expect(client.getHealthMonitor()).toBeInstanceOf(QuestDBHealthMonitor);
|
|
expect(client.queryBuilder()).toBeInstanceOf(QuestDBQueryBuilder);
|
|
expect(client.getInfluxWriter()).toBeInstanceOf(QuestDBInfluxWriter);
|
|
expect(client.getSchemaManager()).toBeInstanceOf(QuestDBSchemaManager);
|
|
});
|
|
|
|
it('should handle connection configuration', () => {
|
|
expect(client.getHttpUrl()).toBe('http://localhost:9000');
|
|
expect(client.getInfluxUrl()).toBe('http://localhost:9009');
|
|
expect(client.connected).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Query Builder', () => {
|
|
it('should build query using query builder', () => {
|
|
const query = client
|
|
.queryBuilder()
|
|
.select('symbol', 'close', 'timestamp')
|
|
.from('ohlcv')
|
|
.whereSymbol('AAPL')
|
|
.whereLastHours(24)
|
|
.orderBy('timestamp', 'DESC')
|
|
.limit(100)
|
|
.build();
|
|
|
|
expect(query).toContain('SELECT symbol, close, timestamp');
|
|
expect(query).toContain('FROM ohlcv');
|
|
expect(query).toContain("symbol = 'AAPL'");
|
|
expect(query).toContain('ORDER BY timestamp DESC');
|
|
expect(query).toContain('LIMIT 100');
|
|
expect(questdbTestHelpers.validateQuestDBQuery(query)).toBe(true);
|
|
});
|
|
|
|
it('should build time-series specific queries', () => {
|
|
const latestQuery = client
|
|
.queryBuilder()
|
|
.select('*')
|
|
.from('ohlcv')
|
|
.latestBy('symbol')
|
|
.build();
|
|
|
|
expect(latestQuery).toContain('LATEST BY symbol');
|
|
expect(questdbTestHelpers.validateQuestDBQuery(latestQuery)).toBe(true);
|
|
|
|
const sampleQuery = client
|
|
.queryBuilder()
|
|
.select('symbol', 'avg(close)')
|
|
.from('ohlcv')
|
|
.sampleBy('1d')
|
|
.build();
|
|
|
|
expect(sampleQuery).toContain('SAMPLE BY 1d');
|
|
expect(questdbTestHelpers.validateQuestDBQuery(sampleQuery)).toBe(true);
|
|
});
|
|
|
|
it('should build aggregation queries', () => {
|
|
const query = client
|
|
.aggregate('ohlcv')
|
|
.select('symbol', 'avg(close) as avg_price', 'max(high) as max_high')
|
|
.whereSymbolIn(['AAPL', 'GOOGL'])
|
|
.groupBy('symbol')
|
|
.sampleBy('1h')
|
|
.build();
|
|
|
|
expect(query).toContain('SELECT symbol, avg(close) as avg_price, max(high) as max_high');
|
|
expect(query).toContain('FROM ohlcv');
|
|
expect(query).toContain("symbol IN ('AAPL', 'GOOGL')");
|
|
expect(query).toContain('SAMPLE BY 1h');
|
|
expect(query).toContain('GROUP BY symbol');
|
|
expect(questdbTestHelpers.validateQuestDBQuery(query)).toBe(true);
|
|
});
|
|
});
|
|
describe('InfluxDB Writer', () => {
|
|
it('should write OHLCV data using InfluxDB line protocol', async () => {
|
|
const ohlcvData = [
|
|
{
|
|
timestamp: new Date('2024-01-01T12:00:00Z'),
|
|
open: 150.0,
|
|
high: 152.0,
|
|
low: 149.5,
|
|
close: 151.5,
|
|
volume: 1000000,
|
|
},
|
|
];
|
|
|
|
// Mock the actual write operation
|
|
const writeSpy = spyOn(client.getInfluxWriter(), 'writeOHLCV');
|
|
writeSpy.mockReturnValue(Promise.resolve());
|
|
await expect(async () => {
|
|
await client.writeOHLCV('AAPL', 'NASDAQ', ohlcvData);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it('should handle batch operations', () => {
|
|
const lines = questdbTestHelpers.generateInfluxDBLines(3);
|
|
expect(lines.length).toBe(3);
|
|
|
|
lines.forEach(line => {
|
|
expect(line).toContain('ohlcv,symbol=TEST');
|
|
expect(line).toMatch(/\d{19}$/); // Nanosecond timestamp
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Schema Manager', () => {
|
|
it('should provide schema access', () => {
|
|
const schema = client.getSchemaManager().getSchema('ohlcv_data');
|
|
|
|
expect(schema).toBeDefined();
|
|
expect(schema?.tableName).toBe('ohlcv_data');
|
|
|
|
const symbolColumn = schema?.columns.find(col => col.name === 'symbol');
|
|
expect(symbolColumn).toBeDefined();
|
|
expect(symbolColumn?.type).toBe('SYMBOL');
|
|
|
|
expect(schema?.partitionBy).toBe('DAY');
|
|
});
|
|
});
|
|
|
|
describe('Health Monitor', () => {
|
|
it('should provide health monitoring capabilities', async () => {
|
|
const healthMonitor = client.getHealthMonitor();
|
|
expect(healthMonitor).toBeInstanceOf(QuestDBHealthMonitor);
|
|
|
|
// Mock health status since we're not connected
|
|
const mockHealthStatus = {
|
|
isHealthy: false,
|
|
lastCheck: new Date(),
|
|
responseTime: 100,
|
|
message: 'Connection not established',
|
|
details: {
|
|
pgPool: false,
|
|
httpEndpoint: false,
|
|
uptime: 0,
|
|
},
|
|
};
|
|
|
|
const healthSpy = spyOn(healthMonitor, 'getHealthStatus');
|
|
healthSpy.mockReturnValue(Promise.resolve(mockHealthStatus));
|
|
|
|
const health = await healthMonitor.getHealthStatus();
|
|
expect(health.isHealthy).toBe(false);
|
|
expect(health.lastCheck).toBeInstanceOf(Date);
|
|
expect(health.message).toBe('Connection not established');
|
|
});
|
|
});
|
|
describe('Time-Series Operations', () => {
|
|
it('should support latest by operations', async () => {
|
|
// Mock the query execution
|
|
const mockResult = {
|
|
rows: [{ symbol: 'AAPL', close: 150.0, timestamp: new Date() }],
|
|
rowCount: 1,
|
|
executionTime: 10,
|
|
metadata: { columns: [] },
|
|
};
|
|
|
|
const querySpy = spyOn(client, 'query');
|
|
querySpy.mockReturnValue(Promise.resolve(mockResult));
|
|
|
|
const result = await client.latestBy('ohlcv', ['symbol', 'close'], 'symbol');
|
|
expect(result.rows.length).toBe(1);
|
|
expect(result.rows[0].symbol).toBe('AAPL');
|
|
});
|
|
|
|
it('should support sample by operations', async () => {
|
|
// Mock the query execution
|
|
const mockResult = {
|
|
rows: [{ symbol: 'AAPL', avg_close: 150.0, timestamp: new Date() }],
|
|
rowCount: 1,
|
|
executionTime: 15,
|
|
metadata: { columns: [] },
|
|
};
|
|
|
|
const querySpy = spyOn(client, 'query');
|
|
querySpy.mockReturnValue(Promise.resolve(mockResult));
|
|
const result = await client.sampleBy(
|
|
'ohlcv',
|
|
['symbol', 'avg(close) as avg_close'],
|
|
'1h',
|
|
'timestamp',
|
|
"symbol = 'AAPL'"
|
|
);
|
|
|
|
expect(result.rows.length).toBe(1);
|
|
expect(result.executionTime).toBe(15);
|
|
});
|
|
});
|
|
|
|
describe('Connection Management', () => {
|
|
it('should handle connection configuration', () => {
|
|
expect(client.getHttpUrl()).toBe('http://localhost:9000');
|
|
expect(client.getInfluxUrl()).toBe('http://localhost:9009');
|
|
expect(client.connected).toBe(false);
|
|
});
|
|
|
|
it('should provide configuration access', () => {
|
|
const config = client.configuration;
|
|
expect(config.host).toBe('localhost');
|
|
expect(config.httpPort).toBe(9000);
|
|
expect(config.user).toBe('admin');
|
|
});
|
|
});
|
|
});
|