Skip to content

Commit

Permalink
Updating test for file upload form POST requests (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
DiogenesAnalytics committed Mar 13, 2024
1 parent 9884bb8 commit 2245d4f
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 29 deletions.
153 changes: 135 additions & 18 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Configuration file for pytest."""

import base64
import json
import os
import random
Expand All @@ -17,6 +18,8 @@
from flask import request
from flask import send_from_directory
from selenium.webdriver.common.keys import Keys
from werkzeug.datastructures import FileStorage
from werkzeug.datastructures import ImmutableMultiDict


def pytest_configure(config):
Expand Down Expand Up @@ -52,6 +55,81 @@ def create_temp_websrc_dir(src: Path, dst: Path, src_files: Tuple[str, ...]) ->
return sub_dir


def get_html_tag_from_mimetype(file: FileStorage, encoded_data: str) -> str:
"""Generate an HTML tag based on the MIME type of the file."""
# create data URL for reuse below
data_url = f"data:{file.mimetype};base64,{encoded_data}"

# match the mimetype
match file.mimetype.split("/")[0]:
case "image":
tag = f"<img src={data_url!r}>"
case "video":
tag = (
f"<video controls>"
f" <source src={data_url!r} type={file.mimetype!r}>"
f" Your browser does not support the video tag."
f"</video>"
)
case "audio":
tag = (
f"<audio controls>"
f" <source src={data_url!r} type={file.mimetype!r}>"
f" Your browser does not support the audio tag."
f"</audio>"
)
case _:
tag = f"<a href={data_url!r}>Download {file.filename}</a>"

return tag


def process_form_data(form_data: ImmutableMultiDict) -> Dict[str, Any]:
"""Process form data to handle multi-values."""
# setup processed results
processed_data: Dict[str, Any] = {}

# check form key/values
for key, value in form_data.items(multi=True):
# check if key indicates file(s)
if key in request.files:
processed_data[key] = ""

# check to see if there are multiple values
elif key in processed_data:
processed_data[key] += f", {value}"

# handle normally
else:
processed_data[key] = value

return processed_data


def process_uploaded_files(processed_data: Dict[str, Any]) -> None:
"""Process uploaded files and generate HTML tags."""
# get list of tuples for key/files pairs
for key, files in request.files.lists():
# loop over each file
for file in files:
# make sure it exists
if file.filename:
# get data from file
file_data = file.read()

# convert to base64 for data URL creation later ...
encoded_data = base64.b64encode(file_data).decode("utf-8")

# create tag
tag = get_html_tag_from_mimetype(file, encoded_data)

# update current results
if key in processed_data:
processed_data[key] += "<br>" + tag
else:
processed_data[key] = tag


def build_flask_app(serve_directory: Path, port: int, submit_route: str) -> Flask:
"""Assembles Flask app to serve static site."""
# instantiate app
Expand Down Expand Up @@ -92,29 +170,19 @@ def serve_scripts(path):
@app.route(submit_route, methods=["POST"])
def submit_form():
"""Render HTML form data as a response form."""
# access form data submitted by the client
form_data = request.form

# create processed dict
processed_data = {}

# log data
print(f"Form data received: {form_data}")

# Process form data to handle multi-values
processed_data = {}
for key, value in form_data.items(multi=True):
if key in processed_data:
# If key already exists, append the value
processed_data[key] += f", {value}"
else:
# If key does not exist, set the value
processed_data[key] = value
print(f"Form data received: {request.form}")

# process data
processed_data = process_form_data(request.form)

# log processed data
print(f"Processed data: {processed_data}")

# render the template with the form data
# process any files
process_uploaded_files(processed_data)

# now render response
return render_template("form_response_template.html", form_data=processed_data)

# return configured and route decorated Flask app
Expand Down Expand Up @@ -155,6 +223,55 @@ def update_form_backend_config(
write_config_file(config, src_path)


@pytest.fixture(scope="function")
def dummy_txt_file_path(tmp_path) -> Path:
"""Create a dummy temporary text file."""
# create a temporary directory
tmpdir = tmp_path / "uploads"
tmpdir.mkdir()

# define the file path
file_path = tmpdir / "test_file.txt"

# write content to the file
with open(file_path, "w") as f:
f.write("This is a test file.")

return file_path


@pytest.fixture(scope="function")
def dummy_txt_file_stream(dummy_txt_file_path) -> FileStorage:
"""Create a Flask FileStorage object from text file."""
# create a FileStorage object
return FileStorage(stream=open(dummy_txt_file_path, "rb"), filename="test_file.txt")


@pytest.fixture(scope="function")
def dummy_txt_file_data_url(dummy_txt_file_path) -> str:
"""Create a data URL for the dummy text file."""
# read the content of the file
with open(dummy_txt_file_path, "rb") as f:
file_content = f.read()

# encode the file content as base64
base64_content = base64.b64encode(file_content).decode("utf-8")

# construct the data URL with the appropriate MIME type
return f"data:text/plain;base64,{base64_content}"


@pytest.fixture(scope="function")
def dummy_form_post_data(dummy_txt_file_stream) -> Dict[str, Any]:
"""Collection of name/value pairs to simulate form post data."""
return {
"name": "John Doe",
"email": "[email protected]",
"message": "This is a test message.",
"text_file": dummy_txt_file_stream,
}


@pytest.fixture(scope="session")
def form_inputs() -> Dict[str, Any]:
"""Defines the values to be submitted for each input type during form tests."""
Expand Down
4 changes: 3 additions & 1 deletion tests/templates/form_response_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
<h2>Contact Form Response</h2>
{% for key, value in form_data.items() %}
<label for="{{ key }}">{{ key }}:</label>
<p>{{ value }}</p>
<p>
{{ value | safe}}
</p>
{% endfor %}
</div>
</body>
Expand Down
62 changes: 52 additions & 10 deletions tests/test_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Tuple

import pytest
from bs4 import BeautifulSoup
from flask import Flask
from seleniumbase import BaseCase

Expand Down Expand Up @@ -113,30 +114,71 @@ def test_serve_scripts_route(session_web_app: Flask) -> None:
assert response.status_code == 200


@pytest.mark.debug
@pytest.mark.flask
@pytest.mark.fixture
def test_submit_form_route(session_web_app: Flask, submit_route: str) -> None:
def test_submit_form_route(
session_web_app: Flask,
submit_route: str,
dummy_form_post_data: Dict[str, Any],
dummy_txt_file_data_url: str,
) -> None:
"""Test the route for submitting a form."""
# get client
client = session_web_app.test_client()

# simulate a form submission
form_data = {
"name": "John Doe",
"email": "[email protected]",
"message": "This is a test message.",
}
response = client.post(submit_route, data=form_data)
# submit response
response = client.post(
submit_route, data=dummy_form_post_data, content_type="multipart/form-data"
)

# assert that the response status code is 200 (OK)
assert response.status_code == 200

# get content
content = response.data.decode("utf-8")

# Optionally, you can check the response content
# check response html header
assert "Contact Form Response" in content
assert all(form_data[key] in content for key in form_data)

# parse the HTML response
soup = BeautifulSoup(response.data, "html.parser")

# find the container div
container = soup.find("div", class_="container")
assert container is not None, "Container div not found in HTML response"

# find and extract form data from the HTML
form_data = {}
labels = container.find_all("label")
for label in labels:
key = label["for"]
# find the <p> tag associated with the label
p_tag = label.find_next_sibling("p")
if p_tag:
# find the <a> tag within the <p> tag
a_tag = p_tag.find("a")
if a_tag:
# extract the value of the "href" attribute from the <a> tag
value = a_tag.get("href")
else:
# if <a> tag is not found, set value to None
value = " ".join(p_tag.stripped_strings)
form_data[key] = value

# define expected form data
expected_form_data = {
"name": dummy_form_post_data["name"],
"email": dummy_form_post_data["email"],
"message": dummy_form_post_data["message"],
"text_file": dummy_txt_file_data_url,
}

# assert that the form data matches the expected form data
for key in expected_form_data:
assert (
form_data[key] == expected_form_data[key]
), "Form data in HTML response does not match expected form data"


@pytest.mark.flask
Expand Down

0 comments on commit 2245d4f

Please sign in to comment.