#1943: unit tests + refactoring HTTP -> Http

This commit is contained in:
Czar.Echavez
2023-10-06 12:10:57 +01:00
parent be6b904db9
commit 853bb9eecc
9 changed files with 147 additions and 41 deletions

View File

@@ -25,8 +25,8 @@ Usage
Implementation Implementation
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
- HTTP request uses a ``HTTPRequestPacket`` object - HTTP request uses a ``HttpRequestPacket`` object
- HTTP reaponse uses a ``HTTPResponsePacket`` object - HTTP response uses a ``HttpResponsePacket`` object
- Extends Service class for integration with ``SoftwareManager``. - Extends Service class for integration with ``SoftwareManager``.
Web Browser (Web Client) Web Browser (Web Client)

View File

@@ -12,7 +12,7 @@ from primaite.simulator.system.applications.database_client import DatabaseClien
from primaite.simulator.system.services.database.database_service import DatabaseService from primaite.simulator.system.services.database.database_service import DatabaseService
from primaite.simulator.system.services.dns.dns_server import DNSServer from primaite.simulator.system.services.dns.dns_server import DNSServer
from primaite.simulator.system.services.red_services.data_manipulation_bot import DataManipulationBot from primaite.simulator.system.services.red_services.data_manipulation_bot import DataManipulationBot
from primaite.simulator.system.services.web_server.web_server_service import WebServer from primaite.simulator.system.services.web_server.web_server import WebServer
def client_server_routed() -> Network: def client_server_routed() -> Network:

View File

@@ -3,7 +3,7 @@ from enum import Enum
from primaite.simulator.network.protocols.packet import DataPacket from primaite.simulator.network.protocols.packet import DataPacket
class HTTPRequestMethod(Enum): class HttpRequestMethod(Enum):
"""Enum list of HTTP Request methods that can be handled by the simulation.""" """Enum list of HTTP Request methods that can be handled by the simulation."""
GET = "GET" GET = "GET"
@@ -25,7 +25,7 @@ class HTTPRequestMethod(Enum):
"""Apply partial modifications to a resource.""" """Apply partial modifications to a resource."""
class HTTPStatusCode(Enum): class HttpStatusCode(Enum):
"""List of available HTTP Statuses.""" """List of available HTTP Statuses."""
OK = 200 OK = 200
@@ -37,6 +37,9 @@ class HTTPStatusCode(Enum):
UNAUTHORIZED = 401 UNAUTHORIZED = 401
"""Auth required.""" """Auth required."""
NOT_FOUND = 404
"""Item not found in server."""
METHOD_NOT_ALLOWED = 405 METHOD_NOT_ALLOWED = 405
"""Method is not supported by server.""" """Method is not supported by server."""
@@ -44,18 +47,18 @@ class HTTPStatusCode(Enum):
"""Error on the server side.""" """Error on the server side."""
class HTTPRequestPacket(DataPacket): class HttpRequestPacket(DataPacket):
"""Class that represents an HTTP Request Packet.""" """Class that represents an HTTP Request Packet."""
request_method: HTTPRequestMethod request_method: HttpRequestMethod
"""The HTTP Request method.""" """The HTTP Request method."""
request_url: str request_url: str
"""URL of request.""" """URL of request."""
class HTTPResponsePacket(DataPacket): class HttpResponsePacket(DataPacket):
"""Class that reprensents an HTTP Response Packet.""" """Class that reprensents an HTTP Response Packet."""
status_code: HTTPStatusCode = None status_code: HttpStatusCode = None
"""Status code of the HTTP response.""" """Status code of the HTTP response."""

View File

