Пример #1
0
 def setUp(self):
     self.ret_chn = ReturnChannel(stdout_callback, {})
     command_list = load_modules()
     self.conf_path = os.path.join(get_directory_of_current_module(self), CONF_FILE_LOCATION)
     self.ret_chn = ReturnChannel(stdout_callback, {})
     self.config = Config('help', '', command_list, [], self.ret_chn, 'shell')
     self.rest_client = RESTBase('sde', 'conf_name', self.config)
     self.config.import_custom_options()
     Config.parse_config_file(self.config, self.conf_path)
Пример #2
0
 def setUp(self):
     ret_chn = ReturnChannel(stdout_callback, {})
     command_list = load_modules()
     self.config = Config('help', '', command_list, [], ret_chn, 'shell')
     self.ldap_helper = LDAPHelper(self.config)
     self.config.import_custom_options()
     self.init_config()
Пример #3
0
    def setUp(self):
        """
            Plugin setup mirrors the setup that occurs during a sync_alm call.
            Also initializes the mock responses.
        """
        command_list = load_modules()
        self.config = Config('help', '', command_list, [], self.ret_chn, 'shell')
        self.init_alm_connector()
        self.pre_parse_config()
        self.config.import_custom_options()
        self.config['alm_reference_context'] = 2
        Config.parse_config_file(self.config, self.conf_path)

        self.post_parse_config()
        self.init_response_generator()
        self.mock_sde_response.initialize(self.config)
        self.mock_alm_response.initialize(self.response_generator)
        self.connector.initialize()

        AlmConnector.init_statuses(self.connector)
Пример #4
0
class TestRestClient(unittest.TestCase):

    def setUp(self):
        self.ret_chn = ReturnChannel(stdout_callback, {})
        command_list = load_modules()
        self.conf_path = os.path.join(get_directory_of_current_module(self), CONF_FILE_LOCATION)
        self.ret_chn = ReturnChannel(stdout_callback, {})
        self.config = Config('help', '', command_list, [], self.ret_chn, 'shell')
        self.rest_client = RESTBase('sde', 'conf_name', self.config)
        self.config.import_custom_options()
        Config.parse_config_file(self.config, self.conf_path)

    def test_context_root_is_empty(self):
        self._check_context_root_matches_target('', '')

    def test_context_root_is_slash(self):
        self._check_context_root_matches_target('/', '')

    def test_context_root_is_notempty(self):
        self._check_context_root_matches_target('branch', '/branch')

    def _check_context_root_matches_target(self, context_root, target):
        self.rest_client.config['sde_context_root'] = context_root
        self.assertTrue(self.rest_client._get_context_root() == target, 'Expected an empty context root')
Пример #5
0
class LDAPHelperTests(unittest.TestCase):
    def setUp(self):
        ret_chn = ReturnChannel(stdout_callback, {})
        command_list = load_modules()
        self.config = Config('help', '', command_list, [], ret_chn, 'shell')
        self.ldap_helper = LDAPHelper(self.config)
        self.config.import_custom_options()
        self.init_config()

    def init_config(self):
        self.config['ldap_method'] = 'LDAP'
        self.config['page_size'] = 1
        self.config['bind_dn'] = 'cn=foo,dc=example,dc=com'

    def test_normalize_dc(self):
        self.assertEquals(self.ldap_helper.normalize_dc('dc=abc'), 'DC=abc')
        self.assertEquals(self.ldap_helper.normalize_dc('DC=def'), 'DC=def')

    def test_compute_base_dn(self):
        self.assertEquals(
            self.ldap_helper.compute_base_dn('cn=foo,ou=bar,dc=abc,dc=com'),
            'DC=abc,DC=com'
        )
        self.assertEquals(
            self.ldap_helper.compute_base_dn('ou=bar,dc=foo,dc=example,dc=org'),
            'DC=foo,DC=example,DC=org'
        )

    def test_initialize_basedn(self):
        self.config['base_dn'] = ''
        self.ldap_helper.initialize()
        self.assertEquals(self.ldap_helper.basedn, 'DC=example,DC=com')

        self.config['base_dn'] = 'DC=foo,DC=org'
        self.ldap_helper.initialize()
        self.assertEquals(self.ldap_helper.basedn, 'DC=foo,DC=org')

    def test_initialize_checks_ldap_method(self):
        self.config['ldap_method'] = 'foo'
        self.assertRaises(LDAPConnectorError, self.ldap_helper.initialize)

    def test_bind_dn_required(self):
        self.config['bind_dn'] = ''
        self.assertRaises(LDAPConnectorError, self.ldap_helper.initialize)

    def test_search(self):
        self.ldap_helper.initialize()
        self.ldap_helper.connection = MockLDAP(range(10))

        results = self.ldap_helper.search(None, None, None)
        self.assertEquals(results, range(10))

    def test_get_group_from_ldap_tuple(self):
        self.ldap_helper.initialize()
        self.ldap_helper.connection = MockLDAP('')
        self.ldap_helper.connect()
        group = self.ldap_helper.get_group_from_ldap_tuple(
            ('CN=mathematicians,OU=SyncGroups,DC=labs,DC=sdelements,DC=com',
             {'member': ['CN=Simeon Poisson,CN=Users,DC=labs,DC=sdelements,DC=com',
                         'CN=PGJ Dirichlet,CN=Users,DC=labs,DC=sdelements,DC=com',
                         'CN=James Bond,CN=Users,DC=labs,DC=sdelements,DC=com',
                         'CN=analyst,OU=SyncGroups,DC=labs,DC=sdelements,DC=com',
                         'CN=bind user,CN=Users,DC=labs,DC=sdelements,DC=com',
                         'CN=David Hilbert,CN=Users,DC=labs,DC=sdelements,DC=com',
                         'CN=john neumann,CN=Users,DC=labs,DC=sdelements,DC=com'],
              'name': ['mathematicians']})
        )
        self.assertEquals(
            group,
            ('mathematicians',
                ['CN=Simeon Poisson,CN=Users,DC=labs,DC=sdelements,DC=com',
                 'CN=PGJ Dirichlet,CN=Users,DC=labs,DC=sdelements,DC=com',
                 'CN=James Bond,CN=Users,DC=labs,DC=sdelements,DC=com',
                 'CN=analyst,OU=SyncGroups,DC=labs,DC=sdelements,DC=com',
                 'CN=bind user,CN=Users,DC=labs,DC=sdelements,DC=com',
                 'CN=David Hilbert,CN=Users,DC=labs,DC=sdelements,DC=com',
                 'CN=john neumann,CN=Users,DC=labs,DC=sdelements,DC=com'],
                'CN=mathematicians,OU=SyncGroups,DC=labs,DC=sdelements,DC=com')
        )

    def test_get_group_users(self):
        self.ldap_helper.initialize()
        self.ldap_helper.connection = MockLDAP([
            ('CN=john neumann,CN=Users,DC=labs,DC=sdelements,DC=com',
                {'givenName': ['john'],
                 'cn': ['john neumann'],
                 'userAccountControl': ['512'],
                 'sn': ['neumann']}),
            ('CN=David Hilbert,CN=Users,DC=labs,DC=sdelements,DC=com',
                {'mail': ['*****@*****.**'],
                 'givenName': ['David'],
                 'cn': ['David Hilbert'],
                 'userAccountControl': ['512'],
                 'sn': ['Hilbert']}),
            ('CN=bind user,CN=Users,DC=labs,DC=sdelements,DC=com',
                {'mail': ['*****@*****.**'],
                 'givenName': ['bind'],
                 'cn': ['bind user'],
                 'userAccountControl': ['66048'],
                 'sn': ['user']})
        ])
        self.ldap_helper.connect()
        group = 'CN=mathematicians,OU=SyncGroups,DC=labs,DC=sdelements,DC=com'
        members = ['CN=bind user,CN=Users,DC=labs,DC=sdelements,DC=com',
                   'CN=David Hilbert,CN=Users,DC=labs,DC=sdelements,DC=com',
                   'CN=john neumann,CN=Users,DC=labs,DC=sdelements,DC=com']
        group_users = self.ldap_helper.get_group_users(group, members)
        self.assertEquals(group_users, [
            {'givenName': ['john'],
             'cn': ['john neumann'],
             'userAccountControl': ['512'],
             'sn': ['neumann']},
            {'mail': ['*****@*****.**'],
             'givenName': ['David'],
             'cn': ['David Hilbert'],
             'userAccountControl': ['512'],
             'sn': ['Hilbert']},
            {'mail': ['*****@*****.**'],
             'givenName': ['bind'],
             'cn': ['bind user'],
             'userAccountControl': ['66048'],
             'sn': ['user']}
        ])

    def test_get_user_email(self):
        self.ldap_helper.initialize()
        users = [{
            # active AD
            'mail': ['*****@*****.**'],
            'userAccountControl': ['512'],
            'cn': ['James Bond'],
            'sn': ['Bond']
        }, {
            # disabled AD
            'mail': ['*****@*****.**'],
            'userAccountControl': ['514'],
            'cn': ['James Bond'],
            'sn': ['Bond']
        }, {
            # AD - not a user
            'cn': ['Administrators']
        }, {
            # AD - user but no email
            'userAccountControl': ['512'],
            'cn': ['James Bond'],
            'sn': ['Bond']
        }, {
            # LDAP - user
            'mail': ['*****@*****.**'],
            'cn': ['James Bond'],
            'sn': ['Bond']
        }]

        expected_responses = [
            '*****@*****.**',
            '',
            '',
            '',
            '*****@*****.**',
        ]

        for user, expected in zip(users, expected_responses):
            self.assertEqual(self.ldap_helper.get_user_email(user), expected)
