예제 #1
0
    def test__do_import_modules_handles_cancel(self, _add_new_module):
        """
        Make sure _do_import_modules() handles the cancel signal correctly. We'll do this by setting
        up a side effect with the first module to call cancel so the second never happens.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        def _side_effect(*args, **kwargs):
            swpf.cancel()

        _add_new_module.side_effect = _side_effect
        metadata = model.RepositoryMetadata()
        module_1 = model.Module('module_1', '1.0.0', 'simon')
        module_2 = model.Module('module_2', '2.0.3', 'garfunkel')
        metadata.modules = [module_1, module_2]

        swpf._do_import_modules(metadata)

        # If _add_new_module was called exactly once, then our cancel was successful because the
        # first call to _add_new_module set the cancel flag, and the loop exited the next time.
        # Because dictionaries are involved in the order in which the modules get downloaded, we
        # don't have a documented guarantee about which module will be the one. Therefore, we'll
        # just assert that only one was downloaded and that it was one of the two.
        self.assertEqual(_add_new_module.call_count, 1)
        downloaded_module = _add_new_module.mock_calls[0][1][1]
        self.assertTrue(downloaded_module in [module_1, module_2])
예제 #2
0
    def test__do_import_modules_handles_cancel(self, _add_new_module):
        """
        Make sure _do_import_modules() handles the cancel signal correctly. We'll do this by setting
        up a side effect with the first module to call cancel so the second never happens.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        def _side_effect(*args, **kwargs):
            swpf.cancel()

        _add_new_module.side_effect = _side_effect
        metadata = model.RepositoryMetadata()
        module_1 = model.Module('module_1', '1.0.0', 'simon')
        module_2 = model.Module('module_2', '2.0.3', 'garfunkel')
        metadata.modules = [module_1, module_2]

        swpf._do_import_modules(metadata)

        # If _add_new_module was called exactly once, then our cancel was successful because the
        # first call to _add_new_module set the cancel flag, and the loop exited the next time.
        # Because dictionaries are involved in the order in which the modules get downloaded, we
        # don't have a documented guarantee about which module will be the one. Therefore, we'll
        # just assert that only one was downloaded and that it was one of the two.
        self.assertEqual(_add_new_module.call_count, 1)
        downloaded_module = _add_new_module.mock_calls[0][1][1]
        self.assertTrue(downloaded_module in [module_1, module_2])
예제 #3
0
    def test_cancel_downloader_none(self):
        """
        Ensure correct operation of the cancel() method when the downloader is None.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        # This should not blow up due to the downloader being None
        swpf.cancel()

        self.assertEqual(swpf._canceled, True)
예제 #4
0
    def test_cancel_downloader_none(self):
        """
        Ensure correct operation of the cancel() method when the downloader is None.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        # This should not blow up due to the downloader being None
        swpf.cancel()

        self.assertEqual(swpf._canceled, True)
예제 #5
0
    def test_cancel_downloader_set(self):
        """
        Ensure correct operation of the cancel() method when the downloader is set.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)
        swpf.downloader = mock.MagicMock()

        swpf.cancel()

        self.assertEqual(swpf._canceled, True)
        swpf.downloader.cancel.assert_called_once_with()
예제 #6
0
    def test_cancel_downloader_set(self):
        """
        Ensure correct operation of the cancel() method when the downloader is set.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)
        swpf.downloader = mock.MagicMock()

        swpf.cancel()

        self.assertEqual(swpf._canceled, True)
        swpf.downloader.cancel.assert_called_once_with()
예제 #7
0
    def setUp(self):
        self.working_dir = tempfile.mkdtemp(prefix='puppet-sync-tests')
        self.repo = Repository('test-repo', working_dir=self.working_dir)
        self.conduit = MockConduit()
        self.config = PluginCallConfiguration({}, {
            constants.CONFIG_FEED: FEED,
        })

        self.method = SynchronizeWithPuppetForge(self.repo, self.conduit,
                                                 self.config)

        self.sample_units = [
            Module(author='a1', name='n1', version='1.0'),
            Module(author='a2', name='n2', version='2.0'),
            Module(author='a3', name='n3', version='3.0')
        ]
