diff --git a/app/main/routes.py b/app/main/routes.py index e2fd79dd..83f61c96 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -45,6 +45,8 @@ paginate, ) from app.main.util.render_utils import ( + UNIVERSAL_VIEWER_SUPPORTED_DOCUMENT_TYPES, + UNIVERSAL_VIEWER_SUPPORTED_IMAGE_TYPES, create_presigned_url, generate_breadcrumb_values, generate_image_manifest, @@ -816,9 +818,12 @@ def generate_manifest(record_id: uuid.UUID): filename = file.FileName file_type = filename.split(".")[-1].lower() - if file_type == "pdf": + if file_type in UNIVERSAL_VIEWER_SUPPORTED_DOCUMENT_TYPES: return generate_pdf_manifest(record_id) - elif file_type in ["png", "jpg", "jpeg"]: + elif file_type in UNIVERSAL_VIEWER_SUPPORTED_IMAGE_TYPES: return generate_image_manifest(s3_file_object, record_id) else: + current_app.app_logger.error( + f"Failed to create manifest for file with ID {file.FileId}" + ) return http_exception(BadRequest()) diff --git a/app/main/util/render_utils.py b/app/main/util/render_utils.py index 9e84f6d9..ff989062 100644 --- a/app/main/util/render_utils.py +++ b/app/main/util/render_utils.py @@ -7,6 +7,125 @@ from app.main.db.models import File, db from app.main.db.queries import get_file_metadata +UNIVERSAL_VIEWER_SUPPORTED_IMAGE_TYPES = [ + "png", + "jpg", + "jpeg", + "gif", + "bmp", + "ico", + "tif", + "tiff", + "3fr", + "arw", + "cr2", + "crw", + "dcr", + "dng", + "erf", + "k25", + "kdc", + "mef", + "mos", + "mrw", + "nef", + "orf", + "pef", + "ptx", + "raf", + "raw", + "rw2", + "sr2", + "srf", + "x3f", + "heic", + "heif", + "jpe", +] + +UNIVERSAL_VIEWER_SUPPORTED_DOCUMENT_TYPES = [ + "pdf", + "docx", + "xlsx", + "pptx", + "odoc", + "osheet", + "oslides", + "txt", + "json", + "md", + "xml", + "html", + "htm", +] + +UNIVERSAL_VIEWER_SUPPORTED_OTHER_TYPES = [ + # text and Code Files + "c", + "cpp", + "cs", + "java", + "py", + "js", + "jsx", + "ts", + "tsx", + "php", + "rb", + "pl", + "sql", + "css", + "scss", + "less", + "sh", + "yaml", + "yml", + "xml", + "xhtml", + "plist", + "properties", + "bat", + "cmd", + "make", + "groovy", + "scala", + "swift", + "diff", + "erl", + "lst", + "out", + "patch", + "sml", + # archives and Compressed Files + "zip", + "rar", + "7z", + "gz", + "bz2", + "tar", + "tgz", + "tbz", + "txz", + # multimedia + "mp3", + "wav", + "ogg", + "mp4", + "webm", + # 3D + "obj", + "stl", + "dae", + "gltf", + "glb", + # other Formats + "ps", + "psd", + "epub", + "djvu", + "csv", +] + def get_file_details(file): """Retrieve file metadata and determine file type and extension.""" diff --git a/app/tests/test_routes.py b/app/tests/test_routes.py index 0e40d19b..9b254590 100644 --- a/app/tests/test_routes.py +++ b/app/tests/test_routes.py @@ -10,6 +10,11 @@ from moto import mock_aws from PIL import Image +from app.main.util.render_utils import ( + UNIVERSAL_VIEWER_SUPPORTED_DOCUMENT_TYPES, + UNIVERSAL_VIEWER_SUPPORTED_IMAGE_TYPES, +) + def verify_cookies_header_row(data): soup = BeautifulSoup(data, "html.parser") @@ -355,3 +360,100 @@ def test_search_route_with_various_cases_standard_user( for key, expected_value in expected_params.items(): assert f"{key}={expected_value}" in response.headers["Location"] + + @pytest.mark.parametrize( + "document_format", UNIVERSAL_VIEWER_SUPPORTED_DOCUMENT_TYPES + ) + @mock_aws + @patch("app.main.routes.boto3.client") + @patch("app.main.routes.generate_pdf_manifest") + def test_generate_manifest_pdf( + self, + mock_pdf, + mock_boto_client, + app, + client: FlaskClient, + mock_all_access_user, + record_files, + document_format, + ): + """ + Test that a PDF manifest is successfully generated. + """ + mock_all_access_user(client) + file = record_files[0]["file_object"] + file.FileName = f"document.{document_format}" + bucket_name = "test_bucket" + app.config["RECORD_BUCKET_NAME"] = bucket_name + + s3_mock = mock_boto_client.return_value + s3_mock.get_object.return_value = {"Body": b"file content"} + + mock_pdf.return_value = ({"mock": "pdf_manifest"}, 200) + response = client.get(f"/record/{file.FileId}/manifest") + + mock_pdf.assert_called_once() + assert response.status_code == 200 + assert json.loads(response.text) == {"mock": "pdf_manifest"} + + @pytest.mark.parametrize( + "image_format", UNIVERSAL_VIEWER_SUPPORTED_IMAGE_TYPES + ) + @mock_aws + @patch("app.main.routes.boto3.client") + @patch("app.main.routes.generate_image_manifest") + def test_generate_manifest_image( + self, + mock_image, + mock_boto_client, + app, + client: FlaskClient, + mock_all_access_user, + record_files, + image_format, + ): + """ + Test that a image manifest is successfully generated. + """ + mock_all_access_user(client) + file = record_files[0]["file_object"] + file.FileName = f"image.{image_format}" + bucket_name = "test_bucket" + app.config["RECORD_BUCKET_NAME"] = bucket_name + + s3_mock = mock_boto_client.return_value + s3_mock.get_object.return_value = {"Body": b"file content"} + + mock_image.return_value = ({"mock": "image_manifest"}, 200) + response = client.get(f"/record/{file.FileId}/manifest") + + mock_image.assert_called_once() + assert response.status_code == 200 + assert json.loads(response.text) == {"mock": "image_manifest"} + + @mock_aws + @patch("app.main.routes.boto3.client") + def test_generate_manifest_unsupported( + self, + mock_boto_client, + app, + client: FlaskClient, + mock_all_access_user, + record_files, + caplog, + ): + """ + Test that an unsupported format will return a bad request and log the error + """ + mock_all_access_user(client) + file = record_files[0]["file_object"] + file.FileName = "unsupported.xyz" + bucket_name = "test_bucket" + app.config["RECORD_BUCKET_NAME"] = bucket_name + + s3_mock = mock_boto_client.return_value + s3_mock.get_object.return_value = {"Body": b"file content"} + + response = client.get(f"/record/{file.FileId}/manifest") + assert response.status_code == 400 + assert "Failed to create manifest for file with ID" in caplog.text