# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Cata-Log - the central hub for digital flyers
# Copyright (C) 2026 David Aderbauer & The Cata-Log Contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from io import BytesIO
from urllib.parse import urljoin
import pytest
from pypdf import PdfReader
from cata_log_hub.api import common
from cata_log_hub.api.v1 import models
[docs]
@pytest.mark.parametrize(
"order",
[item.value for item in models.CatalogOrderChoices],
)
def test_list_catalogs(full_database, client, order):
response = client.get("/api/v1/catalogs", params={"order": order})
assert response.status_code == 200
data = response.json()
assert "results" in data
assert len(data["results"]) == 3
[docs]
def test_list_catalogs__noauth(full_database, noauth_client):
response = noauth_client.get("/api/v1/catalogs")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_catalogs__bad_auth(full_database, bad_auth_client):
response = bad_auth_client.get("/api/v1/catalogs")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_catalogs__noauth__public_get(full_database, noauth_client, public_get):
response = noauth_client.get("/api/v1/catalogs")
assert response.status_code == 200
[docs]
@pytest.mark.parametrize(
"order",
[item.value for item in models.CatalogOrderChoices],
)
def test_list_latest_catalogs(full_database, fake_latest_catalog, client, order):
response = client.get("/api/v1/catalogs/latest", params={"order": order})
assert response.status_code == 200
data = response.json()
assert "results" in data
assert len(data["results"]) == 1
assert data["results"][0]
assert data["results"][0]["id"] == fake_latest_catalog.id
[docs]
def test_list_latest_catalogs__noauth(full_database, noauth_client):
response = noauth_client.get("/api/v1/catalogs/latest")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_latest_catalogs__bad_auth(full_database, bad_auth_client):
response = bad_auth_client.get("/api/v1/catalogs/latest")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_latest_catalogs__noauth__public_get(
full_database, fake_latest_catalog, noauth_client, public_get
):
response = noauth_client.get("/api/v1/catalogs/latest")
assert response.status_code == 200
data = response.json()
assert "results" in data
assert len(data["results"]) == 1
assert data["results"][0]
assert data["results"][0]["id"] == fake_latest_catalog.id
[docs]
@pytest.mark.parametrize(
"order",
[item.value for item in models.CatalogOrderChoices],
)
def test_list_previews_catalogs(full_database, fake_catalog_preview, client, order):
response = client.get("/api/v1/catalogs/previews", params={"order": order})
assert response.status_code == 200
data = response.json()
assert "results" in data
assert len(data["results"]) == 1
assert data["results"][0]
assert data["results"][0]["id"] == fake_catalog_preview.id
[docs]
def test_list_previews_catalogs__noauth(full_database, noauth_client):
response = noauth_client.get("/api/v1/catalogs/previews")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_previews_catalogs__bad_auth(full_database, bad_auth_client):
response = bad_auth_client.get("/api/v1/catalogs/previews")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_previews_catalogs__noauth__public_get(
full_database, fake_catalog_preview, noauth_client, public_get
):
response = noauth_client.get("/api/v1/catalogs/previews")
assert response.status_code == 200
data = response.json()
assert "results" in data
assert len(data["results"]) == 1
assert data["results"][0]
assert data["results"][0]["id"] == fake_catalog_preview.id
[docs]
@pytest.mark.parametrize(
"order",
[item.value for item in models.CatalogOrderChoices],
)
def test_list_current_catalogs(full_database, fake_catalog_current, client, order):
response = client.get("/api/v1/catalogs/current", params={"order": order})
assert response.status_code == 200
data = response.json()
assert "results" in data
assert len(data["results"]) == 1
assert data["results"][0]
assert data["results"][0]["id"] == fake_catalog_current.id
[docs]
def test_list_current_catalogs__noauth(full_database, noauth_client):
response = noauth_client.get("/api/v1/catalogs/current")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_current_catalogs__bad_auth(full_database, bad_auth_client):
response = bad_auth_client.get("/api/v1/catalogs/current")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_current_catalogs__noauth__public_get(
full_database, fake_catalog_current, noauth_client, public_get
):
response = noauth_client.get("/api/v1/catalogs/current")
assert response.status_code == 200
data = response.json()
assert "results" in data
assert len(data["results"]) == 1
assert data["results"][0]
assert data["results"][0]["id"] == fake_catalog_current.id
[docs]
@pytest.mark.parametrize(
"order",
[item.value for item in models.CatalogOrderChoices],
)
def test_list_outdated_catalogs(full_database, fake_catalog_outdated, client, order):
response = client.get("/api/v1/catalogs/outdated", params={"order": order})
assert response.status_code == 200
data = response.json()
assert "results" in data
assert len(data["results"]) == 1
assert data["results"][0]
assert data["results"][0]["id"] == fake_catalog_outdated.id
[docs]
def test_list_outdated_catalogs__noauth(full_database, noauth_client):
response = noauth_client.get("/api/v1/catalogs/outdated")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_outdated_catalogs__bad_auth(full_database, bad_auth_client):
response = bad_auth_client.get("/api/v1/catalogs/outdated")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_outdated_catalogs__noauth__public_get(
full_database, fake_catalog_outdated, noauth_client, public_get
):
response = noauth_client.get("/api/v1/catalogs/outdated")
assert response.status_code == 200
data = response.json()
assert "results" in data
assert len(data["results"]) == 1
assert data["results"][0]
assert data["results"][0]["id"] == fake_catalog_outdated.id
[docs]
def test_get_catalog(fake_catalog, client):
response = client.get(f"/api/v1/catalogs/{fake_catalog.id}")
assert response.status_code == 200
data = response.json()
assert data["id"] == fake_catalog.id
[docs]
def test_get_catalog__noauth(fake_catalog, noauth_client):
response = noauth_client.get(f"/api/v1/catalogs/{fake_catalog.id}")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_get_catalog__bad_auth(fake_catalog, bad_auth_client):
response = bad_auth_client.get(f"/api/v1/catalogs/{fake_catalog.id}")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_get_catalog__noauth__public_get(fake_catalog, noauth_client, public_get):
response = noauth_client.get(f"/api/v1/catalogs/{fake_catalog.id}")
assert response.status_code == 200
data = response.json()
assert data["id"] == fake_catalog.id
[docs]
def test_get_catalog__not_found(client):
response = client.get("/api/v1/catalogs/456")
assert response.status_code == 404
common.HTTPStatusError.model_validate(response.json())
assert response.json() == {"detail": "Catalog not found"}
[docs]
def test_get_catalog_page(fake_catalog, fake_page, client):
response = client.get(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}"
)
assert response.status_code == 200
data = response.json()
assert data["id"] == fake_page.id
[docs]
def test_get_catalog_page__noauth(fake_catalog, fake_page, noauth_client):
response = noauth_client.get(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}"
)
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_get_catalog_page__bad_auth(fake_catalog, fake_page, bad_auth_client):
response = bad_auth_client.get(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}"
)
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_get_catalog_page__noauth__public_get(
fake_catalog, fake_page, noauth_client, public_get
):
response = noauth_client.get(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}"
)
assert response.status_code == 200
data = response.json()
assert data["id"] == fake_page.id
[docs]
@pytest.mark.parametrize(
"order",
[item.value for item in models.PageOrderChoices],
)
def test_list_catalog_pages(fake_catalog, fake_page, client, order):
response = client.get(
f"/api/v1/catalogs/{fake_catalog.id}/pages", params={"order": order}
)
assert response.status_code == 200
data = response.json()
assert "results" in data
assert len(data["results"]) == 1
assert data["results"][0]["id"] == fake_page.id
[docs]
def test_list_catalog_pages__noauth(fake_catalog, noauth_client):
response = noauth_client.get(f"/api/v1/catalogs/{fake_catalog.id}/pages")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_catalog_pages__bad_auth(fake_catalog, bad_auth_client):
response = bad_auth_client.get(f"/api/v1/catalogs/{fake_catalog.id}/pages")
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_list_catalog_pages__noauth__public_get(
fake_catalog, fake_page, noauth_client, public_get
):
response = noauth_client.get(f"/api/v1/catalogs/{fake_catalog.id}/pages")
assert response.status_code == 200
data = response.json()
assert "results" in data
assert len(data["results"]) == 1
assert data["results"][0]["id"] == fake_page.id
[docs]
def test_get_catalog_page__catalog_not_found(client):
response = client.get("/api/v1/catalogs/234/pages/1")
assert response.status_code == 404
common.HTTPStatusError.model_validate(response.json())
assert response.json() == {"detail": "Page not found"}
[docs]
def test_get_catalog_page__page_not_found(client):
response = client.get("/api/v1/catalogs/1/pages/780")
assert response.status_code == 404
common.HTTPStatusError.model_validate(response.json())
assert response.json() == {"detail": "Page not found"}
[docs]
def test_download_latest_provider_catalog__noauth(
full_database, fake_provider, noauth_client
):
response = noauth_client.get(
f"/api/v1/catalogs/{fake_provider.id}/download",
)
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_download_catalog(fake_catalog, full_database, client):
response = client.get(
f"/api/v1/catalogs/{fake_catalog.id}/download",
)
assert response.status_code == 200
assert response.headers
assert "content-type" in response.headers
assert response.headers["content-type"] == "application/pdf"
assert "content-disposition" in response.headers
assert response.headers["content-disposition"].startswith("attachment")
assert response.content
pdf_reader = PdfReader(BytesIO(response.content), strict=True)
assert len(pdf_reader.pages) == 1
assert len(pdf_reader.pages[0].images) == 1
[docs]
def test_download_catalog__noauth(full_database, fake_provider, noauth_client):
response = noauth_client.get(
f"/api/v1/catalogs/{fake_provider.id}/download",
)
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_download_catalog__bad_auth(full_database, fake_provider, bad_auth_client):
response = bad_auth_client.get(
f"/api/v1/catalogs/{fake_provider.id}/download",
)
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_download_catalog__not_found(full_database, client):
response = client.get(
"/api/v1/catalogs/546/download",
)
assert response.status_code == 404
common.HTTPStatusError.model_validate(response.json())
assert response.json()["detail"] == "Catalog not found"
[docs]
def test_embed_catalog(fake_catalog, full_database, client):
response = client.get(
f"/api/v1/catalogs/{fake_catalog.id}/embed",
)
assert response.status_code == 200
assert response.headers
assert "content-type" in response.headers
assert response.headers["content-type"] == "application/pdf"
assert "content-disposition" in response.headers
assert response.headers["content-disposition"].startswith("inline")
assert response.content
pdf_reader = PdfReader(BytesIO(response.content), strict=True)
assert len(pdf_reader.pages) == 1
assert len(pdf_reader.pages[0].images) == 1
[docs]
def test_embed_catalog__noauth(full_database, fake_provider, noauth_client):
response = noauth_client.get(
f"/api/v1/catalogs/{fake_provider.id}/embed",
)
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_embed_catalog__bad_auth(full_database, fake_provider, bad_auth_client):
response = bad_auth_client.get(
f"/api/v1/catalogs/{fake_provider.id}/embed",
)
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_embed_catalog__not_found(full_database, client):
response = client.get(
"/api/v1/catalogs/546/embed",
)
assert response.status_code == 404
common.HTTPStatusError.model_validate(response.json())
assert response.json()["detail"] == "Catalog not found"
[docs]
@pytest.mark.parametrize(("filename"), [None, "page_image.png"])
def test_download_catalog_page(fake_catalog, fake_page, fake_file, client, filename):
response = client.get(
urljoin(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}/download",
f"?filename={filename}" if filename else "",
)
)
assert response.status_code == 200
assert response.content == fake_file.read_bytes()
assert "content-disposition" in response.headers
assert (
response.headers["content-disposition"]
== f'attachment; filename="{filename or fake_file.name}"'
)
[docs]
def test_download_catalog_page__noauth(fake_catalog, fake_page, noauth_client):
response = noauth_client.get(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}/download"
)
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_download_catalog_page__bad_auth(fake_catalog, fake_page, bad_auth_client):
response = bad_auth_client.get(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}/download"
)
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_download_catalog_page__noauth__public_get(
fake_catalog, fake_page, fake_file, noauth_client, public_get
):
response = noauth_client.get(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}/download"
)
assert response.status_code == 200
assert response.content == fake_file.read_bytes()
assert "content-disposition" in response.headers
assert (
response.headers["content-disposition"]
== f'attachment; filename="{fake_file.name}"'
)
[docs]
def test_download_catalog_page__page_not_found(fake_catalog, client):
response = client.get(f"/api/v1/catalogs/{fake_catalog.id}/pages/456/download")
assert response.status_code == 404
common.HTTPStatusError.model_validate(response.json())
assert response.json() == {"detail": "Page not found"}
[docs]
def test_download_catalog_page__catalog_not_found(client):
response = client.get("/api/v1/catalogs/615/pages/1/download")
assert response.status_code == 404
common.HTTPStatusError.model_validate(response.json())
assert response.json() == {"detail": "Page not found"}
[docs]
@pytest.mark.parametrize(("filename"), [None, "page_image.png"])
def test_embed_catalog_page(fake_catalog, fake_page, fake_file, client, filename):
response = client.get(
urljoin(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}/embed",
f"?filename={filename}" if filename else "",
)
)
assert response.status_code == 200
assert response.content == fake_file.read_bytes()
assert "content-disposition" in response.headers
assert (
response.headers["content-disposition"]
== f'inline; filename="{filename or fake_file.name}"'
)
[docs]
def test_embed_catalog_page__noauth(fake_catalog, fake_page, noauth_client):
response = noauth_client.get(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}/embed"
)
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_embed_catalog_page__bad_auth(fake_catalog, fake_page, bad_auth_client):
response = bad_auth_client.get(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}/embed"
)
assert response.status_code == 401
common.HTTPStatusError.model_validate(response.json())
assert "WWW-Authenticate" in response.headers
assert response.headers["WWW-Authenticate"] == "Basic"
[docs]
def test_embed_catalog_page__noauth__public_get(
fake_catalog, fake_page, fake_file, noauth_client, public_get
):
response = noauth_client.get(
f"/api/v1/catalogs/{fake_catalog.id}/pages/{fake_page.number}/embed",
)
assert response.status_code == 200
assert response.content == fake_file.read_bytes()
assert "content-disposition" in response.headers
assert (
response.headers["content-disposition"]
== f'inline; filename="{fake_file.name}"'
)
[docs]
def test_embed_catalog_page__page_not_found(fake_catalog, client):
response = client.get(f"/api/v1/catalogs/{fake_catalog.id}/pages/456/embed")
assert response.status_code == 404
common.HTTPStatusError.model_validate(response.json())
assert response.json() == {"detail": "Page not found"}
[docs]
def test_embed_catalog_page__catalog_not_found(client):
response = client.get("/api/v1/catalogs/615/pages/1/embed")
assert response.status_code == 404
common.HTTPStatusError.model_validate(response.json())
assert response.json() == {"detail": "Page not found"}