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
20 changes: 20 additions & 0 deletions src/api/user_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from src.api.api_client import APIClient
from src.utils.helpers.handle_api_errors import handle_api_errors


class UserAPI:
def __init__(self, client: APIClient):
self.client = client

@handle_api_errors
def register(self, username, email, password):
data = {
"user_name": username,
"email": email,
"password": password,
}
response = self.client.post("user", data=data)
if "user_id" in response:
return response
else:
raise Exception(f"Failed to register: {response}")
15 changes: 14 additions & 1 deletion src/commands/model_file.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime
import json

import click

Expand Down Expand Up @@ -32,13 +33,25 @@ def upload(
else:
click.echo(f"Failed to upload model file: {result['response']['detail']}")

def list_default(self, file_path="src/models/default_models.json"):
click.echo("Default Models:")
with open(file_path, "r") as file:
default_models = json.load(file)
for model in default_models:
click.echo(f" Name: {model['name']}")
click.echo(f" Description: {model['description']}")
click.echo(" Availability:")
for key, value in model["available"].items():
click.echo(f" • {key}: {'Yes' if value else 'No'}")

def list(self):
self.list_default()
result = self.endpoint.get_all_models()
if result["success"]:
if not result["data"]:
click.echo("No models to list.")
return
click.echo("Models:")
click.echo("User Models:")
for model in result["data"]:
click.echo(f" Name: {model['model_name']}")
click.echo(f" ID: {model['model_id']}")
Expand Down
25 changes: 23 additions & 2 deletions src/commands/user_auth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import subprocess
import click

from src.api.api_client import APIClient
from src.api.auth_api import AuthAPI
from src.api.user_api import UserAPI

endpoint = AuthAPI(APIClient())
user_endpoint = UserAPI(APIClient())


@click.command()
Expand All @@ -14,16 +17,34 @@ def login(email, password):
result = endpoint.login(email, password)
if result["success"]:
click.echo(f"Successfully logged in. {result['data']}")
subprocess.run("quack", shell=True)
else:
click.echo(f"Login failed. {result['message']}")


@click.command()
@click.pass_context
def logout(ctx):
def logout():
"""Logout from the application."""
result = endpoint.logout()
if result:
click.echo("Successfully logged out.")
else:
click.echo("No action taken. You were not logged in.")


@click.command()
@click.pass_context
@click.option("--username", prompt=True)
@click.option("--email", prompt=True)
@click.option("--password", prompt=True, hide_input=True)
def register(ctx, username, email, password):
"""Register with Duckington Labs."""
result = user_endpoint.register(username, email, password)
if result["success"]:
click.echo("Welcome to Duckington Labs, you've successfully registered as:")
click.echo(f" - Username: {result['data']['user_name']}")
click.echo(f" - Email: {result['data']['email']}")
click.echo(f" - User ID: {result['data']['user_id']}")
ctx.invoke(login, email=email, password=password)
else:
click.echo(f"Login failed. {result['message']}")
38 changes: 38 additions & 0 deletions src/models/default_models.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"name": "llama3:8b",
"available": {
"FPGA": false,
"GPU": true,
"CPU": false
},
"description": "Meta's Llama 3"
},
{
"name": "llama2:7b",
"available": {
"FPGA": false,
"GPU": true,
"CPU": true
},
"description": "Meta's Llama 2"
},
{
"name": "smollm:135b",
"available": {
"FPGA": false,
"GPU": true,
"CPU": true
},
"description": "Small Language Model"
},
{
"name": "tinyllamas-stories-110m",
"available": {
"FPGA": true,
"GPU": true,
"CPU": true
},
"description": "kparthy's Tiny Llama Stories"
}
]
56 changes: 56 additions & 0 deletions test/api/test_user_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import pytest
from unittest.mock import Mock
from src.api.user_api import UserAPI


@pytest.fixture
def mock_client():
return Mock()


@pytest.fixture
def user_api(mock_client):
return UserAPI(mock_client)


def test_register_success(user_api, mock_client):
mock_client.post.return_value = {
"user_id": "12345",
"user_name": "testuser",
"email": "test@test.com",
}

result = user_api.register("testuser", "test@test.com", "password")

assert result["data"]["user_id"] == "12345"
assert result["data"]["user_name"] == "testuser"
assert result["data"]["email"] == "test@test.com"
mock_client.post.assert_called_once_with(
"user",
data={
"user_name": "testuser",
"email": "test@test.com",
"password": "password",
},
)


def test_register_failure(user_api, mock_client):
mock_client.post.return_value = {"error": "Registration failed"}

result = user_api.register("testuser", "test@test.com", "password")

assert result == {
"success": False,
"error": "Unexpected error",
"message": "Failed to register: {'error': 'Registration failed'}",
}

mock_client.post.assert_called_once_with(
"user",
data={
"user_name": "testuser",
"email": "test@test.com",
"password": "password",
},
)
2 changes: 1 addition & 1 deletion test/commands/test_model_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def test_list_models_success(self):
}
with patch("click.echo") as mock_print:
self.model_commands.list()
mock_print.assert_any_call("Models:")
mock_print.assert_any_call("Default Models:")

def test_get_model_success(self):
with patch("click.echo") as mock_print:
Expand Down
67 changes: 66 additions & 1 deletion test/commands/test_user_auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import unittest
from unittest.mock import patch
from click.testing import CliRunner
from src.commands.user_auth import login, logout
from src.commands.user_auth import login, logout, register


class TestUserAuth(unittest.TestCase):
Expand Down Expand Up @@ -68,3 +68,68 @@ def test_logout_not_logged_in(self):
result = self.runner.invoke(logout)
mock_print.assert_called_with("No action taken. You were not logged in.")
self.assertEqual(result.exit_code, 0)

def test_logout_failure(self):
"""Test logout failure."""
self.mock_instance.logout.return_value = False

with patch("click.echo") as mock_print:
result = self.runner.invoke(logout)
mock_print.assert_called_with("No action taken. You were not logged in.")
self.assertEqual(result.exit_code, 0)

def test_register_success(self):
"""Test successful registration."""

def test_register_success(self):
"""Test successful registration."""
self.mock_instance.register.return_value = {
"success": True,
"data": {
"user_name": "testuser",
"email": "test@test.com",
"user_id": "12345",
},
}

with patch("click.echo") as mock_print:
result = self.runner.invoke(
register,
[
"--username",
"testuser",
"--email",
"test@test.com",
"--password",
"password",
],
)
mock_print.assert_any_call(
"Welcome to Duckington Labs, you've successfully registered as:"
)
mock_print.assert_any_call(" - Username: testuser")
mock_print.assert_any_call(" - Email: test@test.com")
mock_print.assert_any_call(" - User ID: 12345")
self.assertEqual(result.exit_code, 0)

def test_register_failure(self):
"""Test failed registration."""
self.mock_instance.register.return_value = {
"success": False,
"message": "Registration failed",
}

with patch("click.echo") as mock_print:
result = self.runner.invoke(
register,
[
"--username",
"testuser",
"--email",
"test@test.com",
"--password",
"password",
],
)
mock_print.assert_called_with("Login failed. Registration failed")
self.assertEqual(result.exit_code, 0)
Loading