django-helpdesk/helpdesk/tests/utils.py

270 lines
10 KiB
Python
Raw Normal View History

"""UItility functions facilitate making unit testing easier and less brittle."""
2022-09-09 00:40:49 +02:00
from PIL import Image
import email
from email import encoders
from email.message import Message
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
2022-09-09 00:40:49 +02:00
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
2022-09-09 00:40:49 +02:00
import factory
import faker
from io import BytesIO
from numpy.random import randint
2022-09-09 00:40:49 +02:00
import random
import re
import string
import typing
from typing import Any, Optional, Tuple
import unicodedata
def strip_accents(text):
"""
Strip accents from input String. (only works on Pythin 3
:param text: The input string.
:type text: String.
:returns: The processed String.
:rtype: String.
"""
text = unicodedata.normalize('NFD', text)
text = text.encode('ascii', 'ignore')
text = text.decode("utf-8")
return str(text)
def text_to_id(text):
"""
Convert input text to id.
:param text: The input string.
:type text: String.
:returns: The processed String.
:rtype: String.
"""
text = strip_accents(text.lower())
text = re.sub('[ ]+', '_', text)
text = re.sub('[^0-9a-zA-Z_-]', '', text)
return text
def get_random_string(length: int=16) -> str:
return "".join(
[random.choice(string.ascii_letters + string.digits) for _ in range(length)]
)
def generate_random_image(image_format, array_dims):
"""
Creates an image from a random array.
:param image_format: An image format (PNG or JPEG).
:param array_dims: A tuple with array dimensions.
:returns: A byte string with encoded image
:rtype: bytes
"""
image_bytes = randint(low=0, high=255, size=array_dims, dtype='uint8')
io = BytesIO()
image_pil = Image.fromarray(image_bytes)
image_pil.save(io, image_format, subsampling=0, quality=100)
return io.getvalue()
def get_random_image(image_format: str="PNG", size: int=5):
"""
Returns a random image.
Args:
image_format: An image format (PNG or JPEG).
Returns:
A string with encoded image
"""
return generate_random_image(image_format, (size, size, 3))
def get_fake(provider: str, locale: str = "en_US", min_length: int = 5) -> Any:
"""
Generates a random string, float, integer etc based on provider
Provider can be "text', 'sentence', "word"
e.g. `get_fake('name')` ==> 'Buzz Aldrin'
"""
string = factory.Faker(provider).evaluate({}, None, {'locale': locale,})
while len(string) < min_length:
string += factory.Faker(provider).evaluate({}, None, {'locale': locale,})
return string
def get_fake_html(locale: str = "en_US", wrap_in_body_tag=True) -> Any:
"""
Generates a random string, float, integer etc based on provider
Provider can be "text', 'sentence',
e.g. `get_fake('name')` ==> 'Buzz Aldrin'
"""
html = factory.Faker("sentence").evaluate({}, None, {'locale': locale,})
for _ in range(0,4):
html += "<li>" + factory.Faker("sentence").evaluate({}, None, {'locale': locale,}) + "</li>"
for _ in range(0,4):
html += "<p>" + factory.Faker("text").evaluate({}, None, {'locale': locale,})
return f"<body>{html}</body>" if wrap_in_body_tag else html
def generate_email_address(
locale: str="en_US",
use_short_email: bool=False,
real_name_format: Optional[str]="{last_name}, {first_name}",
last_name_override: Optional[str]=None) -> Tuple[str, str, str, str]:
'''
Generate an RFC 2822 email address
:param locale: change this to generate locale specific names
:param use_short_email: defaults to false. If true then does not include real name in email address
:param real_name_format: pass a different format if different than "{last_name}, {first_name}"
:param last_name_override: override the fake name if you want some special characters in the last name
:returns <RFC2822 formatted email for header>, <short email address>, <first name>, <last_name
'''
fake = faker.Faker(locale=locale)
first_name = fake.first_name()
last_name = last_name_override or fake.last_name()
real_name = None if use_short_email else real_name_format.format(first_name=first_name, last_name=last_name)
# Add a random string to ensure we do not generate a real domain name
email_address = "{}.{}@{}".format(
first_name.replace(' ', '').encode("ascii", "ignore").lower().decode(),
last_name.replace(' ', '').encode("ascii", "ignore").lower().decode(),
get_random_string(5) + fake.domain_name()
)
# format email address for RFC 2822 and return
return email.utils.formataddr((real_name, email_address)), email_address, first_name, last_name
def generate_file_mime_part(locale: str="en_US",filename: str = None, content: str = None) -> Message:
"""
:param locale: change this to generate locale specific file name and attachment content
2022-09-08 11:05:09 +02:00
:param filename: pass a file name if you want to specify a specific name otherwise a random name will be generated
:param content: pass a string value if you want have specific content otherwise a random string will be generated
"""
part = MIMEBase('application', 'octet-stream')
part.set_payload(get_fake("text", locale=locale, min_length=1024) if content is None else content)
encoders.encode_base64(part)
if not filename:
filename = get_fake("word", locale=locale, min_length=8) + ".txt"
part.add_header('Content-Disposition', "attachment; filename=%s" % filename)
return part
def generate_image_mime_part(locale: str="en_US",imagename: str = None, disposition_primary_type: str = "attachment") -> Message:
"""
:param locale: change this to generate locale specific file name and attachment content
2022-09-08 11:05:09 +02:00
:param filename: pass a file name if you want to specify a specific name otherwise a random name will be generated
"""
2022-10-09 22:51:32 +02:00
part = MIMEImage(generate_random_image(image_format="JPEG", array_dims=(200, 200)))
#part.set_payload(get_fake("text", locale=locale, min_length=1024))
encoders.encode_base64(part)
if not imagename:
imagename = get_fake("word", locale=locale, min_length=8) + ".jpg"
part.add_header('Content-Disposition', disposition_primary_type + "; filename= %s" % imagename)
return part
2022-09-08 11:05:09 +02:00
def generate_email_list(address_cnt: int = 3,
locale: str="en_US",
use_short_email: bool=False
) -> str:
"""
Generates a list of email addresses formatted for email headers on a Mime part
:param address_cnt: the number of email addresses to string together
:param locale: change this to generate locale specific "real names" and subject
:param use_short_email: produces a email address without "real name" if True
"""
email_address_list = [generate_email_address(locale, use_short_email=use_short_email)[0] for _ in range(0, address_cnt)]
return ",".join(email_address_list)
def add_simple_email_headers(message: Message, locale: str="en_US",
use_short_email: bool=False
) -> typing.Tuple[typing.Tuple[str, str], typing.Tuple[str, str]]:
"""
Adds the key email headers to a Mime part
:param message: the Mime part to add headers to
:param locale: change this to generate locale specific "real names" and subject
:param use_short_email: produces a "To" or "From" that is only the email address if True
"""
to_meta = generate_email_address(locale, use_short_email=use_short_email)
from_meta = generate_email_address(locale, use_short_email=use_short_email)
message['Subject'] = get_fake("sentence", locale=locale)
message['From'] = from_meta[0]
message['To'] = to_meta[0]
return from_meta, to_meta
def generate_mime_part(locale: str="en_US",
part_type: str="plain",
2022-10-09 22:51:32 +02:00
) -> typing.Optional[Message]:
"""
Generates amime part of the sepecified type
:param locale: change this to generate locale specific strings
:param text_type: options are plain, html, image (attachment), file (attachment)
"""
if "plain" == part_type:
body = get_fake("text", locale=locale, min_length=1024)
msg = MIMEText(body, part_type)
elif "html" == part_type:
body = get_fake_html(locale=locale, wrap_in_body_tag=True)
msg = MIMEText(body, part_type)
elif "file" == part_type:
msg = generate_file_mime_part(locale=locale)
elif "image" == part_type:
msg = generate_image_mime_part(locale=locale)
else:
raise Exception("Mime part not implemented: " + part_type)
return msg
def generate_multipart_email(locale: str="en_US",
type_list: typing.List[str]=["plain", "html", "image"],
sub_type: str = None,
use_short_email: bool=False
) -> typing.Tuple[Message, typing.Tuple[str, str], typing.Tuple[str, str]]:
"""
Generates an email including headers with the defined multiparts
:param locale:
:param type_list: options are plain, html, image (attachment), file (attachment)
:param sub_type: multipart sub type that defaults to "mixed" if not specified
:param use_short_email: produces a "To" or "From" that is only the email address if True
"""
msg = MIMEMultipart(sub_type) if sub_type else MIMEMultipart()
for part_type in type_list:
msg.attach(generate_mime_part(locale=locale, part_type=part_type))
2022-09-08 11:05:09 +02:00
from_meta, to_meta = add_simple_email_headers(msg, locale=locale, use_short_email=use_short_email)
return msg, from_meta, to_meta
def generate_text_email(locale: str="en_US",
use_short_email: bool=False
) -> typing.Tuple[Message, typing.Tuple[str, str], typing.Tuple[str, str]]:
"""
Generates an email including headers
"""
body = get_fake("text", locale=locale, min_length=1024)
msg = MIMEText(body)
2022-09-08 11:05:09 +02:00
from_meta, to_meta = add_simple_email_headers(msg, locale=locale, use_short_email=use_short_email)
return msg, from_meta, to_meta
def generate_html_email(locale: str="en_US",
use_short_email: bool=False
) -> typing.Tuple[Message, typing.Tuple[str, str], typing.Tuple[str, str]]:
"""
Generates an email including headers
"""
body = get_fake_html(locale=locale)
msg = MIMEText(body)
2022-09-08 11:05:09 +02:00
from_meta, to_meta = add_simple_email_headers(msg, locale=locale, use_short_email=use_short_email)
return msg, from_meta, to_meta