22
33from __future__ import annotations
44
5- import base64
65import http .server
76import json
87import re
98import textwrap
109import time
1110import wsgiref .handlers
11+ from base64 import b64encode
1212from datetime import datetime
1313from os import path
1414from 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 ]})
217245def 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' ))]})
230258def 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 }})
249281def 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 }})
266304def 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 }})
283327def 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
293344def make_redirect_handler (* , support_head ):
0 commit comments