Skip to content
Open
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
62 changes: 62 additions & 0 deletions tests/mocks/sorobanMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Mock Soroban RPC layer for testing contract interactions
* without requiring a live network connection.
*
* Configurable responses for getContractData, simulateTransaction,
* sendTransaction, and getTransactionStatus.
*/

class SorobanMock {
constructor() {
this.responses = {
getContractData: { success: true, data: null },
simulateTransaction: { success: true, results: [] },
sendTransaction: { success: true, hash: '0x' + 'a'.repeat(64) },
getTransactionStatus: { status: 'SUCCESS' }
};
this.callLog = [];
this.errorMode = null;
}

setErrorMode(mode) { this.errorMode = mode; }
setResponse(method, response) { this.responses[method] = response; }

reset() {
this.responses = {
getContractData: { success: true, data: null },
simulateTransaction: { success: true, results: [] },
sendTransaction: { success: true, hash: '0x' + 'a'.repeat(64) },
getTransactionStatus: { status: 'SUCCESS' }
};
this.callLog = [];
this.errorMode = null;
}

async getContractData(contractId, key) {
this.callLog.push({ method: 'getContractData', contractId, key });
if (this.errorMode === 'getContractData') return { success: false, error: 'CONTRACT_NOT_FOUND' };
return this.responses.getContractData;
}

async simulateTransaction(tx) {
this.callLog.push({ method: 'simulateTransaction', tx });
if (this.errorMode === 'simulateTransaction') return { success: false, error: 'SIMULATION_FAILED' };
return this.responses.simulateTransaction;
}

async sendTransaction(tx) {
this.callLog.push({ method: 'sendTransaction', tx });
if (this.errorMode === 'sendTransaction') return { success: false, error: 'TX_REJECTED' };
return this.responses.sendTransaction;
}

async getTransactionStatus(hash) {
this.callLog.push({ method: 'getTransactionStatus', hash });
if (this.errorMode === 'getTransactionStatus') return { status: 'FAILED', error: 'TIMEOUT' };
return this.responses.getTransactionStatus;
}

getCallLog() { return this.callLog; }
}

module.exports = { SorobanMock };
59 changes: 59 additions & 0 deletions tests/unit/sorobanMock.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const { test, describe, beforeEach } = require('node:test');
const assert = require('node:assert');
const { SorobanMock } = require('../mocks/sorobanMock');

describe('Soroban Mock Layer', () => {
let mock;

beforeEach(() => {
mock = new SorobanMock();
});

test('returns default success for getContractData', async () => {
const result = await mock.getContractData('contract-1', 'key-1');
assert.strictEqual(result.success, true);
assert.strictEqual(result.data, null);
});

test('logs all calls', async () => {
await mock.getContractData('c1', 'k1');
await mock.simulateTransaction({ tx: 'test' });
await mock.sendTransaction({ tx: 'test' });
await mock.getTransactionStatus('hash123');
const log = mock.getCallLog();
assert.strictEqual(log.length, 4);
assert.strictEqual(log[0].method, 'getContractData');
assert.strictEqual(log[1].method, 'simulateTransaction');
assert.strictEqual(log[2].method, 'sendTransaction');
assert.strictEqual(log[3].method, 'getTransactionStatus');
});

test('supports error mode for getContractData', async () => {
mock.setErrorMode('getContractData');
const result = await mock.getContractData('c1', 'k1');
assert.strictEqual(result.success, false);
assert.strictEqual(result.error, 'CONTRACT_NOT_FOUND');
});

test('supports error mode for simulateTransaction', async () => {
mock.setErrorMode('simulateTransaction');
const result = await mock.simulateTransaction({ tx: 'test' });
assert.strictEqual(result.success, false);
assert.strictEqual(result.error, 'SIMULATION_FAILED');
});

test('supports custom responses', async () => {
mock.setResponse('getContractData', { success: true, data: { balance: 1000 } });
const result = await mock.getContractData('c1', 'k1');
assert.strictEqual(result.data.balance, 1000);
});

test('resets to defaults', async () => {
mock.setErrorMode('getContractData');
mock.setResponse('getContractData', { success: true, data: 'custom' });
mock.reset();
const result = await mock.getContractData('c1', 'k1');
assert.strictEqual(result.success, true);
assert.strictEqual(result.data, null);
});
});