Пример #6
0
class LDAPConnectorTests(unittest.TestCase):
    def setUp(self):
        ret_chn = ReturnChannel(stdout_callback, {})
        command_list = load_modules()
        self.config = Config('help', '', command_list, [], ret_chn, 'shell')
        self.connector = LDAPConnector(self.config)
        self.config.import_custom_options()
        self.init_config()

    def init_config(self):
        self.config['ldap_method'] = 'LDAP'
        self.config['page_size'] = 1
        self.config['bind_dn'] = 'cn=foo,dc=example,dc=com'
        self.config['group_mapping'] = {
            "scientists": "group1",
            "mathematicians": "group2",
            "analyst": "group3"
        }

        self.david = 'CN=David Hilbert,CN=Users,DC=example,DC=org'
        self.jon = 'CN=Jon Snow,CN=Users,DC=example,DC=org'
        self.james = 'CN=James Bond,CN=Users,DC=example,DC=org'
        self.users = [self.david, self.jon]

    def expected_directory_users(self):
        expected_users = {}
        for user, group in zip(self.users, ['mathematicians', 'scientists']):
            expected_users[DIRECTORY[user]['mail'][0]] = {
                'full_name': DIRECTORY[user]['cn'][0],
                'groups': [group],
                'last_name': DIRECTORY[user]['sn'][0],
                'email': DIRECTORY[user]['mail'][0],
                'exists': False
            }

        return expected_users

    def test_translate_ldap_group_to_sde_group(self):
        self.connector._groups = {
            'g1': [1, 2, 3],
            'g2': [4, 5, 6]
        }
        mapping = {'mathematicians': 'g1', 'scientists': 'g2'}
        ldap_groups = ['mathematicians', 'programmers']
        expected = [[1, 2, 3]]

        actual = self.connector.translate_ldap_group_to_sde_group(ldap_groups, mapping)
        self.assertEqual(actual, expected)

    def test_calculate_name(self):
        user = {
            'first_name': 'John',
            'last_name': 'Smith',
            'full_name': 'John Smith'
        }

        new_user = self.connector.calculate_name(user)
        self.assertEqual(user, new_user)

        # Missing first name and last name is inferred
        for missing_field in ['first_name', 'last_name']:
            other_user = user.copy()
            del other_user[missing_field]
            new_user = self.connector.calculate_name(other_user)
            self.assertEqual(user, new_user)

        # only full name present with space in it
        other_user = {'full_name': 'John Smith'}
        new_user = self.connector.calculate_name(other_user)
        self.assertEqual(user, new_user)

        # only full name present - no spaces
        other_user = {'full_name': 'Aouda'}
        expected_user = {
            'first_name': 'Aouda',
            'last_name': 'Aouda',
            'full_name': 'Aouda'
        }
        new_user = self.connector.calculate_name(other_user)
        self.assertEqual(new_user, expected_user)

        # either first or last name only
        for field in ['first_name', 'last_name']:
            other_user = {field: 'Aouda'}
            new_user = self.connector.calculate_name(other_user)
            self.assertEqual(new_user['first_name'], new_user['last_name'])

    def test_filter_groups(self):
        groups = {
            'g1': ['a', 'b', 'c'],
            'g2': ['d', 'e', 'f']
        }

        # no filtering
        actual = self.connector.filter_groups(groups, {})
        self.assertEqual(actual, groups)

        # only whitelisted groups should appear
        actual = self.connector.filter_groups(groups, {'groups': ['g1']})

    def test_filter_users(self):
        # only those with emails are returned
        groups = {
            'mathematicians': [DIRECTORY[self.david]],
            'scientists': [DIRECTORY[self.jon]],
            'spies': [DIRECTORY[self.james]]
        }

        self.connector.initialize()
        actual = self.connector.filter_users(groups, {})
        expected = self.expected_directory_users()
        self.assertEqual(actual, expected)

        # only whitelisted ones are returned
        user = self.jon
        email = DIRECTORY[user]['mail'][0]
        filters = {'users': [email]}
        actual = self.connector.filter_users(groups, filters)
        expected = {
            email: {
                'full_name': DIRECTORY[user]['cn'][0],
                'groups': ['scientists'],
                'last_name': DIRECTORY[user]['sn'][0],
                'email': DIRECTORY[user]['mail'][0],
                'exists': False
            }
        }
        self.assertEqual(actual, expected)

    def test_get_users_diff(self):
        dict_a = {'a': 1, 'b': 2, 'c': 3}
        dict_b = {'c': 1, 'd': 2, 'e': 3}

        actual = self.connector._get_users_diff(dict_a, dict_b)
        expected = (
            dict_a,
            dict_b,
            set(['c']),
            set(['d', 'e']),
            set(['a', 'b'])
        )

        self.assertEquals(actual, expected)
