Skip to content

Commit

Permalink
Merge pull request #108 from elliot-100/improve-lookup-errors
Browse files Browse the repository at this point in the history
Add: Meaningful error messages when `get_group()` and `get_event()` lookups fail; add corresponding test suite coverage, plus 'happy path' tests;  misc. output improvements in manual end-to-end test suite.
  • Loading branch information
elliot-100 authored Apr 25, 2024
2 parents b337d64 + e5fb2f3 commit 593de7e
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 34 deletions.
44 changes: 20 additions & 24 deletions manual_test_functions.py
Original file line number Diff line number Diff line change
@@ -1,78 +1,74 @@
"""Use Spond 'get' functions to summarise available data.
Intended as a simple end-to-end test for assurance when making changes.
Uses all existing group, event, message `get_` methods.
Doesn't yet use `get_person(id)` or any `send_`, `update_` methods."""
Intended as a simple end-to-end test for assurance when making changes, where there are s
gaps in test suite coverage.
Doesn't yet use `get_person(user)` or any `send_`, `update_` methods."""

import asyncio
import random

from config import password, username
from spond import spond

DUMMY_ID = "DUMMY_ID"


async def main() -> None:
s = spond.Spond(username=username, password=password)

print("Getting all groups...")
# GROUPS

print("\nGetting all groups...")
groups = await s.get_groups()
print(f"{len(groups)} groups:")
for i, group in enumerate(groups):
print(f"[{i}] {_group_summary(group)}")

print("Getting a random group by id...")
random_group_id = random.choice(groups)["id"]
group = await s.get_group(random_group_id)
print(f"{_group_summary(group)}")
# EVENTS

print("\nGetting up to 10 events...")
events = await s.get_events(max_events=10)
print(f"{len(events)} events:")
for i, event in enumerate(events):
print(f"[{i}] {_event_summary(event)}")

print("Getting a random event by id...")
random_event_id = random.choice(events)["id"]
event = await s.get_event(random_event_id)
print(f"{_event_summary(event)}")
# MESSAGES

print("\nGetting up to 10 messages...")
messages = await s.get_messages()
print(f"{len(messages)} messages:")
for i, message in enumerate(messages):
print(f"[{i}] {_message_summary(message)}")

# No `get_message(id)` function

await s.clientsession.close()


def _group_summary(group) -> str:
return f"id: {group['id']}, " f"name: {group['name']}"
return f"id='{group['id']}', " f"name='{group['name']}'"


def _event_summary(event) -> str:
return (
f"id: {event['id']}, "
f"name: {event['heading']}, "
f"startTimestamp: {event['startTimestamp']}"
f"id='{event['id']}', "
f"heading='{event['heading']}', "
f"startTimestamp='{event['startTimestamp']}'"
)


def _message_summary(message) -> str:
return (
f"id: {message['id']}, "
f"timestamp: {message['message']['timestamp']}, "
f"text: {_abbreviate(message['message']['text'] if message['message'].get('text') else '', length=64)}, "
f"id='{message['id']}', "
f"timestamp='{message['message']['timestamp']}', "
f"text={_abbreviate(message['message']['text'] if message['message'].get('text') else '', length=64)}, "
)


def _abbreviate(text, length) -> str:
"""Abbreviate long text, normalising line endings to escape characters."""
escaped_text = repr(text)
if len(text) > length:
return f"{escaped_text[0:length]}[…]"
return f"{escaped_text}"
return f"{escaped_text[:length]}[…]"
return escaped_text


loop = asyncio.new_event_loop()
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ aiohttp = "^3.8.5"
black = "^24.4.0"
isort = "^5.11.4"
pytest = "^8.1.1"
pytest-asyncio = "^0.23.6"

