tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AddonManagerTest.kt (45114B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 package mozilla.components.feature.addons
      6 
      7 import android.graphics.Bitmap
      8 import androidx.test.ext.junit.runners.AndroidJUnit4
      9 import kotlinx.coroutines.CoroutineScope
     10 import kotlinx.coroutines.Dispatchers
     11 import kotlinx.coroutines.TimeoutCancellationException
     12 import kotlinx.coroutines.launch
     13 import mozilla.components.browser.state.action.WebExtensionAction
     14 import mozilla.components.browser.state.state.BrowserState
     15 import mozilla.components.browser.state.state.WebExtensionState
     16 import mozilla.components.browser.state.state.createTab
     17 import mozilla.components.browser.state.store.BrowserStore
     18 import mozilla.components.concept.engine.Engine
     19 import mozilla.components.concept.engine.EngineSession
     20 import mozilla.components.concept.engine.webextension.ActionHandler
     21 import mozilla.components.concept.engine.webextension.DisabledFlags
     22 import mozilla.components.concept.engine.webextension.DisabledFlags.Companion.APP_SUPPORT
     23 import mozilla.components.concept.engine.webextension.DisabledFlags.Companion.APP_VERSION
     24 import mozilla.components.concept.engine.webextension.DisabledFlags.Companion.BLOCKLIST
     25 import mozilla.components.concept.engine.webextension.DisabledFlags.Companion.SIGNATURE
     26 import mozilla.components.concept.engine.webextension.DisabledFlags.Companion.SOFT_BLOCKLIST
     27 import mozilla.components.concept.engine.webextension.DisabledFlags.Companion.USER
     28 import mozilla.components.concept.engine.webextension.EnableSource
     29 import mozilla.components.concept.engine.webextension.InstallationMethod
     30 import mozilla.components.concept.engine.webextension.Metadata
     31 import mozilla.components.concept.engine.webextension.WebExtension
     32 import mozilla.components.feature.addons.ui.translateName
     33 import mozilla.components.feature.addons.update.AddonUpdater.Status
     34 import mozilla.components.support.test.any
     35 import mozilla.components.support.test.argumentCaptor
     36 import mozilla.components.support.test.eq
     37 import mozilla.components.support.test.mock
     38 import mozilla.components.support.test.robolectric.testContext
     39 import mozilla.components.support.test.rule.MainCoroutineRule
     40 import mozilla.components.support.test.rule.runTestOnMain
     41 import mozilla.components.support.test.whenever
     42 import mozilla.components.support.webextensions.WebExtensionSupport
     43 import org.junit.After
     44 import org.junit.Assert.assertEquals
     45 import org.junit.Assert.assertFalse
     46 import org.junit.Assert.assertNotEquals
     47 import org.junit.Assert.assertNotNull
     48 import org.junit.Assert.assertNull
     49 import org.junit.Assert.assertTrue
     50 import org.junit.Before
     51 import org.junit.Rule
     52 import org.junit.Test
     53 import org.junit.runner.RunWith
     54 import org.mockito.ArgumentMatchers.anyBoolean
     55 import org.mockito.ArgumentMatchers.anyString
     56 import org.mockito.Mockito.doNothing
     57 import org.mockito.Mockito.doThrow
     58 import org.mockito.Mockito.never
     59 import org.mockito.Mockito.spy
     60 import org.mockito.Mockito.times
     61 import org.mockito.Mockito.verify
     62 
     63 @RunWith(AndroidJUnit4::class)
     64 class AddonManagerTest {
     65 
     66    @get:Rule
     67    val coroutinesTestRule = MainCoroutineRule()
     68    private val dispatcher = coroutinesTestRule.testDispatcher
     69 
     70    @Before
     71    fun setup() {
     72        WebExtensionSupport.installedExtensions.clear()
     73    }
     74 
     75    @After
     76    fun after() {
     77        WebExtensionSupport.installedExtensions.clear()
     78    }
     79 
     80    @Test
     81    fun `getAddons - queries addons from provider and updates installation state`() = runTestOnMain {
     82        // Prepare addons provider
     83        // addon1 (ext1) is a featured extension that is already installed.
     84        // addon2 (ext2) is a featured extension that is not installed.
     85        // addon3 (ext3) is a featured extension that is marked as disabled.
     86        // addon4 (ext4) and addon5 (ext5) are not featured extensions but they are installed.
     87        val addonsProvider: AddonsProvider = mock()
     88 
     89        whenever(addonsProvider.getFeaturedAddons(anyBoolean(), eq(3L), language = anyString())).thenReturn(listOf(Addon(id = "ext1"), Addon(id = "ext2"), Addon(id = "ext3")))
     90 
     91        // Prepare engine
     92        val engine: Engine = mock()
     93        val callbackCaptor = argumentCaptor<((List<WebExtension>) -> Unit)>()
     94        whenever(engine.listInstalledWebExtensions(callbackCaptor.capture(), any())).thenAnswer {
     95            callbackCaptor.value.invoke(emptyList())
     96        }
     97        val store = BrowserStore(
     98            BrowserState(
     99                extensions = mapOf(
    100                    "ext1" to WebExtensionState("ext1", "url"),
    101                    "ext4" to WebExtensionState("ext4", "url"),
    102                    "ext5" to WebExtensionState("ext5", "url"),
    103                    // ext6 is a temporarily loaded extension.
    104                    "ext6" to WebExtensionState("ext6", "url"),
    105                    // ext7 is a built-in extension.
    106                    "ext7" to WebExtensionState("ext7", "url"),
    107                ),
    108            ),
    109        )
    110 
    111        WebExtensionSupport.initialize(engine, store)
    112        val ext1: WebExtension = mock()
    113        whenever(ext1.id).thenReturn("ext1")
    114        whenever(ext1.isEnabled()).thenReturn(true)
    115        WebExtensionSupport.installedExtensions["ext1"] = ext1
    116 
    117        // Make `ext3` an extension that is disabled because it wasn't supported.
    118        val newlySupportedExtension: WebExtension = mock()
    119        val metadata: Metadata = mock()
    120        whenever(newlySupportedExtension.isEnabled()).thenReturn(false)
    121        whenever(metadata.disabledFlags).thenReturn(DisabledFlags.select(APP_SUPPORT))
    122        whenever(metadata.optionsPageUrl).thenReturn("http://options-page.moz")
    123        whenever(metadata.openOptionsPageInTab).thenReturn(true)
    124        whenever(newlySupportedExtension.id).thenReturn("ext3")
    125        whenever(newlySupportedExtension.url).thenReturn("site_url")
    126        whenever(newlySupportedExtension.getMetadata()).thenReturn(metadata)
    127        WebExtensionSupport.installedExtensions["ext3"] = newlySupportedExtension
    128 
    129        val ext4: WebExtension = mock()
    130        whenever(ext4.id).thenReturn("ext4")
    131        whenever(ext4.isEnabled()).thenReturn(true)
    132        val ext4Metadata: Metadata = mock()
    133        whenever(ext4Metadata.temporary).thenReturn(false)
    134        whenever(ext4.getMetadata()).thenReturn(ext4Metadata)
    135        WebExtensionSupport.installedExtensions["ext4"] = ext4
    136 
    137        val ext5: WebExtension = mock()
    138        whenever(ext5.id).thenReturn("ext5")
    139        whenever(ext5.isEnabled()).thenReturn(true)
    140        val ext5Metadata: Metadata = mock()
    141        whenever(ext5Metadata.temporary).thenReturn(false)
    142        whenever(ext5.getMetadata()).thenReturn(ext5Metadata)
    143        WebExtensionSupport.installedExtensions["ext5"] = ext5
    144 
    145        val ext6: WebExtension = mock()
    146        whenever(ext6.id).thenReturn("ext6")
    147        whenever(ext6.url).thenReturn("some url")
    148        whenever(ext6.isEnabled()).thenReturn(true)
    149        val ext6Metadata: Metadata = mock()
    150        whenever(ext6Metadata.name).thenReturn("temporarily loaded extension - ext6")
    151        whenever(ext6Metadata.temporary).thenReturn(true)
    152        whenever(ext6.getMetadata()).thenReturn(ext6Metadata)
    153        WebExtensionSupport.installedExtensions["ext6"] = ext6
    154 
    155        val ext7: WebExtension = mock()
    156        whenever(ext7.id).thenReturn("ext7")
    157        whenever(ext7.isEnabled()).thenReturn(true)
    158        whenever(ext7.isBuiltIn()).thenReturn(true)
    159        WebExtensionSupport.installedExtensions["ext7"] = ext7
    160 
    161        // Verify add-ons were updated with state provided by the engine/store.
    162        val addons = AddonManager(store, mock(), addonsProvider, mock()).getAddons()
    163        assertEquals(6, addons.size)
    164 
    165        // ext1 should be installed.
    166        val addon1 = addons.find { it.id == "ext1" }!!
    167 
    168        assertEquals("ext1", addon1.id)
    169        assertNotNull(addon1.installedState)
    170        assertEquals("ext1", addon1.installedState!!.id)
    171        assertTrue(addon1.isEnabled())
    172        assertFalse(addon1.isDisabledAsUnsupported())
    173        assertNull(addon1.installedState.optionsPageUrl)
    174        assertFalse(addon1.installedState.openOptionsPageInTab)
    175 
    176        // ext2 should not be installed.
    177        val addon2 = addons.find { it.id == "ext2" }!!
    178        assertEquals("ext2", addon2.id)
    179        assertNull(addon2.installedState)
    180 
    181        // ext3 should now be marked as supported but still be disabled as unsupported.
    182        val addon3 = addons.find { it.id == "ext3" }!!
    183        assertEquals("ext3", addon3.id)
    184        assertNotNull(addon3.installedState)
    185        assertEquals("ext3", addon3.installedState!!.id)
    186        assertTrue(addon3.isSupported())
    187        assertFalse(addon3.isEnabled())
    188        assertTrue(addon3.isDisabledAsUnsupported())
    189        assertEquals("http://options-page.moz", addon3.installedState.optionsPageUrl)
    190        assertTrue(addon3.installedState.openOptionsPageInTab)
    191 
    192        // ext4 should be installed.
    193        val addon4 = addons.find { it.id == "ext4" }!!
    194        assertEquals("ext4", addon4.id)
    195        assertNotNull(addon4.installedState)
    196        assertEquals("ext4", addon4.installedState!!.id)
    197        assertTrue(addon4.isEnabled())
    198        assertFalse(addon4.isDisabledAsUnsupported())
    199        assertNull(addon4.installedState.optionsPageUrl)
    200        assertFalse(addon4.installedState.openOptionsPageInTab)
    201 
    202        // ext5 should be installed.
    203        val addon5 = addons.find { it.id == "ext5" }!!
    204        assertEquals("ext5", addon5.id)
    205        assertNotNull(addon5.installedState)
    206        assertEquals("ext5", addon5.installedState!!.id)
    207        assertTrue(addon5.isEnabled())
    208        assertFalse(addon5.isDisabledAsUnsupported())
    209        assertNull(addon5.installedState.optionsPageUrl)
    210        assertFalse(addon5.installedState.openOptionsPageInTab)
    211 
    212        // ext6 should be installed.
    213        val addon6 = addons.find { it.id == "ext6" }!!
    214        assertEquals("ext6", addon6.id)
    215        assertNotNull(addon6.installedState)
    216        assertEquals("ext6", addon6.installedState!!.id)
    217        assertTrue(addon6.isEnabled())
    218        assertFalse(addon6.isDisabledAsUnsupported())
    219        assertNull(addon6.installedState.optionsPageUrl)
    220        assertFalse(addon6.installedState.openOptionsPageInTab)
    221    }
    222 
    223    @Test
    224    fun `getAddons - returns temporary add-ons as supported`() = runTestOnMain {
    225        val addonsProvider: AddonsProvider = mock()
    226        whenever(addonsProvider.getFeaturedAddons(anyBoolean(), eq(null), language = anyString())).thenReturn(listOf())
    227 
    228        // Prepare engine
    229        val engine: Engine = mock()
    230        val callbackCaptor = argumentCaptor<((List<WebExtension>) -> Unit)>()
    231        whenever(engine.listInstalledWebExtensions(callbackCaptor.capture(), any())).thenAnswer {
    232            callbackCaptor.value.invoke(emptyList())
    233        }
    234 
    235        val store = BrowserStore()
    236        WebExtensionSupport.initialize(engine, store)
    237 
    238        // Add temporary extension
    239        val temporaryExtension: WebExtension = mock()
    240        val temporaryExtensionIcon: Bitmap = mock()
    241        val temporaryExtensionMetadata: Metadata = mock()
    242        whenever(temporaryExtensionMetadata.temporary).thenReturn(true)
    243        whenever(temporaryExtensionMetadata.name).thenReturn("name")
    244        whenever(temporaryExtension.id).thenReturn("temp_ext")
    245        whenever(temporaryExtension.url).thenReturn("site_url")
    246        whenever(temporaryExtension.getMetadata()).thenReturn(temporaryExtensionMetadata)
    247        WebExtensionSupport.installedExtensions["temp_ext"] = temporaryExtension
    248 
    249        val addonManager = spy(AddonManager(store, mock(), addonsProvider, mock()))
    250 
    251        whenever(addonManager.loadIcon(temporaryExtension)).thenReturn(temporaryExtensionIcon)
    252 
    253        val addons = addonManager.getAddons()
    254        assertEquals(1, addons.size)
    255 
    256        // Temporary extension should be returned and marked as supported
    257        assertEquals("temp_ext", addons[0].id)
    258        assertEquals(1, addons[0].translatableName.size)
    259        assertNotNull(addons[0].translatableName[addons[0].defaultLocale])
    260        assertTrue(addons[0].translatableName.containsValue("name"))
    261        assertNotNull(addons[0].installedState)
    262        assertTrue(addons[0].isSupported())
    263        assertEquals(temporaryExtensionIcon, addons[0].installedState!!.icon)
    264    }
    265 
    266    @Test
    267    fun `getAddons - filters unneeded locales on featured add-ons`() = runTestOnMain {
    268        val addon = Addon(
    269            id = "addon1",
    270            translatableName = mapOf(Addon.DEFAULT_LOCALE to "name", "invalid1" to "Name", "invalid2" to "nombre"),
    271            translatableDescription = mapOf(Addon.DEFAULT_LOCALE to "description", "invalid1" to "Beschreibung", "invalid2" to "descripción"),
    272            translatableSummary = mapOf(Addon.DEFAULT_LOCALE to "summary", "invalid1" to "Kurzfassung", "invalid2" to "resumen"),
    273        )
    274 
    275        val store = BrowserStore()
    276 
    277        val engine: Engine = mock()
    278        val callbackCaptor = argumentCaptor<((List<WebExtension>) -> Unit)>()
    279        whenever(engine.listInstalledWebExtensions(callbackCaptor.capture(), any())).thenAnswer {
    280            callbackCaptor.value.invoke(emptyList())
    281        }
    282 
    283        val addonsProvider: AddonsProvider = mock()
    284        whenever(addonsProvider.getFeaturedAddons(anyBoolean(), eq(null), language = anyString())).thenReturn(listOf(addon))
    285        WebExtensionSupport.initialize(engine, store)
    286 
    287        val addons = AddonManager(store, mock(), addonsProvider, mock()).getAddons()
    288        assertEquals(1, addons[0].translatableName.size)
    289        assertTrue(addons[0].translatableName.contains(addons[0].defaultLocale))
    290        assertEquals(1, addons[0].translatableDescription.size)
    291        assertTrue(addons[0].translatableDescription.contains(addons[0].defaultLocale))
    292        assertEquals(1, addons[0].translatableSummary.size)
    293        assertTrue(addons[0].translatableSummary.contains(addons[0].defaultLocale))
    294    }
    295 
    296    @Test
    297    fun `getAddons - filters unneeded locales on non-featured installed add-ons`() = runTestOnMain {
    298        val addon = Addon(
    299            id = "addon1",
    300            translatableName = mapOf(Addon.DEFAULT_LOCALE to "name", "invalid1" to "Name", "invalid2" to "nombre"),
    301            translatableDescription = mapOf(Addon.DEFAULT_LOCALE to "description", "invalid1" to "Beschreibung", "invalid2" to "descripción"),
    302            translatableSummary = mapOf(Addon.DEFAULT_LOCALE to "summary", "invalid1" to "Kurzfassung", "invalid2" to "resumen"),
    303        )
    304 
    305        val store = BrowserStore()
    306 
    307        val engine: Engine = mock()
    308        val callbackCaptor = argumentCaptor<((List<WebExtension>) -> Unit)>()
    309        whenever(engine.listInstalledWebExtensions(callbackCaptor.capture(), any())).thenAnswer {
    310            callbackCaptor.value.invoke(emptyList())
    311        }
    312 
    313        val addonsProvider: AddonsProvider = mock()
    314        whenever(addonsProvider.getFeaturedAddons(anyBoolean(), eq(null), language = anyString())).thenReturn(emptyList())
    315        WebExtensionSupport.initialize(engine, store)
    316        val extension: WebExtension = mock()
    317        whenever(extension.id).thenReturn(addon.id)
    318        whenever(extension.isEnabled()).thenReturn(true)
    319        whenever(extension.getMetadata()).thenReturn(mock())
    320        WebExtensionSupport.installedExtensions[addon.id] = extension
    321 
    322        val addons = AddonManager(store, mock(), addonsProvider, mock()).getAddons()
    323        assertEquals(1, addons[0].translatableName.size)
    324        assertTrue(addons[0].translatableName.contains(addons[0].defaultLocale))
    325        assertEquals(1, addons[0].translatableDescription.size)
    326        assertTrue(addons[0].translatableDescription.contains(addons[0].defaultLocale))
    327        assertEquals(1, addons[0].translatableSummary.size)
    328        assertTrue(addons[0].translatableSummary.contains(addons[0].defaultLocale))
    329    }
    330 
    331    @Test
    332    fun `getAddons - suspends until pending actions are completed`() = runTestOnMain {
    333        val addon = Addon(
    334            id = "ext1",
    335            installedState = Addon.InstalledState("ext1", "1.0", "", true),
    336        )
    337 
    338        val extension: WebExtension = mock()
    339        whenever(extension.id).thenReturn("ext1")
    340 
    341        val store = BrowserStore()
    342 
    343        val engine: Engine = mock()
    344        val callbackCaptor = argumentCaptor<((List<WebExtension>) -> Unit)>()
    345        whenever(engine.listInstalledWebExtensions(callbackCaptor.capture(), any())).thenAnswer {
    346            callbackCaptor.value.invoke(emptyList())
    347        }
    348        val addonsProvider: AddonsProvider = mock()
    349 
    350        whenever(addonsProvider.getFeaturedAddons(anyBoolean(), eq(null), language = anyString())).thenReturn(listOf(addon))
    351        WebExtensionSupport.initialize(engine, store)
    352        WebExtensionSupport.installedExtensions[addon.id] = extension
    353 
    354        val addonManager = AddonManager(store, mock(), addonsProvider, mock(), dispatcher)
    355        addonManager.installAddon(url = addon.downloadUrl)
    356        addonManager.enableAddon(addon)
    357        addonManager.disableAddon(addon)
    358        addonManager.uninstallAddon(addon)
    359        assertEquals(4, addonManager.pendingAddonActions.size)
    360 
    361        var getAddonsResult: List<Addon>? = null
    362        val nonSuspendingJob = CoroutineScope(Dispatchers.IO).launch {
    363            getAddonsResult = addonManager.getAddons(waitForPendingActions = false)
    364        }
    365 
    366        nonSuspendingJob.join()
    367        assertNotNull(getAddonsResult)
    368 
    369        getAddonsResult = null
    370        val suspendingJob = CoroutineScope(Dispatchers.IO).launch {
    371            getAddonsResult = addonManager.getAddons(waitForPendingActions = true)
    372        }
    373 
    374        addonManager.pendingAddonActions.forEach { it.complete(Unit) }
    375 
    376        suspendingJob.join()
    377        assertNotNull(getAddonsResult)
    378    }
    379 
    380    @Test
    381    fun `getAddons - passes on allowCache parameter`() = runTestOnMain {
    382        val store = BrowserStore()
    383 
    384        val engine: Engine = mock()
    385        val callbackCaptor = argumentCaptor<((List<WebExtension>) -> Unit)>()
    386        whenever(engine.listInstalledWebExtensions(callbackCaptor.capture(), any())).thenAnswer {
    387            callbackCaptor.value.invoke(emptyList())
    388        }
    389        WebExtensionSupport.initialize(engine, store)
    390 
    391        val addonsProvider: AddonsProvider = mock()
    392        whenever(addonsProvider.getFeaturedAddons(anyBoolean(), eq(null), language = anyString())).thenReturn(emptyList())
    393        val addonsManager = AddonManager(store, mock(), addonsProvider, mock(), dispatcher)
    394 
    395        addonsManager.getAddons()
    396        verify(addonsProvider).getFeaturedAddons(eq(true), eq(null), language = anyString())
    397 
    398        addonsManager.getAddons(allowCache = false)
    399        verify(addonsProvider).getFeaturedAddons(eq(false), eq(null), language = anyString())
    400        Unit
    401    }
    402 
    403    @Test
    404    fun `getAddons - passes readTimeoutInSeconds parameter dependent on installed extensions`() = runTestOnMain {
    405        val store = BrowserStore()
    406 
    407        val engine: Engine = mock()
    408        val callbackCaptor = argumentCaptor<((List<WebExtension>) -> Unit)>()
    409        whenever(engine.listInstalledWebExtensions(callbackCaptor.capture(), any())).thenAnswer {
    410            callbackCaptor.value.invoke(emptyList())
    411        }
    412        WebExtensionSupport.initialize(engine, store)
    413 
    414        // Built-in extensions don't count as an installed extension, as they are not displayed to the user.
    415        val ext1: WebExtension = mock()
    416        whenever(ext1.id).thenReturn("ext1")
    417        whenever(ext1.isEnabled()).thenReturn(true)
    418        whenever(ext1.isBuiltIn()).thenReturn(true)
    419        WebExtensionSupport.installedExtensions["ext1"] = ext1
    420 
    421        val addonsProvider: AddonsProvider = mock()
    422        whenever(addonsProvider.getFeaturedAddons(anyBoolean(), readTimeoutInSeconds = any(), language = anyString())).thenReturn(emptyList())
    423        val addonsManager = AddonManager(store, mock(), addonsProvider, mock(), dispatcher)
    424 
    425        addonsManager.getAddons()
    426        verify(addonsProvider).getFeaturedAddons(eq(true), readTimeoutInSeconds = eq(null), language = anyString())
    427        // ^ readTimeoutInSeconds is null, so the default is used.
    428 
    429        // This counts as an installed extension
    430        val ext2: WebExtension = mock()
    431        whenever(ext2.id).thenReturn("ext2")
    432        whenever(ext2.isEnabled()).thenReturn(true)
    433        whenever(ext2.isBuiltIn()).thenReturn(false)
    434        WebExtensionSupport.installedExtensions["ext2"] = ext2
    435 
    436        addonsManager.getAddons()
    437        verify(addonsProvider).getFeaturedAddons(eq(true), readTimeoutInSeconds = eq(3L), language = anyString())
    438        // ^ readTimeoutInSeconds is now minimal to ensure quick loading (bug 1949963).
    439        Unit
    440    }
    441 
    442    @Test
    443    fun `updateAddon - when a extension is updated successfully`() {
    444        val engine: Engine = mock()
    445        val engineSession: EngineSession = mock()
    446        val store = spy(
    447            BrowserStore(
    448                BrowserState(
    449                    tabs = listOf(
    450                        createTab(id = "1", url = "https://www.mozilla.org", engineSession = engineSession),
    451                    ),
    452                    extensions = mapOf("extensionId" to mock()),
    453                ),
    454            ),
    455        )
    456        val onSuccessCaptor = argumentCaptor<((WebExtension?) -> Unit)>()
    457        var updateStatus: Status? = null
    458        val manager = AddonManager(store, engine, mock(), mock(), mock())
    459 
    460        val updatedExt: WebExtension = mock()
    461        whenever(updatedExt.id).thenReturn("extensionId")
    462        whenever(updatedExt.url).thenReturn("url")
    463        whenever(updatedExt.supportActions).thenReturn(true)
    464 
    465        WebExtensionSupport.installedExtensions["extensionId"] = mock()
    466 
    467        val oldExt = WebExtensionSupport.installedExtensions["extensionId"]
    468 
    469        manager.updateAddon("extensionId") { status ->
    470            updateStatus = status
    471        }
    472 
    473        val actionHandlerCaptor = argumentCaptor<ActionHandler>()
    474        val actionCaptor = argumentCaptor<WebExtensionAction.UpdateWebExtensionAction>()
    475 
    476        // Verifying we returned the right status
    477        verify(engine).updateWebExtension(any(), onSuccessCaptor.capture(), any())
    478        onSuccessCaptor.value.invoke(updatedExt)
    479        assertEquals(Status.SuccessfullyUpdated, updateStatus)
    480 
    481        // Verifying we updated the extension in WebExtensionSupport
    482        assertNotEquals(oldExt, WebExtensionSupport.installedExtensions["extensionId"])
    483        assertEquals(updatedExt, WebExtensionSupport.installedExtensions["extensionId"])
    484 
    485        // Verifying we updated the extension in the store
    486        verify(store).dispatch(actionCaptor.capture())
    487        assertEquals(
    488            WebExtensionState(updatedExt.id, updatedExt.url, updatedExt.getMetadata()?.name, updatedExt.isEnabled()),
    489            actionCaptor.allValues.last().updatedExtension,
    490        )
    491 
    492        // Verify that we registered an action handler for all existing sessions on the extension
    493        verify(updatedExt).registerActionHandler(eq(engineSession), actionHandlerCaptor.capture())
    494        actionHandlerCaptor.value.onBrowserAction(updatedExt, engineSession, mock())
    495    }
    496 
    497    @Test
    498    fun `updateAddon - when extension is not installed`() {
    499        var updateStatus: Status? = null
    500 
    501        val manager = AddonManager(mock(), mock(), mock(), mock(), mock())
    502 
    503        manager.updateAddon("extensionId") { status ->
    504            updateStatus = status
    505        }
    506 
    507        assertEquals(Status.NotInstalled, updateStatus)
    508    }
    509 
    510    @Test
    511    fun `updateAddon - when extension is not supported`() {
    512        var updateStatus: Status? = null
    513 
    514        val extension: WebExtension = mock()
    515        whenever(extension.id).thenReturn("unsupportedExt")
    516 
    517        val metadata: Metadata = mock()
    518        whenever(metadata.disabledFlags).thenReturn(DisabledFlags.select(APP_SUPPORT))
    519        whenever(extension.getMetadata()).thenReturn(metadata)
    520 
    521        WebExtensionSupport.installedExtensions["extensionId"] = extension
    522 
    523        val manager = AddonManager(mock(), mock(), mock(), mock(), mock())
    524        manager.updateAddon("extensionId") { status ->
    525            updateStatus = status
    526        }
    527 
    528        assertEquals(Status.NotInstalled, updateStatus)
    529    }
    530 
    531    @Test
    532    fun `updateAddon - when an error happens while updating`() {
    533        val engine: Engine = mock()
    534        val onErrorCaptor = argumentCaptor<((String, Throwable) -> Unit)>()
    535        var updateStatus: Status? = null
    536        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    537 
    538        WebExtensionSupport.installedExtensions["extensionId"] = mock()
    539 
    540        manager.updateAddon("extensionId") { status ->
    541            updateStatus = status
    542        }
    543 
    544        // Verifying we returned the right status
    545        verify(engine).updateWebExtension(any(), any(), onErrorCaptor.capture())
    546        onErrorCaptor.value.invoke("message", Exception())
    547        assertTrue(updateStatus is Status.Error)
    548    }
    549 
    550    @Test
    551    fun `updateAddon - when there is not new updates for the extension`() {
    552        val engine: Engine = mock()
    553        val onSuccessCaptor = argumentCaptor<((WebExtension?) -> Unit)>()
    554        var updateStatus: Status? = null
    555        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    556 
    557        WebExtensionSupport.installedExtensions["extensionId"] = mock()
    558        manager.updateAddon("extensionId") { status ->
    559            updateStatus = status
    560        }
    561 
    562        verify(engine).updateWebExtension(any(), onSuccessCaptor.capture(), any())
    563        onSuccessCaptor.value.invoke(null)
    564        assertEquals(Status.NoUpdateAvailable, updateStatus)
    565    }
    566 
    567    @Test
    568    fun `installAddon successfully`() {
    569        val addon = Addon(id = "ext1")
    570        val engine: Engine = mock()
    571        val onSuccessCaptor = argumentCaptor<((WebExtension) -> Unit)>()
    572 
    573        var installedAddon: Addon? = null
    574        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    575        manager.installAddon(
    576            url = addon.downloadUrl,
    577            installationMethod = InstallationMethod.MANAGER,
    578            onSuccess = {
    579                installedAddon = it
    580            },
    581        )
    582 
    583        verify(engine).installWebExtension(
    584            any(),
    585            eq(InstallationMethod.MANAGER),
    586            onSuccessCaptor.capture(),
    587            any(),
    588        )
    589 
    590        val metadata: Metadata = mock()
    591        val extension: WebExtension = mock()
    592        whenever(metadata.name).thenReturn("nameFromMetadata")
    593        whenever(extension.id).thenReturn("ext1")
    594        whenever(extension.getMetadata()).thenReturn(metadata)
    595        onSuccessCaptor.value.invoke(extension)
    596        assertNotNull(installedAddon)
    597        assertEquals(addon.id, installedAddon!!.id)
    598        assertEquals("nameFromMetadata", installedAddon.translateName(testContext))
    599        assertTrue(manager.pendingAddonActions.isEmpty())
    600    }
    601 
    602    @Test
    603    fun `installAddon failure`() {
    604        val addon = Addon(id = "ext1")
    605        val engine: Engine = mock()
    606        val onErrorCaptor = argumentCaptor<((Throwable) -> Unit)>()
    607 
    608        var throwable: Throwable? = null
    609        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    610        manager.installAddon(
    611            url = addon.downloadUrl,
    612            installationMethod = InstallationMethod.FROM_FILE,
    613            onError = { caught ->
    614                throwable = caught
    615            },
    616        )
    617 
    618        verify(engine).installWebExtension(
    619            url = any(),
    620            installationMethod = eq(InstallationMethod.FROM_FILE),
    621            onSuccess = any(),
    622            onError = onErrorCaptor.capture(),
    623        )
    624 
    625        onErrorCaptor.value.invoke(IllegalStateException("test"))
    626        assertNotNull(throwable!!)
    627        assertTrue(manager.pendingAddonActions.isEmpty())
    628    }
    629 
    630    @Test
    631    fun `uninstallAddon successfully`() {
    632        val installedAddon = Addon(
    633            id = "ext1",
    634            installedState = Addon.InstalledState("ext1", "1.0", "", true),
    635        )
    636 
    637        val extension: WebExtension = mock()
    638        whenever(extension.id).thenReturn("ext1")
    639        WebExtensionSupport.installedExtensions[installedAddon.id] = extension
    640 
    641        val engine: Engine = mock()
    642        val onSuccessCaptor = argumentCaptor<(() -> Unit)>()
    643 
    644        var successCallbackInvoked = false
    645        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    646        manager.uninstallAddon(
    647            installedAddon,
    648            onSuccess = {
    649                successCallbackInvoked = true
    650            },
    651        )
    652        verify(engine).uninstallWebExtension(eq(extension), onSuccessCaptor.capture(), any())
    653 
    654        onSuccessCaptor.value.invoke()
    655        assertTrue(successCallbackInvoked)
    656        assertTrue(manager.pendingAddonActions.isEmpty())
    657    }
    658 
    659    @Test
    660    fun `uninstallAddon failure cases`() {
    661        val addon = Addon(id = "ext1")
    662        val engine: Engine = mock()
    663        val onErrorCaptor = argumentCaptor<((String, Throwable) -> Unit)>()
    664        var throwable: Throwable? = null
    665        var msg: String? = null
    666        val errorCallback = { errorMsg: String, caught: Throwable ->
    667            throwable = caught
    668            msg = errorMsg
    669        }
    670        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    671 
    672        // Extension is not installed so we're invoking the error callback and never the engine
    673        manager.uninstallAddon(addon, onError = errorCallback)
    674        verify(engine, never()).uninstallWebExtension(any(), any(), onErrorCaptor.capture())
    675        assertNotNull(throwable!!)
    676        assertEquals("Addon is not installed", throwable.localizedMessage)
    677 
    678        // Install extension and try again
    679        val extension: WebExtension = mock()
    680        whenever(extension.id).thenReturn("ext1")
    681        WebExtensionSupport.installedExtensions[addon.id] = extension
    682        manager.uninstallAddon(addon, onError = errorCallback)
    683        verify(engine, never()).uninstallWebExtension(any(), any(), onErrorCaptor.capture())
    684 
    685        // Make sure engine error is forwarded to caller
    686        val installedAddon = addon.copy(installedState = Addon.InstalledState(addon.id, "1.0", "", true))
    687        manager.uninstallAddon(installedAddon, onError = errorCallback)
    688        verify(engine).uninstallWebExtension(eq(extension), any(), onErrorCaptor.capture())
    689        onErrorCaptor.value.invoke(addon.id, IllegalStateException("test"))
    690        assertNotNull(throwable)
    691        assertEquals("test", throwable.localizedMessage)
    692        assertEquals(msg, addon.id)
    693        assertTrue(manager.pendingAddonActions.isEmpty())
    694    }
    695 
    696    @Test
    697    fun `add optional permissions successfully`() {
    698        val permission = listOf("permission1")
    699        val origin = listOf("origin")
    700        val addon = Addon(
    701            id = "ext1",
    702            installedState = Addon.InstalledState("ext1", "1.0", "", true),
    703        )
    704 
    705        val extension: WebExtension = mock()
    706        whenever(extension.id).thenReturn("ext1")
    707        WebExtensionSupport.installedExtensions[addon.id] = extension
    708        val metadata: Metadata = mock()
    709        whenever(extension.getMetadata()).thenReturn(metadata)
    710        whenever(metadata.optionalPermissions).thenReturn(permission)
    711        whenever(metadata.grantedOptionalPermissions).thenReturn(permission)
    712        whenever(metadata.optionalOrigins).thenReturn(origin)
    713        whenever(metadata.grantedOptionalOrigins).thenReturn(origin)
    714        val engine: Engine = mock()
    715        val onSuccessCaptor = argumentCaptor<((WebExtension) -> Unit)>()
    716 
    717        var updateAddon: Addon? = null
    718        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    719        manager.addOptionalPermission(
    720            addon,
    721            permission,
    722            origin,
    723            onSuccess = {
    724                updateAddon = it
    725            },
    726        )
    727 
    728        verify(engine).addOptionalPermissions(eq(extension.id), any(), any(), any(), onSuccessCaptor.capture(), any())
    729        onSuccessCaptor.value.invoke(extension)
    730        assertNotNull(updateAddon)
    731        assertEquals(addon.id, updateAddon!!.id)
    732        assertEquals("permission1", updateAddon.optionalPermissions.first().name)
    733        assertEquals(true, updateAddon.optionalPermissions.first().granted)
    734        assertEquals("origin", updateAddon.optionalOrigins.first().name)
    735        assertEquals(true, updateAddon.optionalOrigins.first().granted)
    736        assertTrue(manager.pendingAddonActions.isEmpty())
    737    }
    738 
    739    @Test
    740    fun `add optional with empty permissions and origins`() {
    741        var onErrorWasExecuted = false
    742        val manager = AddonManager(mock(), mock(), mock(), mock(), mock())
    743 
    744        manager.addOptionalPermission(
    745            mock(),
    746            emptyList(),
    747            emptyList(),
    748            onError = {
    749                onErrorWasExecuted = true
    750            },
    751        )
    752 
    753        assertTrue(onErrorWasExecuted)
    754    }
    755 
    756    @Test
    757    fun `remove optional permissions successfully`() {
    758        val permission = listOf("permission1")
    759        val origins = listOf("origin")
    760        val addon = Addon(
    761            id = "ext1",
    762            installedState = Addon.InstalledState("ext1", "1.0", "", true),
    763        )
    764 
    765        val extension: WebExtension = mock()
    766        whenever(extension.id).thenReturn("ext1")
    767        WebExtensionSupport.installedExtensions[addon.id] = extension
    768 
    769        val engine: Engine = mock()
    770        val onSuccessCaptor = argumentCaptor<((WebExtension) -> Unit)>()
    771 
    772        var updateAddon: Addon? = null
    773        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    774        manager.removeOptionalPermission(
    775            addon,
    776            permission,
    777            origins,
    778            onSuccess = {
    779                updateAddon = it
    780            },
    781        )
    782 
    783        verify(engine).removeOptionalPermissions(eq(extension.id), any(), any(), any(), onSuccessCaptor.capture(), any())
    784        onSuccessCaptor.value.invoke(extension)
    785        assertNotNull(updateAddon)
    786        assertEquals(addon.id, updateAddon!!.id)
    787        assertTrue(manager.pendingAddonActions.isEmpty())
    788    }
    789 
    790    @Test
    791    fun `remove optional with empty permissions and origins`() {
    792        var onErrorWasExecuted = false
    793        val manager = AddonManager(mock(), mock(), mock(), mock(), mock())
    794 
    795        manager.removeOptionalPermission(
    796            mock(),
    797            emptyList(),
    798            emptyList(),
    799            onError = {
    800                onErrorWasExecuted = true
    801            },
    802        )
    803 
    804        assertTrue(onErrorWasExecuted)
    805    }
    806 
    807    @Test
    808    fun `enableAddon successfully`() {
    809        val addon = Addon(
    810            id = "ext1",
    811            installedState = Addon.InstalledState("ext1", "1.0", "", true),
    812        )
    813 
    814        val extension: WebExtension = mock()
    815        whenever(extension.id).thenReturn("ext1")
    816        WebExtensionSupport.installedExtensions[addon.id] = extension
    817 
    818        val engine: Engine = mock()
    819        val onSuccessCaptor = argumentCaptor<((WebExtension) -> Unit)>()
    820 
    821        var enabledAddon: Addon? = null
    822        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    823        manager.enableAddon(
    824            addon,
    825            onSuccess = {
    826                enabledAddon = it
    827            },
    828        )
    829 
    830        verify(engine).enableWebExtension(eq(extension), any(), onSuccessCaptor.capture(), any())
    831        onSuccessCaptor.value.invoke(extension)
    832        assertNotNull(enabledAddon)
    833        assertEquals(addon.id, enabledAddon!!.id)
    834        assertTrue(manager.pendingAddonActions.isEmpty())
    835    }
    836 
    837    @Test
    838    fun `enableAddon failure cases`() {
    839        val addon = Addon(id = "ext1")
    840        val engine: Engine = mock()
    841        val onErrorCaptor = argumentCaptor<((Throwable) -> Unit)>()
    842        var throwable: Throwable? = null
    843        val errorCallback = { caught: Throwable ->
    844            throwable = caught
    845        }
    846        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    847 
    848        // Extension is not installed so we're invoking the error callback and never the engine
    849        manager.enableAddon(addon, onError = errorCallback)
    850        verify(engine, never()).enableWebExtension(any(), any(), any(), onErrorCaptor.capture())
    851        assertNotNull(throwable!!)
    852        assertEquals("Addon is not installed", throwable.localizedMessage)
    853 
    854        // Install extension and try again
    855        val extension: WebExtension = mock()
    856        whenever(extension.id).thenReturn("ext1")
    857        WebExtensionSupport.installedExtensions[addon.id] = extension
    858        manager.enableAddon(addon, onError = errorCallback)
    859        verify(engine, never()).enableWebExtension(any(), any(), any(), onErrorCaptor.capture())
    860 
    861        // Make sure engine error is forwarded to caller
    862        val installedAddon = addon.copy(installedState = Addon.InstalledState(addon.id, "1.0", "", true))
    863        manager.enableAddon(installedAddon, source = EnableSource.APP_SUPPORT, onError = errorCallback)
    864        verify(engine).enableWebExtension(eq(extension), eq(EnableSource.APP_SUPPORT), any(), onErrorCaptor.capture())
    865        onErrorCaptor.value.invoke(IllegalStateException("test"))
    866        assertNotNull(throwable)
    867        assertEquals("test", throwable.localizedMessage)
    868        assertTrue(manager.pendingAddonActions.isEmpty())
    869    }
    870 
    871    @Test
    872    fun `disableAddon successfully`() {
    873        val addon = Addon(
    874            id = "ext1",
    875            installedState = Addon.InstalledState("ext1", "1.0", "", true),
    876        )
    877 
    878        val extension: WebExtension = mock()
    879        whenever(extension.id).thenReturn("ext1")
    880        WebExtensionSupport.installedExtensions[addon.id] = extension
    881 
    882        val engine: Engine = mock()
    883        val onSuccessCaptor = argumentCaptor<((WebExtension) -> Unit)>()
    884 
    885        var disabledAddon: Addon? = null
    886        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    887        manager.disableAddon(
    888            addon,
    889            source = EnableSource.APP_SUPPORT,
    890            onSuccess = {
    891                disabledAddon = it
    892            },
    893        )
    894 
    895        verify(engine).disableWebExtension(eq(extension), eq(EnableSource.APP_SUPPORT), onSuccessCaptor.capture(), any())
    896        onSuccessCaptor.value.invoke(extension)
    897        assertNotNull(disabledAddon)
    898        assertEquals(addon.id, disabledAddon!!.id)
    899        assertTrue(manager.pendingAddonActions.isEmpty())
    900    }
    901 
    902    @Test
    903    fun `disableAddon failure cases`() {
    904        val addon = Addon(id = "ext1")
    905        val engine: Engine = mock()
    906        val onErrorCaptor = argumentCaptor<((Throwable) -> Unit)>()
    907        var throwable: Throwable? = null
    908        val errorCallback = { caught: Throwable ->
    909            throwable = caught
    910        }
    911        val manager = AddonManager(mock(), engine, mock(), mock(), mock())
    912 
    913        // Extension is not installed so we're invoking the error callback and never the engine
    914        manager.disableAddon(addon, onError = errorCallback)
    915        verify(engine, never()).disableWebExtension(any(), any(), any(), onErrorCaptor.capture())
    916        assertNotNull(throwable!!)
    917        assertEquals("Addon is not installed", throwable.localizedMessage)
    918 
    919        // Install extension and try again
    920        val extension: WebExtension = mock()
    921        whenever(extension.id).thenReturn("ext1")
    922        WebExtensionSupport.installedExtensions[addon.id] = extension
    923        manager.disableAddon(addon, onError = errorCallback)
    924        verify(engine, never()).disableWebExtension(any(), any(), any(), onErrorCaptor.capture())
    925 
    926        // Make sure engine error is forwarded to caller
    927        val installedAddon = addon.copy(installedState = Addon.InstalledState(addon.id, "1.0", "", true))
    928        manager.disableAddon(installedAddon, onError = errorCallback)
    929        verify(engine).disableWebExtension(eq(extension), any(), any(), onErrorCaptor.capture())
    930        onErrorCaptor.value.invoke(IllegalStateException("test"))
    931        assertNotNull(throwable)
    932        assertEquals("test", throwable.localizedMessage)
    933        assertTrue(manager.pendingAddonActions.isEmpty())
    934    }
    935 
    936    @Test
    937    fun `toInstalledState read from icon cache`() {
    938        val extension: WebExtension = mock()
    939        val metadata: Metadata = mock()
    940 
    941        val manager = spy(AddonManager(mock(), mock(), mock(), mock(), mock()))
    942 
    943        manager.iconsCache["ext1"] = mock()
    944        whenever(extension.id).thenReturn("ext1")
    945        whenever(extension.getMetadata()).thenReturn(metadata)
    946        whenever(extension.isEnabled()).thenReturn(true)
    947        whenever(extension.getDisabledReason()).thenReturn(null)
    948        whenever(extension.isAllowedInPrivateBrowsing()).thenReturn(true)
    949        whenever(metadata.version).thenReturn("version")
    950        whenever(metadata.optionsPageUrl).thenReturn("optionsPageUrl")
    951        whenever(metadata.openOptionsPageInTab).thenReturn(true)
    952 
    953        val installedExtension = manager.toInstalledState(extension)
    954 
    955        assertEquals(manager.iconsCache["ext1"], installedExtension.icon)
    956        assertEquals("version", installedExtension.version)
    957        assertEquals("optionsPageUrl", installedExtension.optionsPageUrl)
    958        assertNull(installedExtension.disabledReason)
    959        assertTrue(installedExtension.openOptionsPageInTab)
    960        assertTrue(installedExtension.enabled)
    961        assertTrue(installedExtension.allowedInPrivateBrowsing)
    962 
    963        verify(manager, times(0)).loadIcon(eq(extension))
    964    }
    965 
    966    @Test
    967    fun `toInstalledState load icon when cache is not available`() {
    968        val extension: WebExtension = mock()
    969        val metadata: Metadata = mock()
    970 
    971        val manager = spy(AddonManager(mock(), mock(), mock(), mock(), mock()))
    972 
    973        whenever(extension.id).thenReturn("ext1")
    974        whenever(extension.getMetadata()).thenReturn(metadata)
    975        whenever(extension.isEnabled()).thenReturn(true)
    976        whenever(extension.getDisabledReason()).thenReturn(null)
    977        whenever(extension.isAllowedInPrivateBrowsing()).thenReturn(true)
    978        whenever(metadata.version).thenReturn("version")
    979        whenever(metadata.optionsPageUrl).thenReturn("optionsPageUrl")
    980        whenever(metadata.openOptionsPageInTab).thenReturn(true)
    981 
    982        val installedExtension = manager.toInstalledState(extension)
    983 
    984        assertEquals(manager.iconsCache["ext1"], installedExtension.icon)
    985        assertEquals("version", installedExtension.version)
    986        assertEquals("optionsPageUrl", installedExtension.optionsPageUrl)
    987        assertNull(installedExtension.disabledReason)
    988        assertTrue(installedExtension.openOptionsPageInTab)
    989        assertTrue(installedExtension.enabled)
    990        assertTrue(installedExtension.allowedInPrivateBrowsing)
    991 
    992        verify(manager).loadIcon(extension)
    993    }
    994 
    995    @Test
    996    fun `loadIcon try to load the icon from extension`() = runTestOnMain {
    997        val extension: WebExtension = mock()
    998 
    999        val manager = spy(AddonManager(mock(), mock(), mock(), mock(), mock()))
   1000 
   1001        whenever(extension.loadIcon(AddonManager.ADDON_ICON_SIZE)).thenReturn(mock())
   1002 
   1003        val icon = manager.loadIcon(extension)
   1004 
   1005        assertNotNull(icon)
   1006    }
   1007 
   1008    @Test
   1009    fun `loadIcon calls tryLoadIconInBackground when TimeoutCancellationException`() =
   1010        runTestOnMain {
   1011            val extension: WebExtension = mock()
   1012 
   1013            val manager = spy(AddonManager(mock(), mock(), mock(), mock(), mock()))
   1014            doNothing().`when`(manager).tryLoadIconInBackground(extension)
   1015 
   1016            doThrow(mock<TimeoutCancellationException>()).`when`(extension)
   1017                .loadIcon(AddonManager.ADDON_ICON_SIZE)
   1018 
   1019            val icon = manager.loadIcon(extension)
   1020 
   1021            assertNull(icon)
   1022            verify(manager).loadIcon(extension)
   1023        }
   1024 
   1025    @Test
   1026    fun `getDisabledReason cases`() {
   1027        val extension: WebExtension = mock()
   1028        val metadata: Metadata = mock()
   1029        whenever(extension.getMetadata()).thenReturn(metadata)
   1030 
   1031        whenever(metadata.disabledFlags).thenReturn(DisabledFlags.select(BLOCKLIST))
   1032        assertEquals(Addon.DisabledReason.BLOCKLISTED, extension.getDisabledReason())
   1033 
   1034        whenever(metadata.disabledFlags).thenReturn(DisabledFlags.select(APP_SUPPORT))
   1035        assertEquals(Addon.DisabledReason.UNSUPPORTED, extension.getDisabledReason())
   1036 
   1037        whenever(metadata.disabledFlags).thenReturn(DisabledFlags.select(USER))
   1038        assertEquals(Addon.DisabledReason.USER_REQUESTED, extension.getDisabledReason())
   1039 
   1040        whenever(metadata.disabledFlags).thenReturn(DisabledFlags.select(SIGNATURE))
   1041        assertEquals(Addon.DisabledReason.NOT_CORRECTLY_SIGNED, extension.getDisabledReason())
   1042 
   1043        whenever(metadata.disabledFlags).thenReturn(DisabledFlags.select(APP_VERSION))
   1044        assertEquals(Addon.DisabledReason.INCOMPATIBLE, extension.getDisabledReason())
   1045 
   1046        whenever(metadata.disabledFlags).thenReturn(DisabledFlags.select(SOFT_BLOCKLIST))
   1047        assertEquals(Addon.DisabledReason.SOFT_BLOCKED, extension.getDisabledReason())
   1048 
   1049        whenever(metadata.disabledFlags).thenReturn(DisabledFlags.select(0))
   1050        assertNull(extension.getDisabledReason())
   1051    }
   1052 }