Skip to content

Commit c2d60e0

Browse files
Merge pull request #44 from Kalgoc/develop
Develop
2 parents 68fd546 + 86cb8ef commit c2d60e0

File tree

6 files changed

+98
-101
lines changed

6 files changed

+98
-101
lines changed

AI/AI.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from textwrap import dedent
55
from dotenv import load_dotenv
66
from categories.models import Category
7+
from categories.exceptions import InvalidCategoryError
78

89

910
GPT_MODEL = "gpt-4-turbo"
@@ -77,5 +78,5 @@ def category_matched_with_id(category):
7778
"Inversión",
7879
]
7980
if category not in categories_allowed:
80-
raise ValueError(f"Categoría no permitida: {category}")
81+
raise InvalidCategoryError(category)
8182
return category

AI/tests.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from unittest.mock import patch
44
from AI import classify_text, category_matched_with_id
55
from textwrap import dedent
6+
from categories.exceptions import InvalidCategoryError
67

78

89
class TestGastosClassifier(unittest.TestCase):
@@ -54,7 +55,7 @@ def test_clean_category(self):
5455
self.assertEqual(category_matched_with_id("vivienda."), "Vivienda")
5556

5657
# Prueba de una categoría no permitida
57-
with self.assertRaises(ValueError):
58+
with self.assertRaises(InvalidCategoryError):
5859
category_matched_with_id("Categoría: Viajes.")
5960

6061

categories/exceptions.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class InvalidCategoryError(Exception):
2+
def __init__(self, category):
3+
self.category = category
4+
self.message = (
5+
"El texto proporcionado no proporciona información suficiente para "
6+
"clasificar el gasto en una categoría. Por favor, se un poco mas descriptivo."
7+
)
8+
self.code = 400
9+
super().__init__(self.message)

expenses/services/categorize_expense.py

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from rest_framework import status
33
from categories.models import Category
44
from AI.AI import get_category_name_from_description
5+
from categories.exceptions import InvalidCategoryError
56

67

78
def categorize_expense_description(data):
@@ -13,5 +14,7 @@ def categorize_expense_description(data):
1314
return data, None
1415
except ValueError as e:
1516
return None, Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
17+
except InvalidCategoryError as e:
18+
return None, Response({"error": str(e)}, status=e.code)
1619
else:
1720
return None, Response({"error": "Description is required"}, status=status.HTTP_400_BAD_REQUEST)

expenses/tests.py

+81-98
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,118 @@
1-
from django.test import TestCase
2-
from unittest.mock import patch
3-
from rest_framework.test import APIClient
1+
from rest_framework.test import APITestCase, APIClient
42
from rest_framework import status
3+
from unittest.mock import patch
4+
from authentication.models import User
5+
from user_expense_type.models import UserExpenseType
6+
from bankcard.models import BankCard
57
from authentication.services.cognito_service import CognitoService
6-
from rest_framework.response import Response
7-
from .views import ExpenseViewSet, ExpenseGroupedByTypeAndCategoryViewSet
88

99