예제 #8
0
    def setUp(self):
        self.working_dir = tempfile.mkdtemp(prefix='puppet-sync-tests')
        self.repo = Repository('test-repo', working_dir=self.working_dir)
        self.conduit = MockConduit()
        self.config = PluginCallConfiguration({}, {
            constants.CONFIG_FEED: FEED,
        })

        self.method = SynchronizeWithPuppetForge(self.repo, self.conduit,
                                                 self.config)
예제 #9
0
    def setUp(self):
        self.working_dir = tempfile.mkdtemp(prefix='puppet-sync-tests')
        self.repo = Repository('test-repo', working_dir=self.working_dir)
        self.conduit = MockConduit()
        self.config = PluginCallConfiguration({}, {
            constants.CONFIG_FEED: FEED,
        })

        self.method = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        self.sample_units = [Module(author='a1', name='n1', version='1.0'),
                             Module(author='a2', name='n2', version='2.0'),
                             Module(author='a3', name='n3', version='3.0')]
예제 #10
0
    def test___init__(self):
        """
        Ensure the __init__() method works properly.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        self.assertEqual(swpf.repo, self.repo)
        self.assertEqual(swpf.sync_conduit, self.conduit)
        self.assertEqual(swpf.config, self.config)
        self.assertTrue(
            isinstance(swpf.progress_report, sync_progress.SyncProgressReport))
        self.assertEqual(swpf.progress_report.conduit, self.conduit)
        self.assertEqual(swpf.downloader, None)
        self.assertEqual(swpf._canceled, False)
예제 #11
0
    def sync_repo(self, repo, sync_conduit, config):
        self.sync_cancelled = False

        # Supports two methods of synchronization.
        # 1. Synchronize with a directory containing a pulp manifest and puppet modules.
        # 2. Synchronize with Puppet Forge.
        # When the feed URL references a PULP_MANIFEST, the directory synchronization
        # method is used.  Otherwise, the puppet forge synchronization method is used.

        # synchronize with a directory
        self.sync_method = SynchronizeWithDirectory(repo, sync_conduit, config)
        report = self.sync_method()

        # When fetching the PULP_MANIFEST is not successful, it's assumed that the
        # feed points to a puppet forge instance and the synchronization is retried
        # using puppet forge method.

        if report.metadata_state == constants.STATE_FAILED:
            self.sync_method = SynchronizeWithPuppetForge(repo, sync_conduit, config)
            report = self.sync_method()

        self.sync_method = None
        return report.build_final_report()
예제 #12
0
    def test_parse_metadata_retrieve_exception_canceled(self, mock_create):
        # Setup
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        def _side_effect(*args, **kwargs):
            swpf.cancel()
            raise Exception("some download error")

        mock_create.side_effect = _side_effect

        # Test
        report = swpf().build_final_report()

        # Verify
        self.assertTrue(report.canceled_flag)

        pr = swpf.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_CANCELED)
        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)
예제 #13
0
class TestSynchronizeWithPuppetForge(unittest.TestCase):
    def setUp(self):
        self.working_dir = tempfile.mkdtemp(prefix='puppet-sync-tests')
        self.repo = Repository('test-repo', working_dir=self.working_dir)
        self.conduit = MockConduit()
        self.config = PluginCallConfiguration({}, {
            constants.CONFIG_FEED: FEED,
        })

        self.method = SynchronizeWithPuppetForge(self.repo, self.conduit,
                                                 self.config)

        self.sample_units = [
            Module(author='a1', name='n1', version='1.0'),
            Module(author='a2', name='n2', version='2.0'),
            Module(author='a3', name='n3', version='3.0')
        ]

    def tearDown(self):
        shutil.rmtree(self.working_dir)

    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(MOCK_PULP_STORAGE_LOCATION)

    def test___init__(self):
        """
        Ensure the __init__() method works properly.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        self.assertEqual(swpf.repo, self.repo)
        self.assertEqual(swpf.sync_conduit, self.conduit)
        self.assertEqual(swpf.config, self.config)
        self.assertTrue(
            isinstance(swpf.progress_report, sync_progress.SyncProgressReport))
        self.assertEqual(swpf.progress_report.conduit, self.conduit)
        self.assertEqual(swpf.downloader, None)
        self.assertEqual(swpf._canceled, False)

    def test_cancel_downloader_none(self):
        """
        Ensure correct operation of the cancel() method when the downloader is None.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        # This should not blow up due to the downloader being None
        swpf.cancel()

        self.assertEqual(swpf._canceled, True)

    def test_cancel_downloader_set(self):
        """
        Ensure correct operation of the cancel() method when the downloader is set.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)
        swpf.downloader = mock.MagicMock()

        swpf.cancel()

        self.assertEqual(swpf._canceled, True)
        swpf.downloader.cancel.assert_called_once_with()

    def test_synchronize_metadata_error(self):
        # Setup
        self.config.repo_plugin_config[constants.CONFIG_FEED] = INVALID_FEED

        # Test
        report = self.method().build_final_report()

        # Verify
        self.assertTrue(not report.success_flag)

        pr = self.method.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_FAILED)
        self.assertEqual(pr.metadata_query_total_count, 1)
        self.assertEqual(pr.metadata_query_finished_count, 0)

        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)
        self.assertEqual(pr.modules_total_count, None)
        self.assertEqual(pr.modules_finished_count, None)

    def test_synchronize_no_feed(self):
        # Setup
        del self.config.repo_plugin_config[constants.CONFIG_FEED]

        # Test
        report = self.method()

        # Verify
        self.assertTrue(not report.success_flag)
        pr = self.method.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_FAILED)
        self.assertTrue(len(pr.metadata_error_message) > 0)
        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)

    @mock.patch(
        'pulp_puppet.plugins.importers.forge.SynchronizeWithPuppetForge._parse_metadata'
    )
    def test_synchronize_no_metadata(self, mock_parse):
        # Setup
        mock_parse.return_value = None

        # Test
        report = self.method().build_final_report()

        # Verify
        self.assertTrue(report is not None)
        self.assertTrue(not report.success_flag)

        pr = self.method.progress_report
        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)

    @mock.patch(
        'pulp_puppet.plugins.importers.forge.SynchronizeWithPuppetForge._create_downloader'
    )
    def test_parse_metadata_retrieve_exception(self, mock_create):
        # Setup
        mock_create.side_effect = Exception()

        # Test
        report = self.method().build_final_report()

        # Verify
        self.assertTrue(not report.success_flag)

        pr = self.method.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_FAILED)
        self.assertEqual(pr.metadata_query_total_count, None)
        self.assertEqual(pr.metadata_query_finished_count, None)
        self.assertTrue(pr.metadata_execution_time is not None)
        self.assertTrue(pr.metadata_error_message is not None)
        self.assertTrue(pr.metadata_exception is not None)
        self.assertTrue(pr.metadata_traceback is not None)

        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)

    @mock.patch(
        'pulp_puppet.plugins.importers.forge.SynchronizeWithPuppetForge._create_downloader'
    )
    def test_parse_metadata_retrieve_exception_canceled(self, mock_create):
        # Setup
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        def _side_effect(*args, **kwargs):
            swpf.cancel()
            raise Exception("some download error")

        mock_create.side_effect = _side_effect

        # Test
        report = swpf().build_final_report()

        # Verify
        self.assertTrue(report.canceled_flag)

        pr = swpf.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_CANCELED)
        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)

    @mock.patch(
        'pulp_puppet.plugins.importers.downloaders.local.LocalDownloader.retrieve_metadata'
    )
    def test_parse_metadata_parse_exception(self, mock_retrieve):
        # Setup
        mock_retrieve.return_value = ['not parsable json']

        # Test
        report = self.method().build_final_report()

        # Test
        self.assertTrue(not report.success_flag)

        pr = self.method.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_FAILED)
        self.assertTrue(pr.metadata_execution_time is not None)
        self.assertTrue(pr.metadata_error_message is not None)
        self.assertTrue(pr.metadata_exception is not None)
        self.assertTrue(pr.metadata_traceback is not None)

        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)

    @mock.patch(
        'pulp_puppet.plugins.importers.forge.SynchronizeWithPuppetForge._do_import_modules'
    )
    @mock.patch('pulp.server.managers.repo._common.get_working_directory',
                return_value='/tmp/')
    def test_import_modules_exception(self, mock_get_working_dir, mock_import):
        # Setup
        mock_import.side_effect = Exception()

        # Test
        report = self.method().build_final_report()

        # Verify
        self.assertTrue(not report.success_flag)

        pr = self.method.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_SUCCESS)
        self.assertEqual(pr.metadata_query_total_count, 1)
        self.assertEqual(pr.metadata_query_finished_count, 1)
        self.assertTrue(pr.metadata_execution_time is not None)
        self.assertTrue(pr.metadata_error_message is None)
        self.assertTrue(pr.metadata_exception is None)
        self.assertTrue(pr.metadata_traceback is None)

        self.assertEqual(pr.modules_state, constants.STATE_FAILED)
        self.assertEqual(pr.modules_total_count, None)
        self.assertEqual(pr.modules_finished_count, None)
        self.assertTrue(pr.modules_execution_time is not None)
        self.assertTrue(pr.modules_error_message is not None)
        self.assertTrue(pr.modules_exception is not None)
        self.assertTrue(pr.modules_traceback is not None)

    @mock.patch('pulp.plugins.loader.api.get_unit_model_by_id',
                return_value=Module)
    @mock.patch('pulp.server.controllers.repository.associate_single_unit')
    @mock.patch('pulp.server.controllers.units.find_units')
    @mock.patch('os.path.isfile')
    def test__resolve_new_units_all_new(self, mock_is_file, mock_find_units,
                                        mock_associate, mock_get_model):
        """
        Test that units which are not in a repo and not downloaded are asked to be downloaded.
        """
        existing = []
        wanted = [unit.unit_key_as_named_tuple for unit in self.sample_units]
        mock_find_units.return_value = self.sample_units

        units_to_download = self.method._resolve_new_units(existing, wanted)

        self.assertFalse(mock_is_file.called)
        self.assertFalse(mock_associate.called)

        # check that all units will be asked to be downloaded
        self.assertEqual(sorted(wanted), sorted(units_to_download))

    @mock.patch('pulp.plugins.loader.api.get_unit_model_by_id',
                return_value=Module)
    @mock.patch('pulp.server.controllers.repository.associate_single_unit')
    @mock.patch('pulp.server.controllers.units.find_units')
    @mock.patch('os.path.isfile')
    def test__resolve_new_units_no_new(self, mock_is_file, mock_find_units,
                                       mock_associate, mock_get_model):
        """
        Test that units which are in a repo and downloaded are not asked to be downloaded.
        """
        units_with_path = copy.copy(self.sample_units)
        for unit in units_with_path:
            unit._storage_path = 'something'

        existing = [unit.unit_key_as_named_tuple for unit in units_with_path]
        wanted = existing
        mock_find_units.return_value = self.sample_units
        mock_is_file.return_value = True

        units_to_download = self.method._resolve_new_units(existing, wanted)

        self.assertEqual(mock_is_file.call_count, 3)

        # all units are already in repo
        self.assertEqual(mock_associate.call_count, 0)

        # check that no units will be asked to be downloaded
        self.assertEqual([], units_to_download)

    @mock.patch('pulp.plugins.loader.api.get_unit_model_by_id',
                return_value=Module)
    @mock.patch('pulp.server.controllers.repository.associate_single_unit')
    @mock.patch('pulp.server.controllers.units.find_units')
    @mock.patch('os.path.isfile')
    def test__resolve_new_units_downloaded(self, mock_is_file, mock_find_units,
                                           mock_associate, mock_get_model):
        """
        Test that units which are not in a repo but downloaded are not asked to be downloaded.
        """
        units_with_path = copy.copy(self.sample_units)
        for unit in units_with_path:
            unit._storage_path = 'something'

        existing = []
        wanted = [unit.unit_key_as_named_tuple for unit in units_with_path]
        mock_find_units.return_value = self.sample_units
        mock_is_file.return_value = True

        units_to_download = self.method._resolve_new_units(existing, wanted)

        self.assertEqual(mock_is_file.call_count, 3)

        # all units are already downloaded but were not in repo
        self.assertEqual(mock_associate.call_count, 3)

        # check that no units will be asked to be downloaded
        self.assertEqual([], units_to_download)
