1
1
import { Messenger } from '@metamask/base-controller' ;
2
2
import { toHex } from '@metamask/controller-utils' ;
3
- import * as controllerUtils from '@metamask/controller-utils' ;
4
3
import type { InternalAccount } from '@metamask/keyring-internal-api' ;
5
4
import type { NetworkState } from '@metamask/network-controller' ;
6
5
import type { PreferencesState } from '@metamask/preferences-controller' ;
@@ -9,6 +8,7 @@ import BN from 'bn.js';
9
8
import { useFakeTimers } from 'sinon' ;
10
9
11
10
import * as multicall from './multicall' ;
11
+ import { RpcBalanceFetcher } from './rpc-service/rpc-balance-fetcher' ;
12
12
import type {
13
13
AllowedActions ,
14
14
AllowedEvents ,
@@ -2986,7 +2986,12 @@ describe('TokenBalancesController', () => {
2986
2986
// Mock the multicall function to never resolve (simulating a hanging request)
2987
2987
const mockGetTokenBalances = jest
2988
2988
. spyOn ( multicall , 'getTokenBalancesForMultipleAddresses' )
2989
- . mockImplementation ( ( ) => new Promise ( ( ) => { } ) ) ; // Never resolves
2989
+ . mockImplementation (
2990
+ ( ) =>
2991
+ new Promise ( ( ) => {
2992
+ // Never resolves - intentional hanging promise for timeout test
2993
+ } ) ,
2994
+ ) ; // Never resolves
2990
2995
2991
2996
// Start the balance update - this should timeout after 15000ms
2992
2997
const updatePromise = controller . updateBalances ( { chainIds : [ chainId ] } ) ;
@@ -3062,4 +3067,141 @@ describe('TokenBalancesController', () => {
3062
3067
} ) . not . toThrow ( ) ;
3063
3068
} ) ;
3064
3069
} ) ;
3070
+
3071
+ describe ( 'Additional coverage tests' , ( ) => {
3072
+ it ( 'should construct controller with allowExternalServices returning false' , ( ) => {
3073
+ // Test line 197: allowExternalServices = () => false
3074
+ const { controller } = setupController ( {
3075
+ config : {
3076
+ allowExternalServices : ( ) => false ,
3077
+ useAccountsAPI : true , // This should be ignored when allowExternalServices is false
3078
+ } ,
3079
+ } ) ;
3080
+
3081
+ expect ( controller ) . toBeDefined ( ) ;
3082
+ // Verify that AccountsAPI fetcher is not created when external services are disabled
3083
+ expect ( controller . state . tokenBalances ) . toStrictEqual ( { } ) ;
3084
+ } ) ;
3085
+
3086
+ it ( 'should handle inactive controller during polling' , async ( ) => {
3087
+ const chainId = '0x1' ;
3088
+ const { controller } = setupController ( ) ;
3089
+
3090
+ // Start polling then immediately stop it
3091
+ controller . startPolling ( { chainIds : [ chainId ] } ) ;
3092
+ controller . stopAllPolling ( ) ;
3093
+
3094
+ // Manually call _executePoll when controller is inactive (covers line 335)
3095
+ // This should return early without doing anything
3096
+ await expect (
3097
+ controller . _executePoll ( { chainIds : [ chainId ] } ) ,
3098
+ ) . resolves . not . toThrow ( ) ;
3099
+ } ) ;
3100
+
3101
+ it ( 'should clear existing timer when starting polling for same interval' , ( ) => {
3102
+ const chainId1 = '0x1' ;
3103
+ const chainId2 = '0x89' ; // Polygon
3104
+
3105
+ const { controller } = setupController ( {
3106
+ config : {
3107
+ interval : 1000 , // Default interval
3108
+ chainPollingIntervals : {
3109
+ [ chainId1 ] : { interval : 5000 } ,
3110
+ [ chainId2 ] : { interval : 5000 } , // Same interval as chainId1
3111
+ } ,
3112
+ } ,
3113
+ } ) ;
3114
+
3115
+ // Start polling for first chain
3116
+ controller . startPolling ( { chainIds : [ chainId1 ] } ) ;
3117
+
3118
+ // Start polling for second chain with same interval (covers line 359)
3119
+ // This should clear the existing timer and create a new one
3120
+ controller . startPolling ( { chainIds : [ chainId1 , chainId2 ] } ) ;
3121
+
3122
+ // Verify controller is defined and functioning
3123
+ expect ( controller ) . toBeDefined ( ) ;
3124
+ expect ( controller . state . tokenBalances ) . toStrictEqual ( { } ) ;
3125
+
3126
+ controller . stopAllPolling ( ) ;
3127
+ } ) ;
3128
+
3129
+ it ( 'should skip fetcher when no chains are supported' , async ( ) => {
3130
+ const chainId = '0x999' ; // Unsupported chain
3131
+ const account = createMockInternalAccount ( ) ;
3132
+
3133
+ const tokens = {
3134
+ allDetectedTokens : { } ,
3135
+ allTokens : {
3136
+ [ chainId ] : {
3137
+ [ account . address ] : [
3138
+ {
3139
+ address : '0x0000000000000000000000000000000000000001' ,
3140
+ symbol : 'TEST' ,
3141
+ decimals : 18 ,
3142
+ } ,
3143
+ ] ,
3144
+ } ,
3145
+ } ,
3146
+ } ;
3147
+
3148
+ const { controller } = setupController ( {
3149
+ tokens,
3150
+ listAccounts : [ account ] ,
3151
+ config : { useAccountsAPI : false } ,
3152
+ } ) ;
3153
+
3154
+ // Mock the RpcBalanceFetcher to not support this specific chain
3155
+ const mockSupports = jest
3156
+ . spyOn ( RpcBalanceFetcher . prototype , 'supports' )
3157
+ . mockReturnValue ( false ) ;
3158
+
3159
+ // This should trigger the continue statement (line 440) when no chains are supported
3160
+ await controller . updateBalances ( { chainIds : [ chainId ] } ) ;
3161
+
3162
+ expect ( mockSupports ) . toHaveBeenCalledWith ( chainId ) ;
3163
+ mockSupports . mockRestore ( ) ;
3164
+ } ) ;
3165
+
3166
+ it ( 'should restart polling when tokens change and controller is active' , ( ) => {
3167
+ const chainId = '0x1' ;
3168
+ const accountAddress = '0x0000000000000000000000000000000000000000' ;
3169
+ const tokenAddress = '0x0000000000000000000000000000000000000001' ;
3170
+ const account = createMockInternalAccount ( { address : accountAddress } ) ;
3171
+
3172
+ const { controller, messenger } = setupController ( {
3173
+ listAccounts : [ account ] ,
3174
+ } ) ;
3175
+
3176
+ // Start polling to make controller active
3177
+ controller . startPolling ( { chainIds : [ chainId ] } ) ;
3178
+
3179
+ // Simulate tokens state change that should restart polling (covers lines 672-673)
3180
+ const newTokensState = {
3181
+ allTokens : {
3182
+ [ chainId ] : {
3183
+ [ accountAddress ] : [
3184
+ { address : tokenAddress , symbol : 'NEW' , decimals : 18 } ,
3185
+ ] ,
3186
+ } ,
3187
+ } ,
3188
+ allDetectedTokens : { } ,
3189
+ detectedTokens : [ ] ,
3190
+ tokens : [ ] ,
3191
+ ignoredTokens : [ ] ,
3192
+ allIgnoredTokens : { } ,
3193
+ } ;
3194
+
3195
+ // This should trigger the polling restart logic
3196
+ messenger . publish ( 'TokensController:stateChange' , newTokensState , [
3197
+ { op : 'replace' , path : [ ] , value : newTokensState } ,
3198
+ ] ) ;
3199
+
3200
+ // Verify controller state was updated
3201
+ expect ( controller ) . toBeDefined ( ) ;
3202
+ expect ( controller . state . tokenBalances ) . toStrictEqual ( { } ) ;
3203
+
3204
+ controller . stopAllPolling ( ) ;
3205
+ } ) ;
3206
+ } ) ;
3065
3207
} ) ;
0 commit comments