Skip to content

Commit

Permalink
refactor: test stub group
Browse files Browse the repository at this point in the history
  • Loading branch information
Amazia Gur authored and Amazia Gur committed Oct 31, 2024
1 parent b40c963 commit b88dc44
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 33 deletions.
10 changes: 6 additions & 4 deletions mockingbird/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import json
from typing import Any, Tuple, Optional, Dict, Callable

from mockingbird.stub_matcher import StubMatcher
from mockingbird.stub_group import StubGroup


class MockingbirdHandler(http.server.SimpleHTTPRequestHandler):
def __init__(self, stub_matcher: StubMatcher, *args, **kwargs):
def __init__(self, stub_matcher: StubGroup, *args, **kwargs):
self.stub_matcher = stub_matcher # Assign the injected StubMatcher
super().__init__(*args, **kwargs)

Expand All @@ -23,14 +23,16 @@ def do_DELETE(self):
self._handle_request("DELETE")

def _handle_request(self, method: str):
matched_stub, path_params = self.stub_matcher.match_stub(method, self.path)
matched_stub, path_params = self.stub_matcher.match(method, self.path)

if matched_stub:
self._send_response(matched_stub, path_params)
else:
self._send_404_response(method)

def _send_response(self, matched_stub: Tuple[int, Any, Optional[Callable]], path_params: Dict[str, str]):
def _send_response(self,
matched_stub: Tuple[int, Any, Optional[Callable]],
path_params: Dict[str, str]):
status_code, response, response_func = matched_stub
if response_func:
status_code, response = response_func()
Expand Down
9 changes: 5 additions & 4 deletions mockingbird/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

from mockingbird.handler import MockingbirdHandler
from mockingbird.route import Route
from mockingbird.stub_matcher import StubMatcher
from mockingbird.stub_group import StubGroup


class MockingbirdServer:
def __init__(self, port: int = 8080):
self.stub_matcher = StubMatcher()
self.stub_matcher = StubGroup()
self.server = socketserver.TCPServer(("", port), self._handler_factory)
self._thread = threading.Thread(target=self.server.serve_forever, daemon=True)
atexit.register(self.shutdown)
Expand All @@ -21,7 +21,7 @@ def _handler_factory(self, *args):
def routes(self, *routes: Route):
for route in routes:
route_config = route.build()
self.stub_matcher.add_stub(
self.stub_matcher.add(
method=route_config["method"],
pattern=route_config["compiled_path"],
status_code=route_config["status"],
Expand All @@ -31,7 +31,8 @@ def routes(self, *routes: Route):
return self

def start(self):
logging.info("MockingbirdServer starting on port %s", self.server.server_address[1])
logging.info("MockingbirdServer starting on port %s",
self.server.server_address[1])
self._thread.start()
return self

Expand Down
39 changes: 39 additions & 0 deletions mockingbird/stub_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import re
from typing import Pattern, Dict, Tuple, Any, Optional, Callable, Union


class StubGroup:
def __init__(self):
self.stubs: Dict[
str,
Dict[
Pattern,
Tuple[int, Any, Optional[
Callable[[], Tuple[int, Any]]]
]]] = {}

def add(self, method: str,
pattern: Union[str, Pattern], status_code: int, response: Any,
response_func: Optional[Callable[[], Tuple[int, Any]]] = None):
if method not in self.stubs:
self.stubs[method] = {}

if isinstance(pattern, str):
pattern = re.compile(f"^{re.sub(r'\\{(\\w+)\\}',
r'(?P<\\1>[^/]+)', pattern)}$")

self.stubs[method][pattern] = (status_code, response, response_func)

def match(self, method: str, path: str):
matched_stub = None
path_params = {}

for compiled_path, (status_code, response, response_func)\
in self.stubs.get(method, {}).items():
match = compiled_path.match(path)
if match:
matched_stub = (status_code, response, response_func)
path_params = match.groupdict()
break

return matched_stub, path_params
25 changes: 0 additions & 25 deletions mockingbird/stub_matcher.py

This file was deleted.

46 changes: 46 additions & 0 deletions tests/test_stub_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from hamcrest import assert_that, none, is_
from mockingbird.stub_group import StubGroup


def test_no_match():
stub_group = StubGroup()
stub_group.add("GET", "/hi", 200, {"message": "hello"})
matched, _ = stub_group.match("GET", "/hello/kuku")
assert_that(matched, none())


def test_match():
stub_group = StubGroup()
stub_group.add("GET", "/hi", 200, {"message": "hello"})
matched, _ = stub_group.match("GET", "/hi")
assert_that(matched, is_((200, {"message": "hello"}, None)))


def test_none_on_partial_match():
stub_group = StubGroup()
stub_group.add("POST", "/hi", 200, {"message": "hello"})
matched, _ = stub_group.match("POST", "/hi/hello")
assert_that(matched, none())


def test_match_w_path_param():
stub_group = StubGroup()
stub_group.add("GET", r"^/hello/(?P<name>\w+)$",
200, {"message": "Hello, {name}!"})

matched, path_param = stub_group.match("GET", "/hello/mockingbird")

assert_that(matched, is_((200, {"message": "Hello, {name}!"}, None)))
assert_that(path_param, is_({"name": "mockingbird"}))


def test_match_stub_with_response_func():
stub_group = StubGroup()

def dynamic_response():
return 202, {"status": "dynamic"}

stub_group.add("GET", r"^/dynamic$",
200, {}, response_func=dynamic_response)
matched, _ = stub_group.match("GET", "/dynamic")
assert_that(matched, is_((200, {}, dynamic_response)))

0 comments on commit b88dc44

Please sign in to comment.