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