Skip to content

Commit

Permalink
pytest
Browse files Browse the repository at this point in the history
  • Loading branch information
avocadodip committed Sep 8, 2024
1 parent 0537530 commit a922e0f
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 90 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI/CD

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install uv flake8 pytest isort
uv pip install -r requirements.txt
- name: Run isort
run: isort . --check-only --diff

- name: Run linter
run: flake8 .

- name: Run tests
run: python -m pytest
Binary file modified celerybeat-schedule.db
Binary file not shown.
86 changes: 8 additions & 78 deletions mini/api/endpoints/users.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
from typing import Any, Dict, List
from fastapi import APIRouter, Body, Depends, Path, Security
from fastapi.responses import JSONResponse
from typing import List

from fastapi import APIRouter, Body, Depends, Path, Request, Security
from pydantic import BaseModel

from config.config import config
from config.container import container
from mini.api.security import verify_api_key
from mini.core.schema.tables import Room, User
from mini.service.user_service import UserService

router = APIRouter()


@router.post("/users", response_model=User)
async def create_user(
id: str = Body(..., title="UUID that Supabase Auth created on the frontend"),
Expand All @@ -21,102 +18,35 @@ async def create_user(
) -> User:
return user_service.create_user(id=id, phone_number=phone_number)


@router.get("/users/{user_id}/rooms")
@router.get("/users/{user_id}/rooms", response_model=List[Room])
def get_user_rooms(
user_id: str = Path(..., title="The ID of the user to get"),
user_service: UserService = Depends(lambda: container.get_user_service()),
api_key: str = Security(verify_api_key),
) -> List[Room] | None:
"""Get all rooms that a user is in"""
) -> List[Room]:
return user_service.get_user_rooms(user_id)


@router.get("/users/{user_id}", response_model=User)
async def get_user(
user_id: str = Path(..., title="The ID of the user to get"),
user_service: UserService = Depends(lambda: container.get_user_service()),
api_key: str = Security(verify_api_key),
) -> User:
"""Get a user by ID. Returns the retrieved User object"""
return user_service.get_user(user_id)


@router.patch("/users/{user_id}", response_model=User)
async def update_user(
user_id: str = Path(..., title="The ID of the user to update"),
user_update: Dict[str, Any] = Body(..., title="The fields to update"),
user_update: dict = Body(..., title="The fields to update"),
user_service: UserService = Depends(lambda: container.get_user_service()),
api_key: str = Security(verify_api_key),
) -> User:
"""Update a user. Returns updated User object"""
return user_service.update_user(user_id=user_id, user_params=user_update)


@router.delete("/users/{user_id}")
async def delete_user(
user_id: str = Path(..., title="The ID of the user to delete"),
user_service: UserService = Depends(lambda: container.get_user_service()),
api_key: str = Security(verify_api_key),
) -> None:
"""Delete a user. Returns the id of the deleted user."""
return user_service.delete_user(user_id)


async def test_user_endpoints():
TEST_ID = "946f4f9d-1111-495e-b59d-5f3704deb11b" # Change as needed

async with httpx.AsyncClient(base_url="http://127.0.0.1:8000") as client:
# Test create_user
user_data = {
"id": "946f4f9d-1111-495e-b59d-5f3704deb11b",
"phone_number": "+13143209682",
}

response = await client.post(
f"/users",
json=user_data,
headers={"Authorization": f"Bearer {config.BACKEND_API_KEY}"},
)
print(
f"POST /users - Status: {response.status_code}, Response: {response.json()}"
)

# # Test get_user
# response = await client.get(
# f"/users/{TEST_ID}",
# headers={"Authorization": f"Bearer {config.BACKEND_API_KEY}"},
# )
# print(
# f"GET /users/{TEST_ID} - Status: {response.status_code}, Response: {response.json()}"
# )

