-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Updating test for file upload form POST requests (#16)
- Loading branch information
1 parent
9884bb8
commit 2245d4f
Showing
3 changed files
with
190 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
@@ -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): | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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.""" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
from typing import Tuple | ||
|
||
import pytest | ||
from bs4 import BeautifulSoup | ||
from flask import Flask | ||
from seleniumbase import BaseCase | ||
|
||
|
@@ -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 | ||
|