110 lines
3.8 KiB
Python
110 lines
3.8 KiB
Python
import threading
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
|
import pytest
|
|
|
|
from tht.adapters.evidence import HttpManifestEvidenceSource
|
|
from tht.ports.evidence import EvidenceSourceError
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
if self.path.startswith("/etag"):
|
|
self.send_response(200)
|
|
self.send_header("ETag", '"abc"')
|
|
self.send_header("Content-Type", "text/markdown")
|
|
self.end_headers()
|
|
self.wfile.write(b"hello")
|
|
elif self.path == "/large":
|
|
self.send_response(200)
|
|
self.send_header("Content-Length", "20")
|
|
self.end_headers()
|
|
self.wfile.write(b"x" * 20)
|
|
elif self.path == "/busy":
|
|
self.send_response(503)
|
|
self.end_headers()
|
|
elif self.path == "/missing":
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
elif self.path == "/redirect-private":
|
|
self.send_response(302)
|
|
self.send_header("Location", f"http://127.0.0.1:{self.server.server_port}/etag")
|
|
self.end_headers()
|
|
else:
|
|
self.send_response(200)
|
|
self.send_header("Last-Modified", "Wed, 21 Oct 2015 07:28:00 GMT")
|
|
self.end_headers()
|
|
self.wfile.write(b"fallback")
|
|
|
|
def log_message(self, format, *args):
|
|
pass
|
|
|
|
|
|
@pytest.fixture
|
|
def server_url():
|
|
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
try:
|
|
yield f"http://127.0.0.1:{server.server_port}"
|
|
finally:
|
|
server.shutdown()
|
|
thread.join()
|
|
|
|
|
|
def test_http_uses_etag_and_strips_query_from_provenance(server_url):
|
|
source = HttpManifestEvidenceSource([f"{server_url}/etag?token=secret"])
|
|
item = next(iter(source.discover()))
|
|
|
|
assert item.fingerprint.startswith("etag:")
|
|
assert item.fingerprint != "etag:abc"
|
|
assert item.uri == f"{server_url}/etag"
|
|
assert "secret" not in item.model_dump_json()
|
|
assert source.acquire(item).content == b"hello"
|
|
|
|
|
|
def test_http_uses_last_modified_then_content_hash(server_url):
|
|
modified = next(iter(HttpManifestEvidenceSource([f"{server_url}/modified"]).discover()))
|
|
assert modified.fingerprint.startswith("last-modified:")
|
|
|
|
class NoValidators(Handler):
|
|
def do_GET(self):
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
self.wfile.write(b"content")
|
|
|
|
server = ThreadingHTTPServer(("127.0.0.1", 0), NoValidators)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
try:
|
|
item = next(iter(HttpManifestEvidenceSource(
|
|
[f"http://127.0.0.1:{server.server_port}/doc"]
|
|
).discover()))
|
|
assert item.fingerprint.startswith("sha256:")
|
|
finally:
|
|
server.shutdown()
|
|
thread.join()
|
|
|
|
|
|
@pytest.mark.parametrize("path,retryable", [("/busy", True), ("/missing", False)])
|
|
def test_http_classifies_status_errors(server_url, path, retryable):
|
|
with pytest.raises(EvidenceSourceError) as caught:
|
|
list(HttpManifestEvidenceSource([server_url + path]).discover())
|
|
assert caught.value.retryable is retryable
|
|
assert server_url not in str(caught.value)
|
|
|
|
|
|
def test_http_rejects_oversize_and_private_redirect(server_url):
|
|
with pytest.raises(EvidenceSourceError) as large:
|
|
list(HttpManifestEvidenceSource([server_url + "/large"], max_bytes=10).discover())
|
|
assert not large.value.retryable
|
|
|
|
with pytest.raises(EvidenceSourceError) as redirect:
|
|
list(HttpManifestEvidenceSource([server_url + "/redirect-private"]).discover())
|
|
assert not redirect.value.retryable
|
|
|
|
|
|
def test_http_rejects_unsupported_manifest_scheme():
|
|
with pytest.raises(ValueError, match="http"):
|
|
HttpManifestEvidenceSource(["file:///tmp/secret"])
|