I am writing tests for legacy software which has a dependency on CGI scripts on a remote machine.
Mocking the dependency would entail modifying the code under test, which is a thing I cannot do at the moment. For this reason, I decided to make a replica of the remote machine for the code to run against (I have made a copy of the CGI scripts on my development machine and provided a way to set up a local http server within my tests).
Since I don't have much experience with CGI, I have written some "meta-tests" to ensure the local server is working correctly.
Test folder structure is:
root
|
`- src/
| `-- #code to test...
|
`-functional_tests/
|-- __init__.py
|-- tests.py
`-- www
`-- cgi-bin
|-- # legacy cgi scripts...
`-- hello_world.sh
#tests.py
from contextlib import contextmanager
from http.client import HTTPResponse
from http.server import CGIHTTPRequestHandler, HTTPServer
import threading
from unittest import TestCase
import urllib.request
from urllib.error import URLError
# custom handler for my dir structure
class _TestHandler(CGIHTTPRequestHandler):
def __init__(self, request, client_address, server):
super().__init__(
request, client_address, server, directory="functional_tests/www"
)
self.cgi_directories = ["/cgi-bin"]
# utility function invoked by tests which involve cgi
@contextmanager
def _setup_cgi_http_server():
# runs a stoppable http.server.HTTPServer instance (see https://stackoverflow.com/a/19211760/19831748)
def _run_server(httpd: HTTPServer, stop_event: threading.Event):
# prevents handle_request() from hanging
httpd.timeout = 0.25
print("http server started...")
while not stop_event.is_set():
httpd.handle_request()
print("...http server stopped")
stop_event = threading.Event()
with HTTPServer(("", 8000), _TestHandler) as httpd:
server_thread = threading.Thread(target=_run_server, args=[httpd, stop_event])
server_thread.start()
yield httpd
stop_event.set()
class TestCGIHTTPServer(TestCase):
def test_can_invoke_cgi(self):
TEST_CGI_URL = "http://localhost:8000/cgi-bin/hello_world.sh"
self.enterContext(_setup_cgi_http_server())
with urllib.request.urlopen(TEST_CGI_URL) as response:
assert isinstance(response, HTTPResponse)
content = response.read().decode("utf-8")
self.assertIn("hello world", content.lower())
def test_server_is_closed_after_exit(self):
with _setup_cgi_http_server() as _:
pass
with self.assertRaises((URLError, ConnectionResetError)):
urllib.request.urlopen("http://localhost:8000")
#!/bin/bash
# hello_world.sh
echo "Content-Type: text/plain"
echo ""
echo "Hello world"
_setup_cgi_http_server()is invoked duringsetUp()\$\endgroup\$