Ejemplo n.º 1
0
class TestInstallCommands(TestCase):
    def setUp(self):
        from tethys_apps.models import TethysApp
        self.src_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
        self.root_app_path = os.path.join(self.src_dir, 'apps', 'tethysapp-test_app')
        self.app_model = TethysApp(
            name='test_app',
            package='test_app'
        )
        self.app_model.save()

    def tearDown(self):
        self.app_model.delete()

    @mock.patch('tethys_cli.cli_colors.pretty_output')
    @mock.patch('builtins.input', side_effect=['x', 'n'])
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.install_commands.call')
    def test_install_file_not_generate(self, mock_call, mock_exit, _, __):
        args = mock.MagicMock(file=None, quiet=False, only_dependencies=False, without_dependencies=False)

        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit, install_commands.install_command, args)
        self.assertEqual(3, len(mock_call.call_args_list))

        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.cli_colors.pretty_output')
    @mock.patch('builtins.input', side_effect=['y'])
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.exit')
    def test_install_file_generate(self, mock_exit, mock_call, _, __):
        args = mock.MagicMock(file=None, quiet=False, only_dependencies=False, without_dependencies=False)
        check_call = ['tethys', 'gen', 'install']

        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit, install_commands.install_command, args)
        mock_call.assert_called_with(check_call)
        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_no_conda_input_file(self, mock_pretty_output, mock_exit, _, __):
        file_path = os.path.join(self.root_app_path, 'install-no-dep.yml')
        args = mock.MagicMock(file=file_path, verbose=False, only_dependencies=False, without_dependencies=False)
        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit, install_commands.install_command, args)

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn("Running application install....", po_call_args[0][0][0])
        self.assertIn("Quiet mode: No additional service setting validation will be performed.", po_call_args[1][0][0])
        self.assertIn("Services Configuration Completed.", po_call_args[2][0][0])
        self.assertIn("Skipping syncstores.", po_call_args[3][0][0])
        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_input_file_with_post(self, mock_pretty_output, mock_exit, _, __):
        file_path = os.path.join(self.root_app_path, 'install-with-post.yml')
        args = mock.MagicMock(file=file_path, verbose=False, only_dependencies=False, without_dependencies=False)
        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit, install_commands.install_command, args)

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn("Running application install....", po_call_args[1][0][0])
        self.assertIn("Quiet mode: No additional service setting validation will be performed.", po_call_args[2][0][0])
        self.assertIn("Services Configuration Completed.", po_call_args[3][0][0])
        self.assertIn("Skipping syncstores.", po_call_args[4][0][0])
        self.assertIn("Running post installation tasks...", po_call_args[5][0][0])
        self.assertIn("Post Script Result: b'test\\n'", po_call_args[6][0][0])
        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.run_sync_stores')
    @mock.patch('tethys_cli.install_commands.run_interactive_services')
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.run_portal_install', return_value=False)
    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_skip_input_file(self, mock_pretty_output, mock_exit, _, __, ___, ____, _____, ______):
        file_path = os.path.join(self.root_app_path, 'install-skip-setup.yml')
        mock_exit.side_effect = SystemExit

        args = mock.MagicMock(file=file_path, verbose=False, only_dependencies=False, without_dependencies=False)
        self.assertRaises(SystemExit, install_commands.install_command, args)

        args = mock.MagicMock(file=file_path, develop=False, only_dependencies=False, without_dependencies=False)
        self.assertRaises(SystemExit, install_commands.install_command, args)

        args = mock.MagicMock(file=file_path, verbose=False, develop=False, force_services=False, quiet=False,
                              no_sync=False, only_dependencies=False, without_dependencies=False)
        self.assertRaises(SystemExit, install_commands.install_command, args)

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertEqual("Skipping package installation, Skip option found.", po_call_args[0][0][0])
        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.conda_run', return_value=['', '', 1])
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_conda_and_pip_package_install(self, mock_pretty_output, mock_exit, mock_conda_run, mock_call, _):
        file_path = os.path.join(self.root_app_path, 'install-dep.yml')
        args = mock.MagicMock(file=file_path, develop=False, verbose=False, services_file=None, update_installed=False,
                              only_dependencies=False, without_dependencies=False)
        mock_exit.side_effect = SystemExit
        self.assertRaises(SystemExit, install_commands.install_command, args)

        mock_conda_run.assert_called_with(Commands.INSTALL, '-c', 'tacaswell', '--freeze-installed', 'geojson',
                                          use_exception_handler=False, stdout=None, stderr=None)

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertEqual("Running conda installation tasks...", po_call_args[0][0][0])
        self.assertIn("Warning: Packages installation ran into an error.", po_call_args[1][0][0])
        self.assertEqual("Running pip installation tasks...", po_call_args[2][0][0])
        self.assertEqual("Running application install....", po_call_args[3][0][0])
        self.assertEqual("Quiet mode: No additional service setting validation will be performed.",
                         po_call_args[4][0][0])
        self.assertEqual("Services Configuration Completed.", po_call_args[5][0][0])

        self.assertEqual(['pip', 'install', 'see'], mock_call.mock_calls[0][1][0])
        self.assertEqual(['python', 'setup.py', 'clean', '--all'], mock_call.mock_calls[1][1][0])
        self.assertEqual(['python', 'setup.py', 'install'], mock_call.mock_calls[2][1][0])
        self.assertEqual(['tethys', 'db', 'sync'], mock_call.mock_calls[3][1][0])

        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.conda_run', return_value=['', '', 1])
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_without_dependencies(self, mock_pretty_output, mock_exit, mock_conda_run, mock_call, _):
        file_path = os.path.join(self.root_app_path, 'install-dep.yml')
        args = mock.MagicMock(file=file_path, develop=False, verbose=False, services_file=None, update_installed=False,
                              only_dependencies=False, without_dependencies=True)
        mock_exit.side_effect = SystemExit
        self.assertRaises(SystemExit, install_commands.install_command, args)

        # Ensure conda command wasn't called to install dependencies
        mock_conda_run.assert_not_called()

        # Make sure 'pip install' isn't in any of the calls
        self.assertFalse(any(['pip install' in ' '.join(mc[1][0]) for mc in mock_call.mock_calls]))

        # Validate output displayed to the user
        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertEqual("Skipping package installation.", po_call_args[0][0][0])
        self.assertEqual("Running application install....", po_call_args[1][0][0])
        self.assertEqual("Quiet mode: No additional service setting validation will be performed.",
                         po_call_args[2][0][0])
        self.assertEqual("Services Configuration Completed.", po_call_args[3][0][0])

        # Verify that the application install still happens
        self.assertEqual(['python', 'setup.py', 'clean', '--all'], mock_call.mock_calls[0][1][0])
        self.assertEqual(['python', 'setup.py', 'install'], mock_call.mock_calls[1][1][0])
        self.assertEqual(['tethys', 'db', 'sync'], mock_call.mock_calls[2][1][0])

        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.conda_run', return_value=['', '', 1])
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_conda_and_pip_package_install_only_dependencies(self, mock_pretty_output, mock_exit, mock_conda_run,
                                                             mock_call, _):
        file_path = os.path.join(self.root_app_path, 'install-dep.yml')
        args = mock.MagicMock(file=file_path, develop=False, verbose=False, services_file=None, update_installed=False,
                              only_dependencies=True, without_dependencies=False)
        mock_exit.side_effect = SystemExit
        self.assertRaises(SystemExit, install_commands.install_command, args)

        mock_conda_run.assert_called_with(Commands.INSTALL, '-c', 'tacaswell', '--freeze-installed', 'geojson',
                                          use_exception_handler=False, stdout=None, stderr=None)

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertEqual("Running conda installation tasks...", po_call_args[0][0][0])
        self.assertIn("Warning: Packages installation ran into an error.", po_call_args[1][0][0])
        self.assertEqual("Running pip installation tasks...", po_call_args[2][0][0])
        self.assertEqual("Installed dependencies only. Skipping remaining installation.", po_call_args[3][0][0])

        self.assertEqual(1, len(mock_call.mock_calls))
        self.assertEqual(['pip', 'install', 'see'], mock_call.mock_calls[0][1][0])

        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.conda_run', return_value=['', '', 1])
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_conda_and_pip_package_install_update_installed(self, mock_pretty_output, mock_exit, mock_conda_run,
                                                            mock_call, _):
        file_path = os.path.join(self.root_app_path, 'install-dep.yml')
        args = mock.MagicMock(file=file_path, develop=False, verbose=False, services_file=None, update_installed=True,
                              only_dependencies=False, without_dependencies=False)
        mock_exit.side_effect = SystemExit
        self.assertRaises(SystemExit, install_commands.install_command, args)

        mock_conda_run.assert_called_with(Commands.INSTALL, '-c', 'tacaswell', 'geojson', use_exception_handler=False,
                                          stdout=None, stderr=None)

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertEqual("Warning: Updating previously installed packages. This could break your Tethys environment.",
                         po_call_args[0][0][0])
        self.assertEqual("Running conda installation tasks...", po_call_args[1][0][0])
        self.assertIn("Warning: Packages installation ran into an error.", po_call_args[2][0][0])
        self.assertEqual("Running pip installation tasks...", po_call_args[3][0][0])
        self.assertEqual("Running application install....", po_call_args[4][0][0])
        self.assertEqual("Quiet mode: No additional service setting validation will be performed.",
                         po_call_args[5][0][0])
        self.assertEqual("Services Configuration Completed.", po_call_args[6][0][0])

        self.assertEqual(['pip', 'install', 'see'], mock_call.mock_calls[0][1][0])
        self.assertEqual(['python', 'setup.py', 'clean', '--all'], mock_call.mock_calls[1][1][0])
        self.assertEqual(['python', 'setup.py', 'install'], mock_call.mock_calls[2][1][0])
        self.assertEqual(['tethys', 'db', 'sync'], mock_call.mock_calls[3][1][0])

        mock_exit.assert_called_with(0)

    @mock.patch('builtins.input', side_effect=['x', 5])
    @mock.patch('tethys_cli.install_commands.get_app_settings')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_interactive_custom_setting_set(self, mock_pretty_output, mock_gas, _):
        mock_cs = mock.MagicMock()
        mock_cs.name = 'mock_cs'
        mock_cs.save.side_effect = [ValidationError('error'), mock.DEFAULT]
        mock_gas.return_value = {'unlinked_settings': [mock_cs]}

        install_commands.run_interactive_services('foo')

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn("Configuring mock_cs", po_call_args[2][0][0])
        self.assertIn("Type", po_call_args[3][0][0])
        self.assertIn("Enter the desired value", po_call_args[4][0][0])
        self.assertIn("Incorrect value type", po_call_args[5][0][0])
        self.assertIn("Enter the desired value", po_call_args[6][0][0])
        self.assertEqual(mock_cs.name + " successfully set with value: 5.", po_call_args[7][0][0])

    @mock.patch('builtins.input', side_effect=[''])
    @mock.patch('tethys_cli.install_commands.get_app_settings')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_interactive_custom_setting_skip(self, mock_pretty_output, mock_gas, _):
        mock_cs = mock.MagicMock()
        mock_cs.name = 'mock_cs'
        mock_gas.return_value = {'unlinked_settings': [mock_cs]}

        install_commands.run_interactive_services('foo')

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn("Configuring mock_cs", po_call_args[2][0][0])
        self.assertIn("Type", po_call_args[3][0][0])
        self.assertIn("Enter the desired value", po_call_args[4][0][0])
        self.assertEqual(f"Skipping setup of {mock_cs.name}", po_call_args[5][0][0])

    @mock.patch('builtins.input', side_effect=KeyboardInterrupt)
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.install_commands.get_app_settings')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_interactive_custom_setting_interrupt(self, mock_pretty_output, mock_gas, mock_exit, _):
        mock_cs = mock.MagicMock()
        mock_cs.name = 'mock_cs'
        mock_gas.return_value = {'unlinked_settings': [mock_cs]}

        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit, install_commands.run_interactive_services, 'foo')

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn("Configuring mock_cs", po_call_args[2][0][0])
        self.assertIn("Type", po_call_args[3][0][0])
        self.assertIn("Enter the desired value", po_call_args[4][0][0])
        self.assertEqual("\nInstall Command cancelled.", po_call_args[5][0][0])

        mock_exit.assert_called_with(0)

    @mock.patch('builtins.input', side_effect=['1', '1', '', '1', '1', KeyboardInterrupt])
    @mock.patch('tethys_cli.install_commands.validate_service_id', side_effect=[False, True])
    @mock.patch('tethys_cli.install_commands.get_setting_type', return_value='persistent')
    @mock.patch('tethys_cli.install_commands.get_setting_type_from_setting',
                side_effect=['ps_database', 'ps_database', 'ps_database', RuntimeError('setting_type_not_found')])
    @mock.patch('tethys_cli.install_commands.get_service_type_from_setting',
                side_effect=['persistent', 'persistent', RuntimeError('service_type_not_found'), 'persistent'])
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.install_commands.services_list_command')
    @mock.patch('tethys_cli.install_commands.get_app_settings')
    @mock.patch('tethys_cli.install_commands.link_service_to_app_setting')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_interactive_service_setting_all(self, mock_pretty_output, mock_lstas, mock_gas, mock_slc,
                                             mock_exit, _, __, ___, ____, _____):
        mock_ss = mock.MagicMock()
        del mock_ss.value
        mock_ss.name = 'mock_ss'
        mock_ss.description = 'This is a fake setting for testing.'
        mock_ss.required = True
        mock_ss.save.side_effect = [ValidationError('error'), mock.DEFAULT]
        mock_gas.return_value = {'unlinked_settings': [mock_ss, mock_ss, mock_ss, mock_ss, mock_ss, mock_ss]}

        mock_s = mock.MagicMock()
        mock_slc.side_effect = [[[]], [[mock_s]], [[mock_s]], [[mock_s]], [[mock_s]], [[mock_s]]]

        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit, install_commands.run_interactive_services, 'foo')

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertEqual('Running Interactive Service Mode. Any configuration options in services.yml or '
                         'portal_config.yml will be ignored...', po_call_args[0][0][0])
        self.assertIn("Hit return at any time to skip a step.", po_call_args[1][0][0])
        self.assertIn("Configuring mock_ss", po_call_args[2][0][0])
        self.assertIn("Type: MagicMock", po_call_args[3][0][0])
        self.assertIn(f"Description: {mock_ss.description}", po_call_args[3][0][0])
        self.assertIn(f"Required: {mock_ss.required}", po_call_args[3][0][0])
        self.assertIn("No compatible services found.", po_call_args[4][0][0])
        self.assertIn("tethys services create persistent -h", po_call_args[4][0][0])
        self.assertIn("Enter the service ID/Name", po_call_args[7][0][0])
        self.assertIn("Incorrect service ID/Name. Please try again.", po_call_args[8][0][0])
        self.assertIn("Enter the service ID/Name", po_call_args[9][0][0])
        self.assertIn(f"Skipping setup of {mock_ss.name}", po_call_args[13][0][0])
        self.assertEqual("service_type_not_found Skipping...", po_call_args[17][0][0])
        self.assertEqual("setting_type_not_found Skipping...", po_call_args[21][0][0])
        self.assertEqual("\nInstall Command cancelled.", po_call_args[25][0][0])

        mock_lstas.assert_called_with('persistent', '1', 'foo', 'ps_database', 'mock_ss')
        mock_exit.assert_called_with(0)

    @mock.patch('builtins.input', side_effect=['x', 5])
    @mock.patch('tethys_cli.install_commands.get_app_settings')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_interactive_no_settings(self, mock_pretty_output, mock_gas, _):
        mock_cs = mock.MagicMock()
        mock_cs.name = 'mock_cs'
        mock_cs.save.side_effect = [ValidationError('error'), mock.DEFAULT]
        mock_gas.return_value = None

        install_commands.run_interactive_services('foo')

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn('No settings found for app "foo". Skipping interactive configuration...', po_call_args[2][0][0])
Ejemplo n.º 2
0
class TestInstallCommands(TestCase):
    def setUp(self):
        from tethys_apps.models import TethysApp
        self.src_dir = os.path.dirname(
            os.path.dirname(os.path.dirname(__file__)))
        self.root_app_path = os.path.join(self.src_dir, 'apps',
                                          'tethysapp-test_app')
        self.app_model = TethysApp(name='test_app', package='test_app')
        self.app_model.save()
        pass

    def tearDown(self):
        self.app_model.delete()
        pass

    @mock.patch('tethys_cli.cli_colors.pretty_output')
    @mock.patch('builtins.input', side_effect=['x', 'n'])
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.install_commands.call')
    def test_install_file_not_generate(self, mock_call, mock_exit, _, __):
        args = mock.MagicMock(file=None, quiet=False)

        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit, install_commands.install_command, args)
        self.assertEqual(3, len(mock_call.call_args_list))

        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.cli_colors.pretty_output')
    @mock.patch('builtins.input', side_effect=['y'])
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.exit')
    def test_install_file_generate(self, mock_exit, mock_call, _, __):
        args = mock.MagicMock(file=None, quiet=False)
        check_call = ['tethys', 'gen', 'install']

        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit, install_commands.install_command, args)
        mock_call.assert_called_with(check_call)
        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_no_conda_input_file(self, mock_pretty_output, mock_exit, _, __):
        file_path = os.path.join(self.root_app_path, 'install-no-dep.yml')
        args = mock.MagicMock(file=file_path, verbose=False)
        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit, install_commands.install_command, args)

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn("Running application install....", po_call_args[0][0][0])
        self.assertIn(
            "Quiet mode: No additional service setting validation will be performed.",
            po_call_args[1][0][0])
        self.assertIn("Services Configuration Completed.",
                      po_call_args[2][0][0])
        self.assertIn("Skipping syncstores.", po_call_args[3][0][0])
        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_input_file_with_post(self, mock_pretty_output, mock_exit, _, __):
        file_path = os.path.join(self.root_app_path, 'install-with-post.yml')
        args = mock.MagicMock(file=file_path, verbose=False)
        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit, install_commands.install_command, args)

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn("Running application install....", po_call_args[1][0][0])
        self.assertIn(
            "Quiet mode: No additional service setting validation will be performed.",
            po_call_args[2][0][0])
        self.assertIn("Services Configuration Completed.",
                      po_call_args[3][0][0])
        self.assertIn("Skipping syncstores.", po_call_args[4][0][0])
        self.assertIn("Running post installation tasks...",
                      po_call_args[5][0][0])
        self.assertIn("Post Script Result: b'test\\n'", po_call_args[6][0][0])
        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.run_sync_stores')
    @mock.patch('tethys_cli.install_commands.run_interactive_services')
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.run_portal_install',
                return_value=False)
    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_skip_input_file(self, mock_pretty_output, mock_exit, _, __, ___,
                             ____, _____, ______):
        file_path = os.path.join(self.root_app_path, 'install-skip-setup.yml')
        mock_exit.side_effect = SystemExit

        args = mock.MagicMock(file=file_path, verbose=False)
        self.assertRaises(SystemExit, install_commands.install_command, args)

        args = mock.MagicMock(file=file_path, develop=False)
        self.assertRaises(SystemExit, install_commands.install_command, args)

        args = mock.MagicMock(file=file_path,
                              verbose=False,
                              develop=False,
                              force_services=False,
                              quiet=False,
                              no_sync=False)
        self.assertRaises(SystemExit, install_commands.install_command, args)

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertEqual("Skipping package installation, Skip option found.",
                         po_call_args[0][0][0])
        mock_exit.assert_called_with(0)

    @mock.patch('tethys_cli.install_commands.run_services')
    @mock.patch('tethys_cli.install_commands.call')
    @mock.patch('tethys_cli.install_commands.conda_run',
                return_value=['', '', 1])
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_conda_and_pip_package_install(self, mock_pretty_output, mock_exit,
                                           mock_conda_run, mock_call, _):
        file_path = os.path.join(self.root_app_path, 'install-dep.yml')
        args = mock.MagicMock(file=file_path,
                              develop=False,
                              verbose=False,
                              services_file=None)
        mock_exit.side_effect = SystemExit
        self.assertRaises(SystemExit, install_commands.install_command, args)

        mock_conda_run.assert_called_with(Commands.INSTALL,
                                          '-c',
                                          'tacaswell',
                                          'geojson',
                                          use_exception_handler=False,
                                          stdout=None,
                                          stderr=None)

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertEqual("Running conda installation tasks...",
                         po_call_args[0][0][0])
        self.assertIn("Warning: Packages installation ran into an error.",
                      po_call_args[1][0][0])
        self.assertEqual("Running pip installation tasks...",
                         po_call_args[2][0][0])
        self.assertEqual("Running application install....",
                         po_call_args[3][0][0])
        self.assertEqual(
            "Quiet mode: No additional service setting validation will be performed.",
            po_call_args[4][0][0])
        self.assertEqual("Services Configuration Completed.",
                         po_call_args[5][0][0])

        self.assertEqual(['pip', 'install', 'see'],
                         mock_call.mock_calls[0][1][0])
        self.assertEqual(['python', 'setup.py', 'clean', '--all'],
                         mock_call.mock_calls[1][1][0])
        self.assertEqual(['python', 'setup.py', 'install'],
                         mock_call.mock_calls[2][1][0])
        self.assertEqual(['tethys', 'db', 'sync'],
                         mock_call.mock_calls[3][1][0])

        mock_exit.assert_called_with(0)

    @mock.patch('builtins.input', side_effect=['x', 5])
    @mock.patch('tethys_cli.install_commands.get_app_settings')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_interactive_custom_setting_set(self, mock_pretty_output,
                                            mock_get_settings, _):
        mock_cs = mock.MagicMock()
        mock_cs.name = 'mock_cs'
        mock_cs.save.side_effect = [ValidationError('error'), mock.DEFAULT]
        mock_get_settings.return_value = {'unlinked_settings': [mock_cs]}

        install_commands.run_interactive_services('foo')

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn("Configuring mock_cs", po_call_args[2][0][0])
        self.assertIn("Type", po_call_args[3][0][0])
        self.assertIn("Enter the desired value", po_call_args[4][0][0])
        self.assertIn("Incorrect value type", po_call_args[5][0][0])
        self.assertIn("Enter the desired value", po_call_args[6][0][0])
        self.assertEqual(mock_cs.name + " successfully set with value: 5.",
                         po_call_args[7][0][0])

    @mock.patch('builtins.input', side_effect=[''])
    @mock.patch('tethys_cli.install_commands.get_app_settings')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_interactive_custom_setting_skip(self, mock_pretty_output,
                                             mock_get_settings, _):
        mock_cs = mock.MagicMock()
        mock_cs.name = 'mock_cs'
        mock_get_settings.return_value = {'unlinked_settings': [mock_cs]}

        install_commands.run_interactive_services('foo')

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn("Configuring mock_cs", po_call_args[2][0][0])
        self.assertIn("Type", po_call_args[3][0][0])
        self.assertIn("Enter the desired value", po_call_args[4][0][0])
        self.assertEqual(f"Skipping setup of {mock_cs.name}",
                         po_call_args[5][0][0])

    @mock.patch('builtins.input', side_effect=KeyboardInterrupt)
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.install_commands.get_app_settings')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_interactive_custom_setting_interrupt(self, mock_pretty_output,
                                                  mock_get_settings, mock_exit,
                                                  _):
        mock_cs = mock.MagicMock()
        mock_cs.name = 'mock_cs'
        mock_get_settings.return_value = {'unlinked_settings': [mock_cs]}

        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit,
                          install_commands.run_interactive_services, 'foo')

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn("Configuring mock_cs", po_call_args[2][0][0])
        self.assertIn("Type", po_call_args[3][0][0])
        self.assertIn("Enter the desired value", po_call_args[4][0][0])
        self.assertEqual("\nInstall Command cancelled.", po_call_args[5][0][0])

        mock_exit.assert_called_with(0)

    @mock.patch('builtins.input',
                side_effect=['1', '1', '', KeyboardInterrupt])
    @mock.patch('tethys_cli.install_commands.get_setting_type',
                return_value='persistent')
    @mock.patch('tethys_cli.install_commands.get_service_from_id',
                side_effect=ValueError)
    @mock.patch('tethys_cli.install_commands.get_service_from_name',
                side_effect=[False, {
                    'service_type': 'st',
                    'linkParam': 'lp'
                }])
    @mock.patch('tethys_cli.install_commands.exit')
    @mock.patch('tethys_cli.install_commands.services_list_command')
    @mock.patch('tethys_cli.install_commands.get_app_settings')
    @mock.patch('tethys_cli.install_commands.link_service_to_app_setting')
    @mock.patch('tethys_cli.cli_colors.pretty_output')
    def test_interactive_service_setting_all(self, mock_pretty_output,
                                             mock_lstas, mock_get_settings,
                                             mock_slc, mock_exit, ____, ___,
                                             __, _):
        mock_ss = mock.MagicMock()
        del mock_ss.value
        mock_ss.name = 'mock_ss'
        mock_ss.save.side_effect = [ValidationError('error'), mock.DEFAULT]
        mock_get_settings.return_value = {
            'unlinked_settings': [mock_ss, mock_ss, mock_ss, mock_ss]
        }

        mock_s = mock.MagicMock()
        mock_slc.side_effect = [[[]], [[mock_s]], [[mock_s]], [[mock_s]]]

        mock_exit.side_effect = SystemExit

        self.assertRaises(SystemExit,
                          install_commands.run_interactive_services, 'foo')

        po_call_args = mock_pretty_output().__enter__().write.call_args_list
        self.assertIn("Configuring mock_ss", po_call_args[2][0][0])
        self.assertIn("Type", po_call_args[3][0][0])
        self.assertIn("No compatible services found.", po_call_args[4][0][0])
        self.assertIn("Enter the service ID/Name", po_call_args[7][0][0])
        self.assertIn("Incorrect service ID/Name.", po_call_args[8][0][0])
        self.assertIn("Enter the service ID/Name", po_call_args[9][0][0])
        self.assertIn(f"Skipping setup of {mock_ss.name}",
                      po_call_args[13][0][0])
        self.assertEqual("\nInstall Command cancelled.",
                         po_call_args[17][0][0])

        mock_lstas.assert_called_with('st', '1', 'foo', 'lp', 'mock_ss')
        mock_exit.assert_called_with(0)

    def test_get_setting_type(self):
        from tethys_apps.models import PersistentStoreDatabaseSetting

        self.assertEqual(
            'persistent',
            install_commands.get_setting_type(
                PersistentStoreDatabaseSetting()))
