Imported Debian patch 4.8.10-2

This commit is contained in:
Timo Aaltonen
2020-11-23 20:48:56 +02:00
committed by Mario Fetka
parent 8bc559c5a1
commit 358acdd85f
917 changed files with 1185414 additions and 1069733 deletions

View File

@@ -2,6 +2,8 @@
# Copyright (C) 2018 FreeIPA Contributors see COPYING for license
#
from datetime import datetime, timedelta
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
@@ -9,19 +11,63 @@ from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
def generate_csr(hostname):
def generate_csr(cn, is_hostname=True):
"""
Generate certificate signing request
:param cn: common name (str|unicode)
:param is_hostname: is the common name a hostname (default: True)
"""
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
hostname = u'{}'.format(hostname)
if isinstance(cn, bytes):
cn = cn.decode()
csr = x509.CertificateSigningRequestBuilder()
csr = csr.subject_name(
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, hostname)])
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)])
)
if is_hostname:
csr = csr.add_extension(
x509.SubjectAlternativeName([x509.DNSName(cn)]),
critical=False
)
csr = csr.sign(key, hashes.SHA256(), default_backend())
return csr.public_bytes(serialization.Encoding.PEM).decode()
def generate_certificate(hostname):
"""
Generate self-signed certificate for some DNS name.
The certificate is valid for 100 days from moment of generation.
:param hostname: DNS name (str|unicode)
"""
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
if isinstance(hostname, bytes):
hostname = hostname.decode()
subject = issuer = x509.Name(
[x509.NameAttribute(NameOID.COMMON_NAME, hostname)]
)
cert = x509.CertificateBuilder()
cert = cert.subject_name(subject).issuer_name(issuer).public_key(
key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.utcnow()
).not_valid_after(
datetime.utcnow() + timedelta(days=100)
).add_extension(
x509.SubjectAlternativeName([x509.DNSName(hostname)]),
critical=False
)
csr = csr.sign(key, hashes.SHA256(), default_backend())
return csr.public_bytes(serialization.Encoding.PEM).decode()
).sign(key, hashes.SHA256(), default_backend())
return cert.public_bytes(serialization.Encoding.PEM).decode()

View File

@@ -31,7 +31,7 @@ FILLED_LOGIN_FORM = {
('username', 'Username', True, True, 'text', 'username',
PKEY, 'Username'),
('password', 'Password', True, True, 'password', 'password',
PASSWD_ITEST_USER, 'Password or Password+One-Time-Password'),
PASSWD_ITEST_USER, 'Password or Password+One-Time Password'),
],
# structure of buttons
# button_name, button_title
@@ -58,12 +58,12 @@ RESET_AND_LOGIN_FORM = {
PKEY, None),
('current_password', 'Current Password', False, True, 'password',
'current_password', '', 'Current Password'),
('otp', 'OTP', False, True, 'password', 'otp', '',
'One-Time-Password'),
('new_password', 'New Password', True, True, 'password',
'new_password', '', 'New Password'),
('verify_password', 'Verify Password', True, True, 'password',
'verify_password', '', 'New Password'),
('otp', 'OTP', False, True, 'password', 'otp', '',
'One-Time Password'),
],
# structure of buttons
# button_name, button_title
@@ -89,12 +89,12 @@ RESET_PASSWORD_FORM = {
'Username'),
('current_password', 'Current Password', True, True, 'password',
'current_password', '', 'Current Password'),
('otp', 'OTP', False, True, 'password', 'otp', '',
'One-Time-Password'),
('new_password', 'New Password', True, True, 'password',
'new_password', '', 'New Password'),
('verify_password', 'Verify Password', True, True, 'password',
'verify_password', '', 'New Password'),
('otp', 'OTP', False, True, 'password', 'otp', '',
'One-Time Password'),
],
# structure of buttons
# button_name, button_title
@@ -121,7 +121,7 @@ EMPTY_LOGIN_FORM = {
('username', 'Username', False, True, 'text', 'username', '',
'Username'),
('password', 'Password', False, True, 'password', 'password', '',
'Password or Password+One-Time-Password'),
'Password or Password+One-Time Password'),
],
# structure of buttons
# button_name, button_title
@@ -146,7 +146,7 @@ LOGIN_FORM = {
('username', 'Username', True, True, 'text', 'username', PKEY,
'Username'),
('password', 'Password', True, True, 'password', 'password', '',
'Password or Password+One-Time-Password'),
'Password or Password+One-Time Password'),
],
# structure of buttons
# button_name, button_title

View File

