Created
October 22, 2025 10:45
-
-
Save ezynda3/c66e299c0b9f1627ed92440a647fbc74 to your computer and use it in GitHub Desktop.
Mongo DB TS Helpers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { config } from 'dotenv' | |
| import { MongoClient } from 'mongodb' | |
| import { consola } from 'consola' | |
| import { type Address, type Hex } from 'viem' | |
| import { getRPCEnvVarName } from '../script/utils/network.js' | |
| import { | |
| type ISafeTxDocument, | |
| type ISafeTransaction, | |
| OperationTypeEnum, | |
| storeTransactionInMongoDB, | |
| getNextNonce, | |
| getPendingTransactionsByNetwork, | |
| getSafeMongoCollection, | |
| } from '../script/deploy/safe/safe-utils.js' | |
| config() | |
| interface IRpcEndpoint { | |
| url: string | |
| priority: number | |
| isActive?: boolean | |
| network: string | |
| environment?: string | |
| } | |
| const TEST_NETWORK = 'testnetwork' | |
| const TEST_CHAIN_ID = 999999 | |
| const TEST_SAFE_ADDRESS = | |
| '0x1234567890123456789012345678901234567890' as Address | |
| const TEST_RPC_URL = 'https://test-rpc.example.com' | |
| const TEST_PROPOSER = '0x0987654321098765432109876543210987654321' as Address | |
| async function testRpcEndpoints(client: MongoClient) { | |
| consola.info('Testing RPC Endpoints CRUD operations...') | |
| const db = client.db('sc_public') | |
| const collection = db.collection('RpcEndpoints') | |
| const testChainName = `test_chain_${Date.now()}` | |
| consola.info('1. CREATE: Adding new RPC endpoint') | |
| const newRpcEndpoint = { | |
| url: TEST_RPC_URL, | |
| priority: 1, | |
| environment: 'production', | |
| } | |
| await collection.updateOne( | |
| { chainName: testChainName }, | |
| { | |
| $set: { lastUpdated: new Date() }, | |
| $push: { rpcs: newRpcEndpoint } as any, | |
| }, | |
| { upsert: true } | |
| ) | |
| consola.success(`✓ Created RPC endpoint for ${testChainName}`) | |
| consola.info('2. READ: Fetching RPC endpoint') | |
| const doc = await collection.findOne({ chainName: testChainName }) | |
| if (!doc || !doc.rpcs || doc.rpcs.length === 0) | |
| throw new Error('Failed to read RPC endpoint') | |
| consola.success(`✓ Read RPC endpoint: ${doc.rpcs[0].url}`) | |
| consola.info('3. UPDATE: Updating RPC endpoint priority') | |
| const newPriority = 10 | |
| await collection.updateOne( | |
| { chainName: testChainName }, | |
| { | |
| $set: { | |
| lastUpdated: new Date(), | |
| 'rpcs.0.priority': newPriority, | |
| }, | |
| } | |
| ) | |
| const updatedDoc = await collection.findOne({ chainName: testChainName }) | |
| if (!updatedDoc || updatedDoc.rpcs[0].priority !== newPriority) | |
| throw new Error('Failed to update RPC endpoint priority') | |
| consola.success(`✓ Updated priority to ${newPriority}`) | |
| consola.info('4. DELETE: Removing RPC endpoint') | |
| const deleteResult = await collection.deleteOne({ chainName: testChainName }) | |
| if (deleteResult.deletedCount !== 1) | |
| throw new Error('Failed to delete RPC endpoint') | |
| consola.success(`✓ Deleted RPC endpoint for ${testChainName}`) | |
| } | |
| async function testPendingTransactions(client: MongoClient) { | |
| consola.info('\nTesting PendingTransactions CRUD operations...') | |
| const db = client.db('sc_private') | |
| const pendingTransactions = db.collection<ISafeTxDocument>( | |
| 'pendingTransactions' | |
| ) | |
| const testNetwork = `${TEST_NETWORK}_${Date.now()}` | |
| const testNonce = 42n | |
| consola.info('1. CREATE: Storing new pending transaction') | |
| const safeTx: ISafeTransaction = { | |
| data: { | |
| to: '0xabcdef1234567890abcdef1234567890abcdef12' as Address, | |
| value: 0n, | |
| data: '0x1234' as Hex, | |
| operation: OperationTypeEnum.Call, | |
| nonce: testNonce, | |
| }, | |
| signatures: new Map(), | |
| } | |
| const safeTxHash = '0xhash123' as Hex | |
| const insertResult = await storeTransactionInMongoDB( | |
| pendingTransactions, | |
| TEST_SAFE_ADDRESS, | |
| testNetwork, | |
| TEST_CHAIN_ID, | |
| safeTx, | |
| safeTxHash, | |
| TEST_PROPOSER | |
| ) | |
| if (!insertResult.insertedId) | |
| throw new Error('Failed to create pending transaction') | |
| consola.success( | |
| `✓ Created pending transaction with ID: ${insertResult.insertedId}` | |
| ) | |
| consola.info('2. READ: Fetching pending transaction') | |
| const txDoc = await pendingTransactions.findOne({ | |
| _id: insertResult.insertedId, | |
| }) | |
| if (!txDoc) throw new Error('Failed to read pending transaction') | |
| consola.success(`✓ Read pending transaction for network: ${txDoc.network}`) | |
| consola.info('3. UPDATE: Adding signature to transaction') | |
| const testSignature = { | |
| signer: TEST_PROPOSER, | |
| data: '0xsignature123' as Hex, | |
| } | |
| await pendingTransactions.updateOne( | |
| { _id: insertResult.insertedId }, | |
| { | |
| $set: { | |
| [`safeTx.signatures.${TEST_PROPOSER.toLowerCase()}`]: testSignature, | |
| }, | |
| } | |
| ) | |
| const updatedTxDoc = await pendingTransactions.findOne({ | |
| _id: insertResult.insertedId, | |
| }) | |
| if ( | |
| !updatedTxDoc || | |
| !updatedTxDoc.safeTx.signatures || | |
| !updatedTxDoc.safeTx.signatures[TEST_PROPOSER.toLowerCase()] | |
| ) | |
| throw new Error('Failed to update transaction with signature') | |
| consola.success(`✓ Updated transaction with signature from ${TEST_PROPOSER}`) | |
| consola.info('4. Testing helper functions') | |
| consola.info('4a. Testing getNextNonce') | |
| const nextNonce = await getNextNonce( | |
| pendingTransactions, | |
| TEST_SAFE_ADDRESS, | |
| testNetwork, | |
| TEST_CHAIN_ID, | |
| testNonce | |
| ) | |
| if (nextNonce !== testNonce + 1n) | |
| throw new Error( | |
| `getNextNonce failed: expected ${testNonce + 1n}, got ${nextNonce}` | |
| ) | |
| consola.success(`✓ getNextNonce returned correct value: ${nextNonce}`) | |
| consola.info( | |
| '4b. Creating second transaction for getPendingTransactionsByNetwork test' | |
| ) | |
| const safeTx2: ISafeTransaction = { | |
| data: { | |
| to: '0xfedcba0987654321fedcba0987654321fedcba09' as Address, | |
| value: 100n, | |
| data: '0x5678' as Hex, | |
| operation: OperationTypeEnum.Call, | |
| nonce: testNonce + 1n, | |
| }, | |
| signatures: new Map(), | |
| } | |
| const insertResult2 = await storeTransactionInMongoDB( | |
| pendingTransactions, | |
| TEST_SAFE_ADDRESS, | |
| testNetwork, | |
| TEST_CHAIN_ID, | |
| safeTx2, | |
| '0xhash456' as Hex, | |
| TEST_PROPOSER | |
| ) | |
| if (!insertResult2.insertedId) | |
| throw new Error('Failed to create second pending transaction') | |
| consola.info('4c. Testing getPendingTransactionsByNetwork') | |
| const txsByNetwork = await getPendingTransactionsByNetwork( | |
| pendingTransactions, | |
| [testNetwork] | |
| ) | |
| if (!txsByNetwork[testNetwork] || txsByNetwork[testNetwork].length !== 2) | |
| throw new Error( | |
| `getPendingTransactionsByNetwork failed: expected 2 transactions, got ${ | |
| txsByNetwork[testNetwork]?.length || 0 | |
| }` | |
| ) | |
| consola.success( | |
| `✓ getPendingTransactionsByNetwork returned ${txsByNetwork[testNetwork].length} transactions` | |
| ) | |
| consola.info('4d. Testing transaction sorting by nonce') | |
| const sortedTxs = txsByNetwork[testNetwork] | |
| if (sortedTxs[0].safeTx.data.nonce >= sortedTxs[1].safeTx.data.nonce) | |
| throw new Error('Transactions not sorted by nonce') | |
| consola.success(`✓ Transactions correctly sorted by nonce`) | |
| consola.info('5. UPDATE: Marking transaction as executed') | |
| await pendingTransactions.updateOne( | |
| { _id: insertResult.insertedId }, | |
| { $set: { status: 'executed' } } | |
| ) | |
| const executedTxDoc = await pendingTransactions.findOne({ | |
| _id: insertResult.insertedId, | |
| }) | |
| if (!executedTxDoc || executedTxDoc.status !== 'executed') | |
| throw new Error('Failed to mark transaction as executed') | |
| consola.success(`✓ Marked transaction as executed`) | |
| consola.info('6. DELETE: Removing test transactions') | |
| const deleteResult = await pendingTransactions.deleteMany({ | |
| network: testNetwork, | |
| }) | |
| if (deleteResult.deletedCount !== 2) | |
| throw new Error( | |
| `Failed to delete all test transactions: deleted ${deleteResult.deletedCount}, expected 2` | |
| ) | |
| consola.success(`✓ Deleted ${deleteResult.deletedCount} test transactions`) | |
| } | |
| async function testFetchRpcsHelper() { | |
| consola.info('\nTesting fetch-rpcs helper functions...') | |
| const MONGODB_URI = process.env.MONGODB_URI | |
| if (!MONGODB_URI) | |
| throw new Error('MONGODB_URI is not defined in the environment') | |
| const client = new MongoClient(MONGODB_URI) | |
| try { | |
| await client.connect() | |
| const db = client.db('sc_public') | |
| const collection = db.collection('RpcEndpoints') | |
| const testChainName = `test_fetch_${Date.now()}` | |
| consola.info('1. Setting up test RPC endpoints') | |
| await collection.insertOne({ | |
| chainName: testChainName, | |
| rpcs: [ | |
| { | |
| url: 'https://rpc1.example.com', | |
| priority: 2, | |
| isActive: true, | |
| }, | |
| { | |
| url: 'https://rpc2.example.com', | |
| priority: 5, | |
| isActive: true, | |
| }, | |
| { | |
| url: 'https://rpc3.example.com', | |
| priority: 1, | |
| isActive: false, | |
| }, | |
| ], | |
| lastUpdated: new Date(), | |
| }) | |
| consola.info('2. Fetching and verifying sorting') | |
| const docs = await collection.find({ chainName: testChainName }).toArray() | |
| let validEndpoints: IRpcEndpoint[] = [] | |
| for (const doc of docs) { | |
| if (doc?.chainName && Array.isArray(doc?.rpcs)) { | |
| validEndpoints = doc.rpcs.filter((r: any) => !!r.url) | |
| validEndpoints.sort((a, b) => b.priority - a.priority) | |
| } | |
| } | |
| if (validEndpoints.length !== 3) | |
| throw new Error(`Expected 3 endpoints, got ${validEndpoints.length}`) | |
| if (validEndpoints[0].priority !== 5) | |
| throw new Error( | |
| `Expected highest priority 5, got ${validEndpoints[0].priority}` | |
| ) | |
| consola.success(`✓ RPC endpoints correctly sorted by priority (descending)`) | |
| consola.info('3. Testing getRPCEnvVarName helper') | |
| const envVarName = getRPCEnvVarName(testChainName) | |
| const expectedName = `ETH_NODE_URI_${testChainName | |
| .toUpperCase() | |
| .replace(/-/g, '_')}` | |
| if (envVarName !== expectedName) | |
| throw new Error( | |
| `getRPCEnvVarName failed: expected ${expectedName}, got ${envVarName}` | |
| ) | |
| consola.success(`✓ getRPCEnvVarName returned: ${envVarName}`) | |
| consola.info('4. Cleanup') | |
| await collection.deleteOne({ chainName: testChainName }) | |
| consola.success(`✓ Cleaned up test data`) | |
| } finally { | |
| await client.close() | |
| } | |
| } | |
| async function testSafeMongoCollection() { | |
| consola.info('\nTesting getSafeMongoCollection helper...') | |
| consola.info('1. Testing getSafeMongoCollection') | |
| const { client, pendingTransactions } = await getSafeMongoCollection() | |
| try { | |
| const collectionName = pendingTransactions.collectionName | |
| if (collectionName !== 'pendingTransactions') | |
| throw new Error( | |
| `Expected collection name 'pendingTransactions', got '${collectionName}'` | |
| ) | |
| consola.success(`✓ getSafeMongoCollection returned correct collection`) | |
| consola.info('2. Verifying database connection') | |
| const dbName = pendingTransactions.dbName | |
| if (dbName !== 'sc_private') | |
| throw new Error(`Expected database name 'sc_private', got '${dbName}'`) | |
| consola.success(`✓ Connected to correct database: ${dbName}`) | |
| } finally { | |
| await client.close() | |
| } | |
| } | |
| async function runTests() { | |
| consola.start('Starting MongoDB Integration Tests\n') | |
| const MONGODB_URI = process.env.MONGODB_URI | |
| if (!MONGODB_URI) { | |
| consola.error('MONGODB_URI is not defined in the environment') | |
| consola.info('Please set MONGODB_URI in your .env file') | |
| process.exit(1) | |
| } | |
| consola.info(`Connecting to MongoDB...`) | |
| const client = new MongoClient(MONGODB_URI) | |
| try { | |
| await client.connect() | |
| consola.success('✓ Connected to MongoDB\n') | |
| await testRpcEndpoints(client) | |
| await testPendingTransactions(client) | |
| await testFetchRpcsHelper() | |
| await testSafeMongoCollection() | |
| consola.success('\n✅ All tests passed!') | |
| } catch (error) { | |
| consola.error('\n❌ Test failed:', error) | |
| process.exit(1) | |
| } finally { | |
| await client.close() | |
| consola.info('\nClosed MongoDB connection') | |
| } | |
| } | |
| runTests() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment