@@ -1115,7 +1115,25 @@ describe('RewardsController', () => {
11151115 describe ( 'optIn' , ( ) => {
11161116 it ( 'should return null when rewards are disabled' , async ( ) => {
11171117 await withController ( { isDisabled : true } , async ( { controller } ) => {
1118- const result = await controller . optIn ( ) ;
1118+ const result = await controller . optIn ( [ MOCK_INTERNAL_ACCOUNT ] ) ;
1119+
1120+ expect ( result ) . toBeNull ( ) ;
1121+ } ) ;
1122+ } ) ;
1123+
1124+ it ( 'should return null when accounts is null' , async ( ) => {
1125+ await withController ( { isDisabled : false } , async ( { controller } ) => {
1126+ const result = await controller . optIn (
1127+ null as unknown as InternalAccount [ ] ,
1128+ ) ;
1129+
1130+ expect ( result ) . toBeNull ( ) ;
1131+ } ) ;
1132+ } ) ;
1133+
1134+ it ( 'should return null when accounts is empty array' , async ( ) => {
1135+ await withController ( { isDisabled : false } , async ( { controller } ) => {
1136+ const result = await controller . optIn ( [ ] ) ;
11191137
11201138 expect ( result ) . toBeNull ( ) ;
11211139 } ) ;
@@ -1126,12 +1144,6 @@ describe('RewardsController', () => {
11261144 { isDisabled : false } ,
11271145 async ( { controller, mockMessengerCall } ) => {
11281146 mockMessengerCall . mockImplementation ( ( actionType ) => {
1129- if (
1130- actionType ===
1131- 'AccountTreeController:getAccountsFromSelectedAccountGroup'
1132- ) {
1133- return [ MOCK_INTERNAL_ACCOUNT ] ;
1134- }
11351147 if ( actionType === 'KeyringController:signPersonalMessage' ) {
11361148 return Promise . resolve ( '0xmocksignature' ) ;
11371149 }
@@ -1141,7 +1153,10 @@ describe('RewardsController', () => {
11411153 return undefined ;
11421154 } ) ;
11431155
1144- const result = await controller . optIn ( 'REF123' ) ;
1156+ const result = await controller . optIn (
1157+ [ MOCK_INTERNAL_ACCOUNT ] ,
1158+ 'REF123' ,
1159+ ) ;
11451160
11461161 expect ( result ) . toBe ( MOCK_SUBSCRIPTION_ID ) ;
11471162 expect (
@@ -1169,12 +1184,6 @@ describe('RewardsController', () => {
11691184 } ,
11701185 async ( { controller, mockMessengerCall } ) => {
11711186 mockMessengerCall . mockImplementation ( ( actionType ) => {
1172- if (
1173- actionType ===
1174- 'AccountTreeController:getAccountsFromSelectedAccountGroup'
1175- ) {
1176- return [ MOCK_INTERNAL_ACCOUNT ] ;
1177- }
11781187 if ( actionType === 'KeyringController:signPersonalMessage' ) {
11791188 return Promise . resolve ( '0xmocksignature' ) ;
11801189 }
@@ -1195,18 +1204,131 @@ describe('RewardsController', () => {
11951204 return undefined ;
11961205 } ) ;
11971206
1198- const result = await controller . optIn ( ) ;
1207+ const result = await controller . optIn ( [ MOCK_INTERNAL_ACCOUNT ] ) ;
11991208
12001209 expect ( result ) . toBe ( MOCK_SUBSCRIPTION_ID ) ;
12011210 } ,
12021211 ) ;
12031212 } ) ;
1213+
1214+ it ( 'should throw error when all accounts fail to opt in' , async ( ) => {
1215+ await withController (
1216+ { isDisabled : false } ,
1217+ async ( { controller, mockMessengerCall } ) => {
1218+ mockMessengerCall . mockImplementation ( ( actionType ) => {
1219+ if ( actionType === 'KeyringController:signPersonalMessage' ) {
1220+ return Promise . resolve ( '0xmocksignature' ) ;
1221+ }
1222+ if ( actionType === 'RewardsDataService:mobileOptin' ) {
1223+ return Promise . reject ( new Error ( 'Opt-in failed' ) ) ;
1224+ }
1225+ return undefined ;
1226+ } ) ;
1227+
1228+ await expect (
1229+ controller . optIn ( [ MOCK_INTERNAL_ACCOUNT ] ) ,
1230+ ) . rejects . toThrow (
1231+ 'Failed to opt in any account from the account group' ,
1232+ ) ;
1233+ } ,
1234+ ) ;
1235+ } ) ;
1236+
1237+ it ( 'should link remaining accounts when one account succeeds' , async ( ) => {
1238+ const account2 : InternalAccount = {
1239+ ...MOCK_INTERNAL_ACCOUNT ,
1240+ id : 'account-2' ,
1241+ address : MOCK_ACCOUNT_ADDRESS_ALT ,
1242+ } ;
1243+
1244+ await withController (
1245+ {
1246+ isDisabled : false ,
1247+ state : {
1248+ rewardsSubscriptions : {
1249+ [ MOCK_SUBSCRIPTION_ID ] : MOCK_SUBSCRIPTION ,
1250+ } ,
1251+ rewardsSubscriptionTokens : {
1252+ [ MOCK_SUBSCRIPTION_ID ] : MOCK_SESSION_TOKEN ,
1253+ } ,
1254+ } ,
1255+ } ,
1256+ async ( { controller, mockMessengerCall } ) => {
1257+ let optInCallCount = 0 ;
1258+ mockMessengerCall . mockImplementation ( ( actionType ) => {
1259+ if ( actionType === 'KeyringController:signPersonalMessage' ) {
1260+ return Promise . resolve ( '0xmocksignature' ) ;
1261+ }
1262+ if ( actionType === 'RewardsDataService:mobileOptin' ) {
1263+ optInCallCount += 1 ;
1264+ if ( optInCallCount === 1 ) {
1265+ // First account fails
1266+ return Promise . reject ( new Error ( 'First account failed' ) ) ;
1267+ }
1268+ // Second account succeeds
1269+ return Promise . resolve ( MOCK_LOGIN_RESPONSE ) ;
1270+ }
1271+ if ( actionType === 'RewardsDataService:mobileJoin' ) {
1272+ return Promise . resolve ( MOCK_SUBSCRIPTION ) ;
1273+ }
1274+ return undefined ;
1275+ } ) ;
1276+
1277+ const result = await controller . optIn ( [
1278+ MOCK_INTERNAL_ACCOUNT ,
1279+ account2 ,
1280+ ] ) ;
1281+
1282+ expect ( result ) . toBe ( MOCK_SUBSCRIPTION_ID ) ;
1283+ expect ( optInCallCount ) . toBe ( 2 ) ;
1284+ } ,
1285+ ) ;
1286+ } ) ;
1287+
1288+ it ( 'should opt in with multiple accounts and link remaining ones' , async ( ) => {
1289+ const account2 : InternalAccount = {
1290+ ...MOCK_INTERNAL_ACCOUNT ,
1291+ id : 'account-2' ,
1292+ address : MOCK_ACCOUNT_ADDRESS_ALT ,
1293+ } ;
1294+
1295+ await withController (
1296+ { isDisabled : false } ,
1297+ async ( { controller, mockMessengerCall } ) => {
1298+ mockMessengerCall . mockImplementation ( ( actionType ) => {
1299+ if ( actionType === 'KeyringController:signPersonalMessage' ) {
1300+ return Promise . resolve ( '0xmocksignature' ) ;
1301+ }
1302+ if ( actionType === 'RewardsDataService:mobileOptin' ) {
1303+ return Promise . resolve ( MOCK_LOGIN_RESPONSE ) ;
1304+ }
1305+ if ( actionType === 'RewardsDataService:mobileJoin' ) {
1306+ return Promise . resolve ( MOCK_SUBSCRIPTION ) ;
1307+ }
1308+ return undefined ;
1309+ } ) ;
1310+
1311+ const result = await controller . optIn (
1312+ [ MOCK_INTERNAL_ACCOUNT , account2 ] ,
1313+ 'REF123' ,
1314+ ) ;
1315+
1316+ expect ( result ) . toBe ( MOCK_SUBSCRIPTION_ID ) ;
1317+ expect (
1318+ controller . state . rewardsAccounts [ MOCK_CAIP_ACCOUNT ] ,
1319+ ) . toMatchObject ( {
1320+ hasOptedIn : true ,
1321+ subscriptionId : MOCK_SUBSCRIPTION_ID ,
1322+ } ) ;
1323+ } ,
1324+ ) ;
1325+ } ) ;
12041326 } ) ;
12051327
12061328 describe ( 'getGeoRewardsMetadata' , ( ) => {
12071329 it ( 'should return unknown location when rewards are disabled' , async ( ) => {
12081330 await withController ( { isDisabled : true } , async ( { controller } ) => {
1209- const result = await controller . getGeoRewardsMetadata ( ) ;
1331+ const result = await controller . getRewardsGeoMetadata ( ) ;
12101332
12111333 expect ( result ) . toEqual ( {
12121334 geoLocation : 'UNKNOWN' ,
@@ -1226,15 +1348,15 @@ describe('RewardsController', () => {
12261348 return undefined ;
12271349 } ) ;
12281350
1229- const result = await controller . getGeoRewardsMetadata ( ) ;
1351+ const result = await controller . getRewardsGeoMetadata ( ) ;
12301352
12311353 expect ( result ) . toEqual ( {
12321354 geoLocation : 'US' ,
12331355 optinAllowedForGeo : true ,
12341356 } ) ;
12351357
12361358 // Verify caching - second call should not fetch again
1237- const cachedResult = await controller . getGeoRewardsMetadata ( ) ;
1359+ const cachedResult = await controller . getRewardsGeoMetadata ( ) ;
12381360 expect ( cachedResult ) . toEqual ( result ) ;
12391361 } ,
12401362 ) ;
@@ -1251,7 +1373,7 @@ describe('RewardsController', () => {
12511373 return undefined ;
12521374 } ) ;
12531375
1254- const result = await controller . getGeoRewardsMetadata ( ) ;
1376+ const result = await controller . getRewardsGeoMetadata ( ) ;
12551377
12561378 expect ( result ) . toEqual ( {
12571379 geoLocation : 'UK' ,
@@ -2562,44 +2684,27 @@ describe('Additional RewardsController edge cases', () => {
25622684
25632685 describe ( 'optIn - edge cases' , ( ) => {
25642686 it ( 'should return null when no accounts in account group' , async ( ) => {
2565- await withController (
2566- { isDisabled : false } ,
2567- async ( { controller, mockMessengerCall } ) => {
2568- mockMessengerCall . mockImplementation ( ( actionType ) => {
2569- if (
2570- actionType ===
2571- 'AccountTreeController:getAccountsFromSelectedAccountGroup'
2572- ) {
2573- return null ;
2574- }
2575- return undefined ;
2576- } ) ;
2577-
2578- const result = await controller . optIn ( ) ;
2687+ await withController ( { isDisabled : false } , async ( { controller } ) => {
2688+ const result = await controller . optIn ( [ ] ) ;
25792689
2580- expect ( result ) . toBeNull ( ) ;
2581- } ,
2582- ) ;
2690+ expect ( result ) . toBeNull ( ) ;
2691+ } ) ;
25832692 } ) ;
25842693
25852694 it ( 'should throw error when all accounts fail to opt in' , async ( ) => {
25862695 await withController (
25872696 { isDisabled : false } ,
25882697 async ( { controller, mockMessengerCall } ) => {
25892698 mockMessengerCall . mockImplementation ( ( actionType ) => {
2590- if (
2591- actionType ===
2592- 'AccountTreeController:getAccountsFromSelectedAccountGroup'
2593- ) {
2594- return [ MOCK_INTERNAL_ACCOUNT ] ;
2595- }
25962699 if ( actionType === 'KeyringController:signPersonalMessage' ) {
25972700 return Promise . reject ( new Error ( 'Signature failed' ) ) ;
25982701 }
25992702 return undefined ;
26002703 } ) ;
26012704
2602- await expect ( controller . optIn ( ) ) . rejects . toThrow (
2705+ await expect (
2706+ controller . optIn ( [ MOCK_INTERNAL_ACCOUNT ] ) ,
2707+ ) . rejects . toThrow (
26032708 'Failed to opt in any account from the account group' ,
26042709 ) ;
26052710 } ,
@@ -2617,12 +2722,6 @@ describe('Additional RewardsController edge cases', () => {
26172722 { isDisabled : false } ,
26182723 async ( { controller, mockMessengerCall } ) => {
26192724 mockMessengerCall . mockImplementation ( ( actionType ) => {
2620- if (
2621- actionType ===
2622- 'AccountTreeController:getAccountsFromSelectedAccountGroup'
2623- ) {
2624- return [ MOCK_INTERNAL_ACCOUNT , account2 ] ;
2625- }
26262725 if ( actionType === 'KeyringController:signPersonalMessage' ) {
26272726 return Promise . resolve ( '0xmocksignature' ) ;
26282727 }
@@ -2635,7 +2734,10 @@ describe('Additional RewardsController edge cases', () => {
26352734 return undefined ;
26362735 } ) ;
26372736
2638- const result = await controller . optIn ( 'REF123' ) ;
2737+ const result = await controller . optIn (
2738+ [ MOCK_INTERNAL_ACCOUNT , account2 ] ,
2739+ 'REF123' ,
2740+ ) ;
26392741
26402742 expect ( result ) . toBe ( MOCK_SUBSCRIPTION_ID ) ;
26412743 } ,
@@ -3050,7 +3152,7 @@ describe('Additional RewardsController edge cases', () => {
30503152 return undefined ;
30513153 } ) ;
30523154
3053- const result = await controller . getGeoRewardsMetadata ( ) ;
3155+ const result = await controller . getRewardsGeoMetadata ( ) ;
30543156
30553157 expect ( result ) . toEqual ( {
30563158 geoLocation : 'UNKNOWN' ,
0 commit comments