107 lines
4.1 KiB
Python
107 lines
4.1 KiB
Python
import struct
|
||
from xml.etree import ElementTree
|
||
|
||
import pytest
|
||
|
||
from app import config
|
||
|
||
|
||
@pytest.fixture(params=["svg", "png"])
|
||
def image_url(request):
|
||
return f"/placeholder.{request.param}"
|
||
|
||
|
||
def test_health(client):
|
||
response = client.get("/healthz")
|
||
|
||
assert response.status_code == 200
|
||
assert response.json == {"status": "ok"}
|
||
assert "Cache-Control" not in response.headers
|
||
|
||
|
||
@pytest.mark.parametrize("custom_dimensions", [False, True])
|
||
def test_image_output(client, image_url, custom_dimensions):
|
||
parameters = {"width": 321, "height": 123, "font_size": 17} if custom_dimensions else {}
|
||
width = parameters.get("width", config.DEFAULT_IMAGE_WIDTH)
|
||
height = parameters.get("height", config.DEFAULT_IMAGE_HEIGHT)
|
||
font_size = parameters.get("font_size", config.DEFAULT_FONT_SIZE)
|
||
|
||
response = client.get(image_url, query_string=parameters)
|
||
|
||
assert response.status_code == 200
|
||
if image_url.endswith(".svg"):
|
||
assert response.mimetype == "image/svg+xml"
|
||
root = ElementTree.fromstring(response.data) # noqa: S314 - trusted SVG generated by the app
|
||
assert root.tag == "{http://www.w3.org/2000/svg}svg"
|
||
assert root.attrib["width"] == str(width)
|
||
assert root.attrib["height"] == str(height)
|
||
text = root.find("{http://www.w3.org/2000/svg}text")
|
||
assert text is not None
|
||
assert text.attrib["font-size"] == str(font_size)
|
||
assert text.text == f"{width} × {height}"
|
||
else:
|
||
assert response.mimetype == "image/png"
|
||
assert response.data[:8] == b"\x89PNG\r\n\x1a\n"
|
||
assert response.data[12:16] == b"IHDR"
|
||
# IHDR starts with width and height as big-endian unsigned 32-bit integers.
|
||
assert struct.unpack(">II", response.data[16:24]) == (width, height)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("parameter", "maximum"),
|
||
[
|
||
("width", config.MAX_IMAGE_WIDTH),
|
||
("height", config.MAX_IMAGE_HEIGHT),
|
||
("font_size", config.MAX_FONT_SIZE),
|
||
],
|
||
)
|
||
@pytest.mark.parametrize("boundary", ["below", "minimum", "maximum", "above"])
|
||
def test_parameter_bounds(client, image_url, parameter, maximum, boundary):
|
||
value = {"below": 0, "minimum": 1, "maximum": maximum, "above": maximum + 1}[boundary]
|
||
# Vary one bound at a time, keeping the other dimensions small for cheap renders.
|
||
parameters = {"width": 10, "height": 10, "font_size": 1, parameter: value}
|
||
|
||
response = client.get(image_url, query_string=parameters)
|
||
|
||
if boundary in {"minimum", "maximum"}:
|
||
assert response.status_code == 200
|
||
else:
|
||
assert response.status_code == 400
|
||
assert response.json == {
|
||
"status": "error",
|
||
"message": f"{parameter} outside of allowable range",
|
||
}
|
||
assert "Cache-Control" not in response.headers
|
||
|
||
|
||
@pytest.mark.parametrize("timeout", [config.CACHE_DEFAULT_TIMEOUT, 120])
|
||
def test_image_cache_control(app, client, image_url, timeout):
|
||
# An override catches headers that hard-code the default timeout.
|
||
app.config["CACHE_DEFAULT_TIMEOUT"] = timeout
|
||
|
||
response = client.get(image_url)
|
||
|
||
assert response.status_code == 200
|
||
assert response.cache_control.public
|
||
assert response.cache_control.max_age == timeout
|
||
assert not response.cache_control.no_cache
|
||
assert not response.cache_control.no_store
|
||
|
||
|
||
def test_cached_image_survives_render_limit(client, image_url, monkeypatch):
|
||
limit_name = "SVG_RENDER_LIMIT" if image_url.endswith(".svg") else "PNG_RENDER_LIMIT"
|
||
# Exhaust the render quota with one request; the request quota remains available.
|
||
monkeypatch.setattr(config, limit_name, "1/minute")
|
||
|
||
first = client.get(image_url, query_string={"width": 10, "height": 10})
|
||
assert first.status_code == 200
|
||
|
||
# A new size needs a render, but the original size should still come from cache.
|
||
uncached = client.get(image_url, query_string={"width": 11, "height": 10})
|
||
assert uncached.status_code == 429
|
||
assert "Cache-Control" not in uncached.headers
|
||
|
||
cached = client.get(image_url, query_string={"width": 10, "height": 10})
|
||
assert cached.status_code == 200
|
||
assert cached.data == first.data
|