From 6f63db12edab104b7b55a48173d530303c19ef1e Mon Sep 17 00:00:00 2001 From: Daniel Akinsanya Date: Wed, 25 Feb 2026 18:52:17 +0100 Subject: [PATCH 1/6] feat(security): implement robust XSS sanitization and checksum-validated Stellar addresses closes #135 --- backend/src/controllers/trading.controller.ts | 171 +----------------- backend/src/routes/trading.ts | 50 +++-- backend/src/schemas/validation.schemas.ts | 98 +++++++++- backend/src/services/user.service.ts | 17 +- backend/tests/security/sanitization.test.ts | 101 +++++++++++ 5 files changed, 257 insertions(+), 180 deletions(-) create mode 100644 backend/tests/security/sanitization.test.ts diff --git a/backend/src/controllers/trading.controller.ts b/backend/src/controllers/trading.controller.ts index 422e38dd..e3c69ba3 100644 --- a/backend/src/controllers/trading.controller.ts +++ b/backend/src/controllers/trading.controller.ts @@ -25,19 +25,11 @@ export class TradingController { const marketId = req.params.marketId as string; const { outcome, amount, minShares } = req.body; - // Validation - if (outcome === undefined || !amount) { - res - .status(400) - .json({ success: false, error: 'Missing outcome or amount' }); - return; - } - const xdr = await tradingService.buildBuySharesTx( userId, userPublicKey, marketId, - Number(outcome), + outcome, BigInt(amount), BigInt(minShares || 0) ); @@ -71,47 +63,13 @@ export class TradingController { const marketId = req.params.marketId as string; const { outcome, amount, minShares } = req.body; - // Validate input - if (outcome === undefined || outcome === null) { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'outcome is required (0 for NO, 1 for YES)', - }, - }); - return; - } - - if (!amount || amount <= 0) { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'amount must be greater than 0', - }, - }); - return; - } - - if (![0, 1].includes(outcome)) { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'outcome must be 0 (NO) or 1 (YES)', - }, - }); - return; - } - // Call service const result = await tradingService.buyShares({ userId, marketId, outcome, - amount, - minShares, + amount: Number(amount), + minShares: minShares ? Number(minShares) : undefined, }); res.status(201).json({ @@ -179,18 +137,11 @@ export class TradingController { const marketId = req.params.marketId as string; const { outcome, shares, minPayout } = req.body; - if (outcome === undefined || !shares) { - res - .status(400) - .json({ success: false, error: 'Missing outcome or shares' }); - return; - } - const xdr = await tradingService.buildSellSharesTx( userId, userPublicKey, marketId, - Number(outcome), + outcome, BigInt(shares), BigInt(minPayout || 0) ); @@ -224,47 +175,13 @@ export class TradingController { const marketId = req.params.marketId as string; const { outcome, shares, minPayout } = req.body; - // Validate input - if (outcome === undefined || outcome === null) { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'outcome is required (0 for NO, 1 for YES)', - }, - }); - return; - } - - if (!shares || shares <= 0) { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'shares must be greater than 0', - }, - }); - return; - } - - if (![0, 1].includes(outcome)) { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'outcome must be 0 (NO) or 1 (YES)', - }, - }); - return; - } - // Call service const result = await tradingService.sellShares({ userId, marketId, outcome, - shares, - minPayout, + shares: Number(shares), + minPayout: minPayout ? Number(minPayout) : undefined, }); res.status(200).json({ @@ -425,46 +342,10 @@ export class TradingController { const marketId = req.params.marketId as string; const { usdcAmount } = req.body; - if (!usdcAmount) { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'usdcAmount is required', - }, - }); - return; - } - - let parsedAmount: bigint; - try { - parsedAmount = BigInt(usdcAmount); - } catch { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'usdcAmount must be a valid integer string', - }, - }); - return; - } - - if (parsedAmount <= BigInt(0)) { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'usdcAmount must be greater than 0', - }, - }); - return; - } - const result = await tradingService.addLiquidity( userId, marketId, - parsedAmount + BigInt(usdcAmount) ); res.status(200).json({ @@ -521,46 +402,10 @@ export class TradingController { const marketId = req.params.marketId as string; const { lpTokens } = req.body; - if (!lpTokens) { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'lpTokens is required', - }, - }); - return; - } - - let parsedTokens: bigint; - try { - parsedTokens = BigInt(lpTokens); - } catch { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'lpTokens must be a valid integer string', - }, - }); - return; - } - - if (parsedTokens <= BigInt(0)) { - res.status(400).json({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'lpTokens must be greater than 0', - }, - }); - return; - } - const result = await tradingService.removeLiquidity( userId, marketId, - parsedTokens + BigInt(lpTokens) ); res.status(200).json({ diff --git a/backend/src/routes/trading.ts b/backend/src/routes/trading.ts index 3f5f126e..d4ff54f7 100644 --- a/backend/src/routes/trading.ts +++ b/backend/src/routes/trading.ts @@ -4,6 +4,14 @@ import { Router } from 'express'; import { tradingController } from '../controllers/trading.controller.js'; import { requireAuth } from '../middleware/auth.middleware.js'; +import { validate } from '../middleware/validation.middleware.js'; +import { + buySharesBody, + sellSharesBody, + addLiquidityBody, + removeLiquidityBody, + marketIdParam, +} from '../schemas/validation.schemas.js'; const router: Router = Router(); @@ -83,8 +91,11 @@ const router: Router = Router(); * 404: * $ref: '#/components/responses/NotFound' */ -router.post('/:marketId/buy', requireAuth, (req, res) => - tradingController.buyShares(req, res) +router.post( + '/:marketId/buy', + requireAuth, + validate({ params: marketIdParam, body: buySharesBody }), + (req, res) => tradingController.buyShares(req, res) ); /** @@ -155,8 +166,11 @@ router.post('/:marketId/buy', requireAuth, (req, res) => * 404: * $ref: '#/components/responses/NotFound' */ -router.post('/:marketId/sell', requireAuth, (req, res) => - tradingController.sellShares(req, res) +router.post( + '/:marketId/sell', + requireAuth, + validate({ params: marketIdParam, body: sellSharesBody }), + (req, res) => tradingController.sellShares(req, res) ); /** @@ -224,15 +238,21 @@ router.get('/:marketId/odds', (req, res) => /** * POST /api/markets/:marketId/liquidity/add - Add USDC Liquidity to Pool */ -router.post('/:marketId/liquidity/add', requireAuth, (req, res) => - tradingController.addLiquidity(req, res) +router.post( + '/:marketId/liquidity/add', + requireAuth, + validate({ params: marketIdParam, body: addLiquidityBody }), + (req, res) => tradingController.addLiquidity(req, res) ); /** * POST /api/markets/:marketId/liquidity/remove - Remove Liquidity from Pool */ -router.post('/:marketId/liquidity/remove', requireAuth, (req, res) => - tradingController.removeLiquidity(req, res) +router.post( + '/:marketId/liquidity/remove', + requireAuth, + validate({ params: marketIdParam, body: removeLiquidityBody }), + (req, res) => tradingController.removeLiquidity(req, res) ); // ─── User-signed Transaction Routes ────────────────────────────────────────── @@ -242,16 +262,22 @@ router.post('/:marketId/liquidity/remove', requireAuth, (req, res) => * POST /api/markets/:marketId/build-tx/buy * Build an unsigned transaction for buying shares */ -router.post('/markets/:marketId/build-tx/buy', requireAuth, (req, res) => - tradingController.buildBuySharesTx(req, res) +router.post( + '/markets/:marketId/build-tx/buy', + requireAuth, + validate({ params: marketIdParam, body: buySharesBody }), + (req, res) => tradingController.buildBuySharesTx(req, res) ); /** * POST /api/markets/:marketId/build-tx/sell * Build an unsigned transaction for selling shares */ -router.post('/markets/:marketId/build-tx/sell', requireAuth, (req, res) => - tradingController.buildSellSharesTx(req, res) +router.post( + '/markets/:marketId/build-tx/sell', + requireAuth, + validate({ params: marketIdParam, body: sellSharesBody }), + (req, res) => tradingController.buildSellSharesTx(req, res) ); /** diff --git a/backend/src/schemas/validation.schemas.ts b/backend/src/schemas/validation.schemas.ts index d3fba634..e968f914 100644 --- a/backend/src/schemas/validation.schemas.ts +++ b/backend/src/schemas/validation.schemas.ts @@ -1,5 +1,6 @@ import { z } from 'zod'; import { MarketCategory } from '@prisma/client'; +import { stellarService } from '../services/stellar.service.js'; // --- Sanitization helper --- @@ -10,9 +11,16 @@ import { MarketCategory } from '@prisma/client'; export function stripHtml(val: string): string { // Strip script tags and their content val = val.replace(/)<[^<]*)*<\/script>/gi, ''); + // Strip style tags and their content + val = val.replace(/)<[^<]*)*<\/style>/gi, ''); + // Strip event handlers (e.g., onclick, onerror) + val = val.replace(/\s+on\w+="[^"]*"/gi, ''); + val = val.replace(/\s+on\w+='[^']*'/gi, ''); + // Strip javascript: pseudo-protocol + val = val.replace(/javascript:[^"']*/gi, ''); // Strip remaining HTML tags val = val.replace(/<[^>]*>/g, ''); - // Strip HTML entities (e.g. & < ' ') + // Strip common HTML entities (e.g. & < ' ') val = val.replace(/&(?:#[0-9]+|#x[0-9a-fA-F]+|[a-zA-Z]+);/g, ''); return val; } @@ -33,7 +41,9 @@ export function sanitizedString(min: number, max: number) { export const stellarAddress = z .string() - .regex(/^G[A-Z0-9]{55}$/, 'Invalid Stellar public key'); + .refine((val) => stellarService.isValidPublicKey(val), { + message: 'Invalid Stellar public key format or checksum', + }); export const uuidParam = z.object({ id: z.string().uuid(), @@ -115,6 +125,90 @@ export const commitPredictionBody = z.object({ ), }); +export const buySharesBody = z.object({ + outcome: z.number().int().min(0).max(1), + amount: z + .string() + .regex(/^\d+$/, 'Amount must be a numeric string (USDC base units)') + .refine( + (val) => { + try { + return BigInt(val) > 0n; + } catch { + return false; + } + }, + { message: 'Amount must be greater than 0' } + ) + .refine( + (val) => { + try { + return BigInt(val) <= 1_000_000_000_000n; + } catch { + return false; + } + }, + { message: 'Amount exceeds maximum limit' } + ), + minShares: z + .string() + .regex(/^\d+$/, 'minShares must be a numeric string') + .optional(), +}); + +export const sellSharesBody = z.object({ + outcome: z.number().int().min(0).max(1), + shares: z + .string() + .regex(/^\d+$/, 'Shares must be a numeric string (base units)') + .refine( + (val) => { + try { + return BigInt(val) > 0n; + } catch { + return false; + } + }, + { message: 'Shares must be greater than 0' } + ), + minPayout: z + .string() + .regex(/^\d+$/, 'minPayout must be a numeric string') + .optional(), +}); + +export const addLiquidityBody = z.object({ + usdcAmount: z + .string() + .regex(/^\d+$/, 'usdcAmount must be a numeric string') + .refine( + (val) => { + try { + return BigInt(val) > 0n; + } catch { + return false; + } + }, + { message: 'usdcAmount must be greater than 0' } + ), +}); + +export const removeLiquidityBody = z.object({ + lpTokens: z + .string() + .regex(/^\d+$/, 'lpTokens must be a numeric string') + .refine( + (val) => { + try { + return BigInt(val) > 0n; + } catch { + return false; + } + }, + { message: 'lpTokens must be greater than 0' } + ), +}); + export const revealPredictionBody = z.object({ predictionId: z.string().uuid(), }); diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index ea4987b1..32514e2b 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -8,6 +8,7 @@ import { NotificationService, } from './notification.service.js'; import { logger } from '../utils/logger.js'; +import { stripHtml } from '../schemas/validation.schemas.js'; export class UserService { private userRepository: UserRepository; @@ -112,15 +113,25 @@ export class UserService { avatarUrl?: string; } ) { + // Sanitize inputs + const sanitizedData = { ...data }; + if (sanitizedData.username) + sanitizedData.username = stripHtml(sanitizedData.username); + if (sanitizedData.displayName) + sanitizedData.displayName = stripHtml(sanitizedData.displayName); + if (sanitizedData.bio) sanitizedData.bio = stripHtml(sanitizedData.bio); + // Check username uniqueness if changing - if (data.username) { - const existing = await this.userRepository.findByUsername(data.username); + if (sanitizedData.username) { + const existing = await this.userRepository.findByUsername( + sanitizedData.username + ); if (existing && existing.id !== userId) { throw new Error('Username already taken'); } } - const user = await this.userRepository.update(userId, data); + const user = await this.userRepository.update(userId, sanitizedData); const { passwordHash: _, twoFaSecret: __, ...userWithoutSensitive } = user; return userWithoutSensitive; } diff --git a/backend/tests/security/sanitization.test.ts b/backend/tests/security/sanitization.test.ts new file mode 100644 index 00000000..24108db1 --- /dev/null +++ b/backend/tests/security/sanitization.test.ts @@ -0,0 +1,101 @@ +import { describe, it, expect } from 'vitest'; +import { stripHtml, stellarAddress, buySharesBody } from '../../src/schemas/validation.schemas.js'; + +describe('Security Audit - Input Sanitization & Validation', () => { + describe('XSS Sanitization (stripHtml)', () => { + it('should strip script tags and their content', () => { + const input = 'Check out this market! '; + expect(stripHtml(input)).toBe('Check out this market! '); + }); + + it('should strip inline event handlers', () => { + const input = ' Click me'; + // Our stripHtml replaces on\w+="[^"]*" with empty string + const result = stripHtml(input); + expect(result).not.toContain('onerror'); + expect(result).not.toContain('alert'); + }); + + it('should strip style tags', () => { + const input = 'Normal text '; + expect(stripHtml(input)).toBe('Normal text '); + }); + + it('should strip javascript: pseudo-protocol', () => { + const input = 'Click'; + const result = stripHtml(input); + expect(result).not.toContain('javascript:'); + expect(result).toBe('Click'); + }); + + it('should strip multiple tags and handle nested-like structures', () => { + const input = '
Title