# # # Test update_user
# user_data = {"subscription_status": "inactive"}
# response = await client.patch(
# f"/users/{TEST_ID}",
# json=user_data,
# headers={"Authorization": f"Bearer {config.BACKEND_API_KEY}"},
# )
# print(
# f"PATCH /users/{TEST_ID} - Status: {response.status_code}, Response: {response.json()}"
# )

# # # Test delete_user
# response = await client.delete(
# f"/users/{TEST_ID}",
# headers={"Authorization": f"Bearer {config.BACKEND_API_KEY}"},
# )
# print(
# f"DELETE /users/{TEST_ID} - Status: {response.status_code}, Response: {response.json()}"
# )


if __name__ == "__main__":
import asyncio

import httpx

from config.config import config

asyncio.run(test_user_endpoints())
) -> JSONResponse:
return user_service.delete_user(user_id)
6 changes: 6 additions & 0 deletions mini/service/payment_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from mini.core.schema.tables import Subscription, Tables, User
from mini.manager.database import DatabaseManager
from mini.manager.payment import CheckoutManager, CustomerManager, SubscriptionManager
from mini.manager.messaging import discord_manager

from .base import Service

Expand Down Expand Up @@ -119,6 +120,11 @@ async def _handle_checkout_session_completed(
condition_value=user_id,
)

discord_manager.send_message_to_channel(
message=f"Subscription created by user {user_id}.",
channel=config.DISCORD_CONFIG.website_activity_webhook_url,
)

except Exception as e:
logger.error(f"Error retrieving subscription information: {e}")
raise e
Expand Down
16 changes: 4 additions & 12 deletions mini/service/user_service.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
from typing import Any, Dict, List

from config.config import config
from fastapi import HTTPException
from fastapi.responses import JSONResponse
from postgrest.exceptions import APIError

from config.config import config
from mini.core.schema.subscription import SubscriptionStatus
from mini.core.schema.tables import Room, Tables, User
from mini.manager.database import DatabaseManager
from mini.manager.payment.customer import CustomerManager
from mini.manager.messaging.discord import discord_manager
from mini.service.base import Service

import requests


