iatoolkit 0.8.1__py3-none-any.whl → 0.63.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iatoolkit might be problematic. Click here for more details.
- iatoolkit/__init__.py +8 -34
- iatoolkit/base_company.py +14 -3
- iatoolkit/common/routes.py +83 -52
- iatoolkit/common/session_manager.py +0 -1
- iatoolkit/common/util.py +0 -27
- iatoolkit/iatoolkit.py +61 -46
- iatoolkit/infra/llm_client.py +7 -8
- iatoolkit/infra/openai_adapter.py +1 -1
- iatoolkit/infra/redis_session_manager.py +48 -2
- iatoolkit/repositories/database_manager.py +17 -2
- iatoolkit/repositories/models.py +31 -6
- iatoolkit/repositories/profile_repo.py +7 -2
- iatoolkit/services/auth_service.py +188 -0
- iatoolkit/services/branding_service.py +147 -0
- iatoolkit/services/dispatcher_service.py +10 -40
- iatoolkit/services/excel_service.py +15 -15
- iatoolkit/services/history_service.py +3 -12
- iatoolkit/services/jwt_service.py +15 -24
- iatoolkit/services/onboarding_service.py +43 -0
- iatoolkit/services/profile_service.py +97 -44
- iatoolkit/services/query_service.py +124 -81
- iatoolkit/services/tasks_service.py +1 -1
- iatoolkit/services/user_feedback_service.py +67 -31
- iatoolkit/services/user_session_context_service.py +112 -54
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/js/{chat_feedback.js → chat_feedback_button.js} +6 -11
- iatoolkit/static/js/chat_history_button.js +126 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +130 -220
- iatoolkit/static/js/chat_onboarding_button.js +97 -0
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +52 -0
- iatoolkit/static/styles/chat_iatoolkit.css +329 -507
- iatoolkit/static/styles/chat_modal.css +95 -56
- iatoolkit/static/styles/landing_page.css +182 -0
- iatoolkit/static/styles/onboarding.css +169 -0
- iatoolkit/system_prompts/query_main.prompt +3 -12
- iatoolkit/templates/_company_header.html +20 -0
- iatoolkit/templates/_login_widget.html +40 -0
- iatoolkit/templates/base.html +8 -3
- iatoolkit/templates/change_password.html +54 -37
- iatoolkit/templates/chat.html +149 -66
- iatoolkit/templates/chat_modals.html +47 -18
- iatoolkit/templates/error.html +41 -8
- iatoolkit/templates/forgot_password.html +37 -24
- iatoolkit/templates/index.html +140 -0
- iatoolkit/templates/login_simulation.html +34 -0
- iatoolkit/templates/onboarding_shell.html +105 -0
- iatoolkit/templates/signup.html +64 -66
- iatoolkit/views/base_login_view.py +81 -0
- iatoolkit/views/change_password_view.py +23 -12
- iatoolkit/views/external_login_view.py +61 -28
- iatoolkit/views/{file_store_view.py → file_store_api_view.py} +9 -2
- iatoolkit/views/forgot_password_view.py +23 -13
- iatoolkit/views/history_api_view.py +52 -0
- iatoolkit/views/home_view.py +58 -25
- iatoolkit/views/index_view.py +14 -0
- iatoolkit/views/init_context_api_view.py +68 -0
- iatoolkit/views/llmquery_api_view.py +45 -0
- iatoolkit/views/login_simulation_view.py +81 -0
- iatoolkit/views/login_view.py +118 -34
- iatoolkit/views/logout_api_view.py +45 -0
- iatoolkit/views/{prompt_view.py → prompt_api_view.py} +7 -7
- iatoolkit/views/signup_view.py +38 -29
- iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
- iatoolkit/views/tasks_review_api_view.py +55 -0
- iatoolkit/views/{user_feedback_view.py → user_feedback_api_view.py} +16 -31
- iatoolkit/views/verify_user_view.py +13 -8
- {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/METADATA +2 -2
- iatoolkit-0.63.4.dist-info/RECORD +113 -0
- {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/top_level.txt +0 -1
- iatoolkit/common/auth.py +0 -200
- iatoolkit/static/images/arrow_up.png +0 -0
- iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
- iatoolkit/static/images/logo_clinica.png +0 -0
- iatoolkit/static/images/logo_iatoolkit.png +0 -0
- iatoolkit/static/images/logo_maxxa.png +0 -0
- iatoolkit/static/images/logo_notaria.png +0 -0
- iatoolkit/static/images/logo_tarjeta.png +0 -0
- iatoolkit/static/images/logo_umayor.png +0 -0
- iatoolkit/static/images/upload.png +0 -0
- iatoolkit/static/js/chat_history.js +0 -117
- iatoolkit/templates/home.html +0 -201
- iatoolkit/templates/login.html +0 -43
- iatoolkit/views/chat_token_request_view.py +0 -98
- iatoolkit/views/chat_view.py +0 -51
- iatoolkit/views/download_file_view.py +0 -58
- iatoolkit/views/external_chat_login_view.py +0 -88
- iatoolkit/views/history_view.py +0 -57
- iatoolkit/views/llmquery_view.py +0 -65
- iatoolkit/views/tasks_review_view.py +0 -83
- iatoolkit-0.8.1.dist-info/RECORD +0 -175
- tests/__init__.py +0 -5
- tests/common/__init__.py +0 -0
- tests/common/test_auth.py +0 -279
- tests/common/test_routes.py +0 -42
- tests/common/test_session_manager.py +0 -59
- tests/common/test_util.py +0 -444
- tests/companies/__init__.py +0 -5
- tests/conftest.py +0 -36
- tests/infra/__init__.py +0 -5
- tests/infra/connectors/__init__.py +0 -5
- tests/infra/connectors/test_google_drive_connector.py +0 -107
- tests/infra/connectors/test_local_file_connector.py +0 -85
- tests/infra/connectors/test_s3_connector.py +0 -95
- tests/infra/test_call_service.py +0 -92
- tests/infra/test_database_manager.py +0 -59
- tests/infra/test_gemini_adapter.py +0 -137
- tests/infra/test_google_chat_app.py +0 -68
- tests/infra/test_llm_client.py +0 -165
- tests/infra/test_llm_proxy.py +0 -122
- tests/infra/test_mail_app.py +0 -94
- tests/infra/test_openai_adapter.py +0 -105
- tests/infra/test_redis_session_manager_service.py +0 -117
- tests/repositories/__init__.py +0 -5
- tests/repositories/test_database_manager.py +0 -87
- tests/repositories/test_document_repo.py +0 -76
- tests/repositories/test_llm_query_repo.py +0 -340
- tests/repositories/test_models.py +0 -38
- tests/repositories/test_profile_repo.py +0 -142
- tests/repositories/test_tasks_repo.py +0 -76
- tests/repositories/test_vs_repo.py +0 -107
- tests/services/__init__.py +0 -5
- tests/services/test_dispatcher_service.py +0 -274
- tests/services/test_document_service.py +0 -181
- tests/services/test_excel_service.py +0 -208
- tests/services/test_file_processor_service.py +0 -121
- tests/services/test_history_service.py +0 -164
- tests/services/test_jwt_service.py +0 -255
- tests/services/test_load_documents_service.py +0 -112
- tests/services/test_mail_service.py +0 -70
- tests/services/test_profile_service.py +0 -379
- tests/services/test_prompt_manager_service.py +0 -190
- tests/services/test_query_service.py +0 -243
- tests/services/test_search_service.py +0 -39
- tests/services/test_sql_service.py +0 -160
- tests/services/test_tasks_service.py +0 -252
- tests/services/test_user_feedback_service.py +0 -389
- tests/services/test_user_session_context_service.py +0 -132
- tests/views/__init__.py +0 -5
- tests/views/test_change_password_view.py +0 -191
- tests/views/test_chat_token_request_view.py +0 -188
- tests/views/test_chat_view.py +0 -98
- tests/views/test_download_file_view.py +0 -149
- tests/views/test_external_chat_login_view.py +0 -120
- tests/views/test_external_login_view.py +0 -102
- tests/views/test_file_store_view.py +0 -128
- tests/views/test_forgot_password_view.py +0 -142
- tests/views/test_history_view.py +0 -336
- tests/views/test_home_view.py +0 -61
- tests/views/test_llm_query_view.py +0 -154
- tests/views/test_login_view.py +0 -114
- tests/views/test_prompt_view.py +0 -111
- tests/views/test_signup_view.py +0 -140
- tests/views/test_tasks_review_view.py +0 -104
- tests/views/test_tasks_view.py +0 -130
- tests/views/test_user_feedback_view.py +0 -214
- tests/views/test_verify_user_view.py +0 -110
- {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/WHEEL +0 -0
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
from unittest.mock import MagicMock, patch
|
|
8
|
-
from iatoolkit.services.tasks_service import TaskService
|
|
9
|
-
from iatoolkit.infra.call_service import CallServiceClient
|
|
10
|
-
from iatoolkit.repositories.models import Task, TaskStatus, TaskType, Company
|
|
11
|
-
from iatoolkit.common.exceptions import IAToolkitException
|
|
12
|
-
from datetime import datetime, timedelta
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TestTaskService:
|
|
16
|
-
|
|
17
|
-
def setup_method(self):
|
|
18
|
-
self.mock_task_repo = MagicMock()
|
|
19
|
-
self.mock_query_service = MagicMock()
|
|
20
|
-
self.mock_profile_repo = MagicMock()
|
|
21
|
-
self.mock_call_service = MagicMock(spec=CallServiceClient)
|
|
22
|
-
|
|
23
|
-
self.task_service = TaskService(
|
|
24
|
-
task_repo=self.mock_task_repo,
|
|
25
|
-
query_service=self.mock_query_service,
|
|
26
|
-
profile_repo=self.mock_profile_repo,
|
|
27
|
-
call_service=self.mock_call_service
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
self.mock_company = Company(id=1, name='test_company', short_name='test_company')
|
|
31
|
-
self.mock_task_type = TaskType(id=10, prompt_template='prompt.tpl')
|
|
32
|
-
|
|
33
|
-
self.task_mock = Task(
|
|
34
|
-
company_id=self.mock_company.id,
|
|
35
|
-
task_type_id=self.mock_task_type.id,
|
|
36
|
-
company_task_id=0,
|
|
37
|
-
client_data={"key": "value"},
|
|
38
|
-
execute_at=datetime.now(),
|
|
39
|
-
status=TaskStatus.pendiente,
|
|
40
|
-
files=[]
|
|
41
|
-
)
|
|
42
|
-
self.task_mock.company = self.mock_company
|
|
43
|
-
self.task_mock.task_type = self.mock_task_type
|
|
44
|
-
|
|
45
|
-
def test_create_task_when_company_not_found(self):
|
|
46
|
-
self.mock_profile_repo.get_company_by_short_name.return_value = None
|
|
47
|
-
|
|
48
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
49
|
-
self.task_service.create_task(
|
|
50
|
-
company_short_name="non_existent_company",
|
|
51
|
-
task_type_name="test_task_type",
|
|
52
|
-
client_data={}
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.INVALID_NAME
|
|
56
|
-
assert "No existe la empresa" in str(excinfo.value)
|
|
57
|
-
|
|
58
|
-
def test_create_task_when_tasktype_not_found(self):
|
|
59
|
-
self.mock_profile_repo.get_company_by_short_name.return_value = self.mock_company
|
|
60
|
-
self.mock_task_repo.get_task_type.return_value = None
|
|
61
|
-
|
|
62
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
63
|
-
self.task_service.create_task(
|
|
64
|
-
company_short_name="test_company",
|
|
65
|
-
task_type_name="non_existent_type",
|
|
66
|
-
client_data={}
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.INVALID_NAME
|
|
70
|
-
assert "No existe el task_type" in str(excinfo.value)
|
|
71
|
-
|
|
72
|
-
def test_create_task_when_future_execution(self):
|
|
73
|
-
future_date = datetime.now() + timedelta(days=1)
|
|
74
|
-
|
|
75
|
-
self.mock_profile_repo.get_company_by_short_name.return_value = self.mock_company
|
|
76
|
-
self.mock_task_repo.get_task_type.return_value = self.mock_task_type
|
|
77
|
-
self.mock_task_repo.create_task.return_value = self.task_mock
|
|
78
|
-
self.task_mock.execute_at = future_date
|
|
79
|
-
|
|
80
|
-
result_task = self.task_service.create_task(
|
|
81
|
-
company_short_name="test_company",
|
|
82
|
-
task_type_name="test_task_type",
|
|
83
|
-
client_data={"key": "value"},
|
|
84
|
-
execute_at=future_date
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
assert result_task.execute_at == future_date
|
|
88
|
-
self.mock_query_service.llm_query.assert_not_called()
|
|
89
|
-
self.mock_task_repo.update_task.assert_not_called()
|
|
90
|
-
|
|
91
|
-
def test_create_task_when_ok(self):
|
|
92
|
-
self.mock_profile_repo.get_company_by_short_name.return_value = self.mock_company
|
|
93
|
-
self.mock_task_repo.get_task_type.return_value = self.mock_task_type
|
|
94
|
-
self.mock_task_repo.create_task.return_value = self.task_mock
|
|
95
|
-
|
|
96
|
-
result_task = self.task_service.create_task(
|
|
97
|
-
company_short_name="test_company",
|
|
98
|
-
task_type_name="test_task_type",
|
|
99
|
-
client_data={"key": "value"},
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
assert result_task.status == TaskStatus.pendiente
|
|
103
|
-
self.mock_query_service.llm_query.assert_not_called()
|
|
104
|
-
|
|
105
|
-
def test_review_task_when_task_not_found(self):
|
|
106
|
-
self.mock_task_repo.get_task_by_id.return_value = None
|
|
107
|
-
|
|
108
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
109
|
-
self.task_service.review_task(task_id=99,
|
|
110
|
-
review_user='pgonzalez',
|
|
111
|
-
approved=True,
|
|
112
|
-
comment='Validación aprobada')
|
|
113
|
-
|
|
114
|
-
assert excinfo.value.error_type.name == "TASK_NOT_FOUND"
|
|
115
|
-
|
|
116
|
-
def test_review_task_when_invalid_status(self):
|
|
117
|
-
self.mock_task_repo.get_task_by_id.return_value = self.task_mock
|
|
118
|
-
|
|
119
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
120
|
-
self.task_service.review_task(task_id=99,
|
|
121
|
-
review_user='pgonzalez',
|
|
122
|
-
approved=True,
|
|
123
|
-
comment='Validación aprobada')
|
|
124
|
-
|
|
125
|
-
assert excinfo.value.error_type.name == "INVALID_STATE"
|
|
126
|
-
|
|
127
|
-
def test_review_task_when_ok(self):
|
|
128
|
-
self.task_mock.status = TaskStatus.ejecutado
|
|
129
|
-
self.mock_task_repo.get_task_by_id.return_value = self.task_mock
|
|
130
|
-
|
|
131
|
-
result = self.task_service.review_task(task_id=99,
|
|
132
|
-
review_user='pgonzalez',
|
|
133
|
-
approved=True,
|
|
134
|
-
comment='Validación aprobada')
|
|
135
|
-
|
|
136
|
-
assert self.task_mock.status == TaskStatus.aprobada
|
|
137
|
-
assert self.task_mock.approved == True
|
|
138
|
-
self.mock_task_repo.update_task.assert_called_once_with(self.task_mock)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def test_execute_task_when_llm_error(self):
|
|
142
|
-
llm_response = {
|
|
143
|
-
"query_id": 456,
|
|
144
|
-
"valid_response": False,
|
|
145
|
-
"error": "IA error"}
|
|
146
|
-
self.mock_query_service.llm_query.return_value = llm_response
|
|
147
|
-
|
|
148
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
149
|
-
self.task_service.execute_task(self.task_mock)
|
|
150
|
-
|
|
151
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.LLM_ERROR
|
|
152
|
-
assert "IA error" in str(excinfo.value)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def test_execute_task_when_llm_response_valid(self):
|
|
156
|
-
llm_response = {"query_id": 123, "valid_response": True}
|
|
157
|
-
self.mock_query_service.llm_query.return_value = llm_response
|
|
158
|
-
result_task = self.task_service.execute_task(self.task_mock)
|
|
159
|
-
|
|
160
|
-
assert result_task.llm_query_id == 123
|
|
161
|
-
assert result_task.status == TaskStatus.ejecutado
|
|
162
|
-
self.mock_task_repo.update_task.assert_called_once_with(result_task)
|
|
163
|
-
|
|
164
|
-
def test_execute_task_when_exception_in_callback(self):
|
|
165
|
-
llm_response = {
|
|
166
|
-
"query_id": 123,
|
|
167
|
-
"valid_response": True,
|
|
168
|
-
"answer": 'an llm answer',
|
|
169
|
-
"additional_data": {}
|
|
170
|
-
}
|
|
171
|
-
self.mock_query_service.llm_query.return_value = llm_response
|
|
172
|
-
self.task_mock.callback_url = "http://test.com"
|
|
173
|
-
self.mock_call_service.post.side_effect =Exception("timeout")
|
|
174
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
175
|
-
result_task = self.task_service.execute_task(self.task_mock)
|
|
176
|
-
|
|
177
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.REQUEST_ERROR
|
|
178
|
-
assert "timeout" in str(excinfo.value)
|
|
179
|
-
|
|
180
|
-
def test_execute_task_when_llm_invalid_response(self):
|
|
181
|
-
llm_response = {
|
|
182
|
-
"query_id": 123,
|
|
183
|
-
"valid_response": False,
|
|
184
|
-
"answer": 'an llm answer',
|
|
185
|
-
"additional_data": {}
|
|
186
|
-
}
|
|
187
|
-
self.mock_query_service.llm_query.return_value = llm_response
|
|
188
|
-
self.task_mock.callback_url = "http://test.com"
|
|
189
|
-
self.task_mock.client_data = {"key": "value"}
|
|
190
|
-
self.mock_call_service.post.return_value = {'status': 'ok'}, 200
|
|
191
|
-
result_task = self.task_service.execute_task(self.task_mock)
|
|
192
|
-
|
|
193
|
-
assert result_task.status == TaskStatus.fallida
|
|
194
|
-
self.mock_call_service.post.assert_called_once()
|
|
195
|
-
|
|
196
|
-
def test_execute_task_when_callback_ok(self):
|
|
197
|
-
llm_response = {
|
|
198
|
-
"query_id": 123,
|
|
199
|
-
"valid_response": True,
|
|
200
|
-
"answer": 'an llm answer',
|
|
201
|
-
"additional_data": {}
|
|
202
|
-
}
|
|
203
|
-
self.mock_query_service.llm_query.return_value = llm_response
|
|
204
|
-
self.task_mock.callback_url = "http://test.com"
|
|
205
|
-
self.task_mock.client_data = {"key": "value"}
|
|
206
|
-
self.mock_call_service.post.return_value = {'status': 'ok'}, 200
|
|
207
|
-
result_task = self.task_service.execute_task(self.task_mock)
|
|
208
|
-
|
|
209
|
-
assert result_task.llm_query_id == 123
|
|
210
|
-
assert result_task.status == TaskStatus.ejecutado
|
|
211
|
-
self.mock_call_service.post.assert_called_once()
|
|
212
|
-
|
|
213
|
-
def test_trigger_pending_tasks_when_exception(self):
|
|
214
|
-
self.mock_task_repo.get_pending_tasks.side_effect = Exception("Error al obtener tareas pendientes")
|
|
215
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
216
|
-
self.task_service.trigger_pending_tasks('open')
|
|
217
|
-
|
|
218
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.TASK_EXECUTION_ERROR
|
|
219
|
-
|
|
220
|
-
def test_trigger_pending_tasks_when_ok(self):
|
|
221
|
-
self.mock_task_repo.get_pending_tasks.return_value = [self.task_mock]
|
|
222
|
-
response = self.task_service.trigger_pending_tasks('open')
|
|
223
|
-
|
|
224
|
-
assert response['message'] == '1 tareas ejecutadas.'
|
|
225
|
-
|
|
226
|
-
@patch("iatoolkit.services.tasks_service.secure_filename", return_value="secure_file.txt")
|
|
227
|
-
def test_get_task_files_when_save_exception(self, mock_secure_filename):
|
|
228
|
-
uploaded_file_mock = MagicMock()
|
|
229
|
-
uploaded_file_mock.read.side_effect = Exception("Error Guardando")
|
|
230
|
-
|
|
231
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
232
|
-
self.task_service.get_task_files([uploaded_file_mock])
|
|
233
|
-
|
|
234
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.FILE_IO_ERROR
|
|
235
|
-
assert "Error al extraer el contenido del archivo secure_file.txt: Error Guardando" in str(excinfo.value)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
@patch("iatoolkit.services.tasks_service.secure_filename", return_value="secure_file.txt")
|
|
239
|
-
def test_get_task_files_when_success(self, mock_secure_filename):
|
|
240
|
-
uploaded_file_mock = MagicMock()
|
|
241
|
-
uploaded_file_mock.filename = "file.txt"
|
|
242
|
-
uploaded_file_mock.content_type = "text/plain"
|
|
243
|
-
|
|
244
|
-
# mockear el método save del archivo
|
|
245
|
-
uploaded_file_mock.save = MagicMock()
|
|
246
|
-
|
|
247
|
-
files_info = self.task_service.get_task_files([uploaded_file_mock])
|
|
248
|
-
|
|
249
|
-
assert len(files_info) == 1
|
|
250
|
-
assert files_info[0]['filename'] == "secure_file.txt"
|
|
251
|
-
assert files_info[0]['type'] == "text/plain"
|
|
252
|
-
|
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
from unittest.mock import MagicMock
|
|
7
|
-
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
8
|
-
from iatoolkit.services.user_feedback_service import UserFeedbackService
|
|
9
|
-
from iatoolkit.repositories.models import Company, UserFeedback
|
|
10
|
-
from iatoolkit.infra.google_chat_app import GoogleChatApp
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class TestUserFeedbackService:
|
|
14
|
-
def setup_method(self):
|
|
15
|
-
self.profile_repo = MagicMock(ProfileRepo)
|
|
16
|
-
self.google_chat_app = MagicMock(GoogleChatApp)
|
|
17
|
-
|
|
18
|
-
# init the service with the mocks
|
|
19
|
-
self.service = UserFeedbackService(
|
|
20
|
-
profile_repo=self.profile_repo,
|
|
21
|
-
google_chat_app=self.google_chat_app
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
self.company = Company(name='my company',
|
|
25
|
-
short_name='test_company',
|
|
26
|
-
logo_file='company_logo.jpg')
|
|
27
|
-
self.profile_repo.get_company_by_short_name.return_value = self.company
|
|
28
|
-
|
|
29
|
-
self.user_feedback = UserFeedback(
|
|
30
|
-
company_id=self.company.id,
|
|
31
|
-
message='feedback message for testing',
|
|
32
|
-
external_user_id='flibedinsky',
|
|
33
|
-
rating=4)
|
|
34
|
-
|
|
35
|
-
# Mock successful Google Chat response
|
|
36
|
-
self.google_chat_app.send_message.return_value = {
|
|
37
|
-
'success': True,
|
|
38
|
-
'message': 'Mensaje enviado correctamente'
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
def test_feedback_when_exception(self):
|
|
42
|
-
self.profile_repo.get_company_by_short_name.side_effect = Exception('an error')
|
|
43
|
-
response = self.service.new_feedback(
|
|
44
|
-
company_short_name='my_company',
|
|
45
|
-
message='feedback message for testing',
|
|
46
|
-
external_user_id='flibedinsky',
|
|
47
|
-
space='spaces/test-space',
|
|
48
|
-
type='MESSAGE_TRIGGER',
|
|
49
|
-
rating=3
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
assert 'an error' == response['error']
|
|
53
|
-
|
|
54
|
-
def test_feedback_when_company_not_exist(self):
|
|
55
|
-
self.profile_repo.get_company_by_short_name.return_value = None
|
|
56
|
-
response = self.service.new_feedback(
|
|
57
|
-
company_short_name='my_company',
|
|
58
|
-
message='feedback message for testing',
|
|
59
|
-
external_user_id='flibedinsky',
|
|
60
|
-
space='spaces/test-space',
|
|
61
|
-
type='MESSAGE_TRIGGER',
|
|
62
|
-
rating=5
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
assert 'No existe la empresa: my_company' == response['error']
|
|
66
|
-
|
|
67
|
-
def test_feedback_when_error_saving_in_database(self):
|
|
68
|
-
self.profile_repo.save_feedback.return_value = None
|
|
69
|
-
response = self.service.new_feedback(
|
|
70
|
-
company_short_name='my_company',
|
|
71
|
-
message='feedback message for testing',
|
|
72
|
-
external_user_id='flibedinsky',
|
|
73
|
-
space='spaces/test-space',
|
|
74
|
-
type='MESSAGE_TRIGGER',
|
|
75
|
-
rating=2
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
assert 'No se pudo guardar el feedback' == response['error']
|
|
79
|
-
|
|
80
|
-
def test_feedback_when_ok(self):
|
|
81
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
82
|
-
response = self.service.new_feedback(
|
|
83
|
-
company_short_name='my_company',
|
|
84
|
-
message='feedback message for testing',
|
|
85
|
-
external_user_id='flibedinsky',
|
|
86
|
-
space='spaces/test-space',
|
|
87
|
-
type='MESSAGE_TRIGGER',
|
|
88
|
-
rating=4
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
assert 'Feedback guardado correctamente' == response['message']
|
|
92
|
-
|
|
93
|
-
# Nuevos tests para Google Chat
|
|
94
|
-
def test_feedback_sends_google_chat_notification_success(self):
|
|
95
|
-
"""Test that Google Chat notification is sent successfully"""
|
|
96
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
97
|
-
|
|
98
|
-
response = self.service.new_feedback(
|
|
99
|
-
company_short_name='my_company',
|
|
100
|
-
message='feedback message for testing',
|
|
101
|
-
external_user_id='flibedinsky',
|
|
102
|
-
space='spaces/AAQAupQldd4',
|
|
103
|
-
type='MESSAGE_TRIGGER',
|
|
104
|
-
rating=5
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
# Verify Google Chat was called
|
|
108
|
-
self.google_chat_app.send_message.assert_called_once()
|
|
109
|
-
|
|
110
|
-
# Verify the call arguments
|
|
111
|
-
call_args = self.google_chat_app.send_message.call_args[1]['message_data']
|
|
112
|
-
assert call_args['type'] == 'MESSAGE_TRIGGER'
|
|
113
|
-
assert call_args['space']['name'] == 'spaces/AAQAupQldd4'
|
|
114
|
-
assert '*Nuevo feedback de my_company*' in call_args['message']['text']
|
|
115
|
-
assert '*Usuario:* flibedinsky' in call_args['message']['text']
|
|
116
|
-
assert '*Mensaje:* feedback message for testing' in call_args['message']['text']
|
|
117
|
-
assert '*Calificación:* 5' in call_args['message']['text']
|
|
118
|
-
|
|
119
|
-
# Verify feedback was still saved successfully
|
|
120
|
-
assert 'Feedback guardado correctamente' == response['message']
|
|
121
|
-
|
|
122
|
-
def test_feedback_google_chat_notification_failure_does_not_affect_save(self):
|
|
123
|
-
"""Test that Google Chat failure doesn't prevent feedback from being saved"""
|
|
124
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
125
|
-
|
|
126
|
-
# Mock Google Chat failure
|
|
127
|
-
self.google_chat_app.send_message.return_value = {
|
|
128
|
-
'success': False,
|
|
129
|
-
'message': 'Error al enviar mensaje'
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
response = self.service.new_feedback(
|
|
133
|
-
company_short_name='my_company',
|
|
134
|
-
message='feedback message for testing',
|
|
135
|
-
external_user_id='flibedinsky',
|
|
136
|
-
space='spaces/AAQAupQldd4',
|
|
137
|
-
type='MESSAGE_TRIGGER',
|
|
138
|
-
rating=1
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
# Verify Google Chat was called
|
|
142
|
-
self.google_chat_app.send_message.assert_called_once()
|
|
143
|
-
|
|
144
|
-
# Verify feedback was still saved successfully despite Google Chat failure
|
|
145
|
-
assert 'Feedback guardado correctamente' == response['message']
|
|
146
|
-
|
|
147
|
-
def test_feedback_google_chat_exception_returns_error(self):
|
|
148
|
-
"""Test that Google Chat exception returns an error response"""
|
|
149
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
150
|
-
|
|
151
|
-
# Mock Google Chat exception
|
|
152
|
-
self.google_chat_app.send_message.side_effect = Exception('Google Chat error')
|
|
153
|
-
|
|
154
|
-
response = self.service.new_feedback(
|
|
155
|
-
company_short_name='my_company',
|
|
156
|
-
message='feedback message for testing',
|
|
157
|
-
external_user_id='flibedinsky',
|
|
158
|
-
space='spaces/AAQAupQldd4',
|
|
159
|
-
type='MESSAGE_TRIGGER',
|
|
160
|
-
rating=3
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
# Verify Google Chat was called
|
|
164
|
-
self.google_chat_app.send_message.assert_called_once()
|
|
165
|
-
|
|
166
|
-
# Verify error response due to Google Chat exception
|
|
167
|
-
assert 'error' in response
|
|
168
|
-
assert 'Google Chat error' in response['error']
|
|
169
|
-
|
|
170
|
-
def test_feedback_google_chat_failure_does_not_affect_save(self):
|
|
171
|
-
"""Test that Google Chat failure (not exception) doesn't prevent feedback from being saved"""
|
|
172
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
173
|
-
|
|
174
|
-
# Mock Google Chat failure (returns success: false, not exception)
|
|
175
|
-
self.google_chat_app.send_message.return_value = {
|
|
176
|
-
'success': False,
|
|
177
|
-
'message': 'Error al enviar mensaje'
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
response = self.service.new_feedback(
|
|
181
|
-
company_short_name='my_company',
|
|
182
|
-
message='feedback message for testing',
|
|
183
|
-
external_user_id='flibedinsky',
|
|
184
|
-
space='spaces/AAQAupQldd4',
|
|
185
|
-
type='MESSAGE_TRIGGER',
|
|
186
|
-
rating=4
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
# Verify Google Chat was called
|
|
190
|
-
self.google_chat_app.send_message.assert_called_once()
|
|
191
|
-
|
|
192
|
-
# Verify feedback was still saved successfully despite Google Chat failure
|
|
193
|
-
assert 'Feedback guardado correctamente' == response['message']
|
|
194
|
-
|
|
195
|
-
def test_feedback_message_format_with_external_user_id(self):
|
|
196
|
-
"""Test the format of the Google Chat message with external_user_id"""
|
|
197
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
198
|
-
|
|
199
|
-
self.service.new_feedback(
|
|
200
|
-
company_short_name='test_company',
|
|
201
|
-
message='Test feedback message',
|
|
202
|
-
external_user_id='user123',
|
|
203
|
-
space='spaces/test-space',
|
|
204
|
-
type='MESSAGE_TRIGGER',
|
|
205
|
-
rating=5
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
call_args = self.google_chat_app.send_message.call_args[1]['message_data']
|
|
209
|
-
expected_message = "*Nuevo feedback de test_company*:\n*Usuario:* user123\n*Mensaje:* Test feedback message\n*Calificación:* 5"
|
|
210
|
-
assert call_args['message']['text'] == expected_message
|
|
211
|
-
assert call_args['type'] == 'MESSAGE_TRIGGER'
|
|
212
|
-
assert call_args['space']['name'] == 'spaces/test-space'
|
|
213
|
-
|
|
214
|
-
def test_feedback_message_format_with_local_user_id(self):
|
|
215
|
-
"""Test the format of the Google Chat message with local_user_id"""
|
|
216
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
217
|
-
|
|
218
|
-
self.service.new_feedback(
|
|
219
|
-
company_short_name='test_company',
|
|
220
|
-
message='Test feedback message',
|
|
221
|
-
local_user_id=456,
|
|
222
|
-
space='spaces/test-space',
|
|
223
|
-
type='MESSAGE_TRIGGER',
|
|
224
|
-
rating=2
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
call_args = self.google_chat_app.send_message.call_args[1]['message_data']
|
|
228
|
-
expected_message = "*Nuevo feedback de test_company*:\n*Usuario:* 456\n*Mensaje:* Test feedback message\n*Calificación:* 2"
|
|
229
|
-
assert call_args['message']['text'] == expected_message
|
|
230
|
-
assert call_args['type'] == 'MESSAGE_TRIGGER'
|
|
231
|
-
assert call_args['space']['name'] == 'spaces/test-space'
|
|
232
|
-
|
|
233
|
-
def test_feedback_message_format_with_both_user_ids(self):
|
|
234
|
-
"""Test that external_user_id takes precedence over local_user_id"""
|
|
235
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
236
|
-
|
|
237
|
-
self.service.new_feedback(
|
|
238
|
-
company_short_name='test_company',
|
|
239
|
-
message='Test feedback message',
|
|
240
|
-
external_user_id='user123',
|
|
241
|
-
local_user_id=456,
|
|
242
|
-
space='spaces/test-space',
|
|
243
|
-
type='MESSAGE_TRIGGER',
|
|
244
|
-
rating=4
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
call_args = self.google_chat_app.send_message.call_args[1]['message_data']
|
|
248
|
-
expected_message = "*Nuevo feedback de test_company*:\n*Usuario:* user123\n*Mensaje:* Test feedback message\n*Calificación:* 4"
|
|
249
|
-
assert call_args['message']['text'] == expected_message
|
|
250
|
-
assert call_args['type'] == 'MESSAGE_TRIGGER'
|
|
251
|
-
assert call_args['space']['name'] == 'spaces/test-space'
|
|
252
|
-
|
|
253
|
-
def test_feedback_google_chat_called_before_save(self):
|
|
254
|
-
"""Test that Google Chat notification is sent before saving to database"""
|
|
255
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
256
|
-
|
|
257
|
-
# Track the order of calls
|
|
258
|
-
call_order = []
|
|
259
|
-
|
|
260
|
-
def mock_send_message(message_data):
|
|
261
|
-
call_order.append('google_chat')
|
|
262
|
-
return {'success': True, 'message': 'Mensaje enviado correctamente'}
|
|
263
|
-
|
|
264
|
-
def mock_save_feedback(feedback):
|
|
265
|
-
call_order.append('save_feedback')
|
|
266
|
-
return UserFeedback
|
|
267
|
-
|
|
268
|
-
self.google_chat_app.send_message.side_effect = mock_send_message
|
|
269
|
-
self.profile_repo.save_feedback.side_effect = mock_save_feedback
|
|
270
|
-
|
|
271
|
-
self.service.new_feedback(
|
|
272
|
-
company_short_name='test_company',
|
|
273
|
-
message='Test feedback message',
|
|
274
|
-
external_user_id='user123',
|
|
275
|
-
space='spaces/test-space',
|
|
276
|
-
type='MESSAGE_TRIGGER',
|
|
277
|
-
rating=3
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
# Verify Google Chat was called before saving
|
|
281
|
-
assert call_order == ['google_chat', 'save_feedback']
|
|
282
|
-
|
|
283
|
-
def test_feedback_with_custom_type_and_space(self):
|
|
284
|
-
"""Test that custom type and space values are used correctly"""
|
|
285
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
286
|
-
|
|
287
|
-
self.service.new_feedback(
|
|
288
|
-
company_short_name='test_company',
|
|
289
|
-
message='Test feedback message',
|
|
290
|
-
external_user_id='user123',
|
|
291
|
-
space='spaces/custom-space-id',
|
|
292
|
-
type='CUSTOM_TYPE',
|
|
293
|
-
rating=1
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
call_args = self.google_chat_app.send_message.call_args[1]['message_data']
|
|
297
|
-
assert call_args['type'] == 'CUSTOM_TYPE'
|
|
298
|
-
assert call_args['space']['name'] == 'spaces/custom-space-id'
|
|
299
|
-
assert '*Nuevo feedback de test_company*' in call_args['message']['text']
|
|
300
|
-
assert '*Calificación:* 1' in call_args['message']['text']
|
|
301
|
-
|
|
302
|
-
def test_feedback_save_feedback_called_with_rating(self):
|
|
303
|
-
"""Test that save_feedback is called with the rating parameter"""
|
|
304
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
305
|
-
|
|
306
|
-
self.service.new_feedback(
|
|
307
|
-
company_short_name='test_company',
|
|
308
|
-
message='Test feedback message',
|
|
309
|
-
external_user_id='user123',
|
|
310
|
-
space='spaces/test-space',
|
|
311
|
-
type='MESSAGE_TRIGGER',
|
|
312
|
-
rating=5
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
# Verify save_feedback was called
|
|
316
|
-
self.profile_repo.save_feedback.assert_called_once()
|
|
317
|
-
|
|
318
|
-
# Get the UserFeedback object that was passed to save_feedback
|
|
319
|
-
saved_feedback = self.profile_repo.save_feedback.call_args[0][0]
|
|
320
|
-
|
|
321
|
-
# Verify the rating was set correctly
|
|
322
|
-
assert saved_feedback.rating == 5
|
|
323
|
-
assert saved_feedback.message == 'Test feedback message'
|
|
324
|
-
assert saved_feedback.external_user_id == 'user123'
|
|
325
|
-
assert saved_feedback.company_id == self.company.id
|
|
326
|
-
|
|
327
|
-
def test_feedback_with_different_rating_values(self):
|
|
328
|
-
"""Test that different rating values work correctly"""
|
|
329
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
330
|
-
|
|
331
|
-
# Test with rating 1
|
|
332
|
-
response1 = self.service.new_feedback(
|
|
333
|
-
company_short_name='test_company',
|
|
334
|
-
message='Test feedback message',
|
|
335
|
-
external_user_id='user123',
|
|
336
|
-
space='spaces/test-space',
|
|
337
|
-
type='MESSAGE_TRIGGER',
|
|
338
|
-
rating=1
|
|
339
|
-
)
|
|
340
|
-
assert 'Feedback guardado correctamente' == response1['message']
|
|
341
|
-
|
|
342
|
-
# Test with rating 5
|
|
343
|
-
response2 = self.service.new_feedback(
|
|
344
|
-
company_short_name='test_company',
|
|
345
|
-
message='Test feedback message',
|
|
346
|
-
external_user_id='user123',
|
|
347
|
-
space='spaces/test-space',
|
|
348
|
-
type='MESSAGE_TRIGGER',
|
|
349
|
-
rating=5
|
|
350
|
-
)
|
|
351
|
-
assert 'Feedback guardado correctamente' == response2['message']
|
|
352
|
-
|
|
353
|
-
# Verify both calls were made
|
|
354
|
-
assert self.profile_repo.save_feedback.call_count == 2
|
|
355
|
-
|
|
356
|
-
# Verify the ratings were saved correctly
|
|
357
|
-
calls = self.profile_repo.save_feedback.call_args_list
|
|
358
|
-
assert calls[0][0][0].rating == 1
|
|
359
|
-
assert calls[1][0][0].rating == 5
|
|
360
|
-
|
|
361
|
-
def test_feedback_google_chat_message_includes_rating(self):
|
|
362
|
-
"""Test that Google Chat message includes the rating in the correct format"""
|
|
363
|
-
self.profile_repo.save_feedback.return_value = UserFeedback
|
|
364
|
-
|
|
365
|
-
self.service.new_feedback(
|
|
366
|
-
company_short_name='test_company',
|
|
367
|
-
message='Test feedback message',
|
|
368
|
-
external_user_id='user123',
|
|
369
|
-
space='spaces/test-space',
|
|
370
|
-
type='MESSAGE_TRIGGER',
|
|
371
|
-
rating=4
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
call_args = self.google_chat_app.send_message.call_args[1]['message_data']
|
|
375
|
-
message_text = call_args['message']['text']
|
|
376
|
-
|
|
377
|
-
# Verify the rating is included in the message
|
|
378
|
-
assert '*Calificación:* 4' in message_text
|
|
379
|
-
|
|
380
|
-
# Verify the complete message format
|
|
381
|
-
expected_parts = [
|
|
382
|
-
'*Nuevo feedback de test_company*:',
|
|
383
|
-
'*Usuario:* user123',
|
|
384
|
-
'*Mensaje:* Test feedback message',
|
|
385
|
-
'*Calificación:* 4'
|
|
386
|
-
]
|
|
387
|
-
|
|
388
|
-
for part in expected_parts:
|
|
389
|
-
assert part in message_text
|