Пример #7
0
class AlmPluginTestBase(object):
    @classmethod
    def setUpClass(cls, test_dir=None, conf_file_location=CONF_FILE_LOCATION, alm_classes=[None, None, None]):
        """
            test_dir             - The directory where we will look for the test config file.
                                   Default is the directory of the calling class
            conf_file_location   - The relative path from the test_dir to the conf file.
                                   Default is the value of CONF_FILE_LOCATION
            classes              - Pass the class name of the connector, api and response generator
                                   to use the default initializer.
                                   Expects:
                                        [alm_connector, alm_api, alm_response_generator
        """
        if test_dir is None:
            test_dir = get_directory_of_current_module(cls)

        cls.connector_cls, cls.api_cls, cls.generator_cls = alm_classes
        cls.test_dir = test_dir
        cls.conf_path = os.path.join(cls.test_dir, conf_file_location)
        cls.mock_sde_response = MOCK_SDE_RESPONSE
        cls.mock_alm_response = MOCK_ALM_RESPONSE
        cls.ret_chn = ReturnChannel(stdout_callback, {})

    def init_alm_connector(self):
        if self.connector_cls is None:
            raise Error('No alm connector found')
        elif self.api_cls is None:
            raise Error('No alm api found')
        else:
            self.connector = self.connector_cls(self.config, self.api_cls(self.config))

    def init_response_generator(self, resp_generator_cls=None):
        resp_generator_cls = resp_generator_cls or self.generator_cls
        if not resp_generator_cls:
            raise Error('No response generator found')
        self.response_generator = resp_generator_cls(self.config, self.test_dir)

    def pre_parse_config(self):
        pass

    def post_parse_config(self):
        pass

    def setUp(self):
        """
            Plugin setup mirrors the setup that occurs during a sync_alm call.
            Also initializes the mock responses.
        """
        command_list = load_modules()
        self.config = Config('help', '', command_list, [], self.ret_chn, 'shell')
        self.init_alm_connector()
        self.pre_parse_config()
        self.config.import_custom_options()
        self.config['alm_reference_context'] = 2
        Config.parse_config_file(self.config, self.conf_path)

        self.post_parse_config()
        self.init_response_generator()
        self.mock_sde_response.initialize(self.config)
        self.mock_alm_response.initialize(self.response_generator)
        self.connector.initialize()

        AlmConnector.init_statuses(self.connector)

    def tearDown(self):
        self.mock_alm_response.teardown()
        self.mock_sde_response.teardown()

    def test_parsing_alm_task(self):
        # Verify that none of the abstract methods inherited from AlmTask will break.
        # This test can be extended to verify the contents of task.
        self.connector.alm_connect()
        test_task = self.mock_sde_response.generate_sde_task()
        test_task = self.connector.transform_task(test_task)
        self.connector.alm_add_task(test_task)
        test_task_result = self.connector.alm_get_task(test_task)
        task_id = test_task_result.get_task_id()
        alm_id = test_task_result.get_alm_id()
        alm_status = test_task_result.get_status()
        alm_timestamp = test_task_result.get_timestamp()
        task_id_regex = '^[^\d]+\d+$'

        self.assertMatch(task_id_regex, task_id,
                         'Task id does not match the expected pattern. pattern:%s, task_id:%s' % (task_id_regex, task_id))
        self.assertEqual(type(alm_timestamp), datetime, 'Expected a datetime object')
        self.assertEqual(test_task['status']['id'], alm_status, 'Expected %s status, got %s' % (test_task['status']['id'], alm_status))
        self.assertNotNone(alm_id, 'Expected a value for alm_id')

        return [test_task, test_task_result]

    def test_alm_connect(self):
        self.connector.alm_connect()

    def test_add_and_get_task(self):
        # The plugin may initialize variables during alm_connect() so we need
        # to call alm_connect() before proceeding
        self.connector.alm_connect()
        test_task = self.mock_sde_response.generate_sde_task()
        test_task = self.connector.transform_task(test_task)
        self.connector.alm_add_task(test_task)
        test_task_result = self.connector.alm_get_task(test_task)

        self.assertNotNone(test_task_result, 'Failed retrieve newly added task')

    def test_alm_priority_map(self):
        priority_map_tests = [
            {
                'description': 'Single mapping',
                'map': {
                    '1-10': 'Simple'
                },
                'expected_pass': True
            },
            {
                'description': 'Three mappings',
                'map': {
                    '1-3': 'Low',
                    '4-6': 'Medium',
                    '7-10': 'High',
                },
                'expected_pass': True
            },
            {
                'description': 'Four mappings, out of order',
                'map': {
                    '1-3': 'Low',
                    '7-8': 'High',
                    '4-6': 'Medium',
                    '9-10': 'Critical',
                },
                'expected_pass': True
            },
            {
                'description': 'Priority 3 duplicated',
                'map': {
                    '1-3': 'Low',
                    '3-10': 'High',
                },
                'expected_pass': False
            },
            {
                'description': 'Priority 3 duplicated',
                'map': {
                    '1-3': 'Low',
                    '3': 'Medium',
                    '4-10': 'High',
                },
                'expected_pass': False
            },
            {
                'description': 'Priority 2 in wrong order',
                'map': {
                    '2-1': 'Low',
                    '3-10': 'High',
                },
                'expected_pass': False
            },
            {
                'description': 'Missing priority 3',
                'map': {
                    '1-2': 'Low',
                    '4-10': 'High',
                },
                'expected_pass': False
            },
            {
                'description': 'Priority 12 is out of range 1-10',
                'map': {
                    '1-12': 'Low',
                    '4-10': 'High',
                },
                'expected_pass': False
            },
            {
                'description': 'Priority 0 is out of range 1-10',
                'map': {
                    '0': 'Low',
                    '4-10': 'High',
                },
                'expected_pass': False
            },
            {
                'description': 'Priority 11 is out of range 1-10',
                'map': {
                    '1-10': 'Low',
                    '11': 'Super High',
                },
                'expected_pass': False
            }
        ]
        for priority_map_test in priority_map_tests:
            self.connector.config['test_alm'] = 'project'
            self.connector.config['alm_priority_map'] = priority_map_test['map']
            try:
                self.connector.initialize()
                if not priority_map_test['expected_pass']:
                    raise AssertionError('Error not detected for %s' % priority_map_test['description'])
            except AlmException:
                if priority_map_test['expected_pass']:
                    raise AssertionError('Unexpected exception thrown for %s' % priority_map_test['description'])

    def test_alm_test_connect(self):
        self.connector.config['test_alm'] = 'project'
        self.connector.synchronize()

    def test_sde_valid_phases(self):
        self.connector.initialize()
        self.connector.config['alm_phases'] = ['development', 'requirements']
        self.connector.synchronize()

    def test_sde_invalid_phases(self):
        self.connector.initialize()
        self.connector.config['alm_phases'] = ['development', 'invalid_phase']
        exception_msg = 'Incorrect alm_phase configuration: invalid_phase is not a valid phase'
        self.assert_exception(AlmException, '', exception_msg, self.connector.synchronize)

    def test_sde_invalid_selected_tasks(self):
        self.connector.config['selected_tasks'] = ['T123', '123']

        try:
            self.connector.initialize()
            raise Exception('Expected an exception to be thrown')
        except UsageError:
            pass

    def test_alm_fixed_title(self):
        task = {
            'id': '1099-T12',
            'title': 'T12: Sample Title'
        }
        config = {
            'alm_context': 'Coffee',
            'sde_application': 'Breakfast',
            'sde_project': 'Sandwich',
            'alm_title_format': '[$application ${project} $context] $task_id ${title}'
        }
        title = '[Breakfast Sandwich Coffee] T12:'
        task_title = AlmConnector.get_alm_task_title(config, task, True)
        self.assertEqual(title, task_title, 'Incorrect fixed alm title: %s' % task_title)

    def test_alm_full_title(self):
        task = {
            'id': '1099-T12',
            'title': 'T12: Sample Title'
        }
        config = {
            'alm_context': 'Coffee',
            'sde_application': 'Breakfast',
            'sde_project': 'Sandwich',
            'alm_title_format': '[$application ${project} $context] $task_id ${title}'
        }
        title = '[Breakfast Sandwich Coffee] T12: Sample Title'
        task_title = AlmConnector.get_alm_task_title(config, task)
        self.assertEqual(title, task_title, 'Incorrect full alm title: %s' % task_title)

    def test_malformed_alm_title_format(self):
        self.connector.config['alm_title_format'] = 'BAD'
        exception_msg = 'Incorrect alm_title_format configuration'
        self.assert_exception(AlmException, '', exception_msg, self.connector.initialize)

    def test_missing_title_in_alm_title_format(self):
        self.connector.config['alm_title_format'] = '${task_id}'
        exception_msg = 'Incorrect alm_title_format configuration. Missing ${title}'
        self.assert_exception(AlmException, '', exception_msg, self.connector.initialize)

    def test_missing_task_id_in_alm_title_format(self):
        self.connector.config['alm_title_format'] = '${title}'
        exception_msg = 'Incorrect alm_title_format configuration. Missing ${task_id}'
        self.assert_exception(AlmException, '', exception_msg, self.connector.initialize)

    def test_missing_context_in_alm_title_format(self):
        self.connector.config['alm_context'] = ''
        self.connector.config['alm_title_format'] = '${context} ${task_id} ${title}'
        exception_msg = 'Missing alm_context in configuration'
        self.assert_exception(AlmException, '', exception_msg, self.connector.initialize)

    def test_sde_invalid_min_priority(self):
        self.connector.config['sde_min_priority'] = 'BAD'
        exception_msg = 'Incorrect sde_min_priority specified in configuration. Valid values are > 0 and <= 10'
        self.assert_exception(AlmException, '', exception_msg, self.connector.initialize)

    def test_sde_too_small_min_priority(self):
        self.connector.config['sde_min_priority'] = 0
        exception_msg = 'Incorrect sde_min_priority specified in configuration. Valid values are > 0 and <= 10'
        self.assert_exception(AlmException, '', exception_msg, self.connector.initialize)

    def test_sde_too_large_min_priority(self):
        self.connector.config['sde_min_priority'] = 11
        exception_msg = 'Incorrect sde_min_priority specified in configuration. Valid values are > 0 and <= 10'
        self.assert_exception(AlmException, '', exception_msg, self.connector.initialize)

    def test_tasks_filter_tags(self):
        self.connector.config['conflict_policy'] = 'sde'
        self.connector.config['sde_min_priority'] = 1
        self.connector.config['sde_tags_filter'] = ['one', 'two']
        self.connector.sde_connect()
        self.connector.alm_connect()

        # clear out default tasks
        self.mock_sde_response.clear_tasks()

        # We expect these tasks to match
        self.mock_sde_response.generate_sde_task(priority=8, tags=['one', 'two', 'three'])
        self.mock_sde_response.generate_sde_task(priority=1, tags=['two', 'one'])

        # These tasks do not match the tags filter criteria
        self.mock_sde_response.generate_sde_task(priority=7, tags=['one'])
        self.mock_sde_response.generate_sde_task(priority=7, tags=['two'])
        self.mock_sde_response.generate_sde_task(priority=6, tags=['four', 'five'])

        tasks = self.connector.sde_get_tasks()
        self.assertTrue(len(tasks) == 5, 'Expected 5 tasks')

        tasks = self.connector.filter_tasks(tasks)
        self.assertTrue(len(tasks) == 2, 'Expected 2 tasks')

        for task in tasks:
            self.assertTrue(set(self.connector.config['sde_tags_filter']).issubset(task['tags']),
                            'Task %s has unexpected priority %d' % (task['id'], task['priority']))

    def test_tasks_filter_empty_tags(self):
        self.connector.config['conflict_policy'] = 'sde'
        self.connector.config['sde_min_priority'] = 1
        self.connector.config['sde_tags_filter'] = []
        self.connector.sde_connect()
        self.connector.alm_connect()

        # clear out default tasks
        self.mock_sde_response.clear_tasks()

        # All these tasks should match
        self.mock_sde_response.generate_sde_task(priority=8, tags=['one', 'two', 'three'])
        self.mock_sde_response.generate_sde_task(priority=7, tags=['one'])
        self.mock_sde_response.generate_sde_task(priority=7, tags=['two'])
        self.mock_sde_response.generate_sde_task(priority=1, tags=['two', 'one'])
        self.mock_sde_response.generate_sde_task(priority=6, tags=['four', 'five'])

        tasks = self.connector.sde_get_tasks()
        self.assertTrue(len(tasks) == 5, 'Expected 5 tasks')

        tasks = self.connector.filter_tasks(tasks)
        self.assertTrue(len(tasks) == 5, 'Expected 5 tasks')

    def test_tasks_filter_priority(self):
        self.connector.config['conflict_policy'] = 'sde'
        self.connector.config['sde_min_priority'] = 7
        self.connector.sde_connect()
        self.connector.alm_connect()

        # clear out default tasks
        self.mock_sde_response.clear_tasks()

        # These tasks should match
        self.mock_sde_response.generate_sde_task(priority=8)
        self.mock_sde_response.generate_sde_task(priority=7)
        self.mock_sde_response.generate_sde_task(priority=7)

        # These tasks should not match
        self.mock_sde_response.generate_sde_task(priority=1)
        self.mock_sde_response.generate_sde_task(priority=6)

        all_tasks = self.connector.sde_get_tasks()
        self.assertTrue(len(all_tasks) == 5, 'Expected 5 tasks')

        tasks = self.connector.filter_tasks(all_tasks)
        self.assertTrue(len(tasks) == 3, 'Expected 3 filtered tasks')

        for task in tasks:
            self.assertTrue(task['priority'] >= self.connector.config['sde_min_priority'],
                            'Task %s has an unexpected priority %d' % (task['id'], task['priority']))

    def test_tasks_filter_verification(self):
        self.connector.config['conflict_policy'] = 'sde'
        self.connector.config['sde_verification_filter'] = ['partial', 'fail']
        self.connector.sde_connect()
        self.connector.alm_connect()

        # clear out default tasks
        self.mock_sde_response.clear_tasks()

        # These tasks should match
        self.mock_sde_response.generate_sde_task(verification_status='partial')
        self.mock_sde_response.generate_sde_task(verification_status='partial')
        self.mock_sde_response.generate_sde_task(verification_status='fail')

        # These tasks should not match
        self.mock_sde_response.generate_sde_task()
        self.mock_sde_response.generate_sde_task(verification_status='pass')

        tasks = self.connector.sde_get_tasks()
        self.assertTrue(len(tasks) == 5, 'Expected 5 tasks')

        tasks = self.connector.filter_tasks(tasks)
        self.assertTrue(len(tasks) == 3, 'Expected 3 tasks')

        for task in tasks:
            self.assertTrue(task['verification_status'] in self.config['sde_verification_filter'],
                            'Task %s has unexpected verification status %s' % (task['id'], task['verification_status']))

    def test_tasks_filter_no_verification(self):
        self.connector.config['conflict_policy'] = 'sde'
        self.connector.config['sde_verification_filter'] = ['none', 'pass']
        self.connector.sde_connect()
        self.connector.alm_connect()

        # clear out default tasks
        self.mock_sde_response.clear_tasks()

        # These tasks should match
        self.mock_sde_response.generate_sde_task(verification_status='pass')
        self.mock_sde_response.generate_sde_task()

        # These tasks should not match
        self.mock_sde_response.generate_sde_task(verification_status='fail')
        self.mock_sde_response.generate_sde_task(verification_status='partial')

        tasks = self.connector.sde_get_tasks()
        self.assertTrue(len(tasks) == 4, 'Expected 4 tasks')

        tasks = self.connector.filter_tasks(tasks)
        self.assertTrue(len(tasks) == 2, 'Expected 2 tasks')

        for task in tasks:
            self.assertTrue(task['verification_status'] in self.config['sde_verification_filter'],
                            'Task %s has unexpected verification status %s' % (task['id'], task['verification_status']))

    def test_tasks_filter_empty_verification(self):
        self.connector.config['conflict_policy'] = 'sde'
        self.connector.config['sde_verification_filter'] = []
        self.connector.sde_connect()
        self.connector.alm_connect()

        # clear out default tasks
        self.mock_sde_response.clear_tasks()

        # These tasks should match
        self.mock_sde_response.generate_sde_task(verification_status='pass')
        self.mock_sde_response.generate_sde_task(verification_status='partial')
        self.mock_sde_response.generate_sde_task(verification_status='fail')
        self.mock_sde_response.generate_sde_task()

        tasks = self.connector.sde_get_tasks()
        self.assertTrue(len(tasks) == 4, 'Expected 4 tasks')

        tasks = self.connector.filter_tasks(tasks)
        self.assertTrue(len(tasks) == 4, 'Expected 4 tasks')

        for task in tasks:
            self.assertTrue(task['verification_status'] in AlmConnector.VERIFICATION_STATUSES + [None],
                            'Task %s has unexpected verification status %s' % (task['id'], task['verification_status']))

    def test_tasks_filter_phase(self):
        self.connector.config['conflict_policy'] = 'sde'
        self.connector.config['sde_min_priority'] = 7
        self.connector.config['alm_phases'] = ['requirements', 'testing']
        self.connector.sde_connect()
        self.connector.alm_connect()

        # clear out default tasks
        self.mock_sde_response.clear_tasks()

        # These tasks should match
        self.mock_sde_response.generate_sde_task(priority=7, phase='requirements')
        self.mock_sde_response.generate_sde_task(priority=7, phase='testing')

        # This task should not match
        self.mock_sde_response.generate_sde_task(priority=7, phase='development')

        tasks = self.connector.sde_get_tasks()
        self.assertTrue(len(tasks) == 3, 'Expected 3 tasks')

        tasks = self.connector.filter_tasks(tasks)
        self.assertTrue(len(tasks) == 2, 'Expected 2 tasks')

        for task in tasks:
            self.assertTrue(task['phase']['slug'] in self.config['alm_phases'],
                            'Task %s has unexpected phase %s' % (task['id'], task['phase']['slug']))

    def test_update_existing_task_sde(self):
        # The plugin may initialize variables during alm_connect() so we need
        # to call alm_connect() before proceeding
        self.connector.config['conflict_policy'] = 'sde'
        self.connector.config['alm_phases'] = ['requirements', 'testing', 'development']
        self.connector.alm_connect()
        test_task = self.mock_sde_response.generate_sde_task()
        test_task = self.connector.transform_task(test_task)
        self.connector.synchronize()
        alm_task = self.connector.alm_get_task(test_task)
        self.assertNotNone(alm_task, 'Expected alm task to be generated')
        self.connector.alm_update_task_status(alm_task, STATUS.DONE)

    def test_update_task_status_to_done(self):
        self.connector.config['conflict_policy'] = 'alm'
        self.connector.config['alm_phases'] = ['requirements', 'testing', 'development']
        self.connector.alm_connect()

        test_task = self.mock_sde_response.generate_sde_task()
        test_task = self.connector.transform_task(test_task)
        self.connector.alm_add_task(test_task)
        alm_task = self.connector.alm_get_task(test_task)

        self.assertNotEqual(alm_task.get_status(), STATUS.DONE,
                            'Cannot update task status to DONE because status is already DONE')

        self.connector.alm_update_task_status(alm_task, STATUS.DONE)
        test_task_result = self.connector.alm_get_task(test_task)

        self.assertEqual(test_task_result.get_status(), STATUS.DONE, 'Failed to update task status to DONE')
        self.connector.synchronize()
        the_task = self.connector.sde_get_task(test_task['id'])
        self.assertEqual(the_task['status'], STATUS.DONE, 'Failed to update SDE task to DONE')

    def test_update_task_status_to_na(self):
        self.connector.alm_connect()
        test_task = self.mock_sde_response.generate_sde_task()
        test_task = self.connector.transform_task(test_task)
        self.connector.alm_add_task(test_task)
        alm_task = self.connector.alm_get_task(test_task)

        self.assertNotEqual(alm_task.get_status(), STATUS.NA, 'Cannot update task status to NA because status is already NA')

        self.connector.alm_update_task_status(alm_task, STATUS.NA)
        test_task_result = self.connector.alm_get_task(test_task)

        self.assertIn(test_task_result.get_status(), [STATUS.DONE, STATUS.NA], 'Failed to update task status to NA')

    def test_update_task_status_to_todo(self):
        self.connector.alm_connect()
        test_task = self.mock_sde_response.generate_sde_task()
        test_task = self.connector.transform_task(test_task)
        test_task['status']['id'] = STATUS.DONE
        self.connector.alm_add_task(test_task)
        alm_task = self.connector.alm_get_task(test_task)

        self.assertNotEqual(alm_task.get_status(), STATUS.TODO,
                            'Cannot update task status to TODO because status is already TODO')

        self.connector.alm_update_task_status(alm_task, STATUS.TODO)
        test_task_result = self.connector.alm_get_task(test_task)

        self.assertEqual(test_task_result.get_status(), STATUS.TODO, 'Failed to update task status to TODO')

    def test_remove_alm_task(self):
        if not self.connector.alm_supports_delete():
            return

        self.connector.alm_connect()
        test_task = self.mock_sde_response.generate_sde_task()
        test_task = self.connector.transform_task(test_task)
        self.connector.alm_add_task(test_task)
        test_task_result = self.connector.alm_get_task(test_task)
        self.assertNotNone(test_task_result, 'Task added to ALM')
        self.connector.alm_remove_task(test_task_result)
        test_task_result = self.connector.alm_get_task(test_task)
        self.assertEqual(test_task_result, None, 'Failed to remove task from ALM')

    def test_synchronize(self):
        # Verify no exceptions are thrown
        self.connector.synchronize()

    def test_api_exceptions_are_handled(self):
        # Check that all api exceptions are properly handled
        for api_target, mock_flag in self.response_generator.rest_api_targets.iteritems():
            self.tearDown()
            self.setUp()
            self.connector.config['conflict_policy'] = 'sde'
            self.connector.config['alm_phases'] = ['requirements', 'testing', 'development']
            self.mock_sde_response.get_response_generator().generator_clear_resources()
            # All response methods should throw an exception if the fail flag is encountered
            self.mock_alm_response.set_response_flags({mock_flag: 'fail'})

            try:
                # This will invoke add, get and update task
                self.connector.alm_connect()
                test_task = self.mock_sde_response.generate_sde_task()
                test_task = self.connector.transform_task(test_task)
                test_task['status']['id'] = STATUS.DONE
                self.connector.alm_add_task(test_task)
                self.connector.synchronize()
                test_task = self.mock_sde_response.generate_sde_task(phase='testing')
                requirement_task = self.mock_sde_response.generate_sde_task(phase='requirements')
                self.assertNotNone(requirement_task, 'Expected requirements task to be generated')
                self.connector.synchronize()
                raise Exception('Expected an AlmException to be thrown for the following target: %s' % api_target)
            except AlmException:
                pass

    def test_alm_timestamp_non_naive(self):
        self.connector.alm_connect()
        test_task = self.mock_sde_response.generate_sde_task()
        test_task = self.connector.transform_task(test_task)
        self.connector.alm_add_task(test_task)
        alm_task = self.connector.alm_get_task(test_task)

        assert(alm_task.get_timestamp().tzinfo is not None)

    @staticmethod
    def assertIn(value, expectations, msg=None):
        if value not in expectations:
            if not msg:
                msg = 'Value %s is not one of the following expected values: %s' % (value, expectations)

            raise AssertionError(msg)

    @staticmethod
    def assertNotNone(obj, msg=None):
        if obj is None:
            if not msg:
                msg = 'Expected a value other than None'

            raise AssertionError(msg)

    @staticmethod
    def assertMatch(regex, str, msg):
        pattern = re.compile(regex)
        result = pattern.match(str)

        if not result:
            if not msg:
                msg = 'String %s did not match the following regular expression: %s' % (str, regex)

            raise AssertionError(msg)

    def assert_exception(self, exception, error_code, reason, fn, *args):
        if not exception:
            raise AssertionError('No exception type specified')
        if not fn:
            raise AssertionError('No function specified')
        if not exception and not error_code:
            raise AssertionError('No error code or error message to assert against')

        try:
            fn.__call__(*args)
            raise AssertionError('Expected an exception to be thrown')
        except Error as err:
            self.assertEqual(type(err), exception, 'Expected exception type %s, Got %s' % (exception, type(err)))

            if error_code:
                self.assertEqual(err.code, error_code, 'Expected error code %s, Got %s' % (error_code, err.code))
            if reason:
                self.assertTrue(reason in err.value, "Expected error message '%s', Got '%s'" % (reason, err.value))
 def setUp(self):
     command_list = load_modules()
     self.config = Config('help', '', command_list, [], self.ret_chn, 'shell')
     self.init_alm_connector()
     self.config.import_custom_options()
     Config.parse_config_file(self.config, self.conf_path)
