# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import unittest
from unittest import mock

from signing import model, modification, test_config


def plist_read(*args):
    bundle_id = test_config.TestConfig().base_bundle_id
    plists = {
        '/$W/App Product.app/Contents/Info.plist': {
            'CFBundleDisplayName': 'Product',
            'CFBundleIdentifier': bundle_id,
            'CFBundleName': 'Product',
            'KSProductID': 'test.ksproduct',
            'KSChannelID-full': '-full',
        },
        '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Alerts).app/Contents/Info.plist': {
            'CFBundleIdentifier': bundle_id + '.AlertNotificationService'
        },
        '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Alerts).app/Contents/Resources/base.lproj/InfoPlist.strings': {
            'CFBundleDisplayName': 'Product'
        },
        '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Aperitif Alerts).app/Contents/Info.plist': {
            'CFBundleIdentifier': bundle_id + '.AlertNotificationService'
        },
        '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Aperitif Alerts).app/Contents/Resources/base.lproj/InfoPlist.strings': {
            'CFBundleDisplayName': 'Product'
        },
        '/$W/app-entitlements.plist': {
            'com.apple.application-identifier': bundle_id
        },
        '/$W/helper-renderer-entitlements.plist': {},
        '/$W/helper-gpu-entitlements.plist': {},
        '/$W/App Product Canary.app/Contents/Resources/test.signing.bundle_id.canary.manifest/Contents/Resources/test.signing.bundle_id.canary.manifest': {
            'pfm_domain': bundle_id
        },
    }
    plists['/$W/App Product Canary.app/Contents/Info.plist'] = plists[
        '/$W/App Product.app/Contents/Info.plist'
    ]
    return plists[args[0]]


def plist_read_with_architecture(*args):
    plist = plist_read(*args)
    plist.update({'KSChannelID': 'arm64', 'KSChannelID-full': 'arm64-full'})
    return plist