[tool.black]
line-length = 88
Expand Down
16 changes: 14 additions & 2 deletions spond/spond.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,20 @@ async def get_group(self, uid) -> dict:
Returns
-------
Details of the group.
Raises
------
IndexError if no group is matched.
"""

if not self.groups:
await self.get_groups()
for group in self.groups:
if group["id"] == uid:
return group
raise IndexError
errmsg = f"No group with id='{uid}'"
raise IndexError(errmsg)

@require_authentication
async def get_person(self, user) -> dict:
Expand Down Expand Up @@ -308,13 +314,19 @@ async def get_event(self, uid) -> dict:
Returns
-------
Details of the event.
Raises
------
IndexError if no event is matched.
"""
if not self.events:
await self.get_events()
for event in self.events:
if event["id"] == uid:
return event
raise IndexError
errmsg = f"No event with id='{uid}'"
raise IndexError(errmsg)

@require_authentication
async def update_event(self, uid, updates: dict):
Expand Down
8 changes: 0 additions & 8 deletions tests/test_imports.py

This file was deleted.

133 changes: 133 additions & 0 deletions tests/test_spond.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""Test suite for Spond class."""

import pytest


from spond.spond import Spond

MOCK_USERNAME, MOCK_PASSWORD = "MOCK_USERNAME", "MOCK_PASSWORD"
MOCK_TOKEN = "MOCK_TOKEN"


# Mock the `require_authentication` decorator to bypass authentication
def mock_require_authentication(func):
async def wrapper(*args, **kwargs):
return await func(*args, **kwargs)

return wrapper


Spond.require_authentication = mock_require_authentication(Spond.get_event)


@pytest.fixture
def mock_events():
"""Mock a minimal list of events."""
return [
{
"id": "ID1",
"name": "Event One",
},
{
"id": "ID2",
"name": "Event Two",
},
]


@pytest.fixture
def mock_groups():
"""Mock a minimal list of groups."""
return [
{
"id": "ID1",
"name": "Group One",
},
{
"id": "ID2",
"name": "Group Two",
},
]


@pytest.fixture
def mock_token():
return MOCK_TOKEN


@pytest.mark.asyncio
async def test_get_event__happy_path(mock_events, mock_token):
"""Test that a valid `id` returns the matching event."""

s = Spond(MOCK_USERNAME, MOCK_PASSWORD)
s.events = mock_events
s.token = mock_token
g = await s.get_event("ID1")

assert g == {
"id": "ID1",
"name": "Event One",
}


@pytest.mark.asyncio
async def test_get_event__no_match_raises_exception(mock_events, mock_token):
"""Test that a non-matched `id` raises IndexError."""

s = Spond(MOCK_USERNAME, MOCK_PASSWORD)
s.events = mock_events
s.token = mock_token

with pytest.raises(IndexError):
await s.get_event("ID3")


@pytest.mark.asyncio
async def test_get_event__blank_id_match_raises_exception(mock_events, mock_token):
"""Test that a blank `id` raises IndexError."""

s = Spond(MOCK_USERNAME, MOCK_PASSWORD)
s.events = mock_events
s.token = mock_token

with pytest.raises(IndexError):
await s.get_event("")


@pytest.mark.asyncio
async def test_get_group__happy_path(mock_groups, mock_token):
"""Test that a valid `id` returns the matching group."""

s = Spond(MOCK_USERNAME, MOCK_PASSWORD)
s.groups = mock_groups
s.token = mock_token
g = await s.get_group("ID2")

assert g == {
"id": "ID2",
"name": "Group Two",
}


@pytest.mark.asyncio
async def test_get_group__no_match_raises_exception(mock_groups, mock_token):
"""Test that a non-matched `id` raises IndexError."""

s = Spond(MOCK_USERNAME, MOCK_PASSWORD)
s.groups = mock_groups
s.token = mock_token

with pytest.raises(IndexError):
await s.get_group("ID3")


@pytest.mark.asyncio
async def test_get_group__blank_id_raises_exception(mock_groups, mock_token):
"""Test that a blank `id` raises IndexError."""

s = Spond(MOCK_USERNAME, MOCK_PASSWORD)
s.groups = mock_groups
s.token = mock_token

with pytest.raises(IndexError):
await s.get_group("")

0 comments on commit 593de7e

Please sign in to comment.