Skip to content

Commit bef7fc2

Browse files
authored
Refactor HTTP handlers in linkcheck builder tests (#11426)
1 parent 1b08535 commit bef7fc2

File tree

1 file changed

+83
-32
lines changed

1 file changed

+83
-32
lines changed

tests/test_build_linkcheck.py

Lines changed: 83 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
from __future__ import annotations
44

5-
import base64
65
import http.server
76
import json
87
import re
98
import textwrap
109
import time
1110
import wsgiref.handlers
11+
from base64 import b64encode
1212
from datetime import datetime
1313
from os import path
1414
from queue import Queue
@@ -195,16 +195,44 @@ def do_GET(self):
195195
)
196196

197197

198-
def capture_headers_handler(records):
199-
class HeadersDumperHandler(http.server.BaseHTTPRequestHandler):
198+
def custom_handler(valid_credentials=(), success_criteria=lambda _: True):
199+
"""
200+
Returns an HTTP request handler that authenticates the client and then determines
201+
an appropriate HTTP response code, based on caller-provided credentials and optional
202+
success criteria, respectively.
203+
"""
204+
expected_token = None
205+
if valid_credentials:
206+
assert len(valid_credentials) == 2, "expected a pair of strings as credentials"
207+
expected_token = b64encode(":".join(valid_credentials).encode()).decode("utf-8")
208+
del valid_credentials
209+
210+
class CustomHandler(http.server.BaseHTTPRequestHandler):
211+
def authenticated(method):
212+
def method_if_authenticated(self):
213+
if expected_token is None:
214+
return method(self)
215+
elif self.headers["Authorization"] == f"Basic {expected_token}":
216+
return method(self)
217+
else:
218+
self.send_response(403, "Forbidden")
219+
self.end_headers()
220+
221+
return method_if_authenticated
222+
223+
@authenticated
200224
def do_HEAD(self):
201225
self.do_GET()
202226

227+
@authenticated
203228
def do_GET(self):
204-
self.send_response(200, "OK")
229+
if success_criteria(self):
230+
self.send_response(200, "OK")
231+
else:
232+
self.send_response(400, "Bad Request")
205233
self.end_headers()
206-
records.append(self.headers.as_string())
207-
return HeadersDumperHandler
234+
235+
return CustomHandler
208236

209237

210238
@pytest.mark.sphinx(
@@ -215,25 +243,29 @@ def do_GET(self):
215243
(r'.*local.*', ('user2', 'hunter2')),
216244
]})
217245
def test_auth_header_uses_first_match(app):
218-
records = []
219-
with http_server(capture_headers_handler(records)):
246+
with http_server(custom_handler(valid_credentials=("user1", "password"))):
220247
app.build()
221248

222-
stdout = "\n".join(records)
223-
encoded_auth = base64.b64encode(b'user1:password').decode('ascii')
224-
assert f"Authorization: Basic {encoded_auth}\n" in stdout
249+
with open(app.outdir / "output.json", encoding="utf-8") as fp:
250+
content = json.load(fp)
251+
252+
assert content["status"] == "working"
225253

226254

227255
@pytest.mark.sphinx(
228256
'linkcheck', testroot='linkcheck-localserver', freshenv=True,
229257
confoverrides={'linkcheck_auth': [(r'^$', ('user1', 'password'))]})
230258
def test_auth_header_no_match(app):
231-
records = []
232-
with http_server(capture_headers_handler(records)):
259+
with http_server(custom_handler(valid_credentials=("user1", "password"))):
233260
app.build()
234261

235-
stdout = "\n".join(records)
236-
assert "Authorization" not in stdout
262+
with open(app.outdir / "output.json", encoding="utf-8") as fp:
263+
content = json.load(fp)
264+
265+
# TODO: should this test's webserver return HTTP 401 here?
266+
# https://github.com/sphinx-doc/sphinx/issues/11433
267+
assert content["info"] == "403 Client Error: Forbidden for url: http://localhost:7777/"
268+
assert content["status"] == "broken"
237269

238270

239271
@pytest.mark.sphinx(
@@ -247,14 +279,20 @@ def test_auth_header_no_match(app):
247279
},
248280
}})
249281
def test_linkcheck_request_headers(app):
250-
records = []
251-
with http_server(capture_headers_handler(records)):
282+
def check_headers(self):
283+
if "X-Secret" in self.headers:
284+
return False
285+
if self.headers["Accept"] != "text/html":
286+
return False
287+
return True
288+
289+
with http_server(custom_handler(success_criteria=check_headers)):
252290
app.build()
253291

254-
stdout = "\n".join(records)
255-
assert "Accept: text/html\n" in stdout
256-
assert "X-Secret" not in stdout
257-
assert "sesami" not in stdout
292+
with open(app.outdir / "output.json", encoding="utf-8") as fp:
293+
content = json.load(fp)
294+
295+
assert content["status"] == "working"
258296

259297

260298
@pytest.mark.sphinx(
@@ -264,14 +302,20 @@ def test_linkcheck_request_headers(app):
264302
"*": {"X-Secret": "open sesami"},
265303
}})
266304
def test_linkcheck_request_headers_no_slash(app):
267-
records = []
268-
with http_server(capture_headers_handler(records)):
305+
def check_headers(self):
306+
if "X-Secret" in self.headers:
307+
return False
308+
if self.headers["Accept"] != "application/json":
309+
return False
310+
return True
311+
312+
with http_server(custom_handler(success_criteria=check_headers)):
269313
app.build()
270314

271-
stdout = "\n".join(records)
272-
assert "Accept: application/json\n" in stdout
273-
assert "X-Secret" not in stdout
274-
assert "sesami" not in stdout
315+
with open(app.outdir / "output.json", encoding="utf-8") as fp:
316+
content = json.load(fp)
317+
318+
assert content["status"] == "working"
275319

276320

277321
@pytest.mark.sphinx(
@@ -281,13 +325,20 @@ def test_linkcheck_request_headers_no_slash(app):
281325
"*": {"X-Secret": "open sesami"},
282326
}})
283327
def test_linkcheck_request_headers_default(app):
284-
records = []
285-
with http_server(capture_headers_handler(records)):
328+
def check_headers(self):
329+
if self.headers["X-Secret"] != "open sesami":
330+
return False
331+
if self.headers["Accept"] == "application/json":
332+
return False
333+
return True
334+
335+
with http_server(custom_handler(success_criteria=check_headers)):
286336
app.build()
287337

288-
stdout = "\n".join(records)
289-
assert "Accepts: application/json\n" not in stdout
290-
assert "X-Secret: open sesami\n" in stdout
338+
with open(app.outdir / "output.json", encoding="utf-8") as fp:
339+
content = json.load(fp)
340+
341+
assert content["status"] == "working"
291342

292343

293344
def make_redirect_handler(*, support_head):

0 commit comments

Comments
 (0)