@mock.patch('signing.commands.read_plist', side_effect=plist_read)
@mock.patch.multiple(
    'signing.commands',
    **{
        m: mock.DEFAULT
        for m in (
            'copy_files',
            'move_file',
            'make_dir',
            'write_file',
            'run_command',
            'write_plist',
        )
    },
)
class TestModification(unittest.TestCase):
    def setUp(self):
        self.paths = model.Paths('/$I', '/$O', '/$W')
        self.config = test_config.TestConfig()

    def _is_framework_unchanged(self, mocks):
        # Determines whether any modifications were made within the framework
        # according to calls to any of the mocked calls in |mocks|. This is done
        # by examining the calls' arguments for a substring pointing into the
        # framework.

        def _do_mock_calls_mention_framework(mock_calls):
            for call in mock_calls:
                for tup in call:
                    for arg in tup:
                        # Don't anchor this substring in a particular directory
                        # because it may appear in any of /$I, /$O, or /$W.
                        # Don't anchor it with App Product.app either, because
                        # it may be renamed (to App Product Canary.app).
                        if (
                            'Contents/Frameworks/Product Framework.framework'
                            in arg
                        ):
                            return True

            return False

        for mocked in mocks.values():
            if _do_mock_calls_mention_framework(mocked.mock_calls):
                return False

        return True

    def test_base_distribution(self, read_plist, **kwargs):
        dist = model.Distribution()
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        kwargs['write_plist'].assert_called_with(
            {
                'CFBundleDisplayName': 'Product',
                'CFBundleIdentifier': config.base_bundle_id,
                'CFBundleName': 'Product',
                'KSProductID': 'test.ksproduct',
                'KSChannelID-full': '-full',
            },
            '/$W/App Product.app/Contents/Info.plist',
            'xml1',
        )

        self.assertEqual(3, kwargs['copy_files'].call_count)
        kwargs['copy_files'].assert_has_calls(
            [
                mock.call(
                    '/$I/Product Packaging/app-entitlements.plist',
                    '/$W/app-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-gpu-entitlements.plist',
                    '/$W/helper-gpu-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-renderer-entitlements.plist',
                    '/$W/helper-renderer-entitlements.plist',
                ),
            ]
        )
        self.assertEqual(0, kwargs['move_file'].call_count)
        self.assertEqual(0, kwargs['write_file'].call_count)

        self.assertTrue(self._is_framework_unchanged(kwargs))

    def test_distribution_with_architecture(self, read_plist, **kwargs):
        read_plist.side_effect = plist_read_with_architecture

        dist = model.Distribution()
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        kwargs['write_plist'].assert_called_with(
            {
                'CFBundleDisplayName': 'Product',
                'CFBundleIdentifier': config.base_bundle_id,
                'CFBundleName': 'Product',
                'KSProductID': 'test.ksproduct',
                'KSChannelID': 'arm64',
                'KSChannelID-full': 'arm64-full',
            },
            '/$W/App Product.app/Contents/Info.plist',
            'xml1',
        )

        self.assertEqual(3, kwargs['copy_files'].call_count)
        kwargs['copy_files'].assert_has_calls(
            [
                mock.call(
                    '/$I/Product Packaging/app-entitlements.plist',
                    '/$W/app-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-gpu-entitlements.plist',
                    '/$W/helper-gpu-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-renderer-entitlements.plist',
                    '/$W/helper-renderer-entitlements.plist',
                ),
            ]
        )
        self.assertEqual(0, kwargs['move_file'].call_count)
        self.assertEqual(0, kwargs['write_file'].call_count)

        self.assertTrue(self._is_framework_unchanged(kwargs))

    def test_distribution_with_brand(self, read_plist, **kwargs):
        dist = model.Distribution(branding_code='MOO')
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        kwargs['write_plist'].assert_called_with(
            {
                'CFBundleDisplayName': 'Product',
                'CFBundleIdentifier': config.base_bundle_id,
                'CFBundleName': 'Product',
                'KSProductID': 'test.ksproduct',
                'KSBrandID': 'MOO',
                'KSChannelID-full': '-full',
            },
            '/$W/App Product.app/Contents/Info.plist',
            'xml1',
        )

        self.assertEqual(3, kwargs['copy_files'].call_count)
        kwargs['copy_files'].assert_has_calls(
            [
                mock.call(
                    '/$I/Product Packaging/app-entitlements.plist',
                    '/$W/app-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-gpu-entitlements.plist',
                    '/$W/helper-gpu-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-renderer-entitlements.plist',
                    '/$W/helper-renderer-entitlements.plist',
                ),
            ]
        )
        self.assertEqual(0, kwargs['move_file'].call_count)

        self.assertTrue(self._is_framework_unchanged(kwargs))

    def test_distribution_with_channel(self, read_plist, **kwargs):
        dist = model.Distribution(channel='dev')
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        kwargs['write_plist'].assert_called_with(
            {
                'CFBundleDisplayName': 'Product',
                'CFBundleIdentifier': config.base_bundle_id,
                'CFBundleName': 'Product',
                'KSProductID': 'test.ksproduct',
                'KSChannelID': 'dev',
                'KSChannelID-full': 'dev-full',
            },
            '/$W/App Product.app/Contents/Info.plist',
            'xml1',
        )

        self.assertEqual(3, kwargs['copy_files'].call_count)
        kwargs['copy_files'].assert_has_calls(
            [
                mock.call(
                    '/$I/Product Packaging/app-entitlements.plist',
                    '/$W/app-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-gpu-entitlements.plist',
                    '/$W/helper-gpu-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-renderer-entitlements.plist',
                    '/$W/helper-renderer-entitlements.plist',
                ),
            ]
        )
        self.assertEqual(0, kwargs['move_file'].call_count)
        self.assertEqual(0, kwargs['write_file'].call_count)

        self.assertTrue(self._is_framework_unchanged(kwargs))

    def test_distribution_with_architecture_and_channel(
        self, read_plist, **kwargs
    ):
        read_plist.side_effect = plist_read_with_architecture

        dist = model.Distribution(channel='dev')
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        kwargs['write_plist'].assert_called_with(
            {
                'CFBundleDisplayName': 'Product',
                'CFBundleIdentifier': config.base_bundle_id,
                'CFBundleName': 'Product',
                'KSProductID': 'test.ksproduct',
                'KSChannelID': 'arm64-dev',
                'KSChannelID-full': 'arm64-dev-full',
            },
            '/$W/App Product.app/Contents/Info.plist',
            'xml1',
        )

        self.assertEqual(3, kwargs['copy_files'].call_count)
        kwargs['copy_files'].assert_has_calls(
            [
                mock.call(
                    '/$I/Product Packaging/app-entitlements.plist',
                    '/$W/app-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-gpu-entitlements.plist',
                    '/$W/helper-gpu-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-renderer-entitlements.plist',
                    '/$W/helper-renderer-entitlements.plist',
                ),
            ]
        )
        self.assertEqual(0, kwargs['move_file'].call_count)
        self.assertEqual(0, kwargs['write_file'].call_count)

        self.assertTrue(self._is_framework_unchanged(kwargs))

    def test_distribution_with_product_dirname(self, read_plist, **kwargs):
        dist = model.Distribution(product_dirname='Farmland/Cows')
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        kwargs['write_plist'].assert_called_with(
            {
                'CFBundleDisplayName': 'Product',
                'CFBundleIdentifier': config.base_bundle_id,
                'CFBundleName': 'Product',
                'KSProductID': 'test.ksproduct',
                'KSChannelID-full': '-full',
                'CrProductDirName': 'Farmland/Cows',
            },
            '/$W/App Product.app/Contents/Info.plist',
            'xml1',
        )

        self.assertEqual(3, kwargs['copy_files'].call_count)
        kwargs['copy_files'].assert_has_calls(
            [
                mock.call(
                    '/$I/Product Packaging/app-entitlements.plist',
                    '/$W/app-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-gpu-entitlements.plist',
                    '/$W/helper-gpu-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-renderer-entitlements.plist',
                    '/$W/helper-renderer-entitlements.plist',
                ),
            ]
        )
        self.assertEqual(0, kwargs['move_file'].call_count)
        self.assertEqual(0, kwargs['write_file'].call_count)

        self.assertTrue(self._is_framework_unchanged(kwargs))

    def test_distribution_with_creator_code(self, read_plist, **kwargs):
        dist = model.Distribution(creator_code='Mooo')
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        kwargs['write_plist'].assert_called_with(
            {
                'CFBundleDisplayName': 'Product',
                'CFBundleIdentifier': config.base_bundle_id,
                'CFBundleName': 'Product',
                'KSProductID': 'test.ksproduct',
                'KSChannelID-full': '-full',
                'CFBundleSignature': 'Mooo',
            },
            '/$W/App Product.app/Contents/Info.plist',
            'xml1',
        )

        self.assertEqual(3, kwargs['copy_files'].call_count)
        kwargs['copy_files'].assert_has_calls(
            [
                mock.call(
                    '/$I/Product Packaging/app-entitlements.plist',
                    '/$W/app-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-gpu-entitlements.plist',
                    '/$W/helper-gpu-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-renderer-entitlements.plist',
                    '/$W/helper-renderer-entitlements.plist',
                ),
            ]
        )
        kwargs['write_file'].assert_called_once_with(
            '/$W/App Product.app/Contents/PkgInfo', 'APPLMooo'
        )
        self.assertEqual(0, kwargs['move_file'].call_count)

    def test_distribution_with_brand_and_channel(self, read_plist, **kwargs):
        dist = model.Distribution(channel='beta', branding_code='RAWR')
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        kwargs['write_plist'].assert_called_with(
            {
                'CFBundleDisplayName': 'Product',
                'CFBundleIdentifier': config.base_bundle_id,
                'CFBundleName': 'Product',
                'KSProductID': 'test.ksproduct',
                'KSChannelID': 'beta',
                'KSChannelID-full': 'beta-full',
                'KSBrandID': 'RAWR',
            },
            '/$W/App Product.app/Contents/Info.plist',
            'xml1',
        )

        self.assertEqual(3, kwargs['copy_files'].call_count)
        kwargs['copy_files'].assert_has_calls(
            [
                mock.call(
                    '/$I/Product Packaging/app-entitlements.plist',
                    '/$W/app-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-gpu-entitlements.plist',
                    '/$W/helper-gpu-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-renderer-entitlements.plist',
                    '/$W/helper-renderer-entitlements.plist',
                ),
            ]
        )
        self.assertEqual(0, kwargs['move_file'].call_count)
        self.assertEqual(0, kwargs['write_file'].call_count)

    def test_customize_channel(self, read_plist, **kwargs):
        dist = model.Distribution(
            channel='canary',
            app_name_fragment='Canary',
            product_dirname='Acme/Product Canary',
            creator_code='Mooo',
            channel_customize=True,
        )
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        # Order of file moves is significant.
        self.assertEqual(
            kwargs['move_file'].mock_calls,
            [
                mock.call('/$W/App Product.app', '/$W/App Product Canary.app'),
                mock.call(
                    '/$W/App Product Canary.app/Contents/MacOS/App Product',
                    '/$W/App Product Canary.app/Contents/MacOS/App Product Canary',
                ),
                mock.call(
                    '/$W/App Product Canary.app/Contents/Resources/test.signing.bundle_id.manifest/Contents/Resources/test.signing.bundle_id.manifest',
                    '/$W/App Product Canary.app/Contents/Resources/test.signing.bundle_id.manifest/Contents/Resources/test.signing.bundle_id.canary.manifest',
                ),
                mock.call(
                    '/$W/App Product Canary.app/Contents/Resources/test.signing.bundle_id.manifest',
                    '/$W/App Product Canary.app/Contents/Resources/test.signing.bundle_id.canary.manifest',
                ),
            ],
        )

        self.assertEqual(9, kwargs['copy_files'].call_count)
        kwargs['copy_files'].assert_has_calls(
            [
                mock.call(
                    '/$I/Product Packaging/app-entitlements.plist',
                    '/$W/app-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-gpu-entitlements.plist',
                    '/$W/helper-gpu-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/helper-renderer-entitlements.plist',
                    '/$W/helper-renderer-entitlements.plist',
                ),
                mock.call(
                    '/$I/Product Packaging/Assets_canary.car',
                    '/$W/App Product Canary.app/Contents/Resources/Assets.car',
                ),
                mock.call(
                    '/$I/Product Packaging/app_canary.icns',
                    '/$W/App Product Canary.app/Contents/Resources/app.icns',
                ),
                mock.call(
                    '/$I/Product Packaging/Assets_canary.car',
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Alerts).app/Contents/Resources/Assets.car',
                ),
                mock.call(
                    '/$I/Product Packaging/app_canary.icns',
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Alerts).app/Contents/Resources/app.icns',
                ),
                mock.call(
                    '/$I/Product Packaging/Assets_canary.car',
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Aperitif Alerts).app/Contents/Resources/Assets.car',
                ),
                mock.call(
                    '/$I/Product Packaging/app_canary.icns',
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Aperitif Alerts).app/Contents/Resources/app.icns',
                ),
            ]
        )
        kwargs['write_file'].assert_called_once_with(
            '/$W/App Product Canary.app/Contents/PkgInfo', 'APPLMooo'
        )

        self.assertEqual(9, kwargs['write_plist'].call_count)
        kwargs['write_plist'].assert_has_calls(
            [
                mock.call(
                    {
                        'CFBundleIdentifier': 'test.signing.bundle_id.canary.AlertNotificationService'
                    },
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Alerts).app/Contents/Info.plist',
                    'xml1',
                ),
                mock.call(
                    {'CFBundleDisplayName': 'Product Canary'},
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Alerts).app/Contents/Resources/base.lproj/InfoPlist.strings',
                    'binary1',
                ),
                mock.call(
                    {
                        'CFBundleIdentifier': 'test.signing.bundle_id.canary.AlertNotificationService'
                    },
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Aperitif Alerts).app/Contents/Info.plist',
                    'xml1',
                ),
                mock.call(
                    {'CFBundleDisplayName': 'Product Canary'},
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Aperitif Alerts).app/Contents/Resources/base.lproj/InfoPlist.strings',
                    'binary1',
                ),
                mock.call(
                    {
                        'CFBundleDisplayName': 'Product Canary',
                        'CFBundleIdentifier': config.base_bundle_id,
                        'CFBundleExecutable': config.app_product,
                        'CFBundleName': 'Product Canary',
                        'KSProductID': 'test.ksproduct.canary',
                        'KSChannelID': 'canary',
                        'KSChannelID-full': 'canary-full',
                        'CrProductDirName': 'Acme/Product Canary',
                        'CFBundleSignature': 'Mooo',
                    },
                    '/$W/App Product Canary.app/Contents/Info.plist',
                    'xml1',
                ),
                mock.call(
                    {
                        'com.apple.application-identifier': 'test.signing.bundle_id.canary'
                    },
                    '/$W/app-entitlements.plist',
                    'xml1',
                ),
                mock.call({}, '/$W/helper-gpu-entitlements.plist', 'xml1'),
                mock.call({}, '/$W/helper-renderer-entitlements.plist', 'xml1'),
                mock.call(
                    {'pfm_domain': 'test.signing.bundle_id.canary'},
                    '/$W/App Product Canary.app/Contents/Resources/test.signing.bundle_id.canary.manifest/Contents/Resources/test.signing.bundle_id.canary.manifest',
                    'xml1',
                ),
            ]
        )

        self.assertFalse(self._is_framework_unchanged(kwargs))

    def test_get_task_allow_no_channel_customize(self, read_plist, **kwargs):
        dist = model.Distribution()
        self.config = test_config.TestConfigInjectGetTaskAllow()
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(4, kwargs['write_plist'].call_count)
        kwargs['write_plist'].assert_has_calls(
            [
                mock.call(
                    {
                        'CFBundleDisplayName': 'Product',
                        'CFBundleIdentifier': config.base_bundle_id,
                        'CFBundleName': 'Product',
                        'KSProductID': 'test.ksproduct',
                        'KSChannelID-full': '-full',
                    },
                    '/$W/App Product.app/Contents/Info.plist',
                    'xml1',
                ),
                mock.call(
                    {
                        'com.apple.security.get-task-allow': True,
                        'com.apple.application-identifier': config.base_bundle_id,
                    },
                    '/$W/app-entitlements.plist',
                    'xml1',
                ),
                mock.call(
                    {'com.apple.security.get-task-allow': True},
                    '/$W/helper-gpu-entitlements.plist',
                    'xml1',
                ),
                mock.call(
                    {'com.apple.security.get-task-allow': True},
                    '/$W/helper-renderer-entitlements.plist',
                    'xml1',
                ),
            ]
        )

    def test_get_task_allow_customize_channel(self, read_plist, **kwargs):
        dist = model.Distribution(
            channel='canary',
            app_name_fragment='Canary',
            product_dirname='Acme/Product Canary',
            creator_code='Mooo',
            channel_customize=True,
        )
        self.config = test_config.TestConfigInjectGetTaskAllow()
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(9, kwargs['write_plist'].call_count)
        kwargs['write_plist'].assert_has_calls(
            [
                mock.call(
                    {
                        'CFBundleIdentifier': 'test.signing.bundle_id.canary.AlertNotificationService'
                    },
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Alerts).app/Contents/Info.plist',
                    'xml1',
                ),
                mock.call(
                    {'CFBundleDisplayName': 'Product Canary'},
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Alerts).app/Contents/Resources/base.lproj/InfoPlist.strings',
                    'binary1',
                ),
                mock.call(
                    {
                        'CFBundleIdentifier': 'test.signing.bundle_id.canary.AlertNotificationService'
                    },
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Aperitif Alerts).app/Contents/Info.plist',
                    'xml1',
                ),
                mock.call(
                    {'CFBundleDisplayName': 'Product Canary'},
                    '/$W/App Product Canary.app/Contents/Frameworks/Product Framework.framework/Helpers/Product Helper (Aperitif Alerts).app/Contents/Resources/base.lproj/InfoPlist.strings',
                    'binary1',
                ),
                mock.call(
                    {
                        'CFBundleDisplayName': 'Product Canary',
                        'CFBundleIdentifier': config.base_bundle_id,
                        'CFBundleExecutable': config.app_product,
                        'CFBundleName': 'Product Canary',
                        'KSProductID': 'test.ksproduct.canary',
                        'KSChannelID': 'canary',
                        'KSChannelID-full': 'canary-full',
                        'CrProductDirName': 'Acme/Product Canary',
                        'CFBundleSignature': 'Mooo',
                    },
                    '/$W/App Product Canary.app/Contents/Info.plist',
                    'xml1',
                ),
                mock.call(
                    {
                        'com.apple.security.get-task-allow': True,
                        'com.apple.application-identifier': 'test.signing.bundle_id.canary',
                    },
                    '/$W/app-entitlements.plist',
                    'xml1',
                ),
                mock.call(
                    {'com.apple.security.get-task-allow': True},
                    '/$W/helper-gpu-entitlements.plist',
                    'xml1',
                ),
                mock.call(
                    {'com.apple.security.get-task-allow': True},
                    '/$W/helper-renderer-entitlements.plist',
                    'xml1',
                ),
                mock.call(
                    {'pfm_domain': 'test.signing.bundle_id.canary'},
                    '/$W/App Product Canary.app/Contents/Resources/test.signing.bundle_id.canary.manifest/Contents/Resources/test.signing.bundle_id.canary.manifest',
                    'xml1',
                ),
            ]
        )

    def test_distribution_with_sxs_channel_removes_google_chrome_scheme(
        self, read_plist, **kwargs
    ):

        def plist_read_with_scheme(*args):
            plist = plist_read(*args)
            if 'Info.plist' in args[0]:
                plist['CFBundleURLTypes'] = [
                    {'CFBundleURLSchemes': ['google-chrome']},
                    {'CFBundleURLSchemes': ['http', 'https']},
                ]
            return plist

        read_plist.side_effect = plist_read_with_scheme

        # product_dirname implies Side-by-Side installation.
        dist = model.Distribution(
            channel='beta', product_dirname='BetaDir', direct_launch_scheme=''
        )
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        written_plist = kwargs['write_plist'].call_args[0][0]
        url_types = written_plist.get('CFBundleURLTypes')
        self.assertIsNotNone(url_types)
        # Should be removed.
        self.assertEqual(len(url_types), 1)
        self.assertEqual(url_types[0]['CFBundleURLSchemes'], ['http', 'https'])

    def test_distribution_with_floating_channel_keeps_google_chrome_scheme(
        self, read_plist, **kwargs
    ):

        def plist_read_with_scheme(*args):
            plist = plist_read(*args)
            if 'Info.plist' in args[0]:
                plist['CFBundleURLTypes'] = [
                    {'CFBundleURLSchemes': ['google-chrome']},
                    {'CFBundleURLSchemes': ['http', 'https']},
                ]
            return plist

        read_plist.side_effect = plist_read_with_scheme

        # No product_dirname implies Floating (replaces Stable).
        dist = model.Distribution(channel='beta')
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        written_plist = kwargs['write_plist'].call_args[0][0]
        url_types = written_plist.get('CFBundleURLTypes')
        self.assertIsNotNone(url_types)
        # Should be kept.
        self.assertEqual(len(url_types), 2)
        self.assertEqual(url_types[0]['CFBundleURLSchemes'], ['google-chrome'])
        self.assertEqual(url_types[1]['CFBundleURLSchemes'], ['http', 'https'])

    def test_distribution_with_explicit_scheme_config(
        self, read_plist, **kwargs
    ):

        def plist_read_with_scheme(*args):
            plist = plist_read(*args)
            if 'Info.plist' in args[0]:
                plist['CFBundleURLTypes'] = [
                    {'CFBundleURLSchemes': ['google-chrome']},
                    {'CFBundleURLSchemes': ['http', 'https']},
                ]
            return plist

        read_plist.side_effect = plist_read_with_scheme

        # Explicit config to 'custom-scheme'.
        # Since tweak_info_plist put 'google-chrome', and it doesn't match
        # 'custom-scheme', it should be removed.
        dist = model.Distribution(
            channel='beta', direct_launch_scheme='custom-scheme'
        )
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        written_plist = kwargs['write_plist'].call_args[0][0]
        url_types = written_plist.get('CFBundleURLTypes')
        self.assertIsNotNone(url_types)
        # Should be removed.
        self.assertEqual(len(url_types), 1)
        self.assertEqual(url_types[0]['CFBundleURLSchemes'], ['http', 'https'])

    def test_distribution_with_explicit_scheme_overrides_sxs_removal(
        self, read_plist, **kwargs
    ):

        def plist_read_with_scheme(*args):
            plist = plist_read(*args)
            if 'Info.plist' in args[0]:
                plist['CFBundleURLTypes'] = [
                    {'CFBundleURLSchemes': ['google-chrome']},
                    {'CFBundleURLSchemes': ['http', 'https']},
                ]
            return plist

        read_plist.side_effect = plist_read_with_scheme

        # SxS (would remove by heuristic) BUT Config says 'google-chrome' (Keep).
        dist = model.Distribution(
            channel='beta',
            product_dirname='BetaDir',
            direct_launch_scheme='google-chrome',
        )
        config = dist.to_config(self.config)

        modification.customize_distribution(self.paths, dist, config)

        self.assertEqual(1, kwargs['write_plist'].call_count)
        written_plist = kwargs['write_plist'].call_args[0][0]
        url_types = written_plist.get('CFBundleURLTypes')
        self.assertIsNotNone(url_types)
        # Should be KEPT.
        self.assertEqual(len(url_types), 2)
        self.assertEqual(url_types[0]['CFBundleURLSchemes'], ['google-chrome'])
