Skip to content
This repository has been archived by the owner on Apr 7, 2022. It is now read-only.

[1LP][RFR] Improve email tests. #10177

Merged
merged 7 commits into from
Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cfme/fixtures/smtp.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,11 @@ def _finalize():
assert collector.poll() is None, "Collector has died. Something must be blocking selected ports"
logger.info("Collector alive")
query_port_open = net_check_remote(mail_query_port, my_ip, force=True)
if query_port_open:
logger.warning('The SMTP collector query port seem to be closed.')
server_port_open = net_check_remote(mail_server_port, my_ip, force=True)
assert query_port_open and server_port_open,\
'Ports {} and {} on the machine executing the tests are closed.\n'\
'The ports are randomly chosen -> turn firewall off.'\
.format(mail_query_port, mail_server_port)
assert server_port_open, (f"The destination {my_ip} port {mail_server_port} seem "
"to be unreachable by the appliance.")
client = SMTPCollectorClient(
my_ip,
mail_query_port
Expand Down
79 changes: 0 additions & 79 deletions cfme/tests/automate/test_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,85 +276,6 @@ def test_task_id_for_method_automation_log(request, generic_catalog_item):
assert result.validate(wait="60s")


@pytest.mark.meta(server_roles="+notifier")
def test_send_email_method(smtp_test, klass):
"""
Polarion:
assignee: dgaikwad
initialEstimate: 1/20h
startsin: 5.10
casecomponent: Automate

Bugzilla:
1688500
1702304
"""
mail_to = fauxfactory.gen_email()
mail_cc = fauxfactory.gen_email()
mail_bcc = fauxfactory.gen_email()
schema_field = fauxfactory.gen_alphanumeric()

# Ruby code to send emails
script = (
'to = "{mail_to}"\n'
'subject = "Hello"\n'
'body = "Hi"\n'
'bcc = "{mail_bcc}"\n'
'cc = "{mail_cc}"\n'
'content_type = "message"\n'
'from = "cfadmin@cfserver.com"\n'
"$evm.execute(:send_email, to, from, subject, body, {{:bcc => bcc, :cc => cc,"
":content_type => content_type}})"
)
script = script.format(mail_cc=mail_cc, mail_bcc=mail_bcc, mail_to=mail_to)

# Adding schema for executing method - send_email which helps to send emails
klass.schema.add_fields({'name': schema_field, 'type': 'Method', 'data_type': 'String'})

# Adding method - send_email for sending mails
method = klass.methods.create(
name=fauxfactory.gen_alphanumeric(),
display_name=fauxfactory.gen_alphanumeric(),
location='inline',
script=script)

# Adding instance to call automate method - send_email
instance = klass.instances.create(
name=fauxfactory.gen_alphanumeric(),
display_name=fauxfactory.gen_alphanumeric(),
description=fauxfactory.gen_alphanumeric(),
fields={schema_field: {'value': method.name}}
)

result = LogValidator(
"/var/www/miq/vmdb/log/evm.log",
matched_patterns=[
'.*:to=>"{mail_to}".*.*:cc=>"{mail_cc}".*.*:bcc=>"{mail_bcc}".*'.format(
mail_to=mail_to, mail_cc=mail_cc, mail_bcc=mail_bcc
)
],
)
result.start_monitoring()

# Executing automate method - send_email using simulation
simulate(
appliance=klass.appliance,
attributes_values={
"namespace": klass.namespace.name,
"class": klass.name,
"instance": instance.name,
},
message="create",
request="Call_Instance",
execute_methods=True,
)
assert result.validate(wait="60s")

# TODO(GH-8820): This issue should be fixed to check mails sent to person in 'cc' and 'bcc'
# Check whether the mail sent via automate method really arrives
wait_for(lambda: len(smtp_test.get_emails(to_address=mail_to)) > 0, num_sec=60, delay=10)


@pytest.fixture(scope="module")
def generic_object_definition(appliance):
# Creating generic object using REST
Expand Down
128 changes: 120 additions & 8 deletions cfme/tests/cloud_infra_common/test_provisioning.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# These tests don't work at the moment, due to the security_groups multi select not working
# in selenium (the group is selected then immediately reset)
import email.parser
import email.policy
import email.utils
import typing
from email.message import EmailMessage
from itertools import chain
from textwrap import dedent

import pytest
Expand All @@ -17,12 +21,17 @@
from cfme.utils import normalize_text
from cfme.utils.appliance.implementations.ui import navigate_to
from cfme.utils.blockers import BZ
from cfme.utils.blockers import GH
from cfme.utils.generators import random_vm_name
from cfme.utils.log import logger
from cfme.utils.providers import ProviderFilter
from cfme.utils.update import update
from cfme.utils.wait import wait_for


Checker = typing.NewType('Checker', typing.Callable[[object], bool])


pytestmark = [
pytest.mark.meta(server_roles="+automate +notifier"),
test_requirements.provision,
Expand Down Expand Up @@ -133,7 +142,6 @@ def _post_approval(smtp_test, provision_request, vm_type, requester, provider, a
approved_from = normalize_text(f"{vm_type} request from {requester}was approved")

wait_for_messages_with_subjects(smtp_test, {approved_subject, approved_from}, num_sec=90)

smtp_test.clear_database()

# Wait for the VM to appear on the provider backend before proceeding
Expand Down Expand Up @@ -186,16 +194,107 @@ def _check_subjects():
message='Some expected subjects not found in the received emails subjects.')


@pytest.mark.meta(automates=[1472844, 1676910, 1818172, 1380197])
def multichecker_factory(all_checkers: typing.Iterable[Checker]) -> Checker:
all_checkers = tuple(all_checkers)

def _item_checker(item):
logger.debug(f'Checking: {item}')
for checker in all_checkers:
if not checker(item):
logger.debug(f'Failure from checker: {checker}.')
return False
else:
logger.debug(f'Success from checker: {checker}.')
return True
return _item_checker


class AddressHeaderChecker:
def __init__(self, example: EmailMessage, checked_field: str):
self.checked_field = checked_field
self.example_values = self.normalized_field_vals(example)

def normalized_field_vals(self, eml: EmailMessage):
addresses = eml.get_all(self.checked_field)
assert addresses
return {a[1] for a in email.utils.getaddresses(addresses)}

def __call__(self, received_email: EmailMessage) -> bool:
found_values = self.normalized_field_vals(received_email)
if not found_values == self.example_values:
logger.debug(f"Field {self.checked_field} values {found_values} "
f"are different to expected {self.example_values}.")
return False
return True

def __str__(self):
return f'{self.__class__.__name__}<{self.checked_field}, {self.example_values}>'


# Note the Bcc is not present in the received email. It is a part of rcpttos.
ADDRESS_FIELDS = "From To Cc rcpttos".split()


def wait_for_expected_email_arrived(smtp, subject, example, num_sec, delay):
eml_checker = (multichecker_factory(AddressHeaderChecker(example, field)
for field in ADDRESS_FIELDS))

def _email_message_with_rcpttos_header(eml):
msg = email.message_from_string(eml['data'], policy=email.policy.strict)
msg.add_header('rcpttos', ', '.join(eml['rcpttos']))
return msg

def _expected_email_arrived():
emails = smtp.get_emails()
messages = (_email_message_with_rcpttos_header(m)
for m in emails if subject in normalize_text(m['subject']))
return all(eml_checker(m) for m in messages)

wait_for(_expected_email_arrived, num_sec=num_sec, delay=delay)


@pytest.fixture(scope='module')
def email_addresses_configuration(request, domain):
original_instance = (
domain.appliance.collections.domains.instantiate("ManageIQ")
.namespaces.instantiate("Configuration")
.classes.instantiate("Email")
.instances.instantiate("Default")
)
original_instance.copy_to(domain=domain)

email_configuration = (
domain.namespaces.instantiate('Configuration')
.classes.instantiate('Email')
.instances.instantiate('Default')
)

test_data = {
'default_recipient': ('default_recipient@example.com',),
'approver': ('approver@example.com',),
'cc': ('first-cc@example.com', 'second-cc@example.com',),
'bcc': ('first-bcc@example.com', 'second-bcc@example.com'),
'from': ('from@example.com',),
}

with update(email_configuration):
email_configuration.fields = {k: {'value': ', '.join(v)} for k, v in test_data.items()}

request.addfinalizer(email_configuration.delete_if_exists)
yield test_data


@pytest.mark.meta(automates=[1472844, 1676910, 1818172, 1380197, 1688500, 1702304, 1783511,
GH(('ManageIQ/manageiq', 20260))])
@pytest.mark.parametrize("action", ["edit", "approve", "deny"])
def test_provision_approval(appliance, provider, vm_name, smtp_test, request,
action, soft_assert):
action, soft_assert, email_addresses_configuration):
""" Tests provisioning approval. Tests couple of things.

* Approve manually
* Approve by editing the request to conform

Prerequisities:
Prerequisites:
* A provider that can provision.
* Automate role enabled
* User with e-mail set so you can receive and view them
Expand Down Expand Up @@ -238,7 +337,6 @@ def test_provision_approval(appliance, provider, vm_name, smtp_test, request,
vm_names = [vm_name + "001", vm_name + "002"]
requester = "vm_provision@cfmeqe.com " # include trailing space for clean formatting
if provider.one_of(CloudProvider):
requester = "" if BZ(1818172).blocks else requester
vm_type = "instance"
else:
vm_type = "virtual machine"
Expand All @@ -255,7 +353,21 @@ def test_provision_approval(appliance, provider, vm_name, smtp_test, request,
# requester includes the trailing space
pending_from = normalize_text(f"{vm_type} request from {requester}pending approval")

def msg_from_dict(msg_dict) -> EmailMessage:
to, = (msg_dict['default_recipient']
if GH(('ManageIQ/manageiq', 20260)).blocks
else msg_dict['approver'])
msg = EmailMessage()
msg.add_header('from', ', '.join(msg_dict['from']))
msg.add_header('cc', ', '.join(msg_dict['cc']))
msg.add_header('to', to)
msg.add_header('rcpttos', ', '.join(chain(msg_dict['cc'], msg_dict['bcc'], [to])))
return msg

wait_for_messages_with_subjects(smtp_test, {pending_subject, pending_from}, num_sec=90)
SUBJ_APPR_PENDING = normalize_text(f'Instance Request from {requester} Pending Approval')
wait_for_expected_email_arrived(smtp_test, SUBJ_APPR_PENDING,
msg_from_dict(email_addresses_configuration), num_sec=1, delay=0)

smtp_test.clear_database()

Expand All @@ -273,7 +385,7 @@ def _action_edit():
}
provision_request = appliance.collections.requests.instantiate(cells=cells)
provision_request.edit_request(values=modifications)
vm_names = [new_vm_name] # Will be just one now
vm_names = [new_vm_name] # Will be just one at this moment
request.addfinalizer(
lambda: collection.instantiate(new_vm_name, provider).cleanup_on_provider()
)
Expand Down
4 changes: 2 additions & 2 deletions cfme/utils/blockers.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ def __init__(self, description, **kwargs):
self.until = kwargs.get('until')
if isinstance(description, (list, tuple)):
try:
self.repo, self.issue = description
self.issue = int(self.issue)
self._repo, issue = description
self.issue = int(issue)
except ValueError:
raise ValueError(
"The GH issue specification must have 2 items and issue must be number")
Expand Down
16 changes: 10 additions & 6 deletions cfme/utils/net.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re
import socket
from collections import defaultdict
from textwrap import dedent

from cfme.fixtures.pytest_store import store
from cfme.utils.log import logger
Expand Down Expand Up @@ -145,14 +146,17 @@ def net_check_remote(port, addr=None, machine_addr=None, ssh_creds=None, force=F
)
with ssh_client:
# on exception => fails with return code 1
cmd = '''python2 -c "
import sys, socket
addr = socket.gethostbyname('%s')
socket.create_connection((addr, %d), timeout=10)
sys.exit(0)
"''' % (addr, port)
cmd = dedent(f'''\
python3 -c "
import sys, socket
addr = socket.gethostbyname('{addr:s}')
socket.create_connection((addr, {port:d}), timeout=10)
sys.exit(0)
"''')
result = ssh_client.run_command(cmd)
_ports[addr][port] = result.success
if not result.success:
logger.debug(f'The net_check_remote failed:\n{result.output}')
return _ports[addr][port]


Expand Down
3 changes: 3 additions & 0 deletions conf/env.yaml.template
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ bugzilla:
- ON_DEV
- NEW
- ASSIGNED
mail_collector:
ports:
smtp: 25
10 changes: 6 additions & 4 deletions data/templates/smtp_result.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,19 @@
<div class="col-md-10">
<strong>{{email.subject}}</strong>
<br />
<em><u>From:</u>&nbsp; {{email.source}}</em>
<em><u>From:</u>&nbsp; {{email.from_address}}</em>
<br />
<em><u>To:</u>&nbsp; {{email.destination}}</em>
<em><u>To:</u>&nbsp; {{email.to_address}}</em>
<br />
<em><u>Received at:</u>&nbsp; {{email.received}}</em>
<em><u>rcpttos:</u>&nbsp; {{email.rcpttos}}</em>
<br />
<em><u>Received at:</u>&nbsp; {{email.time}}</em>
<br />
</div>
</div>
</div>
<div class="panel-body">
<pre>{{email.body}}</pre>
<pre>{{email.text}}</pre>
</div>
</div>
{% endfor %}
Expand Down
Loading