Skip to content

Commit

Permalink
test: added testing for CLI command implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
antoninoLorenzo committed Feb 23, 2025
1 parent 0bab3df commit 1d5bcc0
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 14 deletions.
33 changes: 20 additions & 13 deletions cli/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ def __conversation_list(app_context: AppContext):
response = client.get('/conversations')
response.raise_for_status()
conversations = response.json()
except httpx.HTTPStatusError as status:
console.print(f"[bold red]Error: [/] {status}")
except httpx.HTTPStatusError as _:
console.print(f"[bold red]Error: [/]failed getting conversation list")
return

if len(conversations) == 0:
Expand All @@ -126,8 +126,8 @@ def __conversation_new(app_context: AppContext, conversation_name: str):
)
response.raise_for_status()
conversation = response.json()
except httpx.HTTPError as status:
console.print(f"[bold red]Error: [/] {status}")
except httpx.HTTPError as err:
console.print(f"[bold red]Error: [/]{err.response.status_code}")
return

console.print(f'[{conversation["conversation_id"]}]: {conversation["name"]}')
Expand All @@ -143,8 +143,9 @@ def __conversation_load(app_context: AppContext, conversation_id: int):
response = client.get(f'/conversations/{conversation_id}')
response.raise_for_status()
conversation = response.json()
except httpx.HTTPError as status:
console.print(f"[bold red]Error: [/] {status}")
except httpx.HTTPError as _:
# optional: consider implementing logging
console.print(f"[bold red]Error: [/]invalid conversation_id: {conversation_id}")
return

for message in conversation['messages']:
Expand All @@ -161,13 +162,19 @@ def __conversation_rename(app_context: AppContext, conversation_id: int, new_nam
client = app_context.client
console = app_context.console
try:
response = client.put(
response = client.post(
url=f'/conversations/{conversation_id}',
params={'new_name': new_name}
)
response.raise_for_status()
except httpx.HTTPError as status:
console.print(f"[bold red]Error: [/] {status}")
except httpx.HTTPError as exc:
try:
error_data = exc.response.json()
error_detail = error_data.get("detail")
except ValueError:
error_detail = 'failed renaming conversation'

console.print(f"[bold red]Error: [/]{error_detail}")
return


Expand All @@ -177,8 +184,8 @@ def __conversation_save(app_context: AppContext, conversation_id: int):
try:
response = client.put(f'/conversations/{conversation_id}')
response.raise_for_status()
except httpx.HTTPError as status:
console.print(f"[bold red]Error: [/] {status}")
except httpx.HTTPError as _:
console.print(f"[bold red]Error: [/]failed saving conversation")
return


Expand All @@ -188,6 +195,6 @@ def __conversation_delete(app_context: AppContext, conversation_id: int):
try:
response = client.delete(f'/conversations/{conversation_id}')
response.raise_for_status()
except httpx.HTTPError as status:
console.print(f"[bold red]Error: [/] {status}")
except httpx.HTTPError as _:
console.print(f"[bold red]Error: [/]failed saving conversation")
return
104 changes: 104 additions & 0 deletions test/cli/test_command_implementations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import sys
import io
import re
import functools

import httpx
from rich.console import Console
from pytest import mark, fixture, fail

from cli.app import AppContext
from cli.impl import (
__chat, # how do I test chat since its a REPL itself?
__conversation_new,
__conversation_load,
__conversation_save,
__conversation_delete,
__conversation_rename
)
from test.mock.mock_cli_prompt import build_input_mock


@fixture(scope="session")
def app_context():
try:
client = httpx.Client(base_url='http://127.0.0.1:8000')
client.get('/ping', timeout=5)
except Exception:
fail("API not avaialble")

context = AppContext(
client=client,
console=Console()
)

yield context


# TODO: add test cases (what could go wrong?)
@mark.parametrize('parameters', [
{'conversation_name': 'some_convo'}
])
def test_conversation_new(
app_context: AppContext,
capfd,
monkeypatch,
parameters
):
# when creating new conversation __chat is called automatically (feature).
# prompt_toolkit.PromptSession is not compatible with PyTest, apparently.
monkeypatch.setattr(
'cli.impl.build_input_multiline',
functools.partial(build_input_mock, user_input='back')
)
expected_success_regex = re.compile(r'^\[\d+\]:\s?\S.*') # [digit]: non-empty-string
expected_failure_regex = re.compile(r'^Error:\s\S+.*')

__conversation_new(app_context, parameters['conversation_name'])

capture = capfd.readouterr()
assert re.match(expected_success_regex, capture.out)


@mark.parametrize('parameters', [
{'conversation_id': -1, 'expected': 'error'},
{'conversation_id': 'not_a_number', 'expected': 'error'}
])
def test_conversation_load(
app_context: AppContext,
monkeypatch,
capfd,
parameters
):
monkeypatch.setattr(
'cli.impl.build_input_multiline',
functools.partial(build_input_mock, user_input='back')
)
expected_failure_regex = re.compile(r'^Error:\s\S+.*')

__conversation_load(app_context, parameters['conversation_id'])

capture = capfd.readouterr()
assert re.match(expected_failure_regex, capture.out)


@mark.parametrize('parameters', [
{'conversation_id': -1, 'new_name': 'valid'},
{'conversation_id': 'not_a_number', 'new_name': 'valid'},
{'conversation_id': 1, 'new_name': ''}
])
def test_conversation_rename(
app_context: AppContext,
capfd,
parameters
):
expected_failure_regex = re.compile(r'^Error:\s\S+.*')

__conversation_rename(
app_context,
parameters['conversation_id'],
parameters['new_name']
)

capture = capfd.readouterr()
assert re.match(expected_failure_regex, capture.out)
10 changes: 9 additions & 1 deletion test/mock/mock_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,23 @@ async def new_conversation(name: str) -> Conversation:

@app.get('/conversations/{conversation_id}')
async def get_conversation(conversation_id: int) -> Conversation:
if conversation_id < 0:
raise HTTPException(status_code=400, detail='invalid conversation id')

return Conversation(
conversation_id=conversation_id,
name='untitled',
messages=[Message(role=Role.USER, content='Hi')]
)


@app.post('/conversation/{conversation_id}')
@app.post('/conversations/{conversation_id}')
async def rename_conversation(conversation_id: int, new_name: str):
if len(new_name) == 0:
raise HTTPException(status_code=400, detail='invalid value for new_name')
if conversation_id < 0:
raise HTTPException(status_code=404, detail='conversation not found')

return Conversation(
conversation_id=conversation_id,
name=new_name,
Expand Down
13 changes: 13 additions & 0 deletions test/mock/mock_cli_prompt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@


class MockCLIInput:
def __init__(self, mock_input: str):
self.user_input = mock_input

def prompt(self):
return self.user_input


def build_input_mock(user_input):
return MockCLIInput(user_input)

0 comments on commit 1d5bcc0

Please sign in to comment.