Ejemplo n.º 3
0
class ResourceQuotaHandlerTest(TestCase):
    def setUp(self):
        user = User.objects.create_user(username="******",
                                        email="*****@*****.**",
                                        password="******")

        self.resource_quota_handler = WorkspaceQuotaHandler(user)
        self.resource_quota_user = ResourceQuota(
            codename='test_user_codename',
            name='test_name',
            description='test_description',
            default=1.21,
            units='gb',
            applies_to='django.contrib.auth.models.User',
            impose_default=False,
            help='Exceeded quota',
            _handler='tethys_quotas.handlers.workspace.WorkspaceQuotaHandler')
        self.resource_quota_user.save()
        self.entity_quota_user = UserQuota(
            resource_quota=self.resource_quota_user,
            entity=user,
            value=100,
        )
        self.entity_quota_user.save()

        self.resource_quota_app = ResourceQuota(
            codename='test_app_codename',
            name='test_name',
            description='test_description',
            default=1.21,
            units='gb',
            applies_to='tethys_apps.models.TethysApp',
            impose_default=True,
            help='Exceeded quota',
            _handler='tethys_quotas.handlers.workspace.WorkspaceQuotaHandler')
        self.resource_quota_app.save()

        self.app_model = TethysApp(name='Test App')
        self.app_model.save()
        self.entity_quota_app = TethysAppQuota(
            resource_quota=self.resource_quota_app,
            entity=self.app_model,
            value=200,
        )
        self.entity_quota_app.save()

    def tearDown(self):
        self.resource_quota_user.delete()
        self.entity_quota_user.delete()
        self.resource_quota_app.delete()
        self.entity_quota_app.delete()
        self.app_model.delete()

    @mock.patch('tethys_quotas.utilities.log')
    def test_rqh_check_rq_dne(self, _):
        bad_rqh = WorkspaceQuotaHandler("not.a.user")
        self.assertTrue(bad_rqh.check())

    @mock.patch('tethys_quotas.utilities.log')
    def test_rqh_check_eq_dne(self, _):
        with transaction.atomic():
            user = User.objects.create_user(username="******",
                                            email="*****@*****.**",
                                            password="******")
            resource_quota_handler = WorkspaceQuotaHandler(user)
            self.assertEquals("workspace_quota",
                              resource_quota_handler.codename)
            self.assertTrue(resource_quota_handler.check())

    @mock.patch('tethys_quotas.utilities.log')
    def test_rqh_check_eq_passes(self, _):
        self.assertTrue(self.resource_quota_handler.check())

    @mock.patch('tethys_quotas.utilities.log')
    def test_rqh_check_eq_app_passes(self, _):
        resource_quota_handler = WorkspaceQuotaHandler(self.app_model)

        self.assertTrue(resource_quota_handler.check())
