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
Empty file added packages/api/rest/output.txt
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,41 @@ describe('BlendProtocol - Operations Tests', () => {
blendProtocol.supply(testAddress, testPrivateKey, invalidAsset, '1000000')
).rejects.toThrow('Invalid asset');
});

it('should return pending transaction when privateKey is not provided', async () => {
// Mock transaction builder
const mockTx = {
toXDR: jest.fn(() => 'mocked-xdr')
};
(TransactionBuilder as jest.MockedClass<typeof TransactionBuilder>).mockImplementation(
() =>
({
addOperation: jest.fn().mockReturnThis(),
setTimeout: jest.fn().mockReturnThis(),
build: jest.fn(() => mockTx)
} as any)
);

// Mock simulation success
const mockSimulation = {
result: { mock: 'result' }
};
mockSorobanServer.simulateTransaction = jest
.fn()
.mockResolvedValue(mockSimulation);
(rpc.Api.isSimulationError as jest.Mock).mockReturnValue(false);

// Mock prepareTransaction
const mockPreparedTx = {
toXDR: jest.fn(() => 'prepared-xdr')
};
mockSorobanServer.prepareTransaction = jest.fn().mockResolvedValue(mockPreparedTx);

const result = await blendProtocol.supply(testAddress, null as any, testAsset, '1000000');

expect(result.status).toBe('pending');
expect(mockSorobanServer.prepareTransaction).toHaveBeenCalled();
});
});

// ========================================
Expand Down Expand Up @@ -550,6 +585,41 @@ describe('BlendProtocol - Operations Tests', () => {
blendProtocol.withdraw(testAddress, testPrivateKey, invalidAsset, '500000')
).rejects.toThrow('Invalid asset');
});

it('should return pending transaction when privateKey is not provided', async () => {
// Mock transaction builder
const mockTx = {
toXDR: jest.fn(() => 'mocked-xdr')
};
(TransactionBuilder as jest.MockedClass<typeof TransactionBuilder>).mockImplementation(
() =>
({
addOperation: jest.fn().mockReturnThis(),
setTimeout: jest.fn().mockReturnThis(),
build: jest.fn(() => mockTx)
} as any)
);

// Mock simulation success
const mockSimulation = {
result: { mock: 'result' }
};
mockSorobanServer.simulateTransaction = jest
.fn()
.mockResolvedValue(mockSimulation);
(rpc.Api.isSimulationError as jest.Mock).mockReturnValue(false);

// Mock prepareTransaction
const mockPreparedTx = {
toXDR: jest.fn(() => 'prepared-xdr')
};
mockSorobanServer.prepareTransaction = jest.fn().mockResolvedValue(mockPreparedTx);

const result = await blendProtocol.withdraw(testAddress, null as any, testAsset, '500000');

expect(result.status).toBe('pending');
expect(mockSorobanServer.prepareTransaction).toHaveBeenCalled();
});
});

// ========================================
Expand Down Expand Up @@ -682,6 +752,41 @@ describe('BlendProtocol - Operations Tests', () => {
blendProtocol.borrow(testAddress, testPrivateKey, invalidAsset, '250000')
).rejects.toThrow('Invalid asset');
});

it('should return pending transaction when privateKey is not provided', async () => {
// Mock transaction builder
const mockTx = {
toXDR: jest.fn(() => 'mocked-xdr')
};
(TransactionBuilder as jest.MockedClass<typeof TransactionBuilder>).mockImplementation(
() =>
({
addOperation: jest.fn().mockReturnThis(),
setTimeout: jest.fn().mockReturnThis(),
build: jest.fn(() => mockTx)
} as any)
);

// Mock simulation success
const mockSimulation = {
result: { mock: 'result' }
};
mockSorobanServer.simulateTransaction = jest
.fn()
.mockResolvedValue(mockSimulation);
(rpc.Api.isSimulationError as jest.Mock).mockReturnValue(false);

// Mock prepareTransaction
const mockPreparedTx = {
toXDR: jest.fn(() => 'prepared-xdr')
};
mockSorobanServer.prepareTransaction = jest.fn().mockResolvedValue(mockPreparedTx);

const result = await blendProtocol.borrow(testAddress, null as any, testAsset, '250000');

expect(result.status).toBe('pending');
expect(mockSorobanServer.prepareTransaction).toHaveBeenCalled();
});
});

