Skip to content

Commit

Permalink
fix: modified http error codes to use an enum
Browse files Browse the repository at this point in the history
  • Loading branch information
MountainGod2 committed Apr 11, 2024
1 parent 4dd6206 commit d67e3ce
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 100 deletions.
9 changes: 7 additions & 2 deletions src/chaturbate_poller/chaturbate_poller.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import httpx
from httpx import HTTPStatusError, RequestError

from chaturbate_poller.constants import BASE_URL, ERROR_RANGE_END, ERROR_RANGE_START
from chaturbate_poller.constants import BASE_URL, HttpStatusCode
from chaturbate_poller.logging_config import LOGGING_CONFIG
from chaturbate_poller.models import EventsAPIResponse

Expand Down Expand Up @@ -123,5 +123,10 @@ def need_retry(exception: Exception) -> bool:
"""
if isinstance(exception, HTTPStatusError):
status_code = exception.response.status_code
return ERROR_RANGE_START <= status_code < ERROR_RANGE_END
return status_code in (
HttpStatusCode.INTERNAL_SERVER_ERROR,
HttpStatusCode.BAD_GATEWAY,
HttpStatusCode.SERVICE_UNAVAILABLE,
HttpStatusCode.GATEWAY_TIMEOUT,
)
return False
25 changes: 21 additions & 4 deletions src/chaturbate_poller/constants.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
"""Constants for the chaturbate_poller module."""

from enum import IntEnum

BASE_URL = "https://events.testbed.cb.dev/events/{username}/{token}/"
"""str: The base URL for fetching Chaturbate events."""

ERROR_RANGE_START = 500
"""int: Start of the range of HTTP status codes that are considered errors."""

ERROR_RANGE_END = 600
"""int: End of the range of HTTP status codes that are considered errors."""
class HttpStatusCode(IntEnum):
"""HTTP status codes."""

OK = 200
CREATED = 201
ACCEPTED = 202
NO_CONTENT = 204
BAD_REQUEST = 400
UNAUTHORIZED = 401
FORBIDDEN = 403
NOT_FOUND = 404
METHOD_NOT_ALLOWED = 405
CONFLICT = 409
INTERNAL_SERVER_ERROR = 500
NOT_IMPLEMENTED = 501
BAD_GATEWAY = 502
SERVICE_UNAVAILABLE = 503
GATEWAY_TIMEOUT = 504


API_TIMEOUT = 10
"""int: Maximum time in seconds that the server will wait before sending the nextURL."""
Expand Down
55 changes: 17 additions & 38 deletions src/chaturbate_poller/logging_config.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
"""Logging configuration for the Chaturbate poller."""
"""Logging configuration for the application."""

import logging

CRITICAL_LEVEL = 50
"""int: Custom level for CRITICAL events."""
logging.addLevelName(CRITICAL_LEVEL, "CRITICAL")
"""str: Name for the custom CRITICAL level."""


class CustomFormatter(logging.Formatter):
"""Custom formatter for including module and function names."""
"""Custom log formatter.
Args:
logging.Formatter: The logging formatter class.
"""

def format(self, record: logging.LogRecord) -> str:
"""Format the log record.
def format(
self,
record: logging.LogRecord,
) -> str:
"""Format the log record."""
Args:
record (logging.LogRecord): The log record to format.
Returns:
str: The formatted log record.
"""
record.module = record.module.split(".")[-1]
record.funcName = record.funcName or ""
return super().format(record)


Expand All @@ -26,12 +28,12 @@ def format(
"disable_existing_loggers": False,
"formatters": {
"standard": {
"format": "%(message)s",
"format": "%(asctime)s - %(levelname)s - %(name)s - %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
"detailed": {
"()": CustomFormatter,
"format": "%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(message)s", # noqa: E501
"format": "%(asctime)s - %(levelname)s - %(name)s - %(module)s - %(funcName)s - %(message)s", # noqa: E501
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
Expand All @@ -47,35 +49,12 @@ def format(
"formatter": "detailed",
"level": "DEBUG",
"when": "midnight",
"backupCount": 7,
},
},
"loggers": {
"": {
"handlers": ["console", "file"],
"level": "INFO",
"propagate": True,
},
"chaturbate_poller": {
"handlers": ["file"],
"level": "INFO",
"propagate": False,
},
"httpx": {
"handlers": ["file"],
"level": "CRITICAL",
"propagate": False,
},
"httpcore": {
"handlers": ["file"],
"level": "CRITICAL",
"propagate": False,
},
"backoff": {
"handlers": ["file"],
"level": "CRITICAL",
"propagate": False,
},
},
}
"""dict: Logging configuration for the Chaturbate poller."""
139 changes: 83 additions & 56 deletions tests/test_chaturbate_poller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pytest
from chaturbate_poller.chaturbate_poller import ChaturbateClient, need_retry
from chaturbate_poller.constants import HttpStatusCode
from chaturbate_poller.format_messages import format_message, format_user_event
from chaturbate_poller.logging_config import LOGGING_CONFIG
from chaturbate_poller.models import (
Expand Down Expand Up @@ -124,6 +125,32 @@ def chaturbate_client() -> ChaturbateClient:
return ChaturbateClient(USERNAME, TOKEN)


class TestConstants:
"""Tests for the constants."""

def test_base_url(self) -> None:
"""Test the base URL."""
assert TEST_URL == "https://events.testbed.cb.dev/events/testuser/testtoken/"

def test_http_status_codes(self) -> None:
"""Test the HTTP status codes."""
assert HttpStatusCode.OK == 200 # noqa: PLR2004
assert HttpStatusCode.CREATED == 201 # noqa: PLR2004
assert HttpStatusCode.ACCEPTED == 202 # noqa: PLR2004
assert HttpStatusCode.NO_CONTENT == 204 # noqa: PLR2004
assert HttpStatusCode.BAD_REQUEST == 400 # noqa: PLR2004
assert HttpStatusCode.UNAUTHORIZED == 401 # noqa: PLR2004
assert HttpStatusCode.FORBIDDEN == 403 # noqa: PLR2004
assert HttpStatusCode.NOT_FOUND == 404 # noqa: PLR2004
assert HttpStatusCode.METHOD_NOT_ALLOWED == 405 # noqa: PLR2004
assert HttpStatusCode.CONFLICT == 409 # noqa: PLR2004
assert HttpStatusCode.INTERNAL_SERVER_ERROR == 500 # noqa: PLR2004
assert HttpStatusCode.NOT_IMPLEMENTED == 501 # noqa: PLR2004
assert HttpStatusCode.BAD_GATEWAY == 502 # noqa: PLR2004
assert HttpStatusCode.SERVICE_UNAVAILABLE == 503 # noqa: PLR2004
assert HttpStatusCode.GATEWAY_TIMEOUT == 504 # noqa: PLR2004


class TestChaturbateClientInitialization:
"""Tests for the ChaturbateClient initialization."""

Expand Down Expand Up @@ -317,62 +344,6 @@ async def test_url_construction_with_timeout_none(
assert url == TEST_URL, "URL should be correctly constructed without timeout."


class TestEventFetching:
"""Tests for fetching events."""

# Test url construction with no url passed
@pytest.mark.asyncio()
async def test_fetch_events_no_url(
self,
http_client_mock, # noqa: ANN001
chaturbate_client: ChaturbateClient,
) -> None:
"""Test fetching events with no URL."""
request = Request("GET", TEST_URL)
http_client_mock.return_value = Response(200, json=EVENT_DATA, request=request)
response = await chaturbate_client.fetch_events()
assert isinstance(response, EventsAPIResponse)
http_client_mock.assert_called_once_with(TEST_URL, timeout=None)

@pytest.mark.asyncio()
async def test_fetch_events_malformed_json(
self, chaturbate_client: ChaturbateClient
) -> None:
"""Test fetching events with malformed JSON."""
request = Request("GET", TEST_URL)
self.return_value = Response(200, content=b"{not: 'json'}", request=request)
with pytest.raises(HTTPStatusError):
await chaturbate_client.fetch_events(TEST_URL)

@pytest.mark.asyncio()
@pytest.mark.parametrize(
("status_code", "should_succeed"),
[
(200, True),
(400, False),
(500, False),
],
)
async def test_fetch_events_http_statuses(
self,
http_client_mock, # noqa: ANN001
status_code: int,
should_succeed: bool, # noqa: FBT001
chaturbate_client: ChaturbateClient,
) -> None:
"""Test fetching events with different HTTP statuses."""
request = Request("GET", TEST_URL)
http_client_mock.return_value = Response(
status_code, json=EVENT_DATA, request=request
)
if should_succeed:
response = await chaturbate_client.fetch_events(TEST_URL)
assert isinstance(response, EventsAPIResponse)
else:
with pytest.raises(HTTPStatusError):
await chaturbate_client.fetch_events(TEST_URL)


class TestMessageFormatting:
"""Tests for message formatting."""

Expand Down Expand Up @@ -569,3 +540,59 @@ def test_direct_unknown_user_event(self, example_user: User) -> None:
event = Event(method="unknown", object=event_data, id="UNIQUE_EVENT_ID")
formatted_message = format_user_event(event)
assert formatted_message == "Unknown user event"


class TestEventFetching:
"""Tests for fetching events."""

# Test url construction with no url passed
@pytest.mark.asyncio()
async def test_fetch_events_no_url(
self,
http_client_mock, # noqa: ANN001
chaturbate_client: ChaturbateClient,
) -> None:
"""Test fetching events with no URL."""
request = Request("GET", TEST_URL)
http_client_mock.return_value = Response(200, json=EVENT_DATA, request=request)
response = await chaturbate_client.fetch_events()
assert isinstance(response, EventsAPIResponse)
http_client_mock.assert_called_once_with(TEST_URL, timeout=None)

@pytest.mark.asyncio()
async def test_fetch_events_malformed_json(
self, chaturbate_client: ChaturbateClient
) -> None:
"""Test fetching events with malformed JSON."""
request = Request("GET", TEST_URL)
self.return_value = Response(200, content=b"{not: 'json'}", request=request)
with pytest.raises(HTTPStatusError):
await chaturbate_client.fetch_events(TEST_URL)

@pytest.mark.asyncio()
@pytest.mark.parametrize(
("status_code", "should_succeed"),
[
(200, True),
(400, False),
(500, False),
],
)
async def test_fetch_events_http_statuses(
self,
http_client_mock, # noqa: ANN001
status_code: int,
should_succeed: bool, # noqa: FBT001
chaturbate_client: ChaturbateClient,
) -> None:
"""Test fetching events with different HTTP statuses."""
request = Request("GET", TEST_URL)
http_client_mock.return_value = Response(
status_code, json=EVENT_DATA, request=request
)
if should_succeed:
response = await chaturbate_client.fetch_events(TEST_URL)
assert isinstance(response, EventsAPIResponse)
else:
with pytest.raises(HTTPStatusError):
await chaturbate_client.fetch_events(TEST_URL)

0 comments on commit d67e3ce

Please sign in to comment.