@@ -2,6 +2,19 @@
# Copyright (C) 2018 FreeIPA Contributors see COPYING for license
#
from ipaplatform.constants import constants as platformconstants
# for example, user_u:s0
selinuxuser1 = platformconstants.SELINUX_USERMAP_ORDER.split("$")[0]
selinuxuser2 = platformconstants.SELINUX_USERMAP_ORDER.split("$")[1]
selinux_mcs_max = platformconstants.SELINUX_MCS_MAX
selinux_mls_max = platformconstants.SELINUX_MLS_MAX
second_mls_level = 's{}'.format(list(range(0, selinux_mls_max + 1))[0])
second_mcs_level = 'c{}'.format(list(range(0, selinux_mcs_max + 1))[0])
mcs_range = '{0}.{0}'.format(second_mcs_level)
ENTITY = 'selinuxusermap'
PKEY = 'itest-selinuxusermap'
@@ -9,7 +22,7 @@ DATA = {
'pkey': PKEY,
'add': [
('textbox', 'cn', PKEY),
('textbox', 'ipaselinuxuser', 'user_u:s0'),
('textbox', 'ipaselinuxuser', selinuxuser1),
],
'mod': [
('textarea', 'description', 'itest-selinuxusermap desc'),
@@ -21,7 +34,7 @@ DATA2 = {
'pkey': PKEY2,
'add': [
('textbox', 'cn', PKEY2),
('textbox', 'ipaselinuxuser', 'unconfined_u:s0-s0:c0.c1023'),
('textbox', 'ipaselinuxuser', selinuxuser2),
],
'mod': [
('textarea', 'description', 'itest-selinuxusermap desc2'),
@@ -33,7 +46,7 @@ DATA_MLS_RANGE = {
'pkey': PKEY_MLS_RANGE,
'add': [
('textbox', 'cn', PKEY_MLS_RANGE),
('textbox', 'ipaselinuxuser', 'user_u:s0-s1'),
('textbox', 'ipaselinuxuser', 'foo:s0-{}'.format(second_mls_level)),
],
}
@@ -42,7 +55,9 @@ DATA_MCS_RANGE = {
'pkey': PKEY_MCS_RANGE,
'add': [
('textbox', 'cn', PKEY_MCS_RANGE),
('textbox', 'ipaselinuxuser', 'user_u:s0-s15:c0.c1023'),
('textbox', 'ipaselinuxuser',
'foo:s0-s{}:c0.c{}'.format(selinux_mls_max, selinux_mcs_max)
),
],
}
@@ -51,7 +66,10 @@ DATA_MCS_COMMAS = {
'pkey': PKEY_MCS_COMMAS,
'add': [
('textbox', 'cn', PKEY_MCS_COMMAS),
('textbox', 'ipaselinuxuser', 'user_u:s0-s1:c0,c2,c15.c26'),
('textbox', 'ipaselinuxuser',
'foo:s0-{}:c0,{},{}'.format(
second_mls_level, second_mcs_level, mcs_range),
),
],
}
@@ -60,7 +78,9 @@ DATA_MLS_SINGLE_VAL = {
'pkey': PKEY_MLS_SINGLE_VAL,
'add': [
('textbox', 'cn', PKEY_MLS_SINGLE_VAL),
('textbox', 'ipaselinuxuser', 'user_u:s0-s0:c0.c1023'),
('textbox', 'ipaselinuxuser',
'foo:s0-s0:c0.c{}'.format(selinux_mcs_max)
),
],
}
@@ -69,7 +89,7 @@ DATA_NON_EXIST_SEUSER = {
'pkey': PKEY_NON_EXIST_SEUSER,
'add': [
('textbox', 'cn', PKEY_NON_EXIST_SEUSER),
('textbox', 'ipaselinuxuser', 'abc:s0'),
('textbox', 'ipaselinuxuser', 'foo:s0'),
],
}
@@ -78,7 +98,7 @@ DATA_INVALID_MCS = {
'pkey': PKEY_INVALID_MCS,
'add': [
('textbox', 'cn', PKEY_INVALID_MCS),
('textbox', 'ipaselinuxuser', 'user:s0:c'),
('textbox', 'ipaselinuxuser', 'foo:s0:c'),
],
}
@@ -87,7 +107,7 @@ DATA_INVALID_MLS = {
'pkey': PKEY_INVALID_MLS,
'add': [
('textbox', 'cn', PKEY_INVALID_MLS),
('textbox', 'ipaselinuxuser', 'user'),
('textbox', 'ipaselinuxuser', 'foo'),
],
}

View File

@@ -208,6 +208,19 @@ DATA_NO_LOGIN = {
]
}
PKEY_MEMBER_MANAGER = 'member-manager'
PASSWD_MEMBER_MANAGER = 'Password123'
DATA_MEMBER_MANAGER = {
'pkey': PKEY_MEMBER_MANAGER,
'add': [
('textbox', 'uid', PKEY_MEMBER_MANAGER),
('textbox', 'givenname', 'Name'),
('textbox', 'sn', 'Surname'),
('password', 'userpassword', PASSWD_MEMBER_MANAGER),
('password', 'userpassword2', PASSWD_MEMBER_MANAGER),
],
}
SSH_RSA = (
'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBVmLXpTDhrYkABOPlADFk'
'GV8/QfgQqUQ0xn29hk18t/NTEQOW/Daq4EF84e9aTiopRXIk7jahBLzwWTZI'

View File

@@ -21,16 +21,44 @@
Range tasks
"""
import uuid
from ipatests.test_webui.ui_driver import UI_driver
LOCAL_ID_RANGE = 'ipa-local'
TRUSTED_ID_RANGE = 'ipa-ad-trust'
class range_tasks(UI_driver):
def get_shifts(self, idranges=None):
BASE_RANGE_OVERLAPS_ERROR = (
"Constraint violation: "
"New base range overlaps with existing base range."
)
if not idranges:
result = self.execute_api_from_ui('idrange_find', [], {})
idranges = result['result']['result']
PRIMARY_RID_RANGE_OVERLAPS_ERROR = (
"Constraint violation: "
"New primary rid range overlaps with existing primary rid range."
)
SECONDARY_RID_RANGE_OVERLAPS_ERROR = (
"Constraint violation: "
"New secondary rid range overlaps with existing secondary rid range."
)
PRIMARY_AND_SECONDARY_RID_OVERLAP_ERROR = (
"invalid 'ID Range setup': "
"Primary RID range and secondary RID range cannot overlap"
)
DELETE_PRIMARY_LOCAL_RANGE_ERROR = (
"invalid 'ipabaseid,ipaidrangesize': range modification "
"leaving objects with ID out of the defined range is not allowed"
)
def get_shifts(self):
result = self.execute_api_from_ui('idrange_find', [], {})
idranges = result['result']['result']
max_id = 0
max_rid = 0
@@ -67,50 +95,141 @@ class range_tasks(UI_driver):
domain = trusts[0]['cn']
return domain
def get_data(self, pkey, size=50, add_data=None):
def get_data(self, pkey=None, form_data=None, **kwargs):
if not add_data:
add_data = self.get_add_data(pkey, size=size)
if not pkey:
pkey = 'itest-range-{}'.format(uuid.uuid4().hex[:8])
if form_data:
form_data.cn = pkey
else:
form_data = self.get_add_form_data(pkey, **kwargs)
data = {
'pkey': pkey,
'add': add_data,
'add': form_data.serialize(),
'mod': [
('textbox', 'ipaidrangesize', str(size + 1)),
('textbox', 'ipaidrangesize', str(form_data.size + 1)),
],
}
return data
def get_add_data(self, pkey, range_type='ipa-local', size=50, shift=100, domain=None):
def get_add_form_data(self, pkey, range_type=LOCAL_ID_RANGE, size=50,
domain=None, **kwargs):
"""
Generate RangeAddFormData instance with initial data based on existing
ID ranges.
:param kwargs: overrides default fields values (base_id, base_rid,
secondary_base_rid)
"""
base_id = self.max_id + shift
shift = 100
base_id = kwargs.get('base_id', self.max_id + shift)
self.max_id = base_id + size
base_rid = self.max_rid + shift
self.max_rid = base_rid + size
add = [
('textbox', 'cn', pkey),
('textbox', 'ipabaseid', str(base_id)),
('textbox', 'ipaidrangesize', str(size)),
('textbox', 'ipabaserid', str(base_rid)),
('radio', 'iparangetype', range_type),
('callback', self.check_range_type_mod, range_type)
]
if not domain:
if 'base_rid' in kwargs:
base_rid = kwargs['base_rid']
else:
base_rid = self.max_rid + shift
self.max_rid = base_rid + size
add.append(('textbox', 'ipasecondarybaserid', str(base_rid)))
if domain:
add.append(('textbox', 'ipanttrusteddomainname', domain))
return add
secondary_base_rid = None
if not domain:
if 'secondary_base_rid' in kwargs:
secondary_base_rid = kwargs['secondary_base_rid']
else:
secondary_base_rid = self.max_rid + shift
self.max_rid = secondary_base_rid + size
return RangeAddFormData(
pkey, base_id, base_rid,
secondary_base_rid=secondary_base_rid,
range_type=range_type,
size=size,
domain=domain,
callback=self.check_range_type_mod
)
def get_mod_form_data(self, **kwargs):
return RangeModifyFormData(**kwargs)
def check_range_type_mod(self, range_type):
if range_type == 'ipa-local':
if range_type == LOCAL_ID_RANGE:
self.assert_disabled("[name=ipanttrusteddomainname]")
self.assert_disabled("[name=ipasecondarybaserid]", negative=True)
elif range_type == 'ipa-ad-trust':
self.assert_disabled("[name=ipanttrusteddomainname]", negative=True)
elif range_type == TRUSTED_ID_RANGE:
self.assert_disabled("[name=ipanttrusteddomainname]",
negative=True)
self.assert_disabled("[name=ipasecondarybaserid]")
class RangeAddFormData:
"""
Class for storing and serializing of new ID Range form data.
Warning: Only for data transformation.
Do not put any additional logic here!
"""
def __init__(self, cn, base_id, base_rid=None, secondary_base_rid=None,
range_type=LOCAL_ID_RANGE, size=50, domain=None,
callback=None):
self.cn = cn
self.base_id = base_id
self.base_rid = base_rid
self.secondary_base_rid = secondary_base_rid
self.range_type = range_type
self.size = size
self.domain = domain
self.callback = callback
def serialize(self):
serialized = [
('textbox', 'cn', self.cn),
('textbox', 'ipabaseid', str(self.base_id)),
('textbox', 'ipaidrangesize', str(self.size)),
('textbox', 'ipabaserid', str(self.base_rid)),
('radio', 'iparangetype', self.range_type),
('callback', self.callback, self.range_type),
]
if self.domain:
serialized.append(('textbox',
'ipanttrusteddomainname',
self.domain))
else:
serialized.append(('textbox',
'ipasecondarybaserid',
str(self.secondary_base_rid)))
return serialized
class RangeModifyFormData:
"""
Class for storing and serializing of modified ID Range form data.
"""
def __init__(self, base_id=None, base_rid=None, secondary_base_rid=None,
size=None):
self.base_id = base_id
self.base_rid = base_rid
self.secondary_base_rid = secondary_base_rid
self.size = size
def serialize(self):
serialized = []
if self.base_id is not None:
serialized.append(('textbox', 'ipabaseid', str(self.base_id)))
if self.size is not None:
serialized.append(('textbox', 'ipaidrangesize', str(self.size)))
if self.base_rid is not None:
serialized.append(('textbox', 'ipabaserid', str(self.base_rid)))
if self.secondary_base_rid is not None:
serialized.append(('textbox',
'ipasecondarybaserid',
str(self.secondary_base_rid)))
return serialized

View File

@@ -21,8 +21,7 @@
Automember tests
"""
from ipatests.test_webui.ui_driver import UI_driver
from ipatests.test_webui.ui_driver import screenshot
from ipatests.test_webui.ui_driver import UI_driver, screenshot
import ipatests.test_webui.data_hostgroup as hostgroup
from ipatests.test_webui.test_host import host_tasks
import pytest
@@ -50,16 +49,180 @@ HOST_GROUP_DATA = {
],
}
SEARCH_CASES = {
'name': 'search-123',
'description': 'Short description !@#$%^&*()',
'positive': [
'search-123',
'search',
'search ',
' search',
'SEARCH',
'123',
'!@#$%^&*()',
'hort descr',
'description !',
],
'negative': [
'searc-123',
'321',
'search 123',
'search Short',
'description!',
],
}
# Condition types
INCLUSIVE = 'inclusive'
EXCLUSIVE = 'exclusive'
@pytest.mark.tier1
class test_automember(UI_driver):
class TestAutomember(UI_driver):
AUTOMEMBER_RULE_EXISTS_ERROR = (
'Automember rule with name "{}" already exists'
)
@pytest.fixture(autouse=True)
def automember_setup(self, ui_driver_fsetup):
self.init_app()
def add_user_group_rules(self, *pkeys, **kwargs):
# We implicitly trigger "Add and Add Another" by passing multiple
# records to add_record method.
# TODO: Create more transparent mechanism to test "Add <entity>" dialog
self.add_record(
ENTITY,
[{
'pkey': pkey,
'add': [('combobox', 'cn', pkey)],
} for pkey in pkeys],
facet='searchgroup',
**kwargs
)
def add_host_group_rules(self, *pkeys, **kwargs):
self.add_record(
ENTITY,
[{
'pkey': pkey,
'add': [('combobox', 'cn', pkey)],
} for pkey in pkeys],
facet='searchhostgroup',
**kwargs
)
def add_user(self, pkey, name, surname):
self.add_record('user', {
'pkey': pkey,
'add': [
('textbox', 'uid', pkey),
('textbox', 'givenname', name),
('textbox', 'sn', surname),
]
})
def add_user_group(self, pkey, description=''):
self.add_record('group', {
'pkey': pkey,
'add': [
('textbox', 'cn', pkey),
('textarea', 'description', description),
]
})
def add_host_group(self, pkey, description=''):
self.add_record('hostgroup', {
'pkey': pkey,
'add': [
('textbox', 'cn', pkey),
('textarea', 'description', description),
]
})
def delete_users(self, *pkeys):
self.delete('user', [{'pkey': pkey} for pkey in pkeys])
def delete_user_groups(self, *pkeys):
self.delete('group', [{'pkey': pkey} for pkey in pkeys])
def delete_user_group_rules(self, *pkeys):
self.delete(ENTITY, [{'pkey': pkey} for pkey in pkeys],
facet='searchgroup')
def delete_host_groups(self, *pkeys):
self.delete('hostgroup', [{'pkey': pkey} for pkey in pkeys])
def delete_host_group_rules(self, *pkeys):
self.delete(ENTITY, [{'pkey': pkey} for pkey in pkeys],
facet='searchhostgroup')
def add_conditions(self, conditions, condition_type):
"""
Add conditions to a rule
:param conditions: list of conditions where condition is a pair
(attribute, expression)
:param condition_type: can be 'inclusive' or 'exclusive'
"""
name = 'automember{}regex'.format(condition_type)
attribute, expression = conditions[0]
another_conditions = conditions[1:]
another_conditions.reverse()
self.add_table_record(name, {'fields': [
('selectbox', 'key', attribute),
('textbox', name, expression)
]}, add_another=bool(another_conditions))
while another_conditions:
attribute, expression = another_conditions.pop()
self.add_another_table_record(
{'fields': [
('selectbox', 'key', attribute),
('textbox', name, expression)
]},
add_another=bool(another_conditions)
)
def delete_conditions(self, conditions, condition_type):
"""
Delete rule conditions
:param conditions: list of conditions where condition is a pair
(attribute, expression)
:param condition_type: can be 'inclusive' or 'exclusive'
"""
self.delete_record(
['{}={}'.format(attr, exp) for attr, exp in conditions],
parent=self.get_facet(),
table_name='automember{}regex'.format(condition_type)
)
def open_new_condition_dialog(self, condition_type):
table = self.find_by_selector(
"table[name='automember{}regex'].table".format(condition_type),
strict=True
)
btn = self.find_by_selector(".btn[name=add]", table, strict=True)
btn.click()
self.wait()
def get_host_util(self):
host_util = host_tasks()
host_util.driver = self.driver
host_util.config = self.config
return host_util
@screenshot
def test_crud(self):
"""
Basic CRUD: automember
"""
self.init_app()
# user group rule
self.basic_crud(ENTITY, USER_GROUP_DATA,
@@ -92,44 +255,25 @@ class test_automember(UI_driver):
"""
Test automember rebuild membership feature for hosts
"""
self.init_app()
host_util = host_tasks()
host_util.driver = self.driver
host_util.config = self.config
host_util = self.get_host_util()
domain = self.config.get('ipa_domain')
host1 = 'web1.%s' % domain
host2 = 'web2.%s' % domain
# Add a hostgroup
self.add_record('hostgroup', {
'pkey': 'webservers',
'add': [
('textbox', 'cn', 'webservers'),
('textarea', 'description', 'webservers'),
]
})
self.add_host_group('webservers', 'webservers')
# Add hosts
self.add_record('host', host_util.get_data("web1", domain))
self.add_record('host', host_util.get_data("web2", domain))
# Add an automember rule
self.add_record(
'automember',
{'pkey': 'webservers', 'add': [('combobox', 'cn', 'webservers')]},
facet='searchhostgroup'
)
self.add_host_group_rules('webservers')
# Add a condition for automember rule
self.navigate_to_record('webservers')
self.add_table_record(
'automemberinclusiveregex',
{'fields': [
('selectbox', 'key', 'fqdn'),
('textbox', 'automemberinclusiveregex', '^web[1-9]+')
]}
)
self.add_conditions([('fqdn', '^web[1-9]+')], condition_type=INCLUSIVE)
# Assert that hosts are not members of hostgroup
self.navigate_to_record('webservers', entity='hostgroup')
@@ -152,11 +296,15 @@ class test_automember(UI_driver):
# Remove host from hostgroup
self.delete_record(host1)
# Assert that host is not a member of hostgroup
# Assert that host was re-added to the group.
# The behavior is expected with the plugin default setting:
# the entry cn=Auto Membership Plugin,cn=plugins,cn=config has
# a default value autoMemberProcessModifyOps: on
#
# See https://www.port389.org/docs/389ds/design/automember-postop-modify-design.html # noqa: E501
self.facet_button_click('refresh')
self.wait_for_request()
self.assert_record(host1, negative=True)
self.assert_record(host2, negative=True)
self.assert_record(host1)
# Rebuild membership for all hosts, using action on hosts search facet
self.navigate_by_menu('identity/host')
@@ -170,63 +318,29 @@ class test_automember(UI_driver):
self.assert_record(host2)
# Delete hostgroup, hosts and automember rule
self.delete('hostgroup', [{'pkey': 'webservers'}])
self.delete('host', [{'pkey': host1}, {'pkey': host2}])
self.delete('automember', [{'pkey': 'webservers'}],
facet='searchhostgroup')
self.delete_host_group_rules('webservers')
self.delete_host_groups('webservers')
@screenshot
def test_rebuild_membership_users(self):
"""
Test automember rebuild membership feature for users
"""
self.init_app()
# Add a group
self.add_record('group', {
'pkey': 'devel',
'add': [
('textbox', 'cn', 'devel'),
('textarea', 'description', 'devel'),
]
})
self.add_user_group('devel', 'devel')
# Add a user
self.add_record('user', {
'pkey': 'dev1',
'add': [
('textbox', 'uid', 'dev1'),
('textbox', 'givenname', 'Dev'),
('textbox', 'sn', 'One'),
]
})
# Add another user
self.add_record('user', {
'pkey': 'dev2',
'add': [
('textbox', 'uid', 'dev2'),
('textbox', 'givenname', 'Dev'),
('textbox', 'sn', 'Two'),
]
})
# Add users
self.add_user('dev1', 'Dev', 'One')
self.add_user('dev2', 'Dev', 'Two')
# Add an automember rule
self.add_record(
'automember',
{'pkey': 'devel', 'add': [('combobox', 'cn', 'devel')]},
facet='searchgroup'
)
self.add_user_group_rules('devel')
# Add a condition for automember rule
self.navigate_to_record('devel')
self.add_table_record(
'automemberinclusiveregex',
{'fields': [
('selectbox', 'key', 'uid'),
('textbox', 'automemberinclusiveregex', '^dev[1-9]+')
]}
)
self.add_conditions([('uid', '^dev[1-9]+')], condition_type=INCLUSIVE)
# Assert that users are not members of group
self.navigate_to_record('devel', entity='group')
@@ -249,11 +363,15 @@ class test_automember(UI_driver):
# Remove user from group
self.delete_record('dev1')
# Assert that user is not a member of group
# Assert that user was re-added to the group
# The behavior is expected with the plugin default setting:
# the entry cn=Auto Membership Plugin,cn=plugins,cn=config has
# a default value autoMemberProcessModifyOps: on
#
# See https://www.port389.org/docs/389ds/design/automember-postop-modify-design.html # noqa: E501
self.facet_button_click('refresh')
self.wait_for_request()
self.assert_record('dev1', negative=True)
self.assert_record('dev2', negative=True)
self.assert_record('dev1')
# Rebuild membership for all users, using action on users search facet
self.navigate_by_menu('identity/user_search')
@@ -267,6 +385,325 @@ class test_automember(UI_driver):
self.assert_record('dev2')
# Delete group, users and automember rule
self.delete('group', [{'pkey': 'devel'}])
self.delete('user', [{'pkey': 'dev1'}, {'pkey': 'dev2'}])
self.delete('automember', [{'pkey': 'devel'}], facet='searchgroup')
self.delete_users('dev1', 'dev2')
self.delete_user_group_rules('devel')
self.delete_user_groups('devel')
@screenshot
def test_add_multiple_user_group_rules(self):
"""
Test creating and deleting multiple user group rules
"""
groups = ['group1', 'group2', 'group3']
for group in groups:
self.add_user_group(group)
self.add_user_group_rules(*groups)
self.delete_user_group_rules(*groups)
@screenshot
def test_add_multiple_host_group_rules(self):
"""
Test creating and deleting multiple host group rules
"""
groups = ['group1', 'group2', 'group3']
for group in groups:
self.add_host_group(group)
self.add_host_group_rules(*groups)
self.delete_host_group_rules(*groups)
@screenshot
def test_search_user_group_rule(self):
"""
Test searching user group rules using filter
"""
pkey = SEARCH_CASES['name']
self.add_user_group(pkey, '')
self.add_user_group_rules(pkey)
self.navigate_to_record(pkey)
self.mod_record(ENTITY, {'mod': [
('textarea', 'description', SEARCH_CASES['description']),
]}, facet='usergrouprule')
self.navigate_to_entity(ENTITY, facet='searchgroup')
for text in SEARCH_CASES['positive']:
self.apply_search_filter(text)
self.wait_for_request()
self.assert_record(pkey)
for text in SEARCH_CASES['negative']:
self.apply_search_filter(text)
self.wait_for_request()
self.assert_record(pkey, negative=True)
self.delete_user_group_rules(pkey)
self.delete_user_groups(pkey)
@screenshot
def test_search_host_group_rule(self):
"""
Test searching host group rules using filter
"""
pkey = SEARCH_CASES['name']
self.add_host_group(pkey, '')
self.add_host_group_rules(pkey, navigate=True)
self.navigate_to_record(pkey)
self.mod_record(ENTITY, {'mod': [
('textarea', 'description', SEARCH_CASES['description']),
]}, facet='hostgrouprule')
self.navigate_to_entity(ENTITY, facet='searchhostgroup')
for text in SEARCH_CASES['positive']:
self.apply_search_filter(text)
self.wait_for_request()
self.assert_record(pkey)
for text in SEARCH_CASES['negative']:
self.apply_search_filter(text)
self.wait_for_request()
self.assert_record(pkey, negative=True)
self.delete_host_group_rules(pkey)
self.delete_host_groups(pkey)
@screenshot
def test_add_user_group_rule_conditions(self):
"""
Test creating and deleting user group rule conditions
"""
pkey = 'devel'
one_inc_condition = ('employeetype', '*engineer*')
inc_conditions = [
('cn', 'inclusive-expression'),
('description', 'other-inclusive-expression'),
]
one_exc_condition = ('employeetype', '*manager*')
exc_conditions = [
('cn', 'exclusive-expression'),
('description', 'other-exclusive-expression'),
]
self.add_user_group(pkey)
self.add_user_group_rules(pkey)
self.navigate_to_record(pkey)
self.add_conditions([one_inc_condition], condition_type=INCLUSIVE)
self.add_conditions(inc_conditions, condition_type=INCLUSIVE)
self.add_conditions([one_exc_condition], condition_type=EXCLUSIVE)
self.add_conditions(exc_conditions, condition_type=EXCLUSIVE)
self.delete_conditions([one_inc_condition], condition_type=INCLUSIVE)
self.delete_conditions(inc_conditions, condition_type=INCLUSIVE)
self.delete_conditions([one_exc_condition], condition_type=EXCLUSIVE)
self.delete_conditions(inc_conditions, condition_type=EXCLUSIVE)
self.delete_user_group_rules(pkey)
self.delete_user_groups(pkey)
@screenshot
def test_add_host_group_rule_conditions(self):
"""
Test creating and deleting user group rule conditions
"""
pkey = 'devel'
one_inc_condition = ('ipaclientversion', '4.8')
inc_conditions = [
('cn', 'inclusive-expression'),
('description', 'other-inclusive-expression'),
]
one_exc_condition = ('ipaclientversion', '4.7')
exc_conditions = [
('cn', 'exclusive-expression'),
('description', 'other-exclusive-expression'),
]
self.add_host_group(pkey)
self.add_host_group_rules(pkey)
self.navigate_to_record(pkey)
self.add_conditions([one_inc_condition], condition_type=INCLUSIVE)
self.add_conditions(inc_conditions, condition_type=INCLUSIVE)
self.add_conditions([one_exc_condition], condition_type=EXCLUSIVE)
self.add_conditions(exc_conditions, condition_type=EXCLUSIVE)
self.delete_conditions([one_inc_condition], condition_type=INCLUSIVE)
self.delete_conditions(inc_conditions, condition_type=INCLUSIVE)
self.delete_conditions([one_exc_condition], condition_type=EXCLUSIVE)
self.delete_conditions(inc_conditions, condition_type=EXCLUSIVE)
self.delete_host_group_rules(pkey)
self.delete_host_groups(pkey)
@screenshot
def test_cancel_new_user_group_rule_condition_dialog(self):
"""
Test canceling of creating new user group rule condition
"""
pkey = 'devel'
self.add_user_group(pkey)
self.add_user_group_rules(pkey)
self.navigate_to_record(pkey)
for condition_type in [INCLUSIVE, EXCLUSIVE]:
self.open_new_condition_dialog(condition_type)
self.fill_fields([('selectbox', 'key', 'title')])
self.dialog_button_click('cancel')
self.delete_user_group_rules(pkey)
self.delete_user_groups(pkey)
@screenshot
def test_cancel_new_host_group_rule_condition_dialog(self):
"""
Test canceling of creating new host group rule condition
"""
pkey = 'devel'
self.add_host_group(pkey)
self.add_host_group_rules(pkey)
self.navigate_to_record(pkey)
for condition_type in [INCLUSIVE, EXCLUSIVE]:
self.open_new_condition_dialog(condition_type)
self.fill_fields([('selectbox', 'key', 'serverhostname')])
self.dialog_button_click('cancel')
self.delete_host_group_rules(pkey)
self.delete_host_groups(pkey)
@screenshot
def test_set_default_user_group(self):
"""
Test setting default user group
"""
pkey = 'default-user-group'
user_pkey = 'some-user'
self.add_user_group(pkey)
self.navigate_by_menu('identity/automember/amgroup')
self.select_combobox('automemberdefaultgroup', pkey)
self.dialog_button_click('ok')
self.add_user(user_pkey, 'Some', 'User')
self.navigate_to_record(user_pkey)
self.switch_to_facet('memberof_group')
self.assert_record(pkey)
# Clear default user group
self.navigate_by_menu('identity/automember/amgroup')
self.select_combobox('automemberdefaultgroup', '')
self.dialog_button_click('ok')
self.delete_users(user_pkey)
self.delete_user_groups(pkey)
@screenshot
def test_set_default_host_group(self):
"""
Test setting default host group
"""
pkey = 'default-host-group'
host_util = self.get_host_util()
domain = self.config.get('ipa_domain')
self.add_host_group(pkey)
self.navigate_by_menu('identity/automember/amhostgroup')
self.select_combobox('automemberdefaultgroup', pkey)
self.dialog_button_click('ok')
host_data = host_util.get_data('some-host', domain)
self.add_record('host', host_data)
self.navigate_to_record(host_data['pkey'])
self.switch_to_facet('memberof_hostgroup')
self.assert_record(pkey)
# Clear default host group
self.navigate_by_menu('identity/automember/amhostgroup')
self.select_combobox('automemberdefaultgroup', '')
self.dialog_button_click('ok')
self.delete('host', [{'pkey': host_data['pkey']}])
self.delete_host_groups(pkey)
@screenshot
def test_add_user_group_rule_with_no_group(self):
self.add_record(
ENTITY,
{'pkey': 'empty-user-group', 'add': []},
facet='searchgroup',
negative=True
)
@screenshot
def test_add_host_group_rule_with_no_group(self):
self.add_record(
ENTITY,
{'pkey': 'empty-host-group', 'add': []},
facet='searchhostgroup',
negative=True
)
@screenshot
def test_add_user_group_rules_for_same_group(self):
"""
Test creating user group rules for same group
"""
group_name = 'some-user-group'
self.add_user_group(group_name)
self.add_user_group_rules(group_name)
self.add_user_group_rules(group_name, negative=True, pre_delete=False)
self.assert_last_error_dialog(
self.AUTOMEMBER_RULE_EXISTS_ERROR.format(group_name)
)
self.delete_user_group_rules(group_name)
self.delete_user_groups(group_name)
@screenshot
def test_add_host_group_rules_for_same_group(self):
"""
Test creating host group rules for same group
"""
group_name = 'some-host-group'
self.add_host_group(group_name)
self.add_host_group_rules(group_name)
self.add_host_group_rules(group_name, negative=True, pre_delete=False)
self.assert_last_error_dialog(
self.AUTOMEMBER_RULE_EXISTS_ERROR.format(group_name)
)
self.delete_host_group_rules(group_name)
self.delete_host_groups(group_name)
@screenshot
def test_cancel_group_rule_creating(self):
"""
Test canceling of creating new automember group rule
"""
self.add_user_group_rules('some-user-group', dialog_btn='cancel')
self.add_host_group_rules('some-host-group', dialog_btn='cancel')

View File

@@ -21,8 +21,11 @@
Automount tests
"""
from ipatests.test_webui.ui_driver import UI_driver
from ipatests.test_webui.ui_driver import screenshot
from ipatests.test_webui.ui_driver import UI_driver, screenshot
try:
from selenium.webdriver.common.keys import Keys
except ImportError:
pass
import pytest
LOC_ENTITY = 'automountlocation'
@@ -61,9 +64,165 @@ KEY_DATA = {
]
}
DIRECT_MAPS = [
{
'pkey': 'map1',
'add': [
('radio', 'method', 'add'),
('textbox', 'automountmapname', 'map1'),
('textarea', 'description', 'foobar'),
],
},
{
'pkey': 'map2',
'add': [
('radio', 'method', 'add'),
('textbox', 'automountmapname', 'map2'),
('textarea', 'description', 'foobar'),
],
},
{
'pkey': 'map3',
'add': [
('radio', 'method', 'add'),
('textbox', 'automountmapname', 'map3'),
('textarea', 'description', 'foobar'),
],
},
{
'pkey': 'map4',
'add': [
('radio', 'method', 'add'),
('textbox', 'automountmapname', 'map4'),
('textarea', 'description', 'foobar'),
],
},
]
INDIRECT_MAPS = [
{
'pkey': 'map1',
'add': [
('radio', 'method', 'add'),
('textbox', 'automountmapname', 'map1'),
('textarea', 'description', 'foobar'),
],
},
{
'pkey': 'map2',
'add': [
('radio', 'method', 'add_indirect'),
('textbox', 'automountmapname', 'map2'),
('textarea', 'description', 'foobar'),
('textbox', 'key', 'mount1'),
('textbox', 'parentmap', 'map1'),
],
'mod': [
('textarea', 'description', 'modified'),
]
},
{
'pkey': 'map3',
'add': [
('radio', 'method', 'add_indirect'),
('textbox', 'automountmapname', 'map3'),
('textarea', 'description', 'foobar'),
('textbox', 'key', 'mount2'),
('textbox', 'parentmap', 'map1'),
],
},
{
'pkey': 'map4',
'add': [
('radio', 'method', 'add_indirect'),
('textbox', 'automountmapname', 'map4'),
('textarea', 'description', 'foobar'),
('textbox', 'key', 'mount3'),
('textbox', 'parentmap', 'map1'),
],
},
]
DIRECT_MAP_MOD = {
'pkey': 'map1',
'add': [
('radio', 'method', 'add'),
('textbox', 'automountmapname', 'map1'),
('textarea', 'description', 'desc'),
],
'mod': [
('textarea', 'description', 'modified'),
]
}
INDIRECT_MAP_MOD = {
'pkey': 'map1',
'add': [
('radio', 'method', 'add_indirect'),
('textbox', 'automountmapname', 'map1'),
('textarea', 'description', 'desc'),
('textbox', 'key', 'mount1'),
],
'mod': [
('textarea', 'description', 'modified'),
]
}
class Location:
def __init__(self, location, ui):
self.ui = ui
self.location = location
def __enter__(self):
self.ui.add_record(LOC_ENTITY, self.location)
self.ui.navigate_to_record(self.location['pkey'])
return self
def __exit__(self, *args):
self.ui.delete(LOC_ENTITY, [self.location])
return False
@pytest.mark.tier1
class test_automount(UI_driver):
class TestAutomount(UI_driver):
@pytest.fixture(autouse=True)
def automount_setup(self, ui_driver_fsetup):
self.init_app()
def add_key(self, key, mount_info, **kwargs):
self.add_record(MAP_ENTITY, {
'pkey': key,
'add': [
('textbox', 'automountkey', key),
('textbox', 'automountinformation', mount_info),
],
}, facet='keys', **kwargs)
def _add_record_with_enter_key(self, record):
self.delete_record(record['pkey'])
self.assert_no_dialog()
self.facet_button_click('add')
self.assert_dialog('add')
self.fill_fields(record['add'])
dialog = self.get_dialog('add')
dialog.send_keys(Keys.ENTER)
self.wait_for_request()
def _delete_record_with_enter_key(self, pkey):
assert self.has_record(pkey)
self.select_record(pkey)
self.facet_button_click('remove')
dialog = self.get_dialog('add')
dialog.send_keys(Keys.ENTER)
self.wait_for_request(n=2)
self.wait()
@screenshot
def test_crud(self):
@@ -105,7 +264,7 @@ class test_automount(UI_driver):
self.navigate_by_breadcrumb(LOC_PKEY)
self.delete_record(MAP_PKEY)
## test indirect maps
# test indirect maps
direct_pkey = 'itest-direct'
indirect_pkey = 'itest-indirect'
@@ -143,3 +302,323 @@ class test_automount(UI_driver):
self.delete_record(indirect_pkey)
self.navigate_by_breadcrumb('Automount Locations')
self.delete_record(LOC_PKEY)
@screenshot
def test_add_location_dialog(self):
"""
Test 'Add Automount Location' dialog behaviour
"""
pkeys = ['loc1', 'loc2', 'loc3', 'loc4']
locations = [{
'pkey': pkey,
'add': [('textbox', 'cn', pkey)]
} for pkey in pkeys]
self.navigate_to_entity(LOC_ENTITY)
# Add and add another
self.add_record(LOC_ENTITY, [locations[0], locations[1]],
navigate=False)
self.assert_record(locations[0]['pkey'])
self.assert_record(locations[1]['pkey'])
# Add and edit
self.add_record(LOC_ENTITY, locations[2], dialog_btn='add_and_edit',
navigate=False)
self.assert_facet(LOC_ENTITY, facet='maps')
# Cancel dialog
self.add_record(LOC_ENTITY, locations[3], dialog_btn='cancel')
self.assert_record(locations[3]['pkey'], negative=True)
# Add duplicated
self.add_record(LOC_ENTITY, locations[0], navigate=False,
negative=True, pre_delete=False)
self.assert_last_error_dialog(
'automount location with name "{}" already exists'
.format(locations[0]['pkey'])
)
self.close_all_dialogs()
# Missing field
self.add_record(LOC_ENTITY, {'pkey': 'loc5', 'add': []},
navigate=False, negative=True)
assert self.has_form_error('cn')
self.close_all_dialogs()
# Delete multiple locations
self.delete_record(pkeys)
@screenshot
@pytest.mark.parametrize('maps', [DIRECT_MAPS, INDIRECT_MAPS])
def test_add_map_dialog(self, maps):
"""
Test 'Add Automount Map' dialog behaviour
"""
with Location(LOC_DATA, ui=self):
# Add and add another
self.add_record(LOC_ENTITY, [maps[0], maps[1]],
facet='maps', navigate=False)
self.assert_record(maps[0]['pkey'])
self.assert_record(maps[1]['pkey'])
# Add and edit
self.add_record(LOC_ENTITY, maps[2], dialog_btn='add_and_edit',
facet='maps', navigate=False)
self.assert_facet(MAP_ENTITY, facet='keys')
# Cancel dialog
self.add_record(LOC_ENTITY, maps[3], facet='maps',
dialog_btn='cancel')
self.assert_record(maps[3]['pkey'], negative=True)
# Delete multiple maps
self.delete_record([m['pkey'] for m in maps])
@screenshot
def test_add_indirect_map_with_missing_fields(self):
"""
Test creating automount map without mapname and mountpoint
"""
maps = {
'automountmapname': {
'pkey': 'map1',
'add': [
('radio', 'method', 'add_indirect'),
('textarea', 'description', 'desc'),
('textbox', 'key', 'mount1'),
],
},
'key': {
'pkey': 'map2',
'add': [
('radio', 'method', 'add_indirect'),
('textbox', 'automountmapname', 'map1'),
('textarea', 'description', 'desc'),
],
}
}
with Location(LOC_DATA, ui=self):
for missing_field, map_data in maps.items():
self.add_record(LOC_ENTITY, map_data, negative=True,
facet='maps', navigate=False)
assert self.has_form_error(missing_field)
self.dialog_button_click('cancel')
self.assert_record(map_data['pkey'], negative=True)
@screenshot
def test_add_duplicated_indirect_map(self):
"""
Test creating indirect automount map with duplicated field values
"""
original_name = 'map1'
original_key = 'mount1'
with Location(LOC_DATA, ui=self):
self.add_record(LOC_ENTITY, {
'pkey': original_name,
'add': [
('radio', 'method', 'add_indirect'),
('textbox', 'automountmapname', original_name),
('textarea', 'description', 'desc'),
('textbox', 'key', original_key),
],
}, facet='maps', navigate=False)
self.add_record(LOC_ENTITY, {
'pkey': 'map2',
'add': [
('radio', 'method', 'add_indirect'),
('textbox', 'automountmapname', original_name),
('textarea', 'description', 'desc'),
('textbox', 'key', 'mount2'),
],
}, negative=True, facet='maps', navigate=False, pre_delete=False)
self.assert_last_error_dialog(
'automount map with name "{}" already exists'
.format(original_name)
)
self.close_all_dialogs()
self.add_record(LOC_ENTITY, {
'pkey': 'map3',
'add': [
('radio', 'method', 'add_indirect'),
('textbox', 'automountmapname', 'map3'),
('textarea', 'description', 'desc'),
('textbox', 'key', original_key),
],
}, negative=True, facet='maps', navigate=False, pre_delete=False)
self.assert_last_error_dialog(
'key named {} already exists'.format(original_key)
)
self.close_all_dialogs()
@screenshot
@pytest.mark.parametrize('map_data', [DIRECT_MAP_MOD, INDIRECT_MAP_MOD])
def test_modify_map(self, map_data):
"""
Test automount map 'Settings' tab
"""
with Location(LOC_DATA, ui=self):
self.add_record(LOC_ENTITY, map_data,
facet='maps', navigate=False)
self.navigate_to_record(map_data['pkey'])
self.switch_to_facet('details')
# Refresh
self.fill_fields(map_data['mod'], undo=True)
self.assert_facet_button_enabled('refresh')
self.facet_button_click('refresh')
self.wait_for_request()
self.assert_facet_button_enabled('refresh')
# Revert
self.mod_record(MAP_ENTITY, map_data, facet_btn='revert')
@screenshot
def test_add_key_dialog(self):
"""
Test 'Add Automount Key' dialog behaviour
"""
pkeys = ['key1', 'key2', 'key3', 'key4']
keys = [
{
'pkey': pkey,
'add': [
('textbox', 'automountkey', pkey),
('textbox', 'automountinformation', '/itest/%s' % pkey),
],
} for pkey in pkeys
]
with Location(LOC_DATA, ui=self):
self.add_record(LOC_ENTITY, MAP_DATA, facet='maps', navigate=False)
self.navigate_to_record(MAP_PKEY)
# Add and add another
self.add_record(MAP_ENTITY, [keys[0], keys[1]],
facet='keys', navigate=False)
self.assert_record(keys[0]['pkey'])
self.assert_record(keys[1]['pkey'])
# Add and edit
self.add_record(MAP_ENTITY, keys[2], dialog_btn='add_and_edit',
facet='keys', navigate=False)
self.assert_facet(KEY_ENTITY, facet='details')
# Cancel dialog
self.add_record(MAP_ENTITY, keys[3], facet='keys',
dialog_btn='cancel')
self.assert_record(keys[3]['pkey'], negative=True)
# Delete multiple keys
self.delete_record(pkeys)
@screenshot
def test_add_key_with_missing_fields(self):
"""
Test creating automount key without key name and mount information
"""
keys = {
'automountkey': {
'pkey': 'key1',
'add': [('textbox', 'automountinformation', '/itest/key')],
},
'automountinformation': {
'pkey': 'key2',
'add': [('textbox', 'automountkey', 'key2')],
},
}
with Location(LOC_DATA, ui=self):
self.add_record(LOC_ENTITY, MAP_DATA, facet='maps', navigate=False)
self.navigate_to_record(MAP_PKEY)
for missing_field, key in keys.items():
self.add_record(MAP_ENTITY, key, negative=True,
facet='keys', navigate=False)
assert self.has_form_error(missing_field)
self.dialog_button_click('cancel')
self.assert_record(key['pkey'], negative=True)
@screenshot
def test_add_duplicated_key(self):
"""
Test creating automount key with duplicated field values
"""
with Location(LOC_DATA, ui=self):
self.add_record(LOC_ENTITY, MAP_DATA, facet='maps', navigate=False)
self.navigate_to_record(MAP_PKEY)
key = 'mount1'
self.add_key(key, '/itest/key', navigate=False)
self.add_key(key, '/itest/key2', negative=True, navigate=False,
pre_delete=False)
self.assert_last_error_dialog(
'key named {} already exists'.format(key)
)
self.close_all_dialogs()
@screenshot
def test_modify_key(self):
"""
Test automount key 'Settings'
"""
with Location(LOC_DATA, ui=self):
self.add_record(LOC_ENTITY, MAP_DATA, facet='maps', navigate=False)
self.navigate_to_record(MAP_PKEY)
self.add_record(MAP_ENTITY, KEY_DATA, facet='keys', navigate=False)
self.navigate_to_record(KEY_PKEY)
# Refresh
self.fill_fields(KEY_DATA['mod'])
self.assert_facet_button_enabled('refresh')
self.facet_button_click('refresh')
self.wait_for_request()
self.assert_facet_button_enabled('refresh')
# Revert
self.mod_record(KEY_ENTITY, KEY_DATA, facet_btn='revert')
@screenshot
def test_confirm_dialogs_with_enter_key(self):
"""
Test dialogs can be closed by pressing ENTER
"""
# Add location
self.navigate_to_entity(LOC_ENTITY, 'search')
self._add_record_with_enter_key(LOC_DATA)
# Add map
self.navigate_to_record(LOC_PKEY)
self._add_record_with_enter_key(MAP_DATA)
# Add key
self.navigate_to_record(MAP_PKEY)
self._add_record_with_enter_key(KEY_DATA)
# Delete key
self._delete_record_with_enter_key(KEY_PKEY)
# Delete map
self.navigate_by_breadcrumb(LOC_PKEY)
self._delete_record_with_enter_key(MAP_PKEY)
# Delete location
self.navigate_to_entity(LOC_ENTITY)
self._delete_record_with_enter_key(LOC_PKEY)

View File

@@ -44,26 +44,6 @@ def search_pkey(self, pkey):
self.wait_for_request(n=2)
def add_cert(self, principal, csr):
self.facet_button_click('request_cert')
self.fill_textbox('principal', principal)
self.check_option('add', 'checked')
self.fill_textarea('csr', csr)
self.dialog_button_click('issue')
self.assert_notification(assert_text='Certificate requested')
self.navigate_to_entity(ENTITY)
rows = self.get_rows()
return rows[-1]
def revoke_cert(self, record, reason):
self.navigate_to_entity(ENTITY)
self.navigate_to_row_record(record)
self.action_list_action('revoke_cert', False)
self.select('select[name=revocation_reason]', reason)
self.dialog_button_click('ok')
def check_option_negative(self, date, option):
self.navigate_to_entity(ENTITY)
self.select('select[name=search_option]', option)
@@ -103,12 +83,34 @@ def check_minimum_serial(self, serial, option):
@pytest.mark.tier1
class test_cert(UI_driver):
def setup(self, *args, **kwargs):
super(test_cert, self).setup(*args, **kwargs)
@pytest.fixture(autouse=True)
def cert_setup(self, ui_driver_fsetup):
if not self.has_ca():
self.skip('CA not configured')
def _add_and_revoke_cert(self, reason='1'):
hostname = self.config.get('ipa_server')
csr = generate_csr(hostname)
self.navigate_to_entity(ENTITY)
self.facet_button_click('request_cert')
self.fill_textbox('principal', 'HTTP/{}'.format(hostname))
self.check_option('add', 'checked')
self.fill_textarea('csr', csr)
self.dialog_button_click('issue')
self.assert_notification(assert_text='Certificate requested')
self.navigate_to_entity(ENTITY)
rows = self.get_rows()
cert = rows[-1]
self.navigate_to_row_record(cert)
self.action_list_action('revoke_cert', False)
self.select('select[name=revocation_reason]', reason)
self.dialog_button_click('ok')
self.navigate_to_entity(ENTITY)
return cert
@screenshot
def test_read(self):
"""
@@ -160,18 +162,11 @@ class test_cert(UI_driver):
Try to search certificates by revocation reason
"""
self.init_app()
self.navigate_to_entity(ENTITY)
# add a new cert
hostname = self.config.get('ipa_server')
csr = generate_csr(hostname)
record = add_cert(self, 'HTTP/{}'.format(hostname), csr)
# revoke added cert
revoke_cert(self, record, '1')
# revoke new certificate
self._add_and_revoke_cert()
# search cert by revocation reason
self.navigate_to_entity(ENTITY)
self.select('select[name=search_option]', 'revocation_reason')
search_pkey(self, '1')
rows = self.get_rows()
@@ -248,7 +243,10 @@ class test_cert(UI_driver):
"""
today = date.today()
self.init_app()
self.navigate_to_entity(ENTITY)
# revoke new certificate
self._add_and_revoke_cert()
self.select('select[name=search_option]', 'validnotafter_from')
search_pkey(self, str(today))
rows = self.get_rows()
@@ -284,7 +282,10 @@ class test_cert(UI_driver):
"""
today = date.today()
self.init_app()
self.navigate_to_entity(ENTITY)
# revoke new certificate
self._add_and_revoke_cert()
self.select('select[name=search_option]', 'validnotafter_to')
search_pkey(self, str(today + timedelta(weeks=52 * 30)))
rows = self.get_rows()
@@ -320,7 +321,10 @@ class test_cert(UI_driver):
"""
today = date.today()
self.init_app()
self.navigate_to_entity(ENTITY)
# revoke new certificate
self._add_and_revoke_cert()
self.select('select[name=search_option]', 'validnotbefore_from')
search_pkey(self, str(today))
rows = self.get_rows()
@@ -356,7 +360,10 @@ class test_cert(UI_driver):
"""
today = date.today()
self.init_app()
self.navigate_to_entity(ENTITY)
# revoke new certificate
self._add_and_revoke_cert()
self.select('select[name=search_option]', 'validnotbefore_to')
search_pkey(self, str(today + timedelta(weeks=52 * 30)))
rows = self.get_rows()
@@ -392,7 +399,10 @@ class test_cert(UI_driver):
"""
today = date.today()
self.init_app()
self.navigate_to_entity(ENTITY)
# revoke new certificate
self._add_and_revoke_cert()
self.select('select[name=search_option]', 'issuedon_from')
search_pkey(self, str(today))
rows = self.get_rows()
@@ -424,7 +434,10 @@ class test_cert(UI_driver):
"""
today = date.today()
self.init_app()
self.navigate_to_entity(ENTITY)
# revoke new certificate
self._add_and_revoke_cert()
self.select('select[name=search_option]', 'issuedon_to')
search_pkey(self, str(today))
rows = self.get_rows()
@@ -456,7 +469,10 @@ class test_cert(UI_driver):
"""
today = date.today()
self.init_app()
self.navigate_to_entity(ENTITY)
# revoke new certificate
self._add_and_revoke_cert()
self.select('select[name=search_option]', 'revokedon_from')
search_pkey(self, str(today))
rows = self.get_rows()
@@ -488,7 +504,10 @@ class test_cert(UI_driver):
"""
today = date.today()
self.init_app()
self.navigate_to_entity(ENTITY)
# revoke new certificate
self._add_and_revoke_cert()
self.select('select[name=search_option]', 'revokedon_to')
search_pkey(self, str(today))
rows = self.get_rows()

View File

@@ -164,6 +164,32 @@ class test_config(UI_driver):
self.fill_input(size_limit_s, def_val)
self.facet_button_click('save')
@screenshot
def test_size_limit_notification(self):
"""
Test if no notification is shown when size limit exceeded
"""
self.init_app()
self.navigate_to_entity(config_data.ENTITY)
size_limit_s = 'ipasearchrecordslimit'
def_val = self.get_field_value(size_limit_s)
self.fill_input(size_limit_s, '10')
self.facet_button_click('save')
self.navigate_to_entity('cert')
# wait for a half sec for notification to appear
self.wait(0.5)
warning = self.find_by_selector('div.notification-area .alert-warning')
try:
assert not warning, "Warning present: {}".format(warning.text)
finally:
# restore previous value
self.navigate_to_entity(config_data.ENTITY)
self.fill_input(size_limit_s, def_val)
self.facet_button_click('save')
@screenshot
def test_time_limits(self):
"""

View File

@@ -35,9 +35,8 @@ import pytest
@pytest.mark.tier1
class test_dns(UI_driver):
def setup(self, *args, **kwargs):
super(test_dns, self).setup(*args, **kwargs)
@pytest.fixture(autouse=True)
def dns_setup(self, ui_driver_fsetup):
if not self.has_dns():
self.skip('DNS not configured')

View File

@@ -356,3 +356,63 @@ class test_group(UI_driver):
self.delete(rbac.ROLE_ENTITY, [rbac.ROLE_DATA])
self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA])
self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA])
@screenshot
def test_member_manager_user(self):
"""
Test member manager user has permissions to add and remove group
members
"""
self.init_app()
self.add_record(user.ENTITY, [user.DATA_MEMBER_MANAGER, user.DATA])
self.add_record(group.ENTITY, group.DATA2)
self.navigate_to_record(group.PKEY2)
self.add_associations([user.PKEY_MEMBER_MANAGER],
facet='membermanager_user')
# try to add user to group with member manager permissions
self.logout()
self.login(user.PKEY_MEMBER_MANAGER, user.PASSWD_MEMBER_MANAGER)
self.navigate_to_record(group.PKEY2, entity=group.ENTITY)
self.add_associations([user.PKEY], delete=True)
# re-login as admin and clean up data
self.logout()
self.init_app()
self.delete(user.ENTITY, [user.DATA_MEMBER_MANAGER, user.DATA])
self.delete(group.ENTITY, [group.DATA2])
@screenshot
def test_member_manager_group(self):
"""
Test member managers group has permissions to add and remove group
members
"""
self.init_app()
self.add_record(user.ENTITY, [user.DATA_MEMBER_MANAGER, user.DATA])
self.add_record(group.ENTITY, [group.DATA2, group.DATA3])
self.navigate_to_record(group.PKEY2)
self.add_associations([user.PKEY_MEMBER_MANAGER], facet='member_user')
self.navigate_to_record(group.PKEY3, entity=group.ENTITY)
self.add_associations([group.PKEY2], facet='membermanager_group')
# try to add host to group with member manager permissions
self.logout()
self.login(user.PKEY_MEMBER_MANAGER, user.PASSWD_MEMBER_MANAGER)
self.navigate_to_record(group.PKEY3, entity=group.ENTITY)
self.add_associations([user.PKEY], delete=True)
# re-login as admin and clean up data
self.logout()
self.init_app()
self.delete(user.ENTITY, [user.DATA_MEMBER_MANAGER, user.DATA])
self.delete(group.ENTITY, [group.DATA2, group.DATA3])