Message

'; + expect(stripHtml(input)).toBe('TitleMessage '); + }); + }); + + describe('Stellar Address Validation (checksum)', () => { + it('should accept valid Stellar public keys', () => { + const realValidKey = 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY'; + expect(stellarAddress.safeParse(realValidKey).success).toBe(true); + }); + + it('should reject keys with invalid format', () => { + expect(stellarAddress.safeParse('not-a-key').success).toBe(false); + expect(stellarAddress.safeParse('B' + 'A'.repeat(55)).success).toBe(false); + }); + + it('should reject keys with valid format but invalid checksum', () => { + // G followed by 55 chars, but checksum is likely wrong + const invalidChecksumKey = 'G' + 'A'.repeat(55); + expect(stellarAddress.safeParse(invalidChecksumKey).success).toBe(false); + }); + }); + + describe('Numeric Input Validation (Trading)', () => { + it('should reject negative amounts', () => { + const result = buySharesBody.safeParse({ + outcome: 1, + amount: '-100', + minShares: '0' + }); + expect(result.success).toBe(false); + }); + + it('should reject zero amounts if required to be > 0', () => { + const result = buySharesBody.safeParse({ + outcome: 1, + amount: '0', + minShares: '0' + }); + expect(result.success).toBe(false); + }); + + it('should reject non-numeric strings for amounts', () => { + const result = buySharesBody.safeParse({ + outcome: 1, + amount: 'abc', + minShares: '0' + }); + expect(result.success).toBe(false); + }); + + it('should reject extremely large numbers (overflow protection)', () => { + const result = buySharesBody.safeParse({ + outcome: 1, + amount: '999999999999999999999999999999999999', // very large + minShares: '0' + }); + expect(result.success).toBe(false); + }); + + it('should accept valid numeric strings', () => { + const result = buySharesBody.safeParse({ + outcome: 1, + amount: '1000000', // 1 USDC + minShares: '900000' + }); + expect(result.success).toBe(true); + }); + }); +}); From 6dc48b790b68b071b519394eca785f8154c579bd Mon Sep 17 00:00:00 2001 From: Daniel Akinsanya Date: Wed, 25 Feb 2026 20:35:23 +0100 Subject: [PATCH 2/6] fix(tests): update test fixtures to match new XSS/Stellar validation rules - Replace invalid Stellar key GA5XIGA... with a checksummed valid key GAMCVGJF... across validation.schemas, validation.middleware, and integration tests (the new stellarAddress schema now validates checksum) - Replace non-UUID market IDs (market-1, test-market-id) with valid UUIDs in trading integration tests (uuidParam now validates route params) - Change numeric amount/shares fields to strings in buy/sell tests to match schema expectations (amountUsdc, minShares, shares, minPayout) --- .../integration/trading.integration.test.ts | 64 ++++++++++--------- backend/tests/middleware/integration.test.ts | 4 +- .../middleware/validation.middleware.test.ts | 12 ++-- .../middleware/validation.schemas.test.ts | 2 +- 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/backend/tests/integration/trading.integration.test.ts b/backend/tests/integration/trading.integration.test.ts index 780a4bf9..dec93479 100644 --- a/backend/tests/integration/trading.integration.test.ts +++ b/backend/tests/integration/trading.integration.test.ts @@ -57,7 +57,7 @@ vi.mock('../../src/database/prisma.js', () => ({ update: vi.fn().mockResolvedValue({ id: 'test-user-id', usdcBalance: 900 }), }, market: { - update: vi.fn().mockResolvedValue({ id: 'test-market-id' }), + update: vi.fn().mockResolvedValue({ id: '123e4567-e89b-12d3-a456-426614174000' }), }, })), }, @@ -77,7 +77,7 @@ describe('Trading API - User-Signed Transaction Flow', () => { it('should return unsigned XDR for a valid market', async () => { // Mock market vi.mocked(prisma.market.findUnique).mockResolvedValue({ - id: 'market-1', + id: '123e4567-e89b-12d3-a456-426614174001', status: MarketStatus.OPEN, } as any); @@ -85,7 +85,7 @@ describe('Trading API - User-Signed Transaction Flow', () => { vi.mocked(ammService.buildBuySharesTx).mockResolvedValue('AAAA-UNSIGNED-XDR'); const response = await request(app) - .post('/api/markets/market-1/build-tx/buy') + .post('/api/markets/123e4567-e89b-12d3-a456-426614174001/build-tx/buy') .set('Authorization', `Bearer ${authToken}`) .send({ outcome: 1, @@ -99,7 +99,7 @@ describe('Trading API - User-Signed Transaction Flow', () => { // Verify it called AMM with the user's public key from the JWT expect(ammService.buildBuySharesTx).toHaveBeenCalledWith('GUSER123', { - marketId: 'market-1', + marketId: '123e4567-e89b-12d3-a456-426614174001', outcome: 1, amountUsdc: BigInt(1000), minShares: BigInt(900), @@ -108,12 +108,12 @@ describe('Trading API - User-Signed Transaction Flow', () => { it('should fail if market is not OPEN', async () => { vi.mocked(prisma.market.findUnique).mockResolvedValue({ - id: 'market-1', + id: '123e4567-e89b-12d3-a456-426614174001', status: MarketStatus.CLOSED, } as any); const response = await request(app) - .post('/api/markets/market-1/build-tx/buy') + .post('/api/markets/123e4567-e89b-12d3-a456-426614174001/build-tx/buy') .set('Authorization', `Bearer ${authToken}`) .send({ outcome: 1, @@ -179,7 +179,7 @@ describe('Trading API - Direct Buy Flow', () => { it('should buy shares successfully with valid data', async () => { // Mock market (OPEN) vi.mocked(prisma.market.findUnique).mockResolvedValue({ - id: 'test-market-id', + id: '123e4567-e89b-12d3-a456-426614174000', contractAddress: 'contract', title: 'Test Market', status: MarketStatus.OPEN, @@ -222,12 +222,12 @@ describe('Trading API - Direct Buy Flow', () => { } as any); const response = await request(app) - .post('/api/markets/test-market-id/buy') + .post('/api/markets/123e4567-e89b-12d3-a456-426614174000/buy') .set('Authorization', `Bearer ${authToken}`) .send({ outcome: 1, - amount: 100, - minShares: 90, + amount: '100', + minShares: '90', }) .expect(201); @@ -240,7 +240,7 @@ describe('Trading API - Direct Buy Flow', () => { // Verify AMM was called correctly expect(ammService.buyShares).toHaveBeenCalledWith({ - marketId: 'test-market-id', + marketId: '123e4567-e89b-12d3-a456-426614174000', outcome: 1, amountUsdc: 100, minShares: 90, @@ -249,7 +249,7 @@ describe('Trading API - Direct Buy Flow', () => { it('should reject buy with insufficient balance', async () => { vi.mocked(prisma.market.findUnique).mockResolvedValue({ - id: 'test-market-id', + id: '123e4567-e89b-12d3-a456-426614174000', status: MarketStatus.OPEN, } as any); @@ -259,11 +259,11 @@ describe('Trading API - Direct Buy Flow', () => { } as any); const response = await request(app) - .post('/api/markets/test-market-id/buy') + .post('/api/markets/123e4567-e89b-12d3-a456-426614174000/buy') .set('Authorization', `Bearer ${authToken}`) .send({ outcome: 1, - amount: 100, + amount: '100', }) .expect(400); @@ -277,16 +277,16 @@ describe('Trading API - Direct Buy Flow', () => { it('should reject buy with invalid market (CLOSED)', async () => { vi.mocked(prisma.market.findUnique).mockResolvedValue({ - id: 'test-market-id', + id: '123e4567-e89b-12d3-a456-426614174000', status: MarketStatus.CLOSED, } as any); const response = await request(app) - .post('/api/markets/test-market-id/buy') + .post('/api/markets/123e4567-e89b-12d3-a456-426614174000/buy') .set('Authorization', `Bearer ${authToken}`) .send({ outcome: 1, - amount: 100, + amount: '100', }) .expect(400); @@ -309,7 +309,7 @@ describe('Trading API - Direct Sell Flow', () => { it('should sell shares successfully with valid data', async () => { vi.mocked(prisma.market.findUnique).mockResolvedValue({ - id: 'test-market-id', + id: '123e4567-e89b-12d3-a456-426614174000', } as any); // Mock user has shares @@ -353,12 +353,12 @@ describe('Trading API - Direct Sell Flow', () => { } as any); const response = await request(app) - .post('/api/markets/test-market-id/sell') + .post('/api/markets/123e4567-e89b-12d3-a456-426614174000/sell') .set('Authorization', `Bearer ${authToken}`) .send({ outcome: 1, - shares: 50, - minPayout: 48, + shares: '50', + minPayout: '48', }) .expect(200); @@ -368,7 +368,7 @@ describe('Trading API - Direct Sell Flow', () => { expect(response.body.data).toHaveProperty('txHash'); expect(ammService.sellShares).toHaveBeenCalledWith({ - marketId: 'test-market-id', + marketId: '123e4567-e89b-12d3-a456-426614174000', outcome: 1, shares: 50, minPayout: 48, @@ -389,7 +389,7 @@ describe('Trading API - Odds & Liquidity', () => { it('should return odds successfully', async () => { vi.mocked(prisma.market.findUnique).mockResolvedValue({ - id: 'test-market-id', + id: '123e4567-e89b-12d3-a456-426614174000', } as any); vi.mocked(ammService.getOdds).mockResolvedValue({ @@ -403,7 +403,7 @@ describe('Trading API - Odds & Liquidity', () => { }); const response = await request(app) - .get('/api/markets/test-market-id/odds') + .get('/api/markets/123e4567-e89b-12d3-a456-426614174000/odds') .expect(200); expect(response.body.success).toBe(true); @@ -414,7 +414,7 @@ describe('Trading API - Odds & Liquidity', () => { it('should add liquidity successfully', async () => { vi.mocked(prisma.market.findUnique).mockResolvedValue({ - id: 'test-market-id', + id: '123e4567-e89b-12d3-a456-426614174000', status: MarketStatus.OPEN, } as any); @@ -424,10 +424,14 @@ describe('Trading API - Odds & Liquidity', () => { }); const response = await request(app) - .post('/api/markets/test-market-id/liquidity/add') + .post('/api/markets/123e4567-e89b-12d3-a456-426614174000/liquidity/add') .set('Authorization', `Bearer ${authToken}`) - .send({ usdcAmount: '1000' }) - .expect(200); + .send({ usdcAmount: '1000' }); + + if (response.status !== 200) { + console.log('DEBUG addLiquidity failure:', JSON.stringify(response.body, null, 2)); + } + expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data).toHaveProperty('lpTokensMinted', '500'); @@ -436,7 +440,7 @@ describe('Trading API - Odds & Liquidity', () => { it('should remove liquidity successfully', async () => { vi.mocked(prisma.market.findUnique).mockResolvedValue({ - id: 'test-market-id', + id: '123e4567-e89b-12d3-a456-426614174000', status: MarketStatus.OPEN, } as any); @@ -448,7 +452,7 @@ describe('Trading API - Odds & Liquidity', () => { }); const response = await request(app) - .post('/api/markets/test-market-id/liquidity/remove') + .post('/api/markets/123e4567-e89b-12d3-a456-426614174000/liquidity/remove') .set('Authorization', `Bearer ${authToken}`) .send({ lpTokens: '500' }) .expect(200); diff --git a/backend/tests/middleware/integration.test.ts b/backend/tests/middleware/integration.test.ts index b1b5b63b..e1da78d5 100644 --- a/backend/tests/middleware/integration.test.ts +++ b/backend/tests/middleware/integration.test.ts @@ -33,12 +33,12 @@ describe('Validation and Error Handling Integration', () => { const response = await request(app) .post('/api/test') .send({ - publicKey: 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ' + publicKey: 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY' }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); - expect(response.body.data.publicKey).toBe('GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ'); + expect(response.body.data.publicKey).toBe('GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY'); }); it('should handle validation error with custom business logic', async () => { diff --git a/backend/tests/middleware/validation.middleware.test.ts b/backend/tests/middleware/validation.middleware.test.ts index 8107302a..530d23e8 100644 --- a/backend/tests/middleware/validation.middleware.test.ts +++ b/backend/tests/middleware/validation.middleware.test.ts @@ -33,12 +33,12 @@ describe('Validation Middleware', () => { const response = await request(app) .post('/challenge') .send({ - publicKey: 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ' + publicKey: 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY' }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); - expect(response.body.data.publicKey).toBe('GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ'); + expect(response.body.data.publicKey).toBe('GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY'); }); it('should reject invalid Stellar public key', async () => { @@ -88,7 +88,7 @@ describe('Validation Middleware', () => { const response = await request(app) .post('/login') .send({ - publicKey: 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ', + publicKey: 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY', signature: 'test-signature', nonce: 'test-nonce' }); @@ -107,7 +107,7 @@ describe('Validation Middleware', () => { const response = await request(app) .post('/login') .send({ - publicKey: 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ', + publicKey: 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY', signature: '', nonce: 'test-nonce' }); @@ -233,12 +233,12 @@ describe('Validation Middleware', () => { const response = await request(app) .post('/verify') .send({ - address: 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ' + address: 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY' }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); - expect(response.body.data.address).toBe('GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ'); + expect(response.body.data.address).toBe('GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY'); }); it('should reject invalid Stellar address', async () => { diff --git a/backend/tests/middleware/validation.schemas.test.ts b/backend/tests/middleware/validation.schemas.test.ts index d785c57b..070f2238 100644 --- a/backend/tests/middleware/validation.schemas.test.ts +++ b/backend/tests/middleware/validation.schemas.test.ts @@ -20,7 +20,7 @@ import { // Valid Stellar public key for tests const VALID_STELLAR_KEY = - 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ'; + 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY'; const VALID_UUID = '123e4567-e89b-12d3-a456-426614174000'; // Helper to create a future datetime string From 63d4532ada2274d3554d65e4ab0324f0adbaa2f9 Mon Sep 17 00:00:00 2001 From: Daniel Akinsanya Date: Thu, 26 Feb 2026 11:37:56 +0100 Subject: [PATCH 3/6] chore: fix CI blockers - backend formatting and frontend scripts --- backend/src/services/cron.service.ts | 36 +++++++++++++++++----------- frontend/package-lock.json | 26 +++++++++++++------- frontend/package.json | 5 +++- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/backend/src/services/cron.service.ts b/backend/src/services/cron.service.ts index 79236dcd..e3c22920 100644 --- a/backend/src/services/cron.service.ts +++ b/backend/src/services/cron.service.ts @@ -10,10 +10,7 @@ export class CronService { private marketRepository: MarketRepository; private marketService: MarketService; - constructor( - marketRepo?: MarketRepository, - marketSvc?: MarketService - ) { + constructor(marketRepo?: MarketRepository, marketSvc?: MarketService) { this.marketRepository = marketRepo || new MarketRepository(); this.marketService = marketSvc || new MarketService(); } @@ -53,7 +50,8 @@ export class CronService { let markets; try { - markets = await this.marketRepository.getClosedMarketsAwaitingResolution(); + markets = + await this.marketRepository.getClosedMarketsAwaitingResolution(); } catch (error) { logger.error('Oracle polling: failed to fetch closed markets', { error }); return; @@ -64,20 +62,27 @@ export class CronService { return; } - logger.info(`Oracle polling: checking consensus for ${markets.length} market(s)`); + logger.info( + `Oracle polling: checking consensus for ${markets.length} market(s)` + ); for (const market of markets) { try { const winningOutcome = await oracleService.checkConsensus(market.id); if (winningOutcome === null) { - logger.info(`Oracle polling: no consensus yet for market ${market.id}`); + logger.info( + `Oracle polling: no consensus yet for market ${market.id}` + ); continue; } - logger.info(`Oracle polling: consensus reached for market ${market.id}`, { - winningOutcome, - }); + logger.info( + `Oracle polling: consensus reached for market ${market.id}`, + { + winningOutcome, + } + ); const resolved = await this.marketService.resolveMarket( market.id, @@ -85,10 +90,13 @@ export class CronService { 'oracle-consensus' ); - logger.info(`Oracle polling: market ${market.id} resolved successfully`, { - winningOutcome, - resolvedAt: resolved.resolvedAt, - }); + logger.info( + `Oracle polling: market ${market.id} resolved successfully`, + { + winningOutcome, + resolvedAt: resolved.resolvedAt, + } + ); } catch (error) { logger.error(`Oracle polling: failed to process market ${market.id}`, { error, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c6818974..51fd2f62 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,6 +20,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "prettier": "^3.8.1", "vite": "^7.2.4" } }, @@ -54,7 +55,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1429,7 +1429,6 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1471,7 +1470,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1577,7 +1575,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1799,7 +1796,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2486,7 +2482,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2533,6 +2528,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2548,7 +2559,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2782,7 +2792,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -2904,7 +2913,6 @@ "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend/package.json b/frontend/package.json index b263d311..10ffd290 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,6 +7,8 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", + "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", + "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"", "preview": "vite preview" }, "dependencies": { @@ -22,6 +24,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "prettier": "^3.8.1", "vite": "^7.2.4" } -} +} \ No newline at end of file From 21230a26b870192bcfd4895702bf8b3eda544e7c Mon Sep 17 00:00:00 2001 From: Daniel Akinsanya Date: Thu, 26 Feb 2026 11:53:02 +0100 Subject: [PATCH 4/6] test: fix integration tests by using valid Stellar public keys --- backend/tests/auth.integration.test.ts | 34 +++++++++---------- ...rket-lifecycle.service.integration.test.ts | 2 +- .../integration/treasury.integration.test.ts | 15 +++++--- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/backend/tests/auth.integration.test.ts b/backend/tests/auth.integration.test.ts index 86a776de..ae972b65 100644 --- a/backend/tests/auth.integration.test.ts +++ b/backend/tests/auth.integration.test.ts @@ -118,7 +118,7 @@ describe('Auth Integration Tests', () => { it('should decode valid access token', () => { const payload = { userId: 'user-123', - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', tier: 'EXPERT' as const, }; @@ -133,7 +133,7 @@ describe('Auth Integration Tests', () => { it('should reject tampered token', () => { const token = signAccessToken({ userId: 'user-123', - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', tier: 'BEGINNER', }); @@ -149,7 +149,7 @@ describe('Auth Integration Tests', () => { const sessionData = { userId: 'user-123', tokenId: 'token-456', - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }; @@ -166,7 +166,7 @@ describe('Auth Integration Tests', () => { const oldSession = { userId: 'user-123', tokenId: 'old-token', - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }; @@ -174,7 +174,7 @@ describe('Auth Integration Tests', () => { const newSession = { userId: 'user-123', tokenId: 'new-token', - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }; @@ -380,7 +380,7 @@ describe('Auth Integration Tests', () => { const oldSession = { userId, tokenId: oldTokenId, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }; @@ -390,7 +390,7 @@ describe('Auth Integration Tests', () => { const newSession = { userId, tokenId: newTokenId, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }; @@ -429,7 +429,7 @@ describe('Auth Integration Tests', () => { const oldSession = { userId, tokenId: oldTokenId, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }; @@ -440,7 +440,7 @@ describe('Auth Integration Tests', () => { const newSession = { userId, tokenId: newTokenId, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }; @@ -461,7 +461,7 @@ describe('Auth Integration Tests', () => { const session = { userId, tokenId, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }; @@ -481,7 +481,7 @@ describe('Auth Integration Tests', () => { await sessionService.createSession({ userId, tokenId, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }); @@ -527,7 +527,7 @@ describe('Auth Integration Tests', () => { await sessionService.createSession({ userId, tokenId, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }); @@ -548,7 +548,7 @@ describe('Auth Integration Tests', () => { await sessionService.createSession({ userId, tokenId: `concurrent-token-${i}`, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }); @@ -566,7 +566,7 @@ describe('Auth Integration Tests', () => { await sessionService.createSession({ userId, tokenId, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }); @@ -586,7 +586,7 @@ describe('Auth Integration Tests', () => { await sessionService.createSession({ userId, tokenId, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }); @@ -611,7 +611,7 @@ describe('Auth Integration Tests', () => { sessionService.createSession({ userId, tokenId: `race-token-${i}`, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }) @@ -631,7 +631,7 @@ describe('Auth Integration Tests', () => { await sessionService.createSession({ userId, tokenId, - publicKey: 'GBTEST', + publicKey: 'GDNX7YG5NRHBKIZITO3FIFYXWLDDAL27IPXLQZSNJBZIIVPDTXJS3YNM', createdAt: Date.now(), expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000, }); diff --git a/backend/tests/integration/market-lifecycle.service.integration.test.ts b/backend/tests/integration/market-lifecycle.service.integration.test.ts index 28d13f7b..9cf53396 100644 --- a/backend/tests/integration/market-lifecycle.service.integration.test.ts +++ b/backend/tests/integration/market-lifecycle.service.integration.test.ts @@ -45,7 +45,7 @@ describe('Market Lifecycle Service Integration', () => { email: `lifecycle-${Date.now()}@test.com`, username: `lifecycle_user_${Date.now()}`, passwordHash: 'hash', - walletAddress: 'GTEST' + Math.random().toString(36).substring(2, 15).toUpperCase() + 'X'.repeat(40), + walletAddress: 'GAW2MORAONSQ2XHCUYFIUPHXQ2G6PCQ5K37JTS6A4RANJ4LDVEUFUCXG', usdcBalance: 10000, xlmBalance: 1000 } diff --git a/backend/tests/integration/treasury.integration.test.ts b/backend/tests/integration/treasury.integration.test.ts index b0f785ca..2acbc4e2 100644 --- a/backend/tests/integration/treasury.integration.test.ts +++ b/backend/tests/integration/treasury.integration.test.ts @@ -4,8 +4,8 @@ import express from 'express'; import { signAccessToken } from '../../src/utils/jwt.js'; import { prisma } from '../../src/database/prisma.js'; -const ADMIN_PUBLIC_KEY = 'GADMINTEST1234567890123456789012345678901234567890123456'; -const USER_PUBLIC_KEY = 'GUSERTEST12345678901234567890123456789012345678901234567'; +const ADMIN_PUBLIC_KEY = 'GAM7RGBZYHAZIQIAFGY526I76IXQO3GL6ZPBNFND2HZZRZU2G7JNCPTZ'; +const USER_PUBLIC_KEY = 'GBD3V6YULHA5L2EKMBSB5EWPU3HUA4SR34YWBBQSBTH4HHYO44XEILKF'; process.env.ADMIN_WALLET_ADDRESSES = ADMIN_PUBLIC_KEY; process.env.JWT_ACCESS_SECRET = 'test-jwt-access-secret-min-32-chars-here-for-testing'; @@ -92,7 +92,10 @@ describe('Treasury API Integration Tests', () => { it('should return 403 when non-admin tries to distribute', async () => { const recipients = [ - { address: 'GUSER1TEST12345678901234567890123456789012345678901', amount: '1000' }, + { + address: 'GCZYAMWDARYXBWWDZSD7VU5IW5W5XP3OXFTWSC7ZZ5AR7RZ5EWM3IH2A', + amount: '1000', + }, ]; const response = await request(app) @@ -126,7 +129,8 @@ describe('Treasury API Integration Tests', () => { vi.mocked(blockchainTreasuryService.distributeCreator).mockResolvedValue(mockResult); const marketId = '123e4567-e89b-12d3-a456-426614174000'; - const creatorAddress = 'GCREATORTEST12345678901234567890123456789012345678901234'; // 56 chars + const creatorAddress = + 'GDVF3MDO5RBB5ER7IH5EDONQ33ZWY2HVDUPII2DVSBURNUULEQ5W6GRN'; // 56 chars const response = await request(app) .post('/api/treasury/distribute-creator') @@ -157,7 +161,8 @@ describe('Treasury API Integration Tests', () => { .set('Authorization', `Bearer ${userToken}`) .send({ marketId: 'market-123', - creatorAddress: 'GCREATORTEST12345678901234567890123456789012345678901234', + creatorAddress: + 'GDVF3MDO5RBB5ER7IH5EDONQ33ZWY2HVDUPII2DVSBURNUULEQ5W6GRN', amount: '2000', }); From 28ef0e126516eef2675f3d42ed91cd82adc11e5d Mon Sep 17 00:00:00 2001 From: Daniel Akinsanya Date: Fri, 27 Feb 2026 15:14:22 +0100 Subject: [PATCH 5/6] fix: resolve CI failures (missing imports, syntax errors, and test data) --- backend/src/routes/trading.ts | 8 ++++++++ backend/src/services/prediction.service.ts | 1 + .../tests/middleware/validation.middleware.test.ts | 12 ++++++------ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/backend/src/routes/trading.ts b/backend/src/routes/trading.ts index 76566f4f..ccbdd62e 100644 --- a/backend/src/routes/trading.ts +++ b/backend/src/routes/trading.ts @@ -5,6 +5,14 @@ import { Router } from 'express'; import { tradingController } from '../controllers/trading.controller.js'; import { requireAuth } from '../middleware/auth.middleware.js'; import { tradeRateLimiter } from '../middleware/rateLimit.middleware.js'; +import { validate } from '../middleware/validation.middleware.js'; +import { + marketIdParam, + buySharesBody, + sellSharesBody, + addLiquidityBody, + removeLiquidityBody, +} from '../schemas/validation.schemas.js'; const router: Router = Router(); diff --git a/backend/src/services/prediction.service.ts b/backend/src/services/prediction.service.ts index ecd7f1cd..d3f4f0b1 100644 --- a/backend/src/services/prediction.service.ts +++ b/backend/src/services/prediction.service.ts @@ -15,6 +15,7 @@ import { notifyWinningsClaimed, notifyBalanceUpdated, } from '../websocket/realtime.js'; +import { marketBlockchainService, MarketBlockchainService, } from './blockchain/market.js'; diff --git a/backend/tests/middleware/validation.middleware.test.ts b/backend/tests/middleware/validation.middleware.test.ts index 4aba3731..5902762a 100644 --- a/backend/tests/middleware/validation.middleware.test.ts +++ b/backend/tests/middleware/validation.middleware.test.ts @@ -28,13 +28,13 @@ describe('Validation Middleware', () => { app.use(errorHandler); const response = await request(app).post('/challenge').send({ - publicKey: 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ', + publicKey: 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY', }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data.publicKey).toBe( - 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ' + 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY' ); }); @@ -74,7 +74,7 @@ describe('Validation Middleware', () => { app.use(errorHandler); const response = await request(app).post('/login').send({ - publicKey: 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ', + publicKey: 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY', signature: 'test-signature', nonce: 'test-nonce', }); @@ -90,7 +90,7 @@ describe('Validation Middleware', () => { app.use(errorHandler); const response = await request(app).post('/login').send({ - publicKey: 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ', + publicKey: 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY', signature: '', nonce: 'test-nonce', }); @@ -210,13 +210,13 @@ describe('Validation Middleware', () => { app.use(errorHandler); const response = await request(app).post('/verify').send({ - address: 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ', + address: 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY', }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data.address).toBe( - 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ' + 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY' ); }); From 7ade3eb9cf39cfc9a62a580c157cccf5c7518a76 Mon Sep 17 00:00:00 2001 From: Daniel Akinsanya Date: Fri, 27 Feb 2026 15:19:51 +0100 Subject: [PATCH 6/6] fix: update additional integration tests with valid Stellar keys --- backend/tests/integration/treasury.integration.test.ts | 2 +- backend/tests/middleware/integration.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/tests/integration/treasury.integration.test.ts b/backend/tests/integration/treasury.integration.test.ts index 63e162f3..1bb9900d 100644 --- a/backend/tests/integration/treasury.integration.test.ts +++ b/backend/tests/integration/treasury.integration.test.ts @@ -140,7 +140,7 @@ describe('Treasury API Integration Tests', () => { const marketId = '123e4567-e89b-12d3-a456-426614174000'; const creatorAddress = - 'GCREATORTEST12345678901234567890123456789012345678901234'; // 56 chars + 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY'; // Valid 56 chars const response = await request(app) .post('/api/treasury/distribute-creator') diff --git a/backend/tests/middleware/integration.test.ts b/backend/tests/middleware/integration.test.ts index 7a49915d..0fec3249 100644 --- a/backend/tests/middleware/integration.test.ts +++ b/backend/tests/middleware/integration.test.ts @@ -28,13 +28,13 @@ describe('Validation and Error Handling Integration', () => { app.use(errorHandler); const response = await request(app).post('/api/test').send({ - publicKey: 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ', + publicKey: 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY', }); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data.publicKey).toBe( - 'GA5XIGA5C7QTPTWXQHY6MCJRMTRZDOSHR6EFIBNDQTCQHG262N4GGKXQ' + 'GAMCVGJFOWWCF6N7YSS66DEZQSCGWZU2SCOWIA2NTMCKTODDTPUOOYDY' ); });