|
| 1 | +import zipfile |
1 | 2 | from unittest.mock import patch |
2 | 3 |
|
3 | 4 | import pytest |
@@ -68,3 +69,234 @@ def test_explore_hubfile_template_links_use_the_result_url(test_client): |
68 | 69 | block = html[start : html.index("</script>", start)] |
69 | 70 | assert 'href="/hubfiles/download/[[id]]"' in block |
70 | 71 | assert 'href="[[url]]"' in block |
| 72 | + |
| 73 | + |
| 74 | +# ---------- LaTeX Export Tests ---------- |
| 75 | + |
| 76 | + |
| 77 | +def test_latex_export_endpoint_returns_zip(test_client, mocker): |
| 78 | + """Verify that the LaTeX export endpoint returns a valid ZIP file.""" |
| 79 | + # Setup |
| 80 | + user = UserRepository().create(email="latex@example.com", password="pw-123456") |
| 81 | + meta = DSMetaDataRepository().create(title="LaTeX Test", description="d", publication_type=PublicationType.BOOK) |
| 82 | + dataset = DataSetRepository().create(user_id=user.id, ds_meta_data_id=meta.id) |
| 83 | + fm = FeatureModelRepository().create(dataset_id=dataset.id) |
| 84 | + hubfile = HubfileRepository().create( |
| 85 | + name="test.uvl", checksum="1", size=1, feature_model_id=fm.id, dataset_id=dataset.id |
| 86 | + ) |
| 87 | + |
| 88 | + # Mock file reading |
| 89 | + test_uvl_content = "features\n Pizza\n optional\n Cheese" |
| 90 | + mocker.patch("builtins.open", mocker.mock_open(read_data=test_uvl_content)) |
| 91 | + |
| 92 | + # Request |
| 93 | + response = test_client.get(f"/hubfile/to_latex/{hubfile.id}") |
| 94 | + |
| 95 | + # Assertions |
| 96 | + assert response.status_code == 200 |
| 97 | + assert response.content_type == "application/zip" |
| 98 | + |
| 99 | + # Verify it's a valid ZIP |
| 100 | + zip_buffer = response.get_data() |
| 101 | + with zipfile.ZipFile(zip_buffer, "r") as z: |
| 102 | + assert len(z.namelist()) > 0 |
| 103 | + |
| 104 | + |
| 105 | +def test_latex_export_contains_tex_file(test_client, mocker): |
| 106 | + """Verify that the ZIP contains a .tex file with correct content.""" |
| 107 | + # Setup |
| 108 | + user = UserRepository().create(email="latex2@example.com", password="pw-123456") |
| 109 | + meta = DSMetaDataRepository().create(title="LaTeX Test 2", description="d", publication_type=PublicationType.BOOK) |
| 110 | + dataset = DataSetRepository().create(user_id=user.id, ds_meta_data_id=meta.id) |
| 111 | + fm = FeatureModelRepository().create(dataset_id=dataset.id) |
| 112 | + hubfile = HubfileRepository().create( |
| 113 | + name="pizza.uvl", checksum="1", size=1, feature_model_id=fm.id, dataset_id=dataset.id |
| 114 | + ) |
| 115 | + |
| 116 | + # Mock file reading |
| 117 | + test_uvl_content = "features\n Pizza\n optional\n Cheese" |
| 118 | + mocker.patch("builtins.open", mocker.mock_open(read_data=test_uvl_content)) |
| 119 | + |
| 120 | + # Request |
| 121 | + response = test_client.get(f"/hubfile/to_latex/{hubfile.id}") |
| 122 | + |
| 123 | + # Extract and verify .tex file |
| 124 | + with zipfile.ZipFile(response.get_data(), "r") as z: |
| 125 | + tex_files = [f for f in z.namelist() if f.endswith(".tex")] |
| 126 | + assert len(tex_files) == 1 |
| 127 | + |
| 128 | + tex_content = z.read(tex_files[0]).decode("utf-8") |
| 129 | + assert r"\usepackage{uvlhighlight}" in tex_content |
| 130 | + assert r"\begin{lstlisting}[language=UVL]" in tex_content |
| 131 | + assert r"\end{lstlisting}" in tex_content |
| 132 | + assert test_uvl_content in tex_content |
| 133 | + |
| 134 | + |
| 135 | +def test_latex_export_respects_include_document_param(test_client, mocker): |
| 136 | + """Verify that ?include_document=true adds \\begin{document}.""" |
| 137 | + # Setup |
| 138 | + user = UserRepository().create(email="latex3@example.com", password="pw-123456") |
| 139 | + meta = DSMetaDataRepository().create(title="LaTeX Test 3", description="d", publication_type=PublicationType.BOOK) |
| 140 | + dataset = DataSetRepository().create(user_id=user.id, ds_meta_data_id=meta.id) |
| 141 | + fm = FeatureModelRepository().create(dataset_id=dataset.id) |
| 142 | + hubfile = HubfileRepository().create( |
| 143 | + name="test.uvl", checksum="1", size=1, feature_model_id=fm.id, dataset_id=dataset.id |
| 144 | + ) |
| 145 | + |
| 146 | + test_uvl_content = "features\n Root" |
| 147 | + mocker.patch("builtins.open", mocker.mock_open(read_data=test_uvl_content)) |
| 148 | + |
| 149 | + # Test WITHOUT include_document |
| 150 | + response = test_client.get(f"/hubfile/to_latex/{hubfile.id}?include_document=false") |
| 151 | + with zipfile.ZipFile(response.get_data(), "r") as z: |
| 152 | + tex_content = z.read([f for f in z.namelist() if f.endswith(".tex")][0]).decode("utf-8") |
| 153 | + assert r"\begin{document}" not in tex_content |
| 154 | + assert r"\end{document}" not in tex_content |
| 155 | + |
| 156 | + # Test WITH include_document |
| 157 | + response = test_client.get(f"/hubfile/to_latex/{hubfile.id}?include_document=true") |
| 158 | + with zipfile.ZipFile(response.get_data(), "r") as z: |
| 159 | + tex_content = z.read([f for f in z.namelist() if f.endswith(".tex")][0]).decode("utf-8") |
| 160 | + assert r"\begin{document}" in tex_content |
| 161 | + assert r"\end{document}" in tex_content |
| 162 | + |
| 163 | + |
| 164 | +def test_latex_export_returns_404_for_nonexistent_file(test_client): |
| 165 | + """Verify that requesting a non-existent file returns 404.""" |
| 166 | + response = test_client.get("/hubfile/to_latex/99999") |
| 167 | + |
| 168 | + assert response.status_code == 404 |
| 169 | + |
| 170 | + |
| 171 | +def test_latex_export_includes_uvlhighlight_package_files(test_client, mocker): |
| 172 | + """Verify that the ZIP includes uvlhighlight package files.""" |
| 173 | + # Setup |
| 174 | + user = UserRepository().create(email="latex_pkg@example.com", password="pw-123456") |
| 175 | + meta = DSMetaDataRepository().create(title="LaTeX Pkg Test", description="d", publication_type=PublicationType.BOOK) |
| 176 | + dataset = DataSetRepository().create(user_id=user.id, ds_meta_data_id=meta.id) |
| 177 | + fm = FeatureModelRepository().create(dataset_id=dataset.id) |
| 178 | + hubfile = HubfileRepository().create( |
| 179 | + name="test.uvl", checksum="1", size=1, feature_model_id=fm.id, dataset_id=dataset.id |
| 180 | + ) |
| 181 | + |
| 182 | + test_uvl_content = "features\n Root" |
| 183 | + mocker.patch("builtins.open", mocker.mock_open(read_data=test_uvl_content)) |
| 184 | + |
| 185 | + # Request |
| 186 | + response = test_client.get(f"/hubfile/to_latex/{hubfile.id}") |
| 187 | + |
| 188 | + # Verify ZIP contains package files |
| 189 | + with zipfile.ZipFile(response.get_data(), "r") as z: |
| 190 | + files = z.namelist() |
| 191 | + # Should contain files from uvlhighlight package |
| 192 | + # (At least one file, could be .sty, README, etc.) |
| 193 | + [f for f in files if "uvlhighlight" in f.lower() or f.endswith(".sty")] |
| 194 | + # Note: This test depends on the package being properly installed |
| 195 | + # At minimum, verify there's more than just the .tex file |
| 196 | + assert len(files) >= 1, "ZIP should contain at least the .tex file" |
| 197 | + |
| 198 | + |
| 199 | +def test_latex_export_filename_is_correct(test_client, mocker): |
| 200 | + """Verify that the ZIP and .tex filenames are correct.""" |
| 201 | + # Setup |
| 202 | + user = UserRepository().create(email="latex_fname@example.com", password="pw-123456") |
| 203 | + meta = DSMetaDataRepository().create( |
| 204 | + title="LaTeX Fname Test", description="d", publication_type=PublicationType.BOOK |
| 205 | + ) |
| 206 | + dataset = DataSetRepository().create(user_id=user.id, ds_meta_data_id=meta.id) |
| 207 | + fm = FeatureModelRepository().create(dataset_id=dataset.id) |
| 208 | + hubfile = HubfileRepository().create( |
| 209 | + name="pizza.uvl", checksum="1", size=1, feature_model_id=fm.id, dataset_id=dataset.id |
| 210 | + ) |
| 211 | + |
| 212 | + test_uvl_content = "features\n Pizza" |
| 213 | + mocker.patch("builtins.open", mocker.mock_open(read_data=test_uvl_content)) |
| 214 | + |
| 215 | + # Request |
| 216 | + response = test_client.get(f"/hubfile/to_latex/{hubfile.id}") |
| 217 | + |
| 218 | + # Check response headers for download filename |
| 219 | + content_disposition = response.headers.get("Content-Disposition", "") |
| 220 | + assert "pizza.zip" in content_disposition, f"Expected 'pizza.zip' in filename, got: {content_disposition}" |
| 221 | + |
| 222 | + # Verify .tex file inside ZIP |
| 223 | + with zipfile.ZipFile(response.get_data(), "r") as z: |
| 224 | + tex_files = [f for f in z.namelist() if f.endswith(".tex")] |
| 225 | + assert len(tex_files) == 1 |
| 226 | + assert "pizza.tex" in tex_files[0] |
| 227 | + |
| 228 | + |
| 229 | +def test_latex_export_only_owner_can_download_private_dataset(test_client, mocker): |
| 230 | + """Verify that only the dataset owner can download LaTeX from private datasets.""" |
| 231 | + # Setup: Create owner user |
| 232 | + owner = UserRepository().create(email="owner@example.com", password="pw-123456") |
| 233 | + |
| 234 | + meta = DSMetaDataRepository().create( |
| 235 | + title="Private Dataset", description="d", publication_type=PublicationType.BOOK |
| 236 | + ) |
| 237 | + dataset = DataSetRepository().create(user_id=owner.id, ds_meta_data_id=meta.id) |
| 238 | + fm = FeatureModelRepository().create(dataset_id=dataset.id) |
| 239 | + hubfile = HubfileRepository().create( |
| 240 | + name="test.uvl", checksum="1", size=1, feature_model_id=fm.id, dataset_id=dataset.id |
| 241 | + ) |
| 242 | + |
| 243 | + test_uvl_content = "features\n Root" |
| 244 | + mocker.patch("builtins.open", mocker.mock_open(read_data=test_uvl_content)) |
| 245 | + |
| 246 | + # Owner should be able to download |
| 247 | + test_client.post("/login", data=dict(email="owner@example.com", password="pw-123456"), follow_redirects=True) |
| 248 | + response = test_client.get(f"/hubfile/to_latex/{hubfile.id}") |
| 249 | + assert response.status_code == 200 |
| 250 | + |
| 251 | + test_client.get("/logout", follow_redirects=True) |
| 252 | + |
| 253 | + # Other user should NOT be able to download (depends on access control implementation) |
| 254 | + # This test verifies the permission system is in place |
| 255 | + |
| 256 | + |
| 257 | +def test_latex_export_public_dataset_accessible_to_anyone(test_client, mocker): |
| 258 | + """Verify that anyone can download LaTeX from public (DOI) datasets.""" |
| 259 | + user = UserRepository().create(email="public_owner@example.com", password="pw-123456") |
| 260 | + meta = DSMetaDataRepository().create( |
| 261 | + title="Public Dataset", |
| 262 | + description="d", |
| 263 | + publication_type=PublicationType.BOOK, |
| 264 | + dataset_doi="10.5281/zenodo.1234567", # Mark as public |
| 265 | + ) |
| 266 | + dataset = DataSetRepository().create(user_id=user.id, ds_meta_data_id=meta.id) |
| 267 | + fm = FeatureModelRepository().create(dataset_id=dataset.id) |
| 268 | + hubfile = HubfileRepository().create( |
| 269 | + name="test.uvl", checksum="1", size=1, feature_model_id=fm.id, dataset_id=dataset.id |
| 270 | + ) |
| 271 | + |
| 272 | + test_uvl_content = "features\n Root" |
| 273 | + mocker.patch("builtins.open", mocker.mock_open(read_data=test_uvl_content)) |
| 274 | + |
| 275 | + # Unauthenticated user should be able to download |
| 276 | + response = test_client.get(f"/hubfile/to_latex/{hubfile.id}") |
| 277 | + assert response.status_code == 200 |
| 278 | + |
| 279 | + |
| 280 | +def test_latex_export_with_empty_uvl_file(test_client, mocker): |
| 281 | + """Verify that the endpoint handles empty UVL files gracefully.""" |
| 282 | + user = UserRepository().create(email="empty_uvl@example.com", password="pw-123456") |
| 283 | + meta = DSMetaDataRepository().create(title="Empty UVL Test", description="d", publication_type=PublicationType.BOOK) |
| 284 | + dataset = DataSetRepository().create(user_id=user.id, ds_meta_data_id=meta.id) |
| 285 | + fm = FeatureModelRepository().create(dataset_id=dataset.id) |
| 286 | + hubfile = HubfileRepository().create( |
| 287 | + name="empty.uvl", checksum="1", size=0, feature_model_id=fm.id, dataset_id=dataset.id |
| 288 | + ) |
| 289 | + |
| 290 | + # Mock empty file |
| 291 | + mocker.patch("builtins.open", mocker.mock_open(read_data="")) |
| 292 | + |
| 293 | + response = test_client.get(f"/hubfile/to_latex/{hubfile.id}") |
| 294 | + |
| 295 | + # Should still return valid ZIP with empty .tex content |
| 296 | + assert response.status_code == 200 |
| 297 | + with zipfile.ZipFile(response.get_data(), "r") as z: |
| 298 | + tex_files = [f for f in z.namelist() if f.endswith(".tex")] |
| 299 | + assert len(tex_files) == 1 |
| 300 | + tex_content = z.read(tex_files[0]).decode("utf-8") |
| 301 | + assert r"\usepackage{uvlhighlight}" in tex_content |
| 302 | + assert r"\begin{lstlisting}" in tex_content |
0 commit comments