View File

@@ -154,10 +154,6 @@ class test_hbac(UI_driver):
self.button_click('run_test')
self.assert_dialog('message_dialog')
__hbac_ui_click_on_run_test(self)
self.click_on_link('User name')
self.assert_facet('hbactest', 'user')
__hbac_ui_click_on_run_test(self)
self.click_on_link('Target host')
self.assert_facet('hbactest', 'targethost')

View File

@@ -24,7 +24,7 @@ Host tests
import uuid
from random import randint
from ipatests.test_webui.crypto_utils import generate_csr
from ipatests.test_webui.crypto_utils import generate_certificate, generate_csr
from ipatests.test_webui.ui_driver import UI_driver
from ipatests.test_webui.ui_driver import screenshot
import ipatests.test_webui.data_hostgroup as hostgroup
@@ -48,8 +48,8 @@ ENTITY = 'host'
@pytest.mark.tier1
class host_tasks(UI_driver):
def setup(self, *args, **kwargs):
super(host_tasks, self).setup(*args, **kwargs)
@pytest.fixture(autouse=True)
def hosttasks_setup(self, ui_driver_fsetup):
self.prep_data()
self.prep_data2()
self.prep_data3()
@@ -182,6 +182,8 @@ class test_host(host_tasks):
self.wait_for_request(n=2, d=3)
self.assert_visible(cert_widget_sel)
widget = self.find(cert_widget_sel, By.CSS_SELECTOR)
# cert view
self.action_list_action('view', confirm=False,
parents_css_sel=cert_widget_sel)
@@ -216,7 +218,8 @@ class test_host(host_tasks):
self.wait()
self.select('select', '6')
self.dialog_button_click('ok')
self.wait_for_request(n=2, d=3)
self.wait_while_working(widget)
self.assert_visible(cert_widget_sel + " div.watermark")
# check that revoke action is not enabled
@@ -234,7 +237,7 @@ class test_host(host_tasks):
parents_css_sel=cert_widget_sel)
self.wait()
self.dialog_button_click('ok')
self.wait_for_request(n=2)
self.wait_while_working(widget)
# check that revoke action is enabled
self.assert_action_list_action('revoke',
@@ -254,15 +257,8 @@ class test_host(host_tasks):
def test_arbitrary_certificates(self):
"""
Test managing host arbitrary certificate.
Requires to have 'arbitrary_cert_path' configuration set.
"""
cert_path = self.config.get('arbitrary_cert_path')
if not cert_path:
self.skip('Arbitrary certificate file is not configured')
self.init_app()
cert = self.load_file(cert_path)
self.add_record(ENTITY, self.data)
self.navigate_to_record(self.pkey)
@@ -272,9 +268,11 @@ class test_host(host_tasks):
# add certificate
self.button_click('add', parents_css_sel="div[name='certificate']")
self.assert_dialog()
self.assert_dialog('cert-add-dialog')
cert = generate_certificate(self.pkey)
self.fill_textarea('new_cert', cert)
self.dialog_button_click('add')
self.dialog_button_click('ok')
self.assert_visible("div.certificate-widget")

View File

@@ -23,11 +23,13 @@ Hostgroup tests
from ipatests.test_webui.ui_driver import UI_driver
from ipatests.test_webui.ui_driver import screenshot
import ipatests.test_webui.data_group as group
import ipatests.test_webui.data_hostgroup as hostgroup
from ipatests.test_webui.test_host import host_tasks, ENTITY as HOST_ENTITY
import ipatests.test_webui.data_netgroup as netgroup
import ipatests.test_webui.data_hbac as hbac
import ipatests.test_webui.data_sudo as sudo
import ipatests.test_webui.data_user as user
import pytest
@@ -152,8 +154,8 @@ class test_hostgroup(UI_driver):
self.assert_indirect_record(hbac.RULE_PKEY, hostgroup.ENTITY, 'memberof_hbacrule')
self.assert_indirect_record(sudo.RULE_PKEY, hostgroup.ENTITY, 'memberof_sudorule')
## cleanup
## -------
# cleanup
# -------
self.delete(hostgroup.ENTITY, [hostgroup.DATA, hostgroup.DATA2,
hostgroup.DATA3, hostgroup.DATA4,
hostgroup.DATA5])
@@ -161,6 +163,83 @@ class test_hostgroup(UI_driver):
self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA])
self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA])
@screenshot
def test_member_manager_user(self):
"""
Test member manager user has permissions to add and remove host group
members
"""
self.init_app()
host = host_tasks()
host.driver = self.driver
host.config = self.config
host.prep_data2()
self.add_record(user.ENTITY, [user.DATA_MEMBER_MANAGER])
self.add_record(HOST_ENTITY, host.data2)
self.add_record(hostgroup.ENTITY, hostgroup.DATA)
self.navigate_to_record(hostgroup.PKEY)
self.add_associations([user.PKEY_MEMBER_MANAGER],
facet='membermanager_user')
# try to add host to group with member manager permissions
self.logout()
self.login(user.PKEY_MEMBER_MANAGER, user.PASSWD_MEMBER_MANAGER)
self.navigate_to_record(hostgroup.PKEY, entity=hostgroup.ENTITY)
self.add_associations([host.pkey2], delete=True)
# re-login as admin and clean up data
self.logout()
self.init_app()
self.delete(HOST_ENTITY, [host.data2])
self.delete(user.ENTITY, [user.DATA_MEMBER_MANAGER])
self.delete(hostgroup.ENTITY, [hostgroup.DATA])
@screenshot
def test_member_manager_group(self):
"""
Test member managers group has permissions to add and remove host group
members
"""
self.init_app()
host = host_tasks()
host.driver = self.driver
host.config = self.config
host.prep_data2()
self.add_record(user.ENTITY, user.DATA_MEMBER_MANAGER)
self.add_record(group.ENTITY, [group.DATA2])
self.navigate_to_record(group.PKEY2)
self.add_associations([user.PKEY_MEMBER_MANAGER], facet='member_user')
self.add_record(HOST_ENTITY, host.data2)
self.add_record(hostgroup.ENTITY, hostgroup.DATA)
self.navigate_to_record(hostgroup.PKEY)
self.add_associations([group.PKEY2], facet='membermanager_group')
# try to add host to group with member manager permissions
self.logout()
self.login(user.PKEY_MEMBER_MANAGER, user.PASSWD_MEMBER_MANAGER)
self.navigate_to_record(hostgroup.PKEY, entity=hostgroup.ENTITY)
self.add_associations([host.pkey2], delete=True)
# re-login as admin and clean up data
self.logout()
self.init_app()
self.delete(HOST_ENTITY, [host.data2])
self.delete(user.ENTITY, [user.DATA_MEMBER_MANAGER])
self.delete(group.ENTITY, [group.DATA2])
self.delete(hostgroup.ENTITY, [hostgroup.DATA])
@screenshot
def test_names_and_button(self):
"""
Hostgroup names and buttons

View File

@@ -5,6 +5,7 @@
"""
Test LoginScreen widget and all it's views
"""
import urllib
from ipatests.test_webui.ui_driver import UI_driver
from ipatests.test_webui.ui_driver import screenshot
@@ -18,28 +19,27 @@ except ImportError:
pass
import pytest
from six.moves import urllib
@pytest.mark.tier1
class TestLoginScreen(UI_driver):
def setup(self, *args, **kwargs):
super(TestLoginScreen, self).setup(*args, **kwargs)
@pytest.fixture(autouse=True)
def loginscreen_setup(self, request, ui_driver_fsetup):
self.init_app()
self.add_test_user()
self.logout()
def teardown(self, *args, **kwargs):
# log out first
if (self.logged_in()):
self.logout()
else:
self.load_url(self.get_base_url())
# log in as administrator
self.login()
self.delete_test_user()
super(TestLoginScreen, self).teardown(*args, **kwargs)
def fin():
# log out first
if (self.logged_in()):
self.logout()
else:
self.load_url(self.get_base_url())
# log in as administrator
self.login()
self.delete_test_user()
request.addfinalizer(fin)
def delete_test_user(self):
"""
@@ -124,6 +124,7 @@ class TestLoginScreen(UI_driver):
WebDriverWait(self.driver, 10).until(
lambda d: runner.files_loaded()
)
self.wait()
def relogin_with_new_password(self):
"""
@@ -157,7 +158,7 @@ class TestLoginScreen(UI_driver):
new_pass_field.send_keys(new_password)
verify_pass_field.send_keys(new_password)
verify_pass_field.send_keys(Keys.RETURN)
self.wait()
self.wait(0.5)
self.assert_notification(assert_text='Password change complete',
link_text=link_text, link_url=link_url)
@@ -369,6 +370,7 @@ class TestLoginScreen(UI_driver):
# check click on 'Cancel' button
self.check_cancel()
self.button_click_on_login_screen('login')
self.wait_for_request()
# check if user is not logged
assert not self.logged_in()

View File

@@ -21,10 +21,15 @@
Range tests
"""
import pytest
import ipatests.test_webui.test_trust as trust_mod
from ipatests.test_webui.ui_driver import screenshot
from ipatests.test_webui.task_range import range_tasks
import pytest
from ipatests.test_webui.task_range import (
range_tasks,
LOCAL_ID_RANGE,
TRUSTED_ID_RANGE,
)
ENTITY = 'idrange'
PKEY = 'itest-range'
@@ -33,13 +38,20 @@ PKEY = 'itest-range'
@pytest.mark.tier1
class test_range(range_tasks):
@pytest.fixture(autouse=True)
def range_setup(self, ui_driver_fsetup):
self.init_app()
self.get_shifts()
self.range_types = [LOCAL_ID_RANGE]
if self.has_trusts():
self.range_types.append(TRUSTED_ID_RANGE)
@screenshot
def test_crud(self):
"""
Basic CRUD: range
"""
self.init_app()
self.get_shifts()
self.basic_crud(ENTITY, self.get_data(PKEY), mod=False)
@screenshot
@@ -47,17 +59,9 @@ class test_range(range_tasks):
"""
Test mod operating in a new range
"""
data = self.get_data(PKEY)
self.init_app()
self.navigate_to_entity(ENTITY)
self.get_shifts()
add = self.get_add_data(PKEY)
data = self.get_data(PKEY, add_data=add)
self.add_record(ENTITY, data, facet='search', navigate=False,
facet_btn='add', dialog_name='add',
dialog_btn='add')
self.add_record(ENTITY, data)
self.navigate_to_record(PKEY)
# changes idrange and tries to save it
@@ -88,15 +92,12 @@ class test_range(range_tasks):
- 'ipa-ad-winsync' and 'ipa-ipa-trust' and are not supported yet
https://fedorahosted.org/freeipa/ticket/4323
"""
self.init_app()
self.get_shifts()
pkey_local = 'itest-local'
pkey_ad = 'itest-ad'
column = 'iparangetype'
add = self.get_add_data(pkey_local)
data = self.get_data(pkey_local, add_data=add)
data = self.get_data(pkey_local)
self.add_record(ENTITY, data)
self.assert_record_value('local domain range', pkey_local, column)
@@ -111,13 +112,251 @@ class test_range(range_tasks):
self.navigate_to_entity(ENTITY)
add = self.get_add_data(pkey_ad, range_type='ipa-ad-trust', domain=domain)
data = self.get_data(pkey_ad, add_data=add)
data = self.get_data(pkey_ad, range_type=TRUSTED_ID_RANGE,
domain=domain)
self.add_record(ENTITY, data, navigate=False)
self.assert_record_value('Active Directory domain range', pkey_ad, column)
self.assert_record_value('Active Directory domain range', pkey_ad,
column)
self.delete(trust_mod.ENTITY, [trust_data])
self.navigate_to_entity(ENTITY)
self.delete_record(pkey_ad)
self.delete_record(pkey_local)
@screenshot
def test_add_range_with_special_characters_in_name(self):
"""
Test creating ID Range with special characters in name
"""
data = self.get_data('itest-range-!@#$%^&*')
self.add_record(ENTITY, data, delete=True)
@screenshot
def test_add_range_with_existing_name(self):
"""
Test creating ID Range with existing range name
"""
for range_type in self.range_types:
pkey = 'itest-range-{}'.format(range_type)
data = self.get_data(pkey, range_type=range_type)
self.add_record(ENTITY, data)
self.add_record(ENTITY, data, navigate=False, negative=True,
pre_delete=False)
dialog = self.get_last_error_dialog()
try:
assert ('range with name "{}" already exists'.format(pkey)
in dialog.text)
finally:
self.delete_record(pkey)
@screenshot
def test_add_range_with_existing_base_id(self):
"""
Test creating ID Range with existing base ID
"""
for range_type in self.range_types:
pkey = 'itest-range-original'
form_data = self.get_add_form_data(pkey)
data = self.get_data(pkey, form_data=form_data)
form_data.range_type = range_type
duplicated_data = self.get_data(form_data=form_data)
self.add_record(ENTITY, data)
self.add_record(ENTITY, duplicated_data, navigate=False,
negative=True, pre_delete=False)
dialog = self.get_last_error_dialog()
try:
assert self.BASE_RANGE_OVERLAPS_ERROR in dialog.text
finally:
self.delete_record(pkey)
@screenshot
def test_add_range_overlaps_with_existing(self):
"""
Test creating ID Range with overlapping of existing range
"""
for range_type in self.range_types:
pkey = 'itest-range'
pkey_overlaps = 'itest-range-overlaps'
form_data = self.get_add_form_data(pkey)
data = self.get_data(pkey, form_data=form_data)
form_data_overlaps = self.get_add_form_data(
pkey_overlaps,
base_id=form_data.base_id + form_data.size - 1,
range_type=range_type
)
data_overlaps = self.get_data(form_data=form_data_overlaps)
self.add_record(ENTITY, data)
self.add_record(ENTITY, data_overlaps, navigate=False,
negative=True, pre_delete=False)
dialog = self.get_last_error_dialog()
try:
assert self.BASE_RANGE_OVERLAPS_ERROR in dialog.text
finally:
self.delete_record(pkey)
@screenshot
def test_add_range_with_overlapping_primary_and_secondary_rid(self):
"""
Test creating ID Range with overlapping of primary and secondary RID
"""
form_data = self.get_add_form_data(PKEY)
form_data.secondary_base_rid = form_data.base_rid
data = self.get_data(PKEY, form_data=form_data)
self.add_record(ENTITY, data, negative=True)
dialog = self.get_last_error_dialog()
try:
assert self.PRIMARY_AND_SECONDARY_RID_OVERLAP_ERROR in dialog.text
finally:
self.delete_record(PKEY)
@screenshot
def test_add_range_with_existing_base_rid(self):
"""
Test creating ID Range with existing primary RID base
"""
form_data = self.get_add_form_data(PKEY)
data = self.get_data(PKEY, form_data=form_data)
# Get RID base from previous form
duplicated_data = self.get_data(base_rid=form_data.base_rid)
self.add_record(ENTITY, data)
self.add_record(ENTITY, duplicated_data, navigate=False, negative=True,
pre_delete=False)
dialog = self.get_last_error_dialog()
try:
assert self.PRIMARY_RID_RANGE_OVERLAPS_ERROR in dialog.text
finally:
self.delete_record(PKEY)
@screenshot
def test_add_range_with_existing_secondary_rid(self):
"""
Test creating ID Range with existing secondary RID base
"""
form_data = self.get_add_form_data(PKEY)
data = self.get_data(PKEY, form_data=form_data)
# Get RID base from previous form
duplicated_data = self.get_data(
secondary_base_rid=form_data.secondary_base_rid
)
self.add_record(ENTITY, data)
self.add_record(ENTITY, duplicated_data, navigate=False, negative=True,
pre_delete=False)
dialog = self.get_last_error_dialog()
try:
assert self.SECONDARY_RID_RANGE_OVERLAPS_ERROR in dialog.text
finally:
self.delete_record(PKEY)
@screenshot
def test_add_range_without_rid(self):
"""
Test creating ID Range without giving rid-base or/and
secondary-rid-base values
"""
pkey = 'itest-range-without-rid'
# Without primary RID base
data = self.get_data(pkey, base_rid='')
self.add_record(ENTITY, data, negative=True)
try:
assert self.has_form_error('ipabaserid')
finally:
self.delete_record(pkey)
self.dialog_button_click('cancel')
# Without secondary RID base
data = self.get_data(pkey, secondary_base_rid='')
self.add_record(ENTITY, data, navigate=False, negative=True)
try:
assert self.has_form_error('ipasecondarybaserid')
finally:
self.delete_record(pkey)
self.dialog_button_click('cancel')
# Without primary and secondary RID bases
data = self.get_data(pkey, base_rid='', secondary_base_rid='')
self.add_record(ENTITY, data, navigate=False)
self.delete_record(pkey)
@screenshot
def test_modify_range_with_invalid_or_missing_values(self):
"""
Test modification ID range with empty values of options
"""
cases = [
# Empty values
{
'base_id': '',
'base_rid': '',
'secondary_base_rid': '',
'size': '',
},
# Out of range
{'base_id': 2 ** 32},
{'size': 2 ** 32},
{'base_rid': 2 ** 32},
{'secondary_base_rid': 2 ** 32},
# Invalid value
{'base_id': 1.1},
{'size': 1.1},
{'base_rid': 1.1},
{'secondary_base_rid': 1.1},
]
data = self.get_data(PKEY)
self.add_record(ENTITY, data)
self.navigate_to_record(PKEY)
for values in cases:
form_data = self.get_mod_form_data(**values)
self.fill_fields(form_data.serialize(), undo=True)
self.assert_facet_button_enabled('save')
self.facet_button_click('save')
self.assert_notification(
type='danger',
assert_text='Input form contains invalid or missing values.'
)
self.close_notifications()
self.facet_button_click('revert')
self.delete_record(PKEY)
@screenshot
def test_delete_primary_local_range(self):
"""
Test deleting primary local ID range
"""
ipa_realm = self.config.get('ipa_realm')
pkey = '{}_id_range'.format(ipa_realm)
self.navigate_to_entity(ENTITY)
self.delete_record(pkey)
self.assert_last_error_dialog(
self.DELETE_PRIMARY_LOCAL_RANGE_ERROR,
details=True
)

View File

@@ -21,6 +21,7 @@
SELinux user map tests
"""
from ipaplatform.constants import constants as platformconstants
from ipatests.test_webui.ui_driver import UI_driver
from ipatests.test_webui.ui_driver import screenshot
import ipatests.test_webui.data_user as user
@@ -41,13 +42,18 @@ RULE_ALR_EXIST = 'SELinux User Map rule with name "{}" already exists'
RULE_UPDATED = 'SELinux User Map {} updated'
RULE_ADDED = 'SELinux User Map successfully added'
INVALID_SEUSER = 'SELinux user {} not found in ordering list (in config)'
INVALID_MCS = ("invalid 'selinuxuser': Invalid MCS value, must match c[0-1023]"
".c[0-1023] and/or c[0-1023]-c[0-c0123]")
INVALID_MLS = ("invalid 'selinuxuser': Invalid MLS value, must match "
"s[0-15](-s[0-15])")
INVALID_MCS = ("invalid 'selinuxuser': Invalid MCS value, must match {}, "
"where max category {}").format(
platformconstants.SELINUX_MCS_REGEX,
platformconstants.SELINUX_MCS_MAX)
INVALID_MLS = ("invalid 'selinuxuser': Invalid MLS value, must match {}, "
"where max level {}").format(
platformconstants.SELINUX_MLS_REGEX,
platformconstants.SELINUX_MLS_MAX)
HBAC_DEL_ERR = ('{} cannot be deleted because SELinux User Map {} requires '
'it')
HBAC_MEMBER_ERR = 'HBAC rule and local members cannot both be set'
BATCH_ERR = 'Some operations failed.'
@pytest.mark.tier1
@@ -180,7 +186,7 @@ class test_selinuxusermap(UI_driver):
self.navigate_to_record(selinuxmap.PKEY, entity=selinuxmap.ENTITY)
self.add_table_associations('memberuser_user', ['admin'],
negative=True)
self.assert_last_error_dialog(HBAC_MEMBER_ERR)
self.assert_last_error_dialog(BATCH_ERR)
self.close_all_dialogs()
# test adding HBAC rule together with user (should FAIL)