class UserService(Service):
def __init__(
self, database_manager: DatabaseManager, customer_manager: CustomerManager
Expand Down Expand Up @@ -43,7 +40,6 @@ def create_user(self, id: str, phone_number: str) -> User:
raise HTTPException(
status_code=409, detail="User with this phone number already exists"
)

raise HTTPException(
status_code=500, detail=f"Error creating user: {str(e)}"
)
Expand All @@ -61,7 +57,6 @@ def update_user(self, user_id: str, user_params: Dict[str, Any]) -> User:
if not existing_user:
raise HTTPException(status_code=404, detail="User not found")

# Only update fields that are provided and not None
update_data = {k: v for k, v in user_params.items() if v is not None}

updated_user = self.database_manager.update(
Expand All @@ -74,8 +69,7 @@ def update_user(self, user_id: str, user_params: Dict[str, Any]) -> User:
raise HTTPException(status_code=400, detail="Failed to update user")
return updated_user

def delete_user(self, id: str) -> None:
"""Delete a user. Returns the id of the deleted user."""
def delete_user(self, id: str) -> JSONResponse:
existing_user = self.database_manager.get_row(
Tables.USERS, {Tables.USERS__id: id}
)
Expand All @@ -87,13 +81,11 @@ def delete_user(self, id: str) -> None:
)
if not deleted_user_id:
raise HTTPException(status_code=400, detail="Failed to delete user")
return JSONResponse(status_code=200, content="Succesfully deleted user")
return JSONResponse(status_code=200, content="Successfully deleted user")

def get_user_rooms(self, user_id: str) -> List[Room]:
"""Get all rooms that a user is in"""
rooms = self.database_manager.get_multiple_rows(
table_name=Tables.ROOMS,
conditions={Tables.ROOMS__user_id: user_id},
)

return rooms
return rooms
111 changes: 111 additions & 0 deletions tests/api/test_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import pytest
from fastapi.testclient import TestClient
from main import app
from config.config import config
import uuid

@pytest.fixture(scope="module")
def client():
return TestClient(app)

@pytest.fixture(scope="module")
def api_key_headers():
return {"Authorization": f"Bearer {config.BACKEND_API_KEY}"}

@pytest.fixture(scope="function")
def test_user(client, api_key_headers):
# Create a unique user with a unique phone number
user_data = {
"id": str(uuid.uuid4()),
"phone_number": f"+1314320{uuid.uuid4().hex[:4]}", # Generate a unique phone number
}
response = client.post("/users", json=user_data, headers=api_key_headers)

# Check if creation was successful
assert response.status_code == 200, f"Failed to create user: {response.json()}"

created_user = response.json()

# Yield the created user to the test
yield created_user

# Cleanup: delete the user after the test
delete_response = client.delete(f"/users/{created_user['id']}", headers=api_key_headers)

# Ensure the user deletion was successful
assert delete_response.status_code == 200, f"Failed to delete user: {delete_response.json()}"


def test_create_user(client, api_key_headers):
user_data = {
"id": str(uuid.uuid4()),
"phone_number": "+13143209683",
}
response = client.post("/users", json=user_data, headers=api_key_headers)
assert response.status_code == 200
assert "id" in response.json()
assert response.json()["phone_number"] == user_data["phone_number"]

# Clean up: delete the created user
client.delete(f"/users/{response.json()['id']}", headers=api_key_headers)

def test_get_user(client, api_key_headers, test_user):
response = client.get(f"/users/{test_user['id']}", headers=api_key_headers)
assert response.status_code == 200
assert response.json()["id"] == test_user['id']

def test_get_user_rooms(client, api_key_headers, test_user):
response = client.get(f"/users/{test_user['id']}/rooms", headers=api_key_headers)
assert response.status_code == 200
assert isinstance(response.json(), list)

def test_update_user(client, api_key_headers, test_user):
update_data = {"is_subscribed": "false"}
response = client.patch(f"/users/{test_user['id']}", json=update_data, headers=api_key_headers)
assert response.status_code == 200
assert response.json()["is_subscribed"] == False

def test_delete_user(client, api_key_headers):
# Create a user to delete
user_data = {
"id": str(uuid.uuid4()),
"phone_number": "+13143209684",
}
create_response = client.post("/users", json=user_data, headers=api_key_headers)
assert create_response.status_code == 200
user_id = create_response.json()["id"]

# Delete the user
delete_response = client.delete(f"/users/{user_id}", headers=api_key_headers)
assert delete_response.status_code == 200
assert delete_response.json() == "Successfully deleted user"

# Verify the user is deleted
get_response = client.get(f"/users/{user_id}", headers=api_key_headers)
assert get_response.status_code == 404

def test_get_nonexistent_user(client, api_key_headers):
nonexistent_id = str(uuid.uuid4())
response = client.get(f"/users/{nonexistent_id}", headers=api_key_headers)
assert response.status_code == 404
assert "User not found" in response.json()["detail"]

def test_create_user_duplicate_phone(client, api_key_headers, test_user):
user_data = {
"id": str(uuid.uuid4()),
"phone_number": test_user["phone_number"], # Use the same phone number as test_user
}
response = client.post("/users", json=user_data, headers=api_key_headers)
assert response.status_code == 409
assert "User with this phone number already exists" in response.json()["detail"]

def test_api_key_required(client):
response = client.get("/users/123")
assert response.status_code == 403
assert "Not authenticated" in response.json()["detail"]

def test_invalid_api_key(client):
invalid_headers = {"Authorization": "Bearer invalid_key"}
response = client.get("/users/123", headers=invalid_headers)
assert response.status_code == 403
assert "Could not validate credentials" in response.json()["detail"]

0 comments on commit a922e0f

Please sign in to comment.