@@ -2,7 +2,7 @@ from ipaddress import IPv4Address
from typing import Dict, Optional from typing import Dict, Optional
from urllib.parse import urlparse from urllib.parse import urlparse
from primaite.simulator.network.protocols.http import HTTPRequestMethod, HTTPRequestPacket, HTTPResponsePacket from primaite.simulator.network.protocols.http import HttpRequestMethod, HttpRequestPacket, HttpResponsePacket
from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.network_layer import IPProtocol
from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.network.transmission.transport_layer import Port
from primaite.simulator.system.applications.application import Application from primaite.simulator.system.applications.application import Application
@@ -19,7 +19,7 @@ class WebBrowser(Application):
domain_name_ip_address: Optional[IPv4Address] = None domain_name_ip_address: Optional[IPv4Address] = None
"The IP address of the domain name for the webpage." "The IP address of the domain name for the webpage."
latest_response: HTTPResponsePacket = None latest_response: HttpResponsePacket = None
"""Keeps track of the latest HTTP response.""" """Keeps track of the latest HTTP response."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
@@ -87,7 +87,7 @@ class WebBrowser(Application):
return False return False
# create HTTPRequest payload # create HTTPRequest payload
payload = HTTPRequestPacket(request_method=HTTPRequestMethod.GET, request_url=url) payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url=url)
# send request # send request
return self.send( return self.send(
@@ -98,7 +98,7 @@ class WebBrowser(Application):
def send( def send(
self, self,
payload: HTTPRequestPacket, payload: HttpRequestPacket,
dest_ip_address: Optional[IPv4Address] = None, dest_ip_address: Optional[IPv4Address] = None,
dest_port: Optional[Port] = Port.HTTP, dest_port: Optional[Port] = Port.HTTP,
session_id: Optional[str] = None, session_id: Optional[str] = None,
@@ -120,7 +120,7 @@ class WebBrowser(Application):
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id, **kwargs payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id, **kwargs
) )
def receive(self, payload: HTTPResponsePacket, session_id: Optional[str] = None, **kwargs) -> bool: def receive(self, payload: HttpResponsePacket, session_id: Optional[str] = None, **kwargs) -> bool:
""" """
Receives a payload from the SessionManager. Receives a payload from the SessionManager.
@@ -128,8 +128,8 @@ class WebBrowser(Application):
:param session_id: The Session ID the payload is to originate from. Optional. :param session_id: The Session ID the payload is to originate from. Optional.
:return: True if successful, False otherwise. :return: True if successful, False otherwise.
""" """
if not isinstance(payload, HTTPResponsePacket): if not isinstance(payload, HttpResponsePacket):
self.sys_log.error(f"{self.name} received a packet that is not an HTTPResponsePacket") self.sys_log.error(f"{self.name} received a packet that is not an HttpResponsePacket")
return False return False
self.sys_log.info(f"{self.name}: Received HTTP {payload.status_code.value}") self.sys_log.info(f"{self.name}: Received HTTP {payload.status_code.value}")
self.latest_response = payload self.latest_response = payload

View File

@@ -3,10 +3,10 @@ from typing import Any, Optional
from urllib.parse import urlparse from urllib.parse import urlparse
from primaite.simulator.network.protocols.http import ( from primaite.simulator.network.protocols.http import (
HTTPRequestMethod, HttpRequestMethod,
HTTPRequestPacket, HttpRequestPacket,
HTTPResponsePacket, HttpResponsePacket,
HTTPStatusCode, HttpStatusCode,
) )
from primaite.simulator.network.transmission.network_layer import IPProtocol from primaite.simulator.network.transmission.network_layer import IPProtocol
from primaite.simulator.network.transmission.transport_layer import Port from primaite.simulator.network.transmission.transport_layer import Port
@@ -37,52 +37,52 @@ class WebServer(Service):
# index HTML main file # index HTML main file
self.file_system.create_file(file_name="index.html", folder_name="primaite", real=True) self.file_system.create_file(file_name="index.html", folder_name="primaite", real=True)
def _process_http_request(self, payload: HTTPRequestPacket, session_id: Optional[str] = None) -> bool: def _process_http_request(self, payload: HttpRequestPacket, session_id: Optional[str] = None) -> bool:
""" """
Parse the HTTPRequestPacket. Parse the HttpRequestPacket.
:param: payload: Payload containing th HTTPRequestPacket :param: payload: Payload containing th HttpRequestPacket
:type: payload: HTTPRequestPacket :type: payload: HttpRequestPacket
:param: session_id: Session id of the http request :param: session_id: Session id of the http request
:type: session_id: Optional[str] :type: session_id: Optional[str]
""" """
response = HTTPResponsePacket() response = HttpResponsePacket()
self.sys_log.info(f"{self.name}: Received HTTP {payload.request_method.name} {payload.request_url}") self.sys_log.info(f"{self.name}: Received HTTP {payload.request_method.name} {payload.request_url}")
# check the type of HTTP request # check the type of HTTP request
if payload.request_method == HTTPRequestMethod.GET: if payload.request_method == HttpRequestMethod.GET:
response = self._handle_get_request(payload=payload) response = self._handle_get_request(payload=payload)
elif payload.request_method == HTTPRequestMethod.POST: elif payload.request_method == HttpRequestMethod.POST:
pass pass
else: else:
# send a method not allowed response # send a method not allowed response
response.status_code = HTTPStatusCode.METHOD_NOT_ALLOWED response.status_code = HttpStatusCode.METHOD_NOT_ALLOWED
# send response to web client # send response to web client
self.send(payload=response, session_id=session_id) self.send(payload=response, session_id=session_id)
# return true if response is OK # return true if response is OK
return response.status_code == HTTPStatusCode.OK return response.status_code == HttpStatusCode.OK
def _handle_get_request(self, payload: HTTPRequestPacket) -> HTTPResponsePacket: def _handle_get_request(self, payload: HttpRequestPacket) -> HttpResponsePacket:
""" """
Handle a GET HTTP request. Handle a GET HTTP request.
:param: payload: HTTP request payload :param: payload: HTTP request payload
:type: payload: HTTPRequestPacket :type: payload: HttpRequestPacket
""" """
response = HTTPResponsePacket(status_code=HTTPStatusCode.BAD_REQUEST, payload=payload) response = HttpResponsePacket(status_code=HttpStatusCode.NOT_FOUND, payload=payload)
try: try:
parsed_url = urlparse(payload.request_url) parsed_url = urlparse(payload.request_url)
path = parsed_url.path.strip("/") path = parsed_url.path.strip("/")
if len(path) < 1: if len(path) < 1:
# query succeeded # query succeeded
response.status_code = HTTPStatusCode.OK response.status_code = HttpStatusCode.OK
if path.startswith("users"): if path.startswith("users"):
# get data from DatabaseServer # get data from DatabaseServer
@@ -90,17 +90,17 @@ class WebServer(Service):
# get all users # get all users
if db_client.query("SELECT * FROM user;"): if db_client.query("SELECT * FROM user;"):
# query succeeded # query succeeded
response.status_code = HTTPStatusCode.OK response.status_code = HttpStatusCode.OK
return response return response
except Exception: except Exception:
# something went wrong on the server # something went wrong on the server
response.status_code = HTTPStatusCode.INTERNAL_SERVER_ERROR response.status_code = HttpStatusCode.INTERNAL_SERVER_ERROR
return response return response
def send( def send(
self, self,
payload: HTTPResponsePacket, payload: HttpResponsePacket,
session_id: Optional[str] = None, session_id: Optional[str] = None,
dest_ip_address: Optional[IPv4Address] = None, dest_ip_address: Optional[IPv4Address] = None,
dest_port: Optional[Port] = None, dest_port: Optional[Port] = None,
@@ -138,7 +138,7 @@ class WebServer(Service):
:param: session_id: The id of the session. Optional. :param: session_id: The id of the session. Optional.
""" """
# check if the payload is an HTTPPacket # check if the payload is an HTTPPacket
if not isinstance(payload, HTTPRequestPacket): if not isinstance(payload, HttpRequestPacket):
self.sys_log.error("Payload is not an HTTPPacket") self.sys_log.error("Payload is not an HTTPPacket")
return False return False

View File

@@ -1,6 +1,6 @@
from primaite.simulator.network.hardware.nodes.computer import Computer from primaite.simulator.network.hardware.nodes.computer import Computer
from primaite.simulator.network.hardware.nodes.server import Server from primaite.simulator.network.hardware.nodes.server import Server
from primaite.simulator.network.protocols.http import HTTPStatusCode from primaite.simulator.network.protocols.http import HttpStatusCode
from primaite.simulator.system.applications.application import ApplicationOperatingState from primaite.simulator.system.applications.application import ApplicationOperatingState
from primaite.simulator.system.applications.web_browser import WebBrowser from primaite.simulator.system.applications.web_browser import WebBrowser
from primaite.simulator.system.services.service import ServiceOperatingState from primaite.simulator.system.services.service import ServiceOperatingState
@@ -17,7 +17,7 @@ def test_web_page_home_page(uc2_network):
# latest reponse should have status code 200 # latest reponse should have status code 200
assert web_client.latest_response is not None assert web_client.latest_response is not None
assert web_client.latest_response.status_code == HTTPStatusCode.OK assert web_client.latest_response.status_code == HttpStatusCode.OK
def test_web_page_get_users_page_request_with_domain_name(uc2_network): def test_web_page_get_users_page_request_with_domain_name(uc2_network):
@@ -31,7 +31,7 @@ def test_web_page_get_users_page_request_with_domain_name(uc2_network):
# latest reponse should have status code 200 # latest reponse should have status code 200
assert web_client.latest_response is not None assert web_client.latest_response is not None
assert web_client.latest_response.status_code == HTTPStatusCode.OK assert web_client.latest_response.status_code == HttpStatusCode.OK
def test_web_page_get_users_page_request_with_ip_address(uc2_network): def test_web_page_get_users_page_request_with_ip_address(uc2_network):
@@ -49,4 +49,4 @@ def test_web_page_get_users_page_request_with_ip_address(uc2_network):
# latest reponse should have status code 200 # latest reponse should have status code 200
assert web_client.latest_response is not None assert web_client.latest_response is not None
assert web_client.latest_response.status_code == HTTPStatusCode.OK assert web_client.latest_response.status_code == HttpStatusCode.OK

View File

@@ -0,0 +1,39 @@
import pytest
from primaite.simulator.network.hardware.nodes.computer import Computer
from primaite.simulator.network.protocols.http import HttpResponsePacket, HttpStatusCode
from primaite.simulator.network.transmission.network_layer import IPProtocol
from primaite.simulator.network.transmission.transport_layer import Port
from primaite.simulator.system.applications.web_browser import WebBrowser
@pytest.fixture(scope="function")
def web_client() -> Computer:
node = Computer(
hostname="web_client", ip_address="192.168.1.11", subnet_mask="255.255.255.0", default_gateway="192.168.1.1"
)
return node
def test_create_web_client(web_client):
assert web_client is not None
web_browser: WebBrowser = web_client.software_manager.software["WebBrowser"]
assert web_browser.name is "WebBrowser"
assert web_browser.port is Port.HTTP
assert web_browser.protocol is IPProtocol.TCP
def test_receive_invalid_payload(web_client):
web_browser: WebBrowser = web_client.software_manager.software["WebBrowser"]
assert web_browser.receive(payload={}) is False
def test_receive_payload(web_client):
payload = HttpResponsePacket(status_code=HttpStatusCode.OK)
web_browser: WebBrowser = web_client.software_manager.software["WebBrowser"]
assert web_browser.latest_response is None
web_browser.receive(payload=payload)
assert web_browser.latest_response is not None

View File

@@ -0,0 +1,64 @@
import pytest
from primaite.simulator.network.hardware.nodes.server import Server
from primaite.simulator.network.protocols.http import (
HttpRequestMethod,
HttpRequestPacket,
HttpResponsePacket,
HttpStatusCode,
)
from primaite.simulator.network.transmission.network_layer import IPProtocol
from primaite.simulator.network.transmission.transport_layer import Port
from primaite.simulator.system.services.web_server.web_server import WebServer
@pytest.fixture(scope="function")
def web_server() -> Server:
node = Server(
hostname="web_server", ip_address="192.168.1.10", subnet_mask="255.255.255.0", default_gateway="192.168.1.1"
)
node.software_manager.install(software_class=WebServer)
node.software_manager.software["WebServer"].start()
return node
def test_create_web_server(web_server):
assert web_server is not None
web_server_service: WebServer = web_server.software_manager.software["WebServer"]
assert web_server_service.name is "WebServer"
assert web_server_service.port is Port.HTTP
assert web_server_service.protocol is IPProtocol.TCP
def test_handling_get_request_not_found_path(web_server):
payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url="http://domain.com/fake-path")
web_server_service: WebServer = web_server.software_manager.software["WebServer"]
response: HttpResponsePacket = web_server_service._handle_get_request(payload=payload)
assert response.status_code == HttpStatusCode.NOT_FOUND
def test_handling_get_request_home_page(web_server):
payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url="http://domain.com/")
web_server_service: WebServer = web_server.software_manager.software["WebServer"]
response: HttpResponsePacket = web_server_service._handle_get_request(payload=payload)
assert response.status_code == HttpStatusCode.OK
def test_process_http_request_get(web_server):
payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url="http://domain.com/")
web_server_service: WebServer = web_server.software_manager.software["WebServer"]
assert web_server_service._process_http_request(payload=payload) is True
def test_process_http_request_method_not_allowed(web_server):
payload = HttpRequestPacket(request_method=HttpRequestMethod.DELETE, request_url="http://domain.com/")
web_server_service: WebServer = web_server.software_manager.software["WebServer"]
assert web_server_service._process_http_request(payload=payload) is False