Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import vaultRouter from './routes/vault'
import analyticsRouter from './routes/analytics'
import adminRouter from './routes/admin'
import metricsRouter from './routes/metrics'
import stellarRouter from './routes/stellar'
import { corsMiddleware, jsonBodyParser, payloadSizeErrorHandler, urlencodedBodyParser } from './middleware/corsandbody'

// ── Readiness state ───────────────────────────────────────────────────────────
Expand Down Expand Up @@ -118,6 +119,7 @@ app.use('/api/deposit', depositRouter)
app.use('/api/withdraw', withdrawRouter)
app.use('/api/vault', vaultRouter)
app.use('/api/analytics', analyticsRouter)
app.use('/api/stellar', stellarRouter)

app.use('/metrics', metricsRouter)
// Admin routes (protected, strictest rate limit)
Expand Down
25 changes: 25 additions & 0 deletions src/routes/stellar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Router, Request, Response } from 'express';
import { getEventMetrics } from '../stellar/events';

const router = Router();

/**
* GET /api/stellar/metrics
* Returns current event-processing metrics from the Stellar event listener.
*/
router.get('/metrics', (_req: Request, res: Response) => {
try {
const metrics = getEventMetrics();
res.json({
success: true,
data: metrics,
});
} catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
});
}
});

export default router;
64 changes: 64 additions & 0 deletions src/stellar/__tests__/stellar.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { submitTransaction, waitForConfirmation } from '../client';
import { getOnChainBalance, triggerRebalance } from '../contract';
import { getEventMetrics } from '../events';
import { Transaction } from '@stellar/stellar-sdk';

jest.mock('../client', () => ({
Expand All @@ -10,6 +11,10 @@ jest.mock('../client', () => ({
waitForConfirmation: jest.fn(),
}));

jest.mock('../events', () => ({
getEventMetrics: jest.fn(),
}));

describe('Stellar Integration - Unit Tests', () => {
beforeEach(() => {
jest.clearAllMocks();
Expand Down Expand Up @@ -114,6 +119,65 @@ describe('Stellar Integration - Unit Tests', () => {
});
});

describe('Event Metrics', () => {
it('should return current metrics with all required fields', () => {
const mockMetrics = {
totalProcessed: 42,
totalErrors: 2,
processingRatePerMinute: 10,
errorRate: 0.048,
ledgerLag: 3,
lastDbOperationMs: 12,
lastUpdated: new Date(),
};
(getEventMetrics as jest.Mock).mockReturnValue(mockMetrics);

const metrics = getEventMetrics();

expect(metrics.totalProcessed).toBe(42);
expect(metrics.totalErrors).toBe(2);
expect(metrics.processingRatePerMinute).toBe(10);
expect(metrics.errorRate).toBeCloseTo(0.048, 3);
expect(metrics.ledgerLag).toBe(3);
expect(metrics.lastDbOperationMs).toBe(12);
expect(metrics.lastUpdated).toBeInstanceOf(Date);
});

it('should return zero values for a fresh listener', () => {
const emptyMetrics = {
totalProcessed: 0,
totalErrors: 0,
processingRatePerMinute: 0,
errorRate: 0,
ledgerLag: 0,
lastDbOperationMs: 0,
lastUpdated: new Date(),
};
(getEventMetrics as jest.Mock).mockReturnValue(emptyMetrics);

const metrics = getEventMetrics();

expect(metrics.totalProcessed).toBe(0);
expect(metrics.errorRate).toBe(0);
});

it('should compute errorRate as totalErrors / totalProcessed', () => {
const totalProcessed = 100;
const totalErrors = 5;
const errorRate = totalProcessed > 0 ? totalErrors / totalProcessed : 0;

expect(errorRate).toBeCloseTo(0.05, 3);
});

it('should return zero errorRate when no events processed', () => {
const totalProcessed = 0;
const totalErrors = 0;
const errorRate = totalProcessed > 0 ? totalErrors / totalProcessed : 0;

expect(errorRate).toBe(0);
});
});

describe('Event Parsing', () => {
it('should parse deposit event', () => {
const mockEvent = {
Expand Down
Loading