예제 #14
0
class PuppetModuleImporter(Importer):

    def __init__(self):
        super(PuppetModuleImporter, self).__init__()
        self.sync_method = None
        self.sync_cancelled = False

    @classmethod
    def metadata(cls):
        return {
            'id': constants.IMPORTER_TYPE_ID,
            'display_name': _('Puppet Importer'),
            'types': [constants.TYPE_PUPPET_MODULE]
        }

    def validate_config(self, repo, config):
        return configuration.validate(config)

    def sync_repo(self, repo, sync_conduit, config):
        self.sync_cancelled = False

        # Supports two methods of synchronization.
        # 1. Synchronize with a directory containing a pulp manifest and puppet modules.
        # 2. Synchronize with Puppet Forge.
        # When the feed URL references a PULP_MANIFEST, the directory synchronization
        # method is used.  Otherwise, the puppet forge synchronization method is used.

        # synchronize with a directory
        self.sync_method = SynchronizeWithDirectory(repo, sync_conduit, config)
        report = self.sync_method()

        # When fetching the PULP_MANIFEST is not successful, it's assumed that the
        # feed points to a puppet forge instance and the synchronization is retried
        # using puppet forge method.

        if report.metadata_state == constants.STATE_FAILED:
            self.sync_method = SynchronizeWithPuppetForge(repo, sync_conduit, config)
            report = self.sync_method()

        self.sync_method = None
        return report.build_final_report()

    def import_units(self, source_repo, dest_repo, import_conduit, config, units=None):
        return copier.copy_units(import_conduit, units)

    def upload_unit(self, repo, type_id, unit_key, metadata, file_path, conduit, config):
        report = upload.handle_uploaded_unit(repo, type_id, unit_key, metadata, file_path,
                                             conduit)
        return report

    def cancel_sync_repo(self):
        """
        Cancel a running repository synchronization operation.
        """
        self.sync_cancelled = True
        if self.sync_method is None:
            return
        self.sync_method.cancel()

    def is_sync_cancelled(self):
        """
        Hook into the plugin to check if a cancel request has been issued for a sync.

        :return: True if the sync should stop running; False otherwise
        :rtype: bool
        """
        return self.sync_cancelled