Ejemplo n.º 4
0
class TestTethysAppAdmin(unittest.TestCase):
    def setUp(self):
        from tethys_apps.models import TethysApp
        self.src_dir = os.path.dirname(
            os.path.dirname(os.path.dirname(__file__)))
        self.root_app_path = os.path.join(self.src_dir, 'apps',
                                          'tethysapp-test_app')
        self.app_model = TethysApp(name='test_app', package='test_app')
        self.app_model.save()

        from django.contrib.auth.models import ContentType, Group, Permission

        app_content_type_id = ContentType.objects.get(app_label='tethys_apps',
                                                      model='tethysapp').pk
        self.perm_model = Permission(name='Test Perm | Test',
                                     content_type_id=app_content_type_id,
                                     codename='test_perm:test')
        self.perm_model.save()

        self.group_model = Group(name='test_group')
        self.group_model.save()

    def tearDown(self):
        self.app_model.delete()
        self.perm_model.delete()
        self.group_model.delete()

    def test_TethysAppSettingInline(self):
        expected_template = 'tethys_portal/admin/edit_inline/tabular.html'
        TethysAppSettingInline.model = mock.MagicMock()
        ret = TethysAppSettingInline(mock.MagicMock(), mock.MagicMock())
        self.assertEqual(expected_template, ret.template)

    def test_has_delete_permission(self):
        TethysAppSettingInline.model = mock.MagicMock()
        ret = TethysAppSettingInline(mock.MagicMock(), mock.MagicMock())
        self.assertFalse(ret.has_delete_permission(mock.MagicMock()))

    def test_has_add_permission(self):
        TethysAppSettingInline.model = mock.MagicMock()
        ret = TethysAppSettingInline(mock.MagicMock(), mock.MagicMock())
        self.assertFalse(ret.has_add_permission(mock.MagicMock()))

    def test_CustomSettingInline(self):
        expected_readonly_fields = ('name', 'description', 'type', 'required')
        expected_fields = ('name', 'description', 'type', 'value', 'required')
        expected_model = CustomSetting

        ret = CustomSettingInline(mock.MagicMock(), mock.MagicMock())

        self.assertEqual(expected_readonly_fields, ret.readonly_fields)
        self.assertEqual(expected_fields, ret.fields)
        self.assertEqual(expected_model, ret.model)

    def test_DatasetServiceSettingInline(self):
        expected_readonly_fields = ('name', 'description', 'required',
                                    'engine')
        expected_fields = ('name', 'description', 'dataset_service', 'engine',
                           'required')
        expected_model = DatasetServiceSetting

        ret = DatasetServiceSettingInline(mock.MagicMock(), mock.MagicMock())

        self.assertEqual(expected_readonly_fields, ret.readonly_fields)
        self.assertEqual(expected_fields, ret.fields)
        self.assertEqual(expected_model, ret.model)

    def test_SpatialDatasetServiceSettingInline(self):
        expected_readonly_fields = ('name', 'description', 'required',
                                    'engine')
        expected_fields = ('name', 'description', 'spatial_dataset_service',
                           'engine', 'required')
        expected_model = SpatialDatasetServiceSetting

        ret = SpatialDatasetServiceSettingInline(mock.MagicMock(),
                                                 mock.MagicMock())

        self.assertEqual(expected_readonly_fields, ret.readonly_fields)
        self.assertEqual(expected_fields, ret.fields)
        self.assertEqual(expected_model, ret.model)

    def test_WebProcessingServiceSettingInline(self):
        expected_readonly_fields = ('name', 'description', 'required')
        expected_fields = ('name', 'description', 'web_processing_service',
                           'required')
        expected_model = WebProcessingServiceSetting

        ret = WebProcessingServiceSettingInline(mock.MagicMock(),
                                                mock.MagicMock())

        self.assertEqual(expected_readonly_fields, ret.readonly_fields)
        self.assertEqual(expected_fields, ret.fields)
        self.assertEqual(expected_model, ret.model)

    def test_PersistentStoreConnectionSettingInline(self):
        expected_readonly_fields = ('name', 'description', 'required')
        expected_fields = ('name', 'description', 'persistent_store_service',
                           'required')
        expected_model = PersistentStoreConnectionSetting

        ret = PersistentStoreConnectionSettingInline(mock.MagicMock(),
                                                     mock.MagicMock())

        self.assertEqual(expected_readonly_fields, ret.readonly_fields)
        self.assertEqual(expected_fields, ret.fields)
        self.assertEqual(expected_model, ret.model)

    def test_PersistentStoreDatabaseSettingInline(self):
        expected_readonly_fields = ('name', 'description', 'required',
                                    'spatial', 'initialized')
        expected_fields = ('name', 'description', 'spatial', 'initialized',
                           'persistent_store_service', 'required')
        expected_model = PersistentStoreDatabaseSetting

        ret = PersistentStoreDatabaseSettingInline(mock.MagicMock(),
                                                   mock.MagicMock())

        self.assertEqual(expected_readonly_fields, ret.readonly_fields)
        self.assertEqual(expected_fields, ret.fields)
        self.assertEqual(expected_model, ret.model)

    # Need to check
    def test_PersistentStoreDatabaseSettingInline_get_queryset(self):
        obj = PersistentStoreDatabaseSettingInline(mock.MagicMock(),
                                                   mock.MagicMock())
        mock_request = mock.MagicMock()
        obj.get_queryset(mock_request)

    def test_TethysAppQuotasSettingInline(self):
        expected_readonly_fields = ('name', 'description', 'default', 'units')
        expected_fields = ('name', 'description', 'value', 'default', 'units')
        expected_model = TethysAppQuota

        ret = TethysAppQuotasSettingInline(mock.MagicMock(), mock.MagicMock())

        self.assertEquals(expected_readonly_fields, ret.readonly_fields)
        self.assertEquals(expected_fields, ret.fields)
        self.assertEquals(expected_model, ret.model)

    # Need to check
    # def test_TethysAppQuotasSettingInline_get_queryset(self):
    #     obj = TethysAppQuotasSettingInline(mock.MagicMock(), mock.MagicMock())
    #     mock_request = mock.MagicMock()
    #     obj.get_queryset(mock_request)

    def test_TethysAppAdmin(self):
        expected_readonly_fields = (
            'package',
            'manage_app_storage',
        )
        expected_fields = (
            'package',
            'name',
            'description',
            'icon',
            'tags',
            'enabled',
            'show_in_apps_library',
            'enable_feedback',
            'manage_app_storage',
        )
        expected_inlines = [
            CustomSettingInline, PersistentStoreConnectionSettingInline,
            PersistentStoreDatabaseSettingInline, DatasetServiceSettingInline,
            SpatialDatasetServiceSettingInline,
            WebProcessingServiceSettingInline, TethysAppQuotasSettingInline
        ]

        ret = TethysAppAdmin(mock.MagicMock(), mock.MagicMock())

        self.assertEqual(expected_readonly_fields, ret.readonly_fields)
        self.assertEqual(expected_fields, ret.fields)
        self.assertEqual(expected_inlines, ret.inlines)

    def test_TethysAppAdmin_has_delete_permission(self):
        ret = TethysAppAdmin(mock.MagicMock(), mock.MagicMock())
        self.assertFalse(ret.has_delete_permission(mock.MagicMock()))

    def test_TethysAppAdmin_has_add_permission(self):
        ret = TethysAppAdmin(mock.MagicMock(), mock.MagicMock())
        self.assertFalse(ret.has_add_permission(mock.MagicMock()))

    @mock.patch('tethys_apps.admin.get_quota')
    @mock.patch('tethys_apps.admin._convert_storage_units')
    def test_TethysAppAdmin_manage_app_storage(self, mock_convert,
                                               mock_get_quota):
        ret = TethysAppAdmin(mock.MagicMock(), mock.MagicMock())
        app = mock.MagicMock()
        app.id = 1
        mock_convert.return_value = '0 bytes'
        mock_get_quota.return_value = {'quota': None}
        url = reverse('admin:clear_workspace', kwargs={'app_id': app.id})

        expected_html = format_html("""
                <span>{} of {}</span>
                <a id="clear-workspace" class="btn btn-danger btn-sm"
                href="{url}">
                Clear Workspace</a>
                """.format('0 bytes', "&#8734;", url=url))
        actual_html = ret.manage_app_storage(app)

        self.assertEquals(expected_html.replace(" ", ""),
                          actual_html.replace(" ", ""))

        mock_convert.return_value = '0 bytes'
        mock_get_quota.return_value = {'quota': 5, 'units': 'gb'}
        url = reverse('admin:clear_workspace', kwargs={'app_id': app.id})

        expected_html = format_html("""
                        <span>{} of {}</span>
                        <a id="clear-workspace" class="btn btn-danger btn-sm"
                        href="{url}">
                        Clear Workspace</a>
                        """.format('0 bytes', "0 bytes", url=url))
        actual_html = ret.manage_app_storage(app)

        self.assertEquals(expected_html.replace(" ", ""),
                          actual_html.replace(" ", ""))

    def test_TethysExtensionAdmin(self):
        expected_readonly_fields = ('package', 'name', 'description')
        expected_fields = ('package', 'name', 'description', 'enabled')

        ret = TethysExtensionAdmin(mock.MagicMock(), mock.MagicMock())

        self.assertEqual(expected_readonly_fields, ret.readonly_fields)
        self.assertEqual(expected_fields, ret.fields)

    def test_TethysExtensionAdmin_has_delete_permission(self):
        ret = TethysExtensionAdmin(mock.MagicMock(), mock.MagicMock())
        self.assertFalse(ret.has_delete_permission(mock.MagicMock()))

    def test_TethysExtensionAdmin_has_add_permission(self):
        ret = TethysExtensionAdmin(mock.MagicMock(), mock.MagicMock())
        self.assertFalse(ret.has_add_permission(mock.MagicMock()))

    @mock.patch('django.contrib.auth.admin.UserAdmin.change_view')
    @mock.patch('django.contrib.auth.admin.UserAdmin.add_view')
    def test_admin_site_register_custom_user(self, mock_ua_add_view,
                                             mock_ua_change_view):
        from django.contrib import admin
        ret = CustomUser(mock.MagicMock(), mock.MagicMock())

        # Add custom inline when change_view is called
        ret.change_view(mock.MagicMock())
        mock_ua_change_view.assert_called()
        self.assertIn(UserQuotasSettingInline, ret.inlines)

        # Remove custom inline when change_view is called
        ret.add_view(mock.MagicMock())
        mock_ua_add_view.assert_called()
        self.assertNotIn(UserQuotasSettingInline, ret.inlines)

        # Repeat to complete full cycle (change -> add -> change -> add)
        # Add custom inline when change_view is called
        ret.change_view(mock.MagicMock())
        mock_ua_change_view.assert_called()
        self.assertIn(UserQuotasSettingInline, ret.inlines)

        # Remove custom inline when change_view is called
        ret.add_view(mock.MagicMock())
        mock_ua_add_view.assert_called()
        self.assertNotIn(UserQuotasSettingInline, ret.inlines)

        # Check registration
        registry = admin.site._registry
        self.assertIn(User, registry)
        self.assertIsInstance(registry[User], CustomUser)

    def test_admin_site_register_tethys_app_admin(self):
        from django.contrib import admin
        registry = admin.site._registry
        self.assertIn(TethysApp, registry)
        self.assertIsInstance(registry[TethysApp], TethysAppAdmin)

    def test_admin_site_register_tethys_app_extension(self):
        from django.contrib import admin
        registry = admin.site._registry
        self.assertIn(TethysExtension, registry)
        self.assertIsInstance(registry[TethysExtension], TethysExtensionAdmin)

    def test_admin_site_register_proxy_app(self):
        from django.contrib import admin
        registry = admin.site._registry
        self.assertIn(ProxyApp, registry)

    @mock.patch('tethys_apps.admin.GroupObjectPermission.objects')
    @mock.patch('tethys_apps.admin.TethysApp.objects.all')
    def test_make_gop_app_access_form(self, mock_all_apps, mock_gop):
        mock_all_apps.return_value = [self.app_model]
        mock_gop.filter().values().distinct.return_value = [{'group_id': 9999}]

        ret = make_gop_app_access_form()

        self.assertIn('test_app_permissions', ret.base_fields)
        self.assertIn('test_app_groups', ret.base_fields)

    @mock.patch('tethys_apps.admin.Group.objects')
    @mock.patch('tethys_apps.admin.Permission.objects')
    @mock.patch('tethys_apps.admin.GroupObjectPermission.objects')
    @mock.patch('tethys_apps.admin.TethysApp.objects.all')
    def test_gop_form_init(self, mock_all_apps, mock_gop, mock_perms,
                           mock_groups):
        mock_all_apps.return_value = [self.app_model]
        mock_obj = mock.MagicMock(pk=True)
        mock_gop.values().distinct().filter.return_value = [{
            'object_pk':
            self.app_model.pk
        }]
        mock_gop.values_list().filter().distinct.side_effect = [
            [9999], [9999],
            mock.MagicMock(exclude=mock.MagicMock(
                return_value=[self.app_model.pk, 9999])), [self.app_model.pk]
        ]

        mock_perms.filter().exclude.side_effect = [
            mock_perms.none(), '_permissions_test'
        ]
        mock_groups.filter().exclude.return_value = '_groups_test'

        gop_app_access_form_dynamic = make_gop_app_access_form()
        ret = gop_app_access_form_dynamic(instance=mock_obj)

        self.assertIn(self.app_model, ret.fields['apps'].initial)
        self.assertEqual(ret.fields['test_app_permissions'].initial,
                         '_permissions_test')
        self.assertEqual(ret.fields['test_app_groups'].initial, '_groups_test')

    @mock.patch('tethys_apps.admin.TethysApp.objects.all')
    def test_gop_form_clean(self, mock_all_apps):
        mock_all_apps.return_value = [self.app_model]
        mock_obj = mock.MagicMock(pk=True)
        mock_data = mock.MagicMock(getlist=mock.MagicMock(return_value=[9999]))

        gop_app_access_form_dynamic = make_gop_app_access_form()
        ret = gop_app_access_form_dynamic(instance=mock_obj)

        ret.data = mock_data
        ret.cleaned_data = {}
        ret.clean()

        self.assertIn('test_app_permissions', ret.cleaned_data)
        self.assertIn('test_app_groups', ret.cleaned_data)

    @mock.patch('tethys_apps.admin.remove_perm')
    @mock.patch('tethys_apps.admin.assign_perm')
    @mock.patch('tethys_apps.admin.TethysApp.objects.all')
    def test_gop_form_save_new(self, mock_all_apps, _, __):
        mock_all_apps.return_value = [self.app_model]
        mock_obj = mock.MagicMock(pk=False)
        mock_data = mock.MagicMock(getlist=mock.MagicMock(return_value=[9999]))

        gop_app_access_form_dynamic = make_gop_app_access_form()
        ret = gop_app_access_form_dynamic(instance=mock_obj)

        ret.data = mock_data
        ret.cleaned_data = {'apps': [self.app_model]}
        ret.fields = {'apps': ret.fields['apps']}

        ret.save()

        self.assertEqual(mock_obj.save.call_count, 1)

    @mock.patch('tethys_apps.admin.assign_perm')
    @mock.patch('tethys_apps.admin.remove_perm')
    @mock.patch('tethys_apps.admin.TethysApp.objects')
    def test_gop_form_save_edit_apps(self, mock_apps, mock_remove_perm,
                                     mock_assign_perm):
        mock_apps.all.return_value = [self.app_model]
        mock_diff = mock.MagicMock(return_value=[self.app_model])
        mock_apps.filter.return_value = mock.MagicMock(difference=mock_diff,
                                                       return_value=True)
        mock_obj = mock.MagicMock(pk=True)
        mock_data = mock.MagicMock(getlist=mock.MagicMock(return_value=[9999]))

        gop_app_access_form_dynamic = make_gop_app_access_form()
        ret = gop_app_access_form_dynamic(instance=mock_obj)

        ret.data = mock_data
        ret.cleaned_data = {'apps': [self.app_model]}
        ret.fields = {'apps': ret.fields['apps']}

        ret.save()

        mock_remove_perm.assert_called_with('test_app:access_app', mock_obj,
                                            self.app_model)
        mock_assign_perm.assert_called_with('test_app:access_app', mock_obj,
                                            self.app_model)

    @mock.patch('tethys_apps.admin.assign_perm')
    @mock.patch('tethys_apps.admin.remove_perm')
    @mock.patch('tethys_apps.admin.TethysApp.objects')
    def test_gop_form_save_edit_permissions(self, mock_apps, mock_remove_perm,
                                            mock_assign_perm):
        mock_apps.all.return_value = [self.app_model]
        mock_diff = mock.MagicMock(
            side_effect=[[self.app_model], [self.perm_model]])
        mock_apps.filter.return_value = mock.MagicMock(difference=mock_diff,
                                                       return_value=True)
        mock_obj = mock.MagicMock(pk=True)
        mock_data = mock.MagicMock(getlist=mock.MagicMock(return_value=[9999]))

        gop_app_access_form_dynamic = make_gop_app_access_form()
        ret = gop_app_access_form_dynamic(instance=mock_obj)

        ret.data = mock_data
        ret.cleaned_data = {
            'apps': [self.app_model],
            'test_app_permissions': [self.perm_model]
        }
        ret.fields = {
            'apps': ret.fields['apps'],
            'test_app_permissions': ret.fields['apps']
        }

        ret.save()

        mock_remove_perm.assert_called_with('test_perm:test', mock_obj,
                                            mock_apps.filter())
        mock_assign_perm.assert_called_with('test_perm:test', mock_obj,
                                            mock_apps.filter())

    @mock.patch('tethys_apps.admin.assign_perm')
    @mock.patch('tethys_apps.admin.remove_perm')
    @mock.patch('tethys_apps.admin.GroupObjectPermission.objects')
    @mock.patch('tethys_apps.admin.TethysApp.objects')
    def test_gop_form_save_edit_groups(self, mock_apps, mock_gop,
                                       mock_remove_perm, mock_assign_perm):
        mock_apps.all.return_value = [self.app_model]
        mock_diff = mock.MagicMock(
            side_effect=[[self.app_model],
                         mock.MagicMock(values_list=mock.MagicMock(
                             distinct=[self.group_model.pk]))])
        mock_apps.filter.return_value = mock.MagicMock(difference=mock_diff,
                                                       return_value=True)
        mock_obj = mock.MagicMock(pk=True)
        mock_data = mock.MagicMock(getlist=mock.MagicMock(return_value=[9999]))
        mock_gop.filter().values_list().distinct.return_value = [
            self.perm_model.pk
        ]

        gop_app_access_form_dynamic = make_gop_app_access_form()
        ret = gop_app_access_form_dynamic(instance=mock_obj)

        ret.data = mock_data
        ret.cleaned_data = {
            'apps': [self.app_model],
            'test_app_groups': mock.MagicMock()
        }
        ret.cleaned_data['test_app_groups'].values_list(
        ).distinct.return_value = [self.group_model.pk]
        ret.fields = {
            'apps': ret.fields['apps'],
            'test_app_groups': ret.fields['apps']
        }

        ret.save()

        mock_remove_perm.assert_called_with('test_perm:test', mock_obj,
                                            mock_apps.filter())
        mock_assign_perm.assert_called_with('test_perm:test', mock_obj,
                                            mock_apps.filter())

    @mock.patch('tethys_apps.admin.tethys_log.warning')
    @mock.patch('tethys_apps.admin.make_gop_app_access_form')
    def test_admin_programming_error(self, mock_gop_form, mock_logwarning):
        mock_gop_form.side_effect = ProgrammingError

        register_custom_group()

        mock_gop_form.assert_called()
        mock_logwarning.assert_called_with('Unable to register CustomGroup.')

    @mock.patch('tethys_apps.admin.tethys_log.warning')
    @mock.patch('tethys_apps.admin.admin.site.register')
    def test_admin_user_keys_programming_error(self, mock_register,
                                               mock_logwarning):
        mock_register.side_effect = ProgrammingError

        register_user_keys_admin()

        mock_register.assert_called()
        mock_logwarning.assert_called_with('Unable to register UserKeys.')