// ========================================
Expand Down Expand Up @@ -814,6 +919,41 @@ describe('BlendProtocol - Operations Tests', () => {
blendProtocol.repay(testAddress, testPrivateKey, invalidAsset, '250000')
).rejects.toThrow('Invalid asset');
});

it('should return pending transaction when privateKey is not provided', async () => {
// Mock transaction builder
const mockTx = {
toXDR: jest.fn(() => 'mocked-xdr')
};
(TransactionBuilder as jest.MockedClass<typeof TransactionBuilder>).mockImplementation(
() =>
({
addOperation: jest.fn().mockReturnThis(),
setTimeout: jest.fn().mockReturnThis(),
build: jest.fn(() => mockTx)
} as any)
);

// Mock simulation success
const mockSimulation = {
result: { mock: 'result' }
};
mockSorobanServer.simulateTransaction = jest
.fn()
.mockResolvedValue(mockSimulation);
(rpc.Api.isSimulationError as jest.Mock).mockReturnValue(false);

// Mock prepareTransaction
const mockPreparedTx = {
toXDR: jest.fn(() => 'prepared-xdr')
};
mockSorobanServer.prepareTransaction = jest.fn().mockResolvedValue(mockPreparedTx);

const result = await blendProtocol.repay(testAddress, null as any, testAsset, '250000');

expect(result.status).toBe('pending');
expect(mockSorobanServer.prepareTransaction).toHaveBeenCalled();
});
});

// ========================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ describe('BlendProtocol', () => {
let mockHorizonServer: any;

