@@ -20,10 +20,12 @@ describe('RpcClient', () => {
2020
2121 mockGetBlock = jest . fn ( ) ;
2222 mockGetTransactionReceipt = jest . fn ( ) ;
23+ const mockReadContract = jest . fn ( ) ;
2324
2425 mockClient = {
2526 getBlock : mockGetBlock ,
2627 getTransactionReceipt : mockGetTransactionReceipt ,
28+ readContract : mockReadContract ,
2729 } ;
2830
2931 // Setup mocks
@@ -175,6 +177,89 @@ describe('RpcClient', () => {
175177 } ) ;
176178 } ) ;
177179
180+ describe ( 'getSourceLocators' , ( ) => {
181+ let client : RpcClient ;
182+ let mockReadContract : jest . Mock ;
183+
184+ beforeEach ( ( ) => {
185+ client = new RpcClient ( { chainId : 1301 , maxRetries : 0 } ) ;
186+ mockReadContract = mockClient . readContract ;
187+ } ) ;
188+
189+ it ( 'should fetch source locators for a workload ID' , async ( ) => {
190+ const workloadId = '0x71d62ba17902d590dad932310a7ec12feffa25454d7009c2084aa6f4c488953f' as `0x${string } `;
191+ const mockMetadata = {
192+ commitHash : '490fb2be109f0c2626c347bb3e43e97826c8f844' ,
193+ sourceLocators : [ 'https://github.com/example/repo1/c41fa4d500f6fb4e4fe46c23b34b26367e10beb4' , 'https://github.com/example/repo2/86ebf9de12466aaae1485eb6fc80ae3c78954edf' ]
194+ } ;
195+ mockReadContract . mockResolvedValue ( mockMetadata ) ;
196+
197+ const result = await client . getSourceLocators ( workloadId ) ;
198+
199+ expect ( mockReadContract ) . toHaveBeenCalledWith (
200+ expect . objectContaining ( {
201+ address : '0x3b03b3caabd49ca12de9eba46a6a2950700b1db4' ,
202+ functionName : 'getWorkloadMetadata' ,
203+ args : [ workloadId ] ,
204+ } )
205+ ) ;
206+ expect ( result ) . toEqual ( [ 'https://github.com/example/repo1/c41fa4d500f6fb4e4fe46c23b34b26367e10beb4' , 'https://github.com/example/repo2/86ebf9de12466aaae1485eb6fc80ae3c78954edf' ] ) ;
207+ } ) ;
208+
209+ it ( 'should handle empty source locators' , async ( ) => {
210+ const workloadId = '0x71d62ba17902d590dad932310a7ec12feffa25454d7009c2084aa6f4c488953f' as `0x${string } `;
211+ const mockMetadata = {
212+ commitHash : '490fb2be109f0c2626c347bb3e43e97826c8f844' ,
213+ sourceLocators : [ ]
214+ } ;
215+ mockReadContract . mockResolvedValue ( mockMetadata ) ;
216+
217+ const result = await client . getSourceLocators ( workloadId ) ;
218+
219+ expect ( result ) . toEqual ( [ ] ) ;
220+ } ) ;
221+
222+ it ( 'should retry on transient failures' , async ( ) => {
223+ const workloadId = '0x71d62ba17902d590dad932310a7ec12feffa25454d7009c2084aa6f4c488953f' as `0x${string } `;
224+ const mockMetadata = {
225+ commitHash : '490fb2be109f0c2626c347bb3e43e97826c8f844' ,
226+ sourceLocators : [ 'https://github.com/example/repo/86ebf9de12466aaae1485eb6fc80ae3c78954edf' ]
227+ } ;
228+
229+ const clientWithRetry = new RpcClient ( {
230+ chainId : 1301 ,
231+ maxRetries : 2 ,
232+ initialRetryDelay : 10
233+ } ) ;
234+ const mockReadContractWithRetry = clientWithRetry . getClient ( ) . readContract as jest . Mock ;
235+
236+ mockReadContractWithRetry
237+ . mockRejectedValueOnce ( new Error ( 'Network error' ) )
238+ . mockResolvedValueOnce ( mockMetadata ) ;
239+
240+ const result = await clientWithRetry . getSourceLocators ( workloadId ) ;
241+
242+ expect ( mockReadContractWithRetry ) . toHaveBeenCalledTimes ( 2 ) ;
243+ expect ( result ) . toEqual ( [ 'https://github.com/example/repo/86ebf9de12466aaae1485eb6fc80ae3c78954edf' ] ) ;
244+ } ) ;
245+
246+ it ( 'should throw NetworkError after max retries' , async ( ) => {
247+ const workloadId = '0x71d62ba17902d590dad932310a7ec12feffa25454d7009c2084aa6f4c488953f' as `0x${string } `;
248+
249+ const clientWithRetry = new RpcClient ( {
250+ chainId : 1301 ,
251+ maxRetries : 1 ,
252+ initialRetryDelay : 10
253+ } ) ;
254+ const mockReadContractWithRetry = clientWithRetry . getClient ( ) . readContract as jest . Mock ;
255+
256+ mockReadContractWithRetry . mockRejectedValue ( new Error ( 'Network error' ) ) ;
257+
258+ await expect ( clientWithRetry . getSourceLocators ( workloadId ) ) . rejects . toThrow ( NetworkError ) ;
259+ expect ( mockReadContractWithRetry ) . toHaveBeenCalledTimes ( 2 ) ;
260+ } ) ;
261+ } ) ;
262+
178263 describe ( 'retry logic' , ( ) => {
179264 it ( 'should retry failed requests with exponential backoff' , async ( ) => {
180265 const client = new RpcClient ( {
@@ -284,10 +369,15 @@ describe('RpcClient', () => {
284369 commitHash : '490fb2be109f0c2626c347bb3e43e97826c8f844' ,
285370 } ,
286371 } ;
372+ const mockMetadata = {
373+ commitHash : '490fb2be109f0c2626c347bb3e43e97826c8f844' ,
374+ sourceLocators : [ 'https://github.com/example/repo1/86ebf9de12466aaae1485eb6fc80ae3c78954edf' , 'https://github.com/example/repo2/f6cf154d5a26c632548d85998c2a7dab40d8ef02' ]
375+ } ;
287376
288377 mockGetBlock . mockResolvedValue ( mockBlock ) ;
289378 mockGetTransactionReceipt . mockResolvedValue ( mockReceipt ) ;
290379 mockParseEventLogs . mockReturnValue ( [ mockLog ] ) ;
380+ mockClient . readContract . mockResolvedValue ( mockMetadata ) ;
291381
292382 const result = await client . getFlashtestationTx ( blockNumber ) ;
293383
@@ -299,12 +389,19 @@ describe('RpcClient', () => {
299389 logs : mockReceipt . logs ,
300390 } )
301391 ) ;
392+ expect ( mockClient . readContract ) . toHaveBeenCalledWith (
393+ expect . objectContaining ( {
394+ functionName : 'getWorkloadMetadata' ,
395+ args : [ '0x71d62ba17902d590dad932310a7ec12feffa25454d7009c2084aa6f4c488953f' ] ,
396+ } )
397+ ) ;
302398 expect ( result ) . toEqual ( {
303399 caller : '0xcaBBa9e7f4b3A885C5aa069f88469ac711Dd4aCC' ,
304400 workloadId : '0x71d62ba17902d590dad932310a7ec12feffa25454d7009c2084aa6f4c488953f' ,
305401 version : 1 ,
306402 blockContentHash : '0x846604baa7db2297b9c4058106cc5869bcdbb753760981dbcd6d345d3d5f3e0f' ,
307403 commitHash : '490fb2be109f0c2626c347bb3e43e97826c8f844' ,
404+ sourceLocators : [ 'https://github.com/example/repo1/86ebf9de12466aaae1485eb6fc80ae3c78954edf' , 'https://github.com/example/repo2/f6cf154d5a26c632548d85998c2a7dab40d8ef02' ] ,
308405 } ) ;
309406 } ) ;
310407
0 commit comments