10-
class ExpenseViewSetTestCase(TestCase):
10+
class ExpenseViewSetTestCase(APITestCase):
1111
def setUp(self):
1212
self.client = APIClient()
13-
self.view = ExpenseViewSet()
14-
self.expense = {
15-
"user_expense_type": 1,
13+
self.user = User.objects.create_user(
14+
username="test@email.com",
15+
email="test@email.com",
16+
password="TestPassword1",
17+
first_name="Test User",
18+
user_id="389704ef-1e4f-4000-a801-bf887a1c88f2",
19+
)
20+
self.user_expense_type = UserExpenseType.objects.create(
21+
username="389704ef-1e4f-4000-a801-bf887a1c88f2", set_by_user=False, name="Personal"
22+
)
23+
self.bankcard = BankCard.objects.create(
24+
user_id=self.user, account_number=123456, bank_name="Test Bank", card_type="debit"
25+
)
26+
self.expense_data = {
27+
"user_expense_type": self.user_expense_type.id,
1628
"category": 1,
17-
"bankcard_id": 1,
29+
"bankcard_id": self.bankcard.id,
1830
"amount": 100,
31+
"description": "Test description",
32+
"username": "389704ef-1e4f-4000-a801-bf887a1c88f2",
1933
}
2034

21-
@patch.object(ExpenseViewSet, "create")
22-
@patch.object(CognitoService, "login_user")
23-
def test_create(self, mock_login_user, mock_create):
24-
mock_login_user.return_value = {
25-
"AuthenticationResult": {"AccessToken": "mock_access_token", "IdToken": "mock_id_token"}
26-
}
27-
mock_create.return_value = Response(status=status.HTTP_201_CREATED, data=self.expense)
28-
29-
request = self.client.post("/expenses/", self.expense)
30-
request.headers["Authorization"] = "Bearer mock_access_token"
35+
@patch("authentication.decorators.get_user_id_from_token")
36+
@patch("expenses.views.categorize_expense_description")
37+
@patch("expenses.views.get_user_id_from_token")
38+
def test_create(self, mock_get_user_id, mock_categorize, mock_auth_get_user_id):
39+
mock_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
40+
mock_categorize.return_value = self.expense_data, None
41+
mock_auth_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
3142

32-
response = self.client.post("/expenses/", self.expense)
43+
response = self.client.post("/expenses/", self.expense_data)
3344

3445
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
3546
self.assertEqual(response.data["amount"], 100)
3647

37-
@patch.object(ExpenseViewSet, "list")
38-
@patch.object(CognitoService, "login_user")
39-
def test_list(self, mock_login_user, mock_list):
40-
mock_login_user.return_value = {
41-
"AuthenticationResult": {"AccessToken": "mock_access_token", "IdToken": "mock_id_token"}
42-
}
43-
mock_list.return_value = Response(status=status.HTTP_200_OK, data=self.expense)
44-
45-
request = self.client.get("/expenses/")
46-
request.headers["Authorization"] = "Bearer mock_access_token"
48+
@patch("authentication.decorators.get_user_id_from_token")
49+
@patch("expenses.views.categorize_expense_description")
50+
@patch("expenses.views.get_user_id_from_token")
51+
def test_list(self, mock_get_user_id, mock_categorize, mock_auth_get_user_id):
52+
mock_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
53+
mock_categorize.return_value = self.expense_data, None
54+
mock_auth_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
4755

56+
response = self.client.post("/expenses/", self.expense_data)
57+
response = self.client.post("/expenses/", self.expense_data)
4858
response = self.client.get("/expenses/")
4959

5060
self.assertEqual(response.status_code, status.HTTP_200_OK)
51-
self.assertEqual(response.data["amount"], 100)
61+
self.assertEqual(len(response.data), 2)
5262

53-
@patch.object(ExpenseViewSet, "retrieve")
54-
@patch.object(CognitoService, "login_user")
55-
def test_retrieve(self, mock_login_user, mock_retrieve):
56-
mock_login_user.return_value = {
57-
"AuthenticationResult": {"AccessToken": "mock_access_token", "IdToken": "mock_id_token"}
58-
}
59-
mock_retrieve.return_value = Response(status=status.HTTP_200_OK, data=self.expense)
60-
61-
request = self.client.get("/expenses/1/")
62-
request.headers["Authorization"] = "Bearer mock_access_token"
63+
@patch("authentication.decorators.get_user_id_from_token")
64+
@patch("expenses.views.categorize_expense_description")
65+
@patch("expenses.views.get_user_id_from_token")
66+
def test_retrieve(self, mock_get_user_id, mock_categorize, mock_auth_get_user_id):
67+
mock_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
68+
mock_categorize.return_value = self.expense_data, None
69+
mock_auth_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
6370

64-
response = self.client.get("/expenses/1/")
71+
response = self.client.post("/expenses/", self.expense_data)
72+
response = self.client.get(f"/expenses/{response.data['id']}/")
6573

6674
self.assertEqual(response.status_code, status.HTTP_200_OK)
6775
self.assertEqual(response.data["amount"], 100)
6876

69-
@patch.object(ExpenseViewSet, "destroy")
70-
@patch.object(CognitoService, "login_user")
71-
def test_destroy(self, mock_login_user, mock_destroy):
72-
mock_login_user.return_value = {
73-
"AuthenticationResult": {"AccessToken": "mock_access_token", "IdToken": "mock_id_token"}
74-
}
75-
mock_destroy.return_value = Response(status=status.HTTP_204_NO_CONTENT)
76-
77-
request = self.client.delete("/expenses/1/")
78-
request.headers["Authorization"] = "Bearer mock_access_token"
79-
80-
response = self.client.delete("/expenses/1/")
81-
82-
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
77+
@patch("authentication.decorators.get_user_id_from_token")
78+
@patch("expenses.views.categorize_expense_description")
79+
@patch("expenses.views.get_user_id_from_token")
80+
def test_update(self, mock_get_user_id, mock_categorize, mock_auth_get_user_id):
81+
mock_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
82+
mock_categorize.return_value = self.expense_data, None
83+
mock_auth_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
8384

84-
@patch.object(ExpenseViewSet, "partial_update")
85-
@patch.object(CognitoService, "login_user")
86-
def test_partial_update(self, mock_login_user, mock_partial_update):
87-
mock_login_user.return_value = {
88-
"AuthenticationResult": {"AccessToken": "mock_access_token", "IdToken": "mock_id_token"}
89-
}
90-
new_expense = {
91-
"expense_type": 1,
92-
"category": 1,
93-
"bankcard_id": 1,
94-
"amount": 200,
95-
}
96-
mock_partial_update.return_value = Response(status=status.HTTP_200_OK, data=new_expense)
97-
98-
request = self.client.put("/expenses/1/", {"amount": 200})
99-
request.headers["Authorization"] = "Bearer mock_access_token"
100-
101-
response = self.client.put("/expenses/1/", {"amount": 200})
85+
response = self.client.post("/expenses/", self.expense_data)
86+
response = self.client.put(f"/expenses/{response.data['id']}/", {"amount": 200})
10287

10388
self.assertEqual(response.status_code, status.HTTP_200_OK)
10489
self.assertEqual(response.data["amount"], 200)
10590

91+
@patch("authentication.decorators.get_user_id_from_token")
92+
@patch("expenses.views.categorize_expense_description")
93+
@patch("expenses.views.get_user_id_from_token")
94+
def test_delete(self, mock_get_user_id, mock_categorize, mock_auth_get_user_id):
95+
mock_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
96+
mock_categorize.return_value = self.expense_data, None
97+
mock_auth_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
10698

107-
class ExpenseGroupedByTypeAndCategoryViewSetTestCase(TestCase):
108-
def setUp(self):
109-
self.client = APIClient()
110-
self.view = ExpenseGroupedByTypeAndCategoryViewSet()
111-
self.expense = {
112-
"expense_type": 1,
113-
"category": 1,
114-
"bankcard_id": 1,
115-
"amount": 100,
116-
}
117-
self.expenses_grouped = {"Personal": {"Comida": 100}}
99+
response = self.client.post("/expenses/", self.expense_data)
100+
response = self.client.delete(f"/expenses/{response.data['id']}/")
118101

119-
@patch.object(ExpenseGroupedByTypeAndCategoryViewSet, "list")
120-
@patch.object(CognitoService, "login_user")
121-
def test_list(self, mock_login_user, mock_list):
122-
mock_login_user.return_value = {
123-
"AuthenticationResult": {"AccessToken": "mock_access_token", "IdToken": "mock_id_token"}
124-
}
125-
mock_list.return_value = Response(status=status.HTTP_200_OK, data=self.expenses_grouped)
102+
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
126103

127-
request = self.client.get("/expenses/grouped/")
128-
request.headers["Authorization"] = "Bearer mock_access_token"
104+
@patch("authentication.decorators.get_user_id_from_token")
105+
@patch("expenses.views.categorize_expense_description")
106+
@patch("expenses.views.get_user_id_from_token")
107+
def test_grouped(self, mock_get_user_id, mock_categorize, mock_auth_get_user_id):
108+
mock_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
109+
mock_categorize.return_value = self.expense_data, None
110+
mock_auth_get_user_id.return_value = "389704ef-1e4f-4000-a801-bf887a1c88f2"
129111

112+
response = self.client.post("/expenses/", self.expense_data)
113+
response = self.client.post("/expenses/", self.expense_data)
130114
response = self.client.get("/expenses/grouped/")
131115

132116
self.assertEqual(response.status_code, status.HTTP_200_OK)
133-
self.assertIn("Personal", response.data)
134-
self.assertIn("Comida", response.data["Personal"])
135-
self.assertEqual(response.data["Personal"]["Comida"], 100)
117+
self.assertEqual(len(response.data), 1)
118+
self.assertEqual(response.data["Personal"]["Comida"], 200)

expenses/views.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def get_user_id_from_token(self, request):
3636
def list(self, request):
3737
try:
3838
username = get_user_id_from_token(request)
39-
expenses = Expense.objects.filter(username=username)
39+
expenses = Expense.objects.filter(username=username).order_by("-created_at")
4040

4141
start_date = request.query_params.get("start_date")
4242
end_date = request.query_params.get("end_date")

0 commit comments

Comments
 (0)