Files
Tirsvad ec6ed213fb MIL-004 request headers (Step 3)
Delivers MIL-004 (lecture Step 3): `DEFAULT_HEADERS` (User-Agent, Accept-Language, Accept, Accept-Encoding, Sec-Fetch-*, Priority, Upgrade-Insecure-Requests; no Host and no X-Amzn-Trace-Id) is defined once in `constants.py` and sent by `RequestsPageFetcher`. Tests assert the headers, the absence of Host/trace id, the timeout and read-only behaviour, with no network.

Review: code against QC-PY-001 as RC-013 (Go, autonomous, not independent). 38 pytest tests, ruff and mypy --strict pass. Screenshots compare the browser's headers and the bot's headers at httpbin.org/headers.

Task: MIL-004#1
Task: MIL-004#2
Task: MIL-004#3
Task: MIL-004#4
2026-10-28 14:45:46 +08:00

86 lines
2.5 KiB
Python

import pytest
import requests
from amazon_price_tracker.constants import DEFAULT_HEADERS, REQUEST_TIMEOUT_SECONDS
from amazon_price_tracker.infrastructure.http_client import RequestsPageFetcher
Call = tuple[str, dict[str, str], float]
class FakeResponse:
def __init__(self, text: str, status_code: int) -> None:
self.text = text
self.status_code = status_code
def raise_for_status(self) -> None:
if self.status_code >= 400:
raise requests.HTTPError(f"status {self.status_code}")
def _patch_get(monkeypatch: pytest.MonkeyPatch, response: FakeResponse) -> list[Call]:
calls: list[Call] = []
def fake_get(url: str, headers: dict[str, str], timeout: float) -> FakeResponse:
calls.append((url, headers, timeout))
return response
monkeypatch.setattr(requests, "get", fake_get)
return calls
def test_fetch_returns_text_using_url_and_timeout(
monkeypatch: pytest.MonkeyPatch,
) -> None:
calls = _patch_get(monkeypatch, FakeResponse("<p>hi</p>", 200))
html = RequestsPageFetcher().fetch("https://example.test/")
assert html == "<p>hi</p>"
assert calls == [
("https://example.test/", dict(DEFAULT_HEADERS), REQUEST_TIMEOUT_SECONDS)
]
def test_fetch_sends_user_agent_and_accept_language(
monkeypatch: pytest.MonkeyPatch,
) -> None:
calls = _patch_get(monkeypatch, FakeResponse("", 200))
RequestsPageFetcher().fetch("https://example.test/")
headers = calls[0][1]
assert headers["User-Agent"]
assert headers["Accept-Language"]
def test_fetch_omits_host_and_trace_id_headers(
monkeypatch: pytest.MonkeyPatch,
) -> None:
calls = _patch_get(monkeypatch, FakeResponse("", 200))
RequestsPageFetcher().fetch("https://example.test/")
assert "Host" not in calls[0][1]
assert "X-Amzn-Trace-Id" not in calls[0][1]
def test_fetch_uses_overriding_headers(monkeypatch: pytest.MonkeyPatch) -> None:
calls = _patch_get(monkeypatch, FakeResponse("", 200))
RequestsPageFetcher(headers={"User-Agent": "custom"}).fetch("https://example.test/")
assert calls[0][1] == {"User-Agent": "custom"}
assert calls[0][2] == REQUEST_TIMEOUT_SECONDS
def test_default_headers_are_read_only() -> None:
with pytest.raises(TypeError):
DEFAULT_HEADERS["User-Agent"] = "x" # type: ignore[index]
def test_fetch_raises_on_http_error(monkeypatch: pytest.MonkeyPatch) -> None:
_patch_get(monkeypatch, FakeResponse("", 404))
with pytest.raises(requests.HTTPError):
RequestsPageFetcher().fetch("https://example.test/")