""" Test helpers: Transient ITN handler. """ from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager from email.header import Header # noqa: F401 from http import HTTPStatus from http.server import HTTPServer, BaseHTTPRequestHandler from queue import Queue from typing import Dict, Iterator, Union # noqa: F401 from urllib.parse import parse_qs class PayFastITNHandler(BaseHTTPRequestHandler): """ Minimal ITN callback handler, for testing. Expects the server to have a `itn_queue` to put the result to. """ def do_POST(self): # type: () -> None assert self.command == 'POST' assert self.path == '/' # Parse the request body, and post to the queue. data = self.read_request_data() itn_queue = self.server.itn_queue # type: ignore itn_queue # type: Queue itn_queue.put(data) self.send_response(HTTPStatus.OK) self.end_headers() def read_request_body(self): # type: () -> str [content_length_header] = self.headers.get_all('Content-Length') # type: Union[str, Header] content_length = int(str(content_length_header)) content_bytes = self.rfile.read(content_length) content = content_bytes.decode('utf-8') # XXX return content def read_request_data(self): # type: () -> Dict[str, str] assert self.headers['Content-Type'] == 'application/x-www-form-urlencoded' content = self.read_request_body() data_lists = parse_qs( content, # Strict options: keep_blank_values=True, strict_parsing=True, errors='strict', ) # Flatten the listed values. data_flat = {k: v for (k, [v]) in data_lists.items()} return data_flat @contextmanager def itn_handler(host, port): # type: (str, int) -> Iterator[Queue] """ Usage:: with itn_handler(ITN_HOST, ITN_PORT) as itn_queue: # ...complete PayFast payment... itn_data = itn_queue.get(timeout=2) """ server_address = (host, port) http_server = HTTPServer(server_address, PayFastITNHandler) http_server.itn_queue = Queue() # type: ignore executor = ThreadPoolExecutor(max_workers=1) executor.submit(http_server.serve_forever) try: yield http_server.itn_queue # type: ignore finally: http_server.shutdown()