Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ def send_request(
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))

response_line = b"HTTP/1.1 200 OK\r\n"
request_line = b"GET / HTTP/1.1\r\n"
headers = b"".join(
[b"User-Agent: CrappyClient/0.0.1\r\n", b"Content-Type: text/plain\r\n"]
[
f"Host: {host}:{port}\r\n".encode("utf-8"),
b"User-Agent: CrappyClient/0.0.1\r\n",
b"Content-Type: text/plain\r\n",
f"Content-Length: {len(request_payload)}\r\n".encode("utf-8"),
]
)
blank_line = b"\r\n"

request = b"".join([response_line, headers, blank_line, request_payload])
request = b"".join([request_line, headers, blank_line, request_payload])
s.sendall(request)
data = s.recv(1024)

Expand Down
4 changes: 2 additions & 2 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
DEFAULT_RESPONSE = b"Request received!"


def contruct_response(response_payload: bytes = DEFAULT_RESPONSE) -> bytes:
def construct_response(response_payload: bytes = DEFAULT_RESPONSE) -> bytes:
response_line = b"HTTP/1.1 200 OK\r\n"
headers = b"".join(
[b"Server: CrappyServer/0.0.1\r\n", b"Content-Type: text/plain\r\n"]
Expand Down Expand Up @@ -61,7 +61,7 @@ def main() -> None:
)
args = parser.parse_args()

start_server(response=contruct_response(args.response_payload.encode("utf-8")))
start_server(response=construct_response(args.response_payload.encode("utf-8")))


if __name__ == "__main__":
Expand Down
28 changes: 27 additions & 1 deletion tests/acceptance/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,29 @@ def get_container_logs(container_id: str) -> str: # type: ignore
pass


def wait_for_server_ready(
server_name: str, max_attempts: int = 20, delay: float = 0.5
) -> None:
"""Quick and dirty wait for server to be ready by attempting to connect to it"""
for attempt in range(max_attempts):
try:
client = podman.PodmanClient()
container = client.containers.get(server_name)
exit_code, _ = container.exec_run("ss -tulpn | grep LISTEN")

if exit_code == 0:
return
except Exception:
pass

print(f"Waiting for server to be ready... (attempt {attempt+1}/{max_attempts})")
time.sleep(delay)

raise TimeoutError(
f"Server did not become ready after {max_attempts * delay} seconds"
)


# environment setup as fixtures


Expand All @@ -36,8 +59,11 @@ def server_container(podman_network: str) -> Generator[str, None, None]:
name="test-server",
detach=True,
)
time.sleep(10) # wait for server to start

wait_for_server_ready("test-server", max_attempts=10)

yield "test-server"

container.stop()
container.remove()

Expand Down
14 changes: 8 additions & 6 deletions tests/integration/test_client_server.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import random
import threading
import time
from typing import Callable, Generator
from typing import Callable, Generator, TypeAlias

import pytest

from client import send_request
from server import contruct_response, start_server
from server import construct_response, start_server

# fun with types
ServerFactoryCallable = Callable[[bytes], tuple[threading.Thread, int]]
ServerThread: TypeAlias = threading.Thread
ServerPort: TypeAlias = int
ServerFactoryCallable = Callable[[bytes], tuple[ServerThread, ServerPort]]
ServerFactoryFixture = Generator[ServerFactoryCallable, None, None]


Expand Down Expand Up @@ -53,12 +55,12 @@ def create_server(
port = random.randint(8081, 9080)

def run_server() -> None:
start_server(contruct_response(response_payload), port=port)
start_server(construct_response(response_payload), port=port)

thread = threading.Thread(target=run_server, daemon=True)
threads.append((thread, port))
thread.start()
time.sleep(0.1)
time.sleep(0.1) # should be replaced with a proper polling/readiness check

return thread, port

Expand Down Expand Up @@ -106,7 +108,7 @@ def test_extra_long_request_payload_rejected(
"""Test that a request larger than the default buffer size of 1024 the server
will reject this with 'Connection reset by peer'"""
thread, port = server_factory(b"Request received!")
very_long_payload = b"bla" * 1000000000
very_long_payload = b"bla" * 100000000

with pytest.raises(ConnectionResetError):
request, response = send_request(very_long_payload, port=port)
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def test_send_request() -> None:
We pretend-check the client is 'HTTP complicant' by checking it includes the expected headers
"""
payload = b"test payload"
request = b"HTTP/1.1 200 OK\r\nUser-Agent: CrappyClient/0.0.1\r\nContent-Type: text/plain\r\n\r\ntest payload"
request = b"GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nUser-Agent: CrappyClient/0.0.1\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\ntest payload"
response = b"HTTP/1.1 200 OK\r\nServer: CrappyServer/0.0.1\r\nContent-Type: text/plain\r\n\r\nRequest received!"

# mock the server response
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest # noqa: F401

from server import contruct_response, start_server
from server import construct_response, start_server


def test_server_connection() -> None:
Expand All @@ -13,7 +13,7 @@ def test_server_connection() -> None:
host = "127.0.0.1"
port = 8888
client_request = b"HTTP/1.1 200 OK\r\nUser-Agent: CrappyClient/0.0.1\r\nContent-Type: text/plain\r\n\r\ntest payload from client"
server_response = contruct_response(b"My test response")
server_response = construct_response(b"My test response")

mock_conn = Mock()
mock_socket = Mock()
Expand Down