const testAddress = 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5';
const testBorrowerAddress = 'GDXFZ4UXBQPTPLQHZJ2IZ3MJRZ6G7CRGSKXM3XDMIFV4KQDLXP2KPXK5';
const testPrivateKey = 'SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4';
const testAsset: Asset = {
code: 'USDC',
Expand Down Expand Up @@ -416,7 +417,7 @@ describe('BlendProtocol', () => {
const result = await blendProtocol.liquidate(
testAddress,
testPrivateKey,
'borrower-address',
testBorrowerAddress,
testAsset,
debtAmount,
testAsset
Expand All @@ -442,7 +443,7 @@ describe('BlendProtocol', () => {
await expect(blendProtocol.liquidate(
testAddress,
testPrivateKey,
'borrower-address',
testBorrowerAddress,
testAsset,
'50',
testAsset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,20 @@ describe('SoroswapProtocol', () => {
expect(stats.utilizationRate).toBe(0);
expect(stats.timestamp).toBeInstanceOf(Date);
});

it('should handle errors in getStats gracefully', async () => {
// Force an error by corrupting the internal state
const originalNew = Date.prototype.getTime;
Date.prototype.getTime = (() => { throw new Error('Time error'); }) as any;

const stats = await soroswapProtocol.getStats();

// Should still return default stats due to error handling
expect(stats).toBeDefined();

// Restore original function
Date.prototype.getTime = originalNew;
});
Comment on lines +249 to +261
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify actual getStats + handleError behavior in implementation.
rg -n "getStats\\s*\\(" packages/core/defi-protocols/src/protocols/soroswap/soroswap-protocol.ts -C8
rg -n "handleError\\s*\\(" packages/core/defi-protocols/src/protocols/base-protocol.ts -C6

Repository: Galaxy-KJ/Galaxy-DevKit

Length of output: 1054


🏁 Script executed:

#!/bin/bash
# Get full getStats implementation including catch block
sed -n '100,150p' packages/core/defi-protocols/src/protocols/soroswap/soroswap-protocol.ts

Repository: Galaxy-KJ/Galaxy-DevKit

Length of output: 1401


getStats error-path test is brittle and likely not testing the intended branch.

Line 252 monkey-patches Date.prototype.getTime, but new Date() does not call getTime() during construction—it only initializes the Date object. This means the patch never triggers an error, and the catch block is never exercised. The test becomes a false-positive. Additionally, global mutation restoration is not guarded with finally.

Suggested test hardening
 it('should handle errors in getStats gracefully', async () => {
-  // Force an error by corrupting the internal state
-  const originalNew = Date.prototype.getTime;
-  Date.prototype.getTime = (() => { throw new Error('Time error'); }) as any;
-
-  const stats = await soroswapProtocol.getStats();
-
-  // Should still return default stats due to error handling
-  expect(stats).toBeDefined();
-
-  // Restore original function
-  Date.prototype.getTime = originalNew;
+  const RealDate = Date;
+  (global as any).Date = class extends RealDate {
+    constructor(...args: any[]) {
+      if (args.length === 0) throw new Error('Time error');
+      super(...args as []);
+    }
+  } as any;
+  try {
+    await expect(soroswapProtocol.getStats()).rejects.toThrow(/Time error|getStats/i);
+  } finally {
+    (global as any).Date = RealDate;
+  }
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('should handle errors in getStats gracefully', async () => {
// Force an error by corrupting the internal state
const originalNew = Date.prototype.getTime;
Date.prototype.getTime = (() => { throw new Error('Time error'); }) as any;
const stats = await soroswapProtocol.getStats();
// Should still return default stats due to error handling
expect(stats).toBeDefined();
// Restore original function
Date.prototype.getTime = originalNew;
});
it('should handle errors in getStats gracefully', async () => {
const RealDate = Date;
(global as any).Date = class extends RealDate {
constructor(...args: any[]) {
if (args.length === 0) throw new Error('Time error');
super(...args as []);
}
} as any;
try {
await expect(soroswapProtocol.getStats()).rejects.toThrow(/Time error|getStats/i);
} finally {
(global as any).Date = RealDate;
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/defi-protocols/__tests__/protocols/soroswap-protocol.test.ts`
around lines 249 - 261, The test currently monkey-patches Date.prototype.getTime
which doesn't trigger during construction; instead patch Date.now (or use
jest.spyOn(Date, 'now').mockImplementation(() => { throw new Error('Time
error'); })) so that soroswapProtocol.getStats() will hit the error path, and
wrap the mutation and assertion in a try/finally (or ensure mockRestore in
finally) to always restore the original implementation; target the
soroswapProtocol.getStats test and replace the Date.prototype.getTime patch with
Date.now mocking and guaranteed restoration.

});

// ==========================================
Expand Down Expand Up @@ -775,6 +789,25 @@ describe('SoroswapProtocol', () => {
const pool = await soroswapProtocol.getLiquidityPool(tokenA, tokenB);
expect(pool.address).toBe('');
});

it('should handle fromScVal error when parsing pair address', async () => {
(soroswapProtocol as any).sorobanServer.simulateTransaction.mockResolvedValueOnce({
result: { retval: { type: 'address' } }
});
(Address.fromScVal as jest.Mock).mockImplementationOnce(() => {
throw new Error('fromScVal parse error');
});

const pool = await soroswapProtocol.getLiquidityPool(tokenA, tokenB);
expect(pool.address).toBe('');
});

it('should throw on general errors in getLiquidityPool', async () => {
// Force an error by corrupting factoryContract
(soroswapProtocol as any).factoryContract = { call: () => { throw new Error('Contract error'); } };

await expect(soroswapProtocol.getLiquidityPool(tokenA, tokenB)).rejects.toThrow('Contract error');
});
});

// ==========================================
Expand Down Expand Up @@ -1034,6 +1067,17 @@ describe('SoroswapProtocol', () => {

expect(analytics.feeApr).toBe(0);
});

it('should throw on general errors in getPoolAnalytics', async () => {
// Force an error by making sorobanServer undefined
const originalServer = (soroswapProtocol as any).sorobanServer;
(soroswapProtocol as any).sorobanServer = undefined;

await expect(soroswapProtocol.getPoolAnalytics(poolAddress)).rejects.toThrow();

// Restore original server
(soroswapProtocol as any).sorobanServer = originalServer;
});
});

// ==========================================
Expand Down Expand Up @@ -1087,5 +1131,12 @@ describe('SoroswapProtocol', () => {

await expect(uninitProtocol.getAllPoolsAnalytics()).rejects.toThrow(/not initialized/);
});

it('should throw on general errors in getAllPoolsAnalytics', async () => {
// Force an error by making getAllPairs throw an error
jest.spyOn(soroswapProtocol, 'getAllPairs').mockRejectedValueOnce(new Error('Network error'));

await expect(soroswapProtocol.getAllPoolsAnalytics()).rejects.toThrow('Network error');
});
});
});
Binary file added packages/core/defi-protocols/coverage_report.txt
Binary file not shown.
Loading
Loading