View File

@@ -21,6 +21,7 @@
Service tests
"""
from ipatests.test_webui.crypto_utils import generate_certificate, generate_csr
from ipatests.test_webui.ui_driver import UI_driver
from ipatests.test_webui.ui_driver import screenshot
import pytest
@@ -147,21 +148,17 @@ class test_service(sevice_tasks):
"""
Test service certificate actions
Requires to have CA installed and 'service_csr_path' configuration
option set.
Requires to have CA installed.
"""
if not self.has_ca():
self.skip('CA is not configured')
csr_path = self.config.get('service_csr_path')
if not csr_path:
self.skip('CSR file is not configured')
self.init_app()
data = self.prep_data()
pkey = data.get('pkey')
csr = self.load_file(csr_path)
hostname = self.config.get('ipa_server')
csr = generate_csr(hostname)
cert_widget_sel = "div.certificate-widget"
self.add_record(ENTITY, data)
@@ -178,6 +175,8 @@ class test_service(sevice_tasks):
self.wait_for_request(n=2, d=3)
self.assert_visible(cert_widget_sel)
widget = self.find(cert_widget_sel, By.CSS_SELECTOR)
# cert view
self.action_list_action('view', confirm=False,
parents_css_sel=cert_widget_sel)
@@ -218,7 +217,8 @@ class test_service(sevice_tasks):
self.wait()
self.select('select', '6')
self.dialog_button_click('ok')
self.wait_for_request(n=2, d=3)
self.wait_while_working(widget)
self.assert_visible(cert_widget_sel + " div.watermark")
# check that revoke action is not enabled
@@ -241,7 +241,7 @@ class test_service(sevice_tasks):
parents_css_sel=cert_widget_sel)
self.wait()
self.dialog_button_click('ok')
self.wait_for_request(n=2)
self.wait_while_working(widget)
# check that revoke action is enabled
self.assert_action_list_action('revoke',
@@ -266,6 +266,8 @@ class test_service(sevice_tasks):
self.wait()
self.select('select', '1')
self.dialog_button_click('ok')
self.close_notifications()
self.wait_while_working(widget)
# check that revoke action is not enabled
self.assert_action_list_action('revoke', enabled=False,
@@ -285,17 +287,12 @@ class test_service(sevice_tasks):
def test_arbitrary_certificates(self):
"""
Test managing service arbitrary certificate.
Requires to have 'arbitrary_cert_path' configuration set.
"""
cert_path = self.config.get('arbitrary_cert_path')
if not cert_path:
self.skip('Arbitrary certificate file is not configured')
self.init_app()
data = self.prep_data()
pkey = data.get('pkey')
cert = self.load_file(cert_path)
hostname = self.config.get('ipa_server')
cert = generate_certificate(hostname)
cert_widget_sel = "div.certificate-widget"
self.add_record(ENTITY, data)
@@ -306,9 +303,9 @@ class test_service(sevice_tasks):
# add certificate
self.button_click('add', parents_css_sel="div[name='certificate']")
self.assert_dialog()
self.assert_dialog('cert-add-dialog')
self.fill_textarea('new_cert', cert)
self.dialog_button_click('add')
self.dialog_button_click('ok')
self.assert_visible(cert_widget_sel)
@@ -626,11 +623,8 @@ class test_service(sevice_tasks):
if not self.has_ca():
self.skip('CA is not configured')
csr_path = self.config.get('service_csr_path')
if not csr_path:
self.skip('CSR file is not configured')
csr = self.load_file(csr_path)
hostname = self.config.get('ipa_server')
csr = generate_csr(hostname)
self.init_app()
pkey = self.get_service_pkey('cifs')

View File

@@ -21,6 +21,8 @@
Trust tests
"""
import ipatests.test_webui.data_group as group
import ipatests.test_webui.data_idviews as idview
from ipatests.test_webui.ui_driver import UI_driver
from ipatests.test_webui.ui_driver import screenshot
from ipatests.test_webui.task_range import range_tasks
@@ -29,6 +31,8 @@ import pytest
ENTITY = 'trust'
CONFIG_ENTITY = 'trustconfig'
DEFAULT_TRUST_VIEW = 'Default Trust View'
CONFIG_DATA = {
'mod': [
['combobox', 'ipantfallbackprimarygroup', 'admins'],
@@ -45,6 +49,10 @@ CONFIG_DATA2 = {
@pytest.mark.tier1
class trust_tasks(UI_driver):
@pytest.fixture(autouse=True)
def trusttasks_setup(self, ui_driver_fsetup):
pass
def get_data(self, add_data=None):
domain = self.config.get('ad_domain')
@@ -98,8 +106,10 @@ class trust_tasks(UI_driver):
@pytest.mark.tier1
class test_trust(trust_tasks):
def setup(self, *args, **kwargs):
super(test_trust, self).setup(*args, **kwargs)
request_timeout = 120
@pytest.fixture(autouse=True)
def trust_setup(self, trusttasks_setup):
if not self.has_trusts():
self.skip('Trusts not configured')
@@ -127,9 +137,9 @@ class test_trust(trust_tasks):
r_tasks.driver = self.driver
r_tasks.config = self.config
r_tasks.get_shifts()
range_add = r_tasks.get_add_data('')
base_id = range_add[2][2]
range_size = range_add[3][2]
range_form = r_tasks.get_add_form_data('')
base_id = range_form.base_id
range_size = range_form.size
range_pkey = self.get_range_name()
column = 'iparangetype'
@@ -143,7 +153,6 @@ class test_trust(trust_tasks):
self.assert_record_value('Active Directory domain range', range_pkey, column)
self.delete_record(range_pkey)
self.request_timeout = 60
add = self.get_add_data('ipa-ad-trust-posix', base_id, range_size)
data = self.get_data(add_data=add)
self.add_record(ENTITY, data, delete=True)
@@ -159,3 +168,40 @@ class test_trust(trust_tasks):
self.mod_record(CONFIG_ENTITY, CONFIG_DATA)
self.mod_record(CONFIG_ENTITY, CONFIG_DATA2)
@screenshot
def test_group_member_idoverrideuser(self):
self.init_app()
# Create new trust
data = self.get_data()
self.add_record(ENTITY, data)
# Create an user ID override
ad_domain = self.config.get('ad_domain')
ad_admin = self.config.get('ad_admin')
idoverrideuser_pkey = '{}@{}'.format(ad_admin, ad_domain).lower()
self.navigate_to_record(DEFAULT_TRUST_VIEW, entity=idview.ENTITY)
self.add_record(idview.ENTITY, {
'pkey': idoverrideuser_pkey,
'add': [
('textbox', 'ipaanchoruuid_default', idoverrideuser_pkey),
],
}, facet='idoverrideuser')
# Create new group and add the user ID override there
self.navigate_to_entity(group.ENTITY)
self.add_record(group.ENTITY, group.DATA)
self.navigate_to_record(group.PKEY)
self.add_associations([idoverrideuser_pkey],
facet='member_idoverrideuser', delete=True)
# Clean up data
self.navigate_to_entity(group.ENTITY)
self.delete_record(group.PKEY)
self.navigate_to_record(DEFAULT_TRUST_VIEW, entity=idview.ENTITY)
self.delete_record(idoverrideuser_pkey)
self.navigate_to_entity(ENTITY)
self.delete_record(ad_domain)

View File

@@ -21,6 +21,7 @@
User tests
"""
from ipatests.test_webui.crypto_utils import generate_csr
from ipatests.test_webui.ui_driver import UI_driver
from ipatests.test_webui.ui_driver import screenshot
import ipatests.test_webui.data_user as user
@@ -57,6 +58,7 @@ LONG_LOGIN = "invalid 'login': can be at most 32 characters"
INV_PASSWD = ("invalid 'password': Leading and trailing spaces are "
"not allowed")
@pytest.mark.tier1
class user_tasks(UI_driver):
def load_file(self, path):
@@ -122,11 +124,16 @@ class test_user(user_tasks):
self.navigate_to_entity(user.ENTITY)
self.navigate_to_record(user.PKEY)
self.add_associations([group.PKEY, 'editors'], facet='memberof_group', delete=True)
self.add_associations([netgroup.PKEY], facet='memberof_netgroup', delete=True)
self.add_associations([rbac.ROLE_PKEY], facet='memberof_role', delete=True)
self.add_associations([hbac.RULE_PKEY], facet='memberof_hbacrule', delete=True)
self.add_associations([sudo.RULE_PKEY], facet='memberof_sudorule', delete=True)
self.add_associations([group.PKEY, 'editors'],
facet='memberof_group', delete=True)
self.add_associations([netgroup.PKEY],
facet='memberof_netgroup', delete=True)
self.add_associations([rbac.ROLE_PKEY],
facet='memberof_role', delete=True)
self.add_associations([hbac.RULE_PKEY],
facet='memberof_hbacrule', delete=True)
self.add_associations([sudo.RULE_PKEY],
facet='memberof_sudorule', delete=True)
# cleanup
# -------
@@ -178,13 +185,17 @@ class test_user(user_tasks):
self.navigate_to_record(user.PKEY)
self.assert_indirect_record(group.PKEY2, user.ENTITY, 'memberof_group')
self.assert_indirect_record(netgroup.PKEY, user.ENTITY, 'memberof_netgroup')
self.assert_indirect_record(rbac.ROLE_PKEY, user.ENTITY, 'memberof_role')
self.assert_indirect_record(hbac.RULE_PKEY, user.ENTITY, 'memberof_hbacrule')
self.assert_indirect_record(sudo.RULE_PKEY, user.ENTITY, 'memberof_sudorule')
self.assert_indirect_record(netgroup.PKEY,
user.ENTITY, 'memberof_netgroup')
self.assert_indirect_record(rbac.ROLE_PKEY,
user.ENTITY, 'memberof_role')
self.assert_indirect_record(hbac.RULE_PKEY,
user.ENTITY, 'memberof_hbacrule')
self.assert_indirect_record(sudo.RULE_PKEY,
user.ENTITY, 'memberof_sudorule')
## cleanup
## -------
# cleanup
# -------
self.delete(user.ENTITY, [user.DATA])
self.delete(group.ENTITY, [group.DATA, group.DATA2])
self.delete(netgroup.ENTITY, [netgroup.DATA])
@@ -210,7 +221,8 @@ class test_user(user_tasks):
self.reset_password_action(pwd)
self.assert_text_field('has_password', '******')
self.action_list_action('unlock')
# unlock option should be disabled for new user
self.assert_action_list_action('unlock', enabled=False)
# delete
self.delete_action(user.ENTITY, user.PKEY, action='delete_active_user')
@@ -220,20 +232,13 @@ class test_user(user_tasks):
"""
Test user certificate actions
Requires to have CA installed and 'user_csr_path' configuration option
set.
Requires to have CA installed.
"""
if not self.has_ca():
self.skip('CA is not configured')
csr_path = self.config.get('user_csr_path')
if not csr_path:
self.skip('CSR file is not configured')
self.init_app()
# ENHANCEMENT: generate csr dynamically
csr = self.load_file(csr_path)
cert_widget_sel = "div.certificate-widget"
self.add_record(user.ENTITY, user.DATA)
@@ -242,7 +247,10 @@ class test_user(user_tasks):
self.navigate_to_record(user.PKEY)
# cert request
csr = generate_csr(user.PKEY, False)
self.action_list_action('request_cert', confirm=False)
self.wait(seconds=2)
self.assert_dialog()
self.fill_text("textarea[name='csr']", csr)
self.dialog_button_click('issue')
@@ -355,7 +363,7 @@ class test_user(user_tasks):
self.navigate_to_record(user.PKEY, entity=user.ENTITY)
self.reset_password_action(pwd)
#re-login as new user
# re-login as new user
self.logout()
self.init_app(user.PKEY, pwd)
@@ -653,6 +661,29 @@ class test_user(user_tasks):
self.delete_record([user.PKEY, user.PKEY2, user.PKEY_NO_LOGIN,
'nsurname10'])
@screenshot
def test_menu_click_minimized_window(self):
"""
Test if menu is clickable when there is notification
in minimized browser window.
related: https://pagure.io/freeipa/issue/8120
"""
self.init_app()
self.driver.set_window_size(570, 600)
self.add_record(user.ENTITY, user.DATA2, negative=True)
self.assert_notification(assert_text=USR_ADDED)
menu_button = self.find('.navbar-toggle', By.CSS_SELECTOR)
menu_button.click()
self.assert_record(user.PKEY2)
self.close_notifications()
self.driver.maximize_window()
# cleanup
self.delete(user.ENTITY, [user.DATA2])
@pytest.mark.tier1
class test_user_no_private_group(UI_driver):

View File

@@ -32,6 +32,10 @@ import pytest
@pytest.mark.tier1
class vault_tasks(UI_driver):
@pytest.fixture(autouse=True)
def vault_tasks_setup(self, ui_driver_fsetup):
pass
def prep_service_data(self):
host = self.config.get('ipa_server')
@@ -64,8 +68,8 @@ class vault_tasks(UI_driver):
@pytest.mark.tier1
class test_vault(vault_tasks):
def setup(self, *args, **kwargs):
super(test_vault, self).setup(*args, **kwargs)
@pytest.fixture(autouse=True)
def vault_setup(self, vault_tasks_setup):
if not self.has_kra():
self.skip('KRA not configured')

View File

@@ -28,25 +28,26 @@ from datetime import datetime
import time
import re
import os
from functools import wraps
import unittest
import paramiko
# pylint: disable=import-error
from six.moves.urllib.error import URLError
# pylint: enable=import-error
from functools import wraps
from urllib.error import URLError
import pytest
try:
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import InvalidElementStateException
from selenium.common.exceptions import StaleElementReferenceException
from selenium.common.exceptions import UnexpectedAlertPresentException
from selenium.common.exceptions import WebDriverException
from selenium.common.exceptions import ElementClickInterceptedException
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.support.expected_conditions import alert_is_present
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support.ui import Select
NO_SELENIUM = False
@@ -57,8 +58,12 @@ try:
NO_YAML = False
except ImportError:
NO_YAML = True
from ipaplatform.paths import paths
from ipatests.pytest_ipa.integration import tasks
ENV_MAP = {
'MASTER': 'ipa_server',
'ADMINID': 'ipa_admin',
@@ -95,11 +100,9 @@ def screenshot(fn):
Should be applied on methods of UI_driver subclasses
"""
@wraps(fn)
def screenshot_wrapper(*args):
def screenshot_wrapper(*args, **kwargs):
try:
return fn(*args)
except unittest.SkipTest:
raise
return fn(*args, **kwargs)
except Exception:
self = args[0]
name = '%s_%s_%s' % (
@@ -112,26 +115,68 @@ def screenshot(fn):
return screenshot_wrapper
class UI_driver(object):
def dismiss_unexpected_alert(fn):
"""
Temporary fix for UnexpectedAlertPresentException.
It is regression in Firefox 55
Fixed in Firefox 65:
https://bugzilla.mozilla.org/show_bug.cgi?id=1503015
"""
@wraps(fn)
def wrapped(*args, **kwargs):
self = args[0]
try:
return fn(*args, **kwargs)
except UnexpectedAlertPresentException:
if alert_is_present()(self.driver):
self.driver.switch_to.alert.dismiss()
# One retry is enough for now.
# But in the case of catching two alerts at the same time
# loop or recursive call should be used.
return fn(*args, **kwargs)
return wrapped
def repeat_on_stale_parent_reference(fn):
"""
The decorator repeats a function once when StaleElementReferenceException
is caught.
It is not applicable if a parent reference is created outside a function.
"""
@wraps(fn)
def wrapped(*args, **kwargs):
if ('parent' in kwargs) and kwargs['parent'] is None:
try:
return fn(*args, **kwargs)
except StaleElementReferenceException:
pass
return fn(*args, **kwargs)
return wrapped
class UI_driver:
"""
Base class for all UI integration tests
"""
request_timeout = 60
@classmethod
def setup_class(cls):
@pytest.fixture(autouse=True, scope="class")
def ui_driver_setup(self, request):
cls = request.cls
if NO_SELENIUM:
raise unittest.SkipTest('Selenium not installed')
pytest.skip('Selenium not installed')
cls.load_config()
def setup(self):
@pytest.fixture(autouse=True)
def ui_driver_fsetup(self, request):
self.driver = self.get_driver()
self.driver.maximize_window()
def teardown(self):
self.driver.delete_all_cookies()
self.driver.quit()
def fin():
self.driver.delete_all_cookies()
self.driver.quit()
request.addfinalizer(fin)
@classmethod
def load_config(cls):
@@ -149,9 +194,9 @@ class UI_driver(object):
with open(path, 'r') as conf:
cls.config = yaml.load(conf)
except yaml.YAMLError as e:
raise unittest.SkipTest("Invalid Web UI config.\n%s" % e)
pytest.skip("Invalid Web UI config.\n%s" % e)
except IOError as e:
raise unittest.SkipTest(
pytest.skip(
"Can't load Web UI test config: %s" % e
)
else:
@@ -190,7 +235,7 @@ class UI_driver(object):
if driver_type == 'remote':
if 'host' not in cls.config:
raise unittest.SkipTest('Selenium server host not configured')
pytest.skip('Selenium server host not configured')
host = cls.config["host"]
if browser == 'chrome':
@@ -206,11 +251,11 @@ class UI_driver(object):
command_executor='http://%s:%d/wd/hub' % (host, port),
desired_capabilities=capabilities)
except URLError as e:
raise unittest.SkipTest(
pytest.skip(
'Error connecting to selenium server: %s' % e
)
except RuntimeError as e:
raise unittest.SkipTest(
pytest.skip(
'Error while establishing webdriver: %s' % e
)
else:
@@ -226,16 +271,17 @@ class UI_driver(object):
ff_log_path = cls.config.get("geckodriver_log_path")
driver = webdriver.Firefox(fp, log_path=ff_log_path)
except URLError as e:
raise unittest.SkipTest(
pytest.skip(
'Error connecting to selenium server: %s' % e
)
except RuntimeError as e:
raise unittest.SkipTest(
pytest.skip(
'Error while establishing webdriver: %s' % e
)
return driver
@dismiss_unexpected_alert
def find(self, expression, by='id', context=None, many=False, strict=False):
"""
Helper which calls selenium find_element_by_xxx methods.
@@ -270,6 +316,10 @@ class UI_driver(object):
return result
def find_by_selector(self, expression, context=None, **kwargs):
return self.find(expression, by=By.CSS_SELECTOR, context=context,
**kwargs)
def files_loaded(self):
"""
Test if dependencies were loaded. (Checks if UI has been rendered)
@@ -333,6 +383,19 @@ class UI_driver(object):
self.wait()
self.wait(d)
def wait_while_working(self, widget, implicit=0.2):
"""
Wait while working widget active
"""
working_widget = self.find('.working-widget', By.CSS_SELECTOR, widget)
self.wait(implicit)
WebDriverWait(self.driver, self.request_timeout).until_not(
lambda d: working_widget.is_displayed()
)
self.wait(0.5)
def xpath_has_val(self, attr, val):
"""
Create xpath expression for matching a presence of item in attribute
@@ -453,7 +516,7 @@ class UI_driver(object):
parent = parts[0:-1]
self.navigate_by_menu('/'.join(parent), complete)
s = ".navbar a[href='#%s']" % item
s = ".navbar li[data-name='%s'] a" % item
link = self.find(s, By.CSS_SELECTOR, strict=True)
assert link.is_displayed(), 'Navigation link is not displayed: %s' % item
link.click()
@@ -687,9 +750,16 @@ class UI_driver(object):
def _button_click(self, selector, parent, name=''):
btn = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
# The small timeout (up to 5 seconds) allows to prevent exceptions when
# driver attempts to click a button before it is rendered.
WebDriverWait(self.driver, 5, 0.2).until(
lambda d: btn.is_displayed(),
'Button is not displayed: %s' % (name or selector)
)
self.move_to_element_in_page(btn)
disabled = btn.get_attribute("disabled")
assert btn.is_displayed(), 'Button is not displayed: %s' % name
assert not disabled, 'Invalid button state: disabled. Button: %s' % name
btn.click()
self.wait_for_request()
@@ -769,7 +839,8 @@ class UI_driver(object):
parent = self.get_form()
tb = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
try:
tb.clear()
tb.send_keys(Keys.CONTROL + 'a')
tb.send_keys(Keys.DELETE)
tb.send_keys(value)
except InvalidElementStateException as e:
msg = "Invalid Element State, el: %s, value: %s, error: %s" % (selector, value, e)
@@ -807,6 +878,11 @@ class UI_driver(object):
parent = self.get_form()
self.fill_text(search_field_s, value, parent)
def apply_search_filter(self, value):
self.fill_search_filter(value)
actions = ActionChains(self.driver)
actions.send_keys(Keys.ENTER).perform()
def add_multivalued(self, name, value, parent=None):
"""
Add new value to multivalued textbox
@@ -955,7 +1031,8 @@ class UI_driver(object):
self.wait_for_request()
list_cnt = self.find('.combobox-widget-list', By.CSS_SELECTOR, cb, strict=True)
opt_s = "select[name=list] option[value='%s']" % value
opt_s = 'select[name=list] option'
opt_s += "[value='%s']" % value if value else ':not([value])'
option = self.find(opt_s, By.CSS_SELECTOR, cb)
if combobox_input:
@@ -1138,6 +1215,7 @@ class UI_driver(object):
val = el.text
return val
@repeat_on_stale_parent_reference
def has_record(self, pkey, parent=None, table_name=None):
"""
Check if table contains specific record.
@@ -1541,7 +1619,7 @@ class UI_driver(object):
self.delete_record(pkey, data.get('del'))
self.close_notifications()
def add_table_record(self, name, data, parent=None):
def add_table_record(self, name, data, parent=None, add_another=False):
"""
Add record to dnsrecord table, association table and similar
"""
@@ -1554,9 +1632,25 @@ class UI_driver(object):
btn.click()
self.wait()
self.fill_fields(data['fields'])
self.dialog_button_click('add')
if not add_another:
self.dialog_button_click('add')
self.wait_for_request()
def add_another_table_record(self, data, add_another=False):
"""
Add table record after creating previous one in the same dialog
TODO: Create class to manipulate such type of dialog
"""
self.dialog_button_click('add_and_add_another')
self.wait_for_request()
self.fill_fields(data['fields'])
if not add_another:
self.dialog_button_click('add')
self.wait_for_request()
def prepare_associations(
self, pkeys, facet=None, facet_btn='add', member_pkeys=None,
confirm_btn='add', search=False):
@@ -1884,20 +1978,13 @@ class UI_driver(object):
hostname = self.config.get('ipa_server')
password = self.config.get('ipa_password')
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=hostname, username=login, password=password)
ssh.exec_command(cmd)
except paramiko.AuthenticationException:
self.skip('Authentication to server {} failed'.format(hostname))
except paramiko.SSHException as e:
self.skip('Unable to establish SSH connection: {}'.format(e))
except Exception as e:
self.skip('Unable to proceed: {}'.format(e))
finally:
ssh.close()
tasks.run_ssh_cmd(
to_host=hostname, username=login,
auth_method="password", password=password,
cmd=cmd
)
@dismiss_unexpected_alert
def has_class(self, el, cls):
"""
Check if el has CSS class
@@ -1905,11 +1992,23 @@ class UI_driver(object):
class_attr = el.get_attribute("class")
return bool(class_attr) and cls in class_attr.split()
def has_form_error(self, name):
"""
Check if form field has error
TODO: Move to some mixin class
"""
form_group = self.find(
'//input[@name="{}"]/ancestor'
'::div[contains(@class, "form-group")]'.format(name),
By.XPATH
)
return self.has_class(form_group, 'has-error')
def skip(self, reason):
"""
Skip tests
"""
raise unittest.SkipTest(reason)
pytest.skip(reason)
def assert_text(self, selector, value, parent=None):
"""
@@ -2001,7 +2100,7 @@ class UI_driver(object):
Assert that current facet is correct
"""
info = self.get_facet_info()
if not facet is None:
if facet is not None:
assert info["name"] == facet, "Invalid facet. Expected: %s, Got: %s " % (facet, info["name"])
assert info["entity"] == entity, "Invalid entity. Expected: %s, Got: %s " % (entity, info["entity"])