예제 #15
0
class TestSynchronizeWithPuppetForge(unittest.TestCase):

    def setUp(self):
        self.working_dir = tempfile.mkdtemp(prefix='puppet-sync-tests')
        self.repo = Repository('test-repo', working_dir=self.working_dir)
        self.conduit = MockConduit()
        self.config = PluginCallConfiguration({}, {
            constants.CONFIG_FEED: FEED,
        })

        self.method = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        self.sample_units = [Module(author='a1', name='n1', version='1.0'),
                             Module(author='a2', name='n2', version='2.0'),
                             Module(author='a3', name='n3', version='3.0')]

    def tearDown(self):
        shutil.rmtree(self.working_dir)

    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(MOCK_PULP_STORAGE_LOCATION)

    def test___init__(self):
        """
        Ensure the __init__() method works properly.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        self.assertEqual(swpf.repo, self.repo)
        self.assertEqual(swpf.sync_conduit, self.conduit)
        self.assertEqual(swpf.config, self.config)
        self.assertTrue(isinstance(swpf.progress_report, sync_progress.SyncProgressReport))
        self.assertEqual(swpf.progress_report.conduit, self.conduit)
        self.assertEqual(swpf.downloader, None)
        self.assertEqual(swpf._canceled, False)

    def test_cancel_downloader_none(self):
        """
        Ensure correct operation of the cancel() method when the downloader is None.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        # This should not blow up due to the downloader being None
        swpf.cancel()

        self.assertEqual(swpf._canceled, True)

    def test_cancel_downloader_set(self):
        """
        Ensure correct operation of the cancel() method when the downloader is set.
        """
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)
        swpf.downloader = mock.MagicMock()

        swpf.cancel()

        self.assertEqual(swpf._canceled, True)
        swpf.downloader.cancel.assert_called_once_with()

    def test_synchronize_metadata_error(self):
        # Setup
        self.config.repo_plugin_config[constants.CONFIG_FEED] = INVALID_FEED

        # Test
        report = self.method().build_final_report()

        # Verify
        self.assertTrue(not report.success_flag)

        pr = self.method.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_FAILED)
        self.assertEqual(pr.metadata_query_total_count, 1)
        self.assertEqual(pr.metadata_query_finished_count, 0)

        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)
        self.assertEqual(pr.modules_total_count, None)
        self.assertEqual(pr.modules_finished_count, None)

    def test_synchronize_no_feed(self):
        # Setup
        del self.config.repo_plugin_config[constants.CONFIG_FEED]

        # Test
        report = self.method()

        # Verify
        self.assertTrue(not report.success_flag)
        pr = self.method.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_FAILED)
        self.assertTrue(len(pr.metadata_error_message) > 0)
        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)

    @mock.patch('pulp_puppet.plugins.importers.forge.SynchronizeWithPuppetForge._parse_metadata')
    def test_synchronize_no_metadata(self, mock_parse):
        # Setup
        mock_parse.return_value = None

        # Test
        report = self.method().build_final_report()

        # Verify
        self.assertTrue(report is not None)
        self.assertTrue(not report.success_flag)

        pr = self.method.progress_report
        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)

    @mock.patch('pulp_puppet.plugins.importers.forge.SynchronizeWithPuppetForge._create_downloader')
    def test_parse_metadata_retrieve_exception(self, mock_create):
        # Setup
        mock_create.side_effect = Exception()

        # Test
        report = self.method().build_final_report()

        # Verify
        self.assertTrue(not report.success_flag)

        pr = self.method.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_FAILED)
        self.assertEqual(pr.metadata_query_total_count, None)
        self.assertEqual(pr.metadata_query_finished_count, None)
        self.assertTrue(pr.metadata_execution_time is not None)
        self.assertTrue(pr.metadata_error_message is not None)
        self.assertTrue(pr.metadata_exception is not None)
        self.assertTrue(pr.metadata_traceback is not None)

        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)

    @mock.patch('pulp_puppet.plugins.importers.forge.SynchronizeWithPuppetForge._create_downloader')
    def test_parse_metadata_retrieve_exception_canceled(self, mock_create):
        # Setup
        swpf = SynchronizeWithPuppetForge(self.repo, self.conduit, self.config)

        def _side_effect(*args, **kwargs):
            swpf.cancel()
            raise Exception("some download error")

        mock_create.side_effect = _side_effect

        # Test
        report = swpf().build_final_report()

        # Verify
        self.assertTrue(report.canceled_flag)

        pr = swpf.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_CANCELED)
        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)

    @mock.patch('pulp_puppet.plugins.importers.downloaders.local.LocalDownloader.retrieve_metadata')
    def test_parse_metadata_parse_exception(self, mock_retrieve):
        # Setup
        mock_retrieve.return_value = ['not parsable json']

        # Test
        report = self.method().build_final_report()

        # Test
        self.assertTrue(not report.success_flag)

        pr = self.method.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_FAILED)
        self.assertTrue(pr.metadata_execution_time is not None)
        self.assertTrue(pr.metadata_error_message is not None)
        self.assertTrue(pr.metadata_exception is not None)
        self.assertTrue(pr.metadata_traceback is not None)

        self.assertEqual(pr.modules_state, constants.STATE_NOT_STARTED)

    @mock.patch('pulp_puppet.plugins.importers.forge.SynchronizeWithPuppetForge._do_import_modules')
    @mock.patch('pulp.server.managers.repo._common.get_working_directory', return_value='/tmp/')
    def test_import_modules_exception(self, mock_get_working_dir, mock_import):
        # Setup
        mock_import.side_effect = Exception()

        # Test
        report = self.method().build_final_report()

        # Verify
        self.assertTrue(not report.success_flag)

        pr = self.method.progress_report
        self.assertEqual(pr.metadata_state, constants.STATE_SUCCESS)
        self.assertEqual(pr.metadata_query_total_count, 1)
        self.assertEqual(pr.metadata_query_finished_count, 1)
        self.assertTrue(pr.metadata_execution_time is not None)
        self.assertTrue(pr.metadata_error_message is None)
        self.assertTrue(pr.metadata_exception is None)
        self.assertTrue(pr.metadata_traceback is None)

        self.assertEqual(pr.modules_state, constants.STATE_FAILED)
        self.assertEqual(pr.modules_total_count, None)
        self.assertEqual(pr.modules_finished_count, None)
        self.assertTrue(pr.modules_execution_time is not None)
        self.assertTrue(pr.modules_error_message is not None)
        self.assertTrue(pr.modules_exception is not None)
        self.assertTrue(pr.modules_traceback is not None)

    @mock.patch('pulp.plugins.loader.api.get_unit_model_by_id', return_value=Module)
    @mock.patch('pulp.server.controllers.repository.associate_single_unit')
    @mock.patch('pulp.server.controllers.units.find_units')
    @mock.patch('os.path.isfile')
    def test__resolve_new_units_all_new(self, mock_is_file, mock_find_units, mock_associate,
                                        mock_get_model):
        """
        Test that units which are not in a repo and not downloaded are asked to be downloaded.
        """
        existing = []
        wanted = [unit.unit_key_as_named_tuple for unit in self.sample_units]
        mock_find_units.return_value = self.sample_units

        units_to_download = self.method._resolve_new_units(existing, wanted)

        self.assertFalse(mock_is_file.called)
        self.assertFalse(mock_associate.called)

        # check that all units will be asked to be downloaded
        self.assertEqual(sorted(wanted), sorted(units_to_download))

    @mock.patch('pulp.plugins.loader.api.get_unit_model_by_id', return_value=Module)
    @mock.patch('pulp.server.controllers.repository.associate_single_unit')
    @mock.patch('pulp.server.controllers.units.find_units')
    @mock.patch('os.path.isfile')
    def test__resolve_new_units_no_new(self, mock_is_file, mock_find_units, mock_associate,
                                       mock_get_model):
        """
        Test that units which are in a repo and downloaded are not asked to be downloaded.
        """
        units_with_path = copy.copy(self.sample_units)
        for unit in units_with_path:
            unit._storage_path = 'something'

        existing = [unit.unit_key_as_named_tuple for unit in units_with_path]
        wanted = existing
        mock_find_units.return_value = self.sample_units
        mock_is_file.return_value = True

        units_to_download = self.method._resolve_new_units(existing, wanted)

        self.assertEqual(mock_is_file.call_count, 3)

        # all units are already in repo
        self.assertEqual(mock_associate.call_count, 0)

        # check that no units will be asked to be downloaded
        self.assertEqual([], units_to_download)

    @mock.patch('pulp.plugins.loader.api.get_unit_model_by_id', return_value=Module)
    @mock.patch('pulp.server.controllers.repository.associate_single_unit')
    @mock.patch('pulp.server.controllers.units.find_units')
    @mock.patch('os.path.isfile')
    def test__resolve_new_units_downloaded(self, mock_is_file, mock_find_units, mock_associate,
                                           mock_get_model):
        """
        Test that units which are not in a repo but downloaded are not asked to be downloaded.
        """
        units_with_path = copy.copy(self.sample_units)
        for unit in units_with_path:
            unit._storage_path = 'something'

        existing = []
        wanted = [unit.unit_key_as_named_tuple for unit in units_with_path]
        mock_find_units.return_value = self.sample_units
        mock_is_file.return_value = True

        units_to_download = self.method._resolve_new_units(existing, wanted)

        self.assertEqual(mock_is_file.call_count, 3)

        # all units are already downloaded but were not in repo
        self.assertEqual(mock_associate.call_count, 3)

        # check that no units will be asked to be downloaded
        self.assertEqual([], units_to_download)