class AlmPluginLiveTestBase(object):
    @classmethod
    def setUpClass(cls, connector, api):

        if 'sdetools' not in config:
            raise Exception("Missing configuration for sdetools config_path")

        cls.connector_cls = connector
        cls.api_cls = api
        cls.conf_path = config['sdetools']['config_path']
        cls.ret_chn = ReturnChannel(stdout_callback, {})

    def setUp(self):
        command_list = load_modules()
        self.config = Config('help', '', command_list, [], self.ret_chn, 'shell')
        self.init_alm_connector()
        self.config.import_custom_options()
        Config.parse_config_file(self.config, self.conf_path)

    def init_alm_connector(self):
        if self.connector_cls is None:
            raise Error('No alm connector found')
        elif self.api_cls is None:
            raise Error('No alm api found')
        else:
            self.connector = self.connector_cls(self.config, self.api_cls(self.config))

    def test_bad_user(self):
        self.config['alm_user'] = '******'
        self.connector.config = self.config
        self.connector.initialize()
        try:
            self.connector.alm_connect()
        except AlmException:
            pass

    def test_bad_server(self):
        self.config['alm_server'] = 'XXXXX'
        self.connector.config = self.config
        self.connector.initialize()
        try:
            self.connector.alm_connect()
        except AlmException:
            pass

    def test_sde_connect(self):
        self.config['test_alm'] = 'server'
        self.connector.config = self.config
        self.connector.initialize()
        self.connector.sde_connect()
        self.assertTrue(self.connector.is_sde_connected, True)

    def test_alm_connect_server(self):
        self.config['test_alm'] = 'server'
        self.connector.config = self.config
        self.connector.initialize()
        self.connector.alm_connect()

    def test_alm_connect_project(self):
        self.config['test_alm'] = 'project'
        self.connector.config = self.config
        self.connector.initialize()
        self.connector.alm_connect()

    def test_alm_connect_settings(self):
        self.config['test_alm'] = 'settings'
        self.connector.config = self.config
        self.connector.initialize()
        self.connector.alm_connect()

    def test_alm_task_delete(self):
        self.config['test_alm'] = ''
        self.connector.config = self.config
        self.connector.initialize()

        if not self.connector.alm_supports_delete() or not self.config['start_fresh']:
            return

        self.connector.config['start_fresh'] = True
        self.connector.sde_connect()
        self.connector.alm_connect()
        tasks = self.connector.sde_get_tasks()
        filtered_tasks = self.connector.filter_tasks(tasks)

        if not filtered_tasks:
            return

        test_task = filtered_tasks[0]
        alm_task1 = self.connector.alm_get_task(test_task)
        if not alm_task1:
            ref = self.connector.alm_add_task(test_task)
            self.assertNotNone(ref, 'Could not create ALM issue for %s' % test_task['id'])
            alm_task1 = self.connector.alm_get_task(test_task)
        self.assertNotNone(alm_task1, 'Missing Alm task for %s' % test_task['id'])

        self.connector.alm_remove_task(alm_task1)
        alm_task2 = self.connector.alm_get_task(test_task)
        if alm_task2:
            self.assertNotEqual(alm_task1.get_alm_id(), alm_task2.get_alm_id(),
                                'Could not delete task %s in alm' % test_task['id'])

    def synchronize(self, options):
        options['test_alm'] = ''

        self._update_config(options)
        self.connector.initialize()

        # Only refresh tasks if the configuration has this option explicitly set
        refresh_tasks = self.connector.config['start_fresh'] and self.connector.alm_supports_delete()

        alm_tasks = {}

        for i in xrange(2):

            # clean out any existing issues on the first run, if possible
            if i == 1 and refresh_tasks:
                self.connector.config['start_fresh'] = True
            else:
                self.connector.config['start_fresh'] = False

            # synchronize the two systems
            self.connector.synchronize()

            tasks = self.connector.sde_get_tasks()
            filtered_tasks = self.connector.filter_tasks(tasks)

            for task in filtered_tasks:
                alm_task = self.connector.alm_get_task(task)
                self.assertNotNone(alm_task, 'Missing Alm task for %s' % task['id'])

                if self.config['alm_standard_workflow']:
                    self.assertTrue(self.connector.status_match(alm_task.get_status(), task['status']['id']))
                    # invert the status and get ready to synchronize again
                    self.connector.sde_update_task_status(task, self._inverted_status(task['status']['id']))

                # Remaining tests are only applicable if connector supports delete
                if not refresh_tasks:
                    continue

                # Make sure we're creating new ALM tasks
                if i == 0:
                    alm_tasks[task['id']] = alm_task.get_alm_id()
                elif i == 1:
                    self.assertNotEqual(alm_tasks[task['id']], alm_task.get_alm_id())
                    # we don't need the alm task anymore
                    self.connector.alm_remove_task(alm_task)

    def test_synchronize_sde_as_master(self):
        self.synchronize({'conflict_policy': 'sde'})

    def test_synchronize_alm_as_master(self):
        self.synchronize({'conflict_policy': 'alm'})

    def _update_config(self, options):
        for key in options:
            self.config[key] = options[key]
        self.connector.config = self.config

    def test_synchronize_similar_tasks(self):
        self.connector.initialize()
        self.connector.sde_connect()
        self.connector.alm_connect()

        tasks = self.connector.sde_get_tasks()
        filtered_tasks = self.connector.filter_tasks(tasks)
        self.assertTrue(len(filtered_tasks) > 0)
        task = filtered_tasks[0]
        test_task = task.copy()

        task_id = test_task['id']
        title = test_task['title']

        # construct a similar task (T21 => T21222222) and add to the ALM
        task['id'] = '%s222222' % test_task['id']
        task['title'] = '%s: Sample Title' % AlmConnector._extract_task_id(task['id'])
        task = self.connector.transform_task(task)

        # re-use an issue if one already exists in the ALM
        alm_task1 = self.connector.alm_get_task(task)
        if not alm_task1:
            ref = self.connector.alm_add_task(task)
            self.assertNotNone(ref, 'Could not add issue to ALM for %s' % task['id'])
            alm_task1 = self.connector.alm_get_task(task)
        self.assertNotNone(alm_task1, 'Missing ALM task for %s' % task['id'])

        # Try to sync a similar task
        test_task['id'] = task_id
        test_task['title'] = title
        test_task = self.connector.transform_task(test_task)
        alm_task2 = self.connector.alm_get_task(test_task)
        if not alm_task2:
            ref = self.connector.alm_add_task(test_task)
            self.assertNotNone(ref, 'Could not add issue to ALM for %s' % test_task['id'])
            alm_task2 = self.connector.alm_get_task(test_task)
        self.assertNotNone(alm_task2, 'Missing ALM task for %s' % test_task['id'])

        self.assertNotEqual(alm_task1.get_alm_id(), alm_task2.get_alm_id())

        # clean-up issues in the ALM
        if not self.connector.alm_supports_delete() or not self.connector.config['start_fresh']:
            return

        if alm_task1:
            self.connector.alm_remove_task(alm_task1)

        if alm_task2:
            self.connector.alm_remove_task(alm_task2)

    def test_custom_titles(self):

        scenario_options = {
            'alm_standard_workflow': False,
            'alm_context': 'Context',
        }

        scenario1_alm_title_format = '[$application-$project] $title'
        scenario2_alm_title_format = '[$context] $title'

        self._update_config(scenario_options)
        self.connector.initialize()
        self.connector.sde_connect()
        self.connector.alm_connect()

        tasks = self.connector.sde_get_tasks()
        filtered_tasks = self.connector.filter_tasks(tasks)

        for task in filtered_tasks:
            # Find the corresponding scenario1 alm task

            # setup the alm_title_format and initialize everything
            self.connector.config['alm_title_format'] = scenario1_alm_title_format

            scenario1_task = self.connector.transform_task(task.copy())
            scenario1_alm_task = self.connector.alm_get_task(scenario1_task)
            if not scenario1_alm_task:
                ref = self.connector.alm_add_task(scenario1_task)
                self.assertNotNone(ref, 'Could not add issue to ALM for %s' % scenario1_task['id'])
                scenario1_alm_task = self.connector.alm_get_task(scenario1_task)
            self.assertNotNone(scenario1_alm_task, 'Could not retrieve ALM issue with title: %s' %
                               scenario1_task['alm_full_title'])

            # Find the corresponding scenario2 alm task

            # setup the alm_title_format and initialize everything
            self.connector.config['alm_title_format'] = scenario2_alm_title_format

            scenario2_task = self.connector.transform_task(task.copy())
            scenario2_alm_task = self.connector.alm_get_task(scenario2_task)
            if not scenario2_alm_task:
                ref = self.connector.alm_add_task(scenario2_task)
                self.assertNotNone(ref, 'Could not add issue to ALM for %s' % scenario2_task['id'])
                scenario2_alm_task = self.connector.alm_get_task(scenario2_task)
            self.assertNotNone(scenario2_alm_task, 'Could not retrieve ALM issue with title: %s' %
                               scenario2_task['alm_full_title'])

            # Check that these alm tasks are distinct for the same sde task
            self.assertNotEqual(scenario1_alm_task.get_alm_id(), scenario2_alm_task.get_alm_id())

            # Update the first alm task to the opposite of the second alm task's status
            scenario2_status = scenario2_alm_task.get_status()
            self.connector.alm_update_task_status(scenario1_alm_task, self._inverted_status(scenario2_status))

            scenario1_alm_task = self.connector.alm_get_task(scenario1_task)
            scenario2_alm_task = self.connector.alm_get_task(scenario2_task)

            # Make sure the first alm task status updated and the second one remained the same
            self.assertTrue(self.connector.status_match(scenario1_alm_task.get_status(),
                                                        self._inverted_status(scenario2_status)))
            self.assertTrue(self.connector.status_match(scenario2_alm_task.get_status(), scenario2_status))

            # Update the second alm task to the opposite of the first alm task's status
            scenario1_status = scenario1_alm_task.get_status()
            self.connector.alm_update_task_status(scenario2_alm_task, self._inverted_status(scenario1_status))
            scenario2_alm_task = self.connector.alm_get_task(scenario2_task)
            scenario1_alm_task = self.connector.alm_get_task(scenario1_task)

            # Make sure the second alm task status updated and the first one remained the same
            self.assertTrue(self.connector.status_match(scenario2_alm_task.get_status(),
                                                        self._inverted_status(scenario1_status)))
            self.assertTrue(self.connector.status_match(scenario1_alm_task.get_status(), scenario1_status))

        # clean-up issues in the ALM
        if not self.connector.alm_supports_delete():
            return

        if scenario1_alm_task:
            self.connector.alm_remove_task(scenario1_alm_task)

        if scenario2_alm_task:
            self.connector.alm_remove_task(scenario2_alm_task)

    def _inverted_status(self, status):
        return STATUS.DONE if status == STATUS.TODO else STATUS.TODO

    @staticmethod
    def assertNotNone(obj, msg=None):
        if obj is None:
            if not msg:
                msg = 'Expected a value other than None'

            raise AssertionError(msg)