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.

Files changed (159) hide show
  1. iatoolkit/__init__.py +8 -34
  2. iatoolkit/base_company.py +14 -3
  3. iatoolkit/common/routes.py +83 -52
  4. iatoolkit/common/session_manager.py +0 -1
  5. iatoolkit/common/util.py +0 -27
  6. iatoolkit/iatoolkit.py +61 -46
  7. iatoolkit/infra/llm_client.py +7 -8
  8. iatoolkit/infra/openai_adapter.py +1 -1
  9. iatoolkit/infra/redis_session_manager.py +48 -2
  10. iatoolkit/repositories/database_manager.py +17 -2
  11. iatoolkit/repositories/models.py +31 -6
  12. iatoolkit/repositories/profile_repo.py +7 -2
  13. iatoolkit/services/auth_service.py +188 -0
  14. iatoolkit/services/branding_service.py +147 -0
  15. iatoolkit/services/dispatcher_service.py +10 -40
  16. iatoolkit/services/excel_service.py +15 -15
  17. iatoolkit/services/history_service.py +3 -12
  18. iatoolkit/services/jwt_service.py +15 -24
  19. iatoolkit/services/onboarding_service.py +43 -0
  20. iatoolkit/services/profile_service.py +97 -44
  21. iatoolkit/services/query_service.py +124 -81
  22. iatoolkit/services/tasks_service.py +1 -1
  23. iatoolkit/services/user_feedback_service.py +67 -31
  24. iatoolkit/services/user_session_context_service.py +112 -54
  25. iatoolkit/static/images/fernando.jpeg +0 -0
  26. iatoolkit/static/js/{chat_feedback.js → chat_feedback_button.js} +6 -11
  27. iatoolkit/static/js/chat_history_button.js +126 -0
  28. iatoolkit/static/js/chat_logout_button.js +36 -0
  29. iatoolkit/static/js/chat_main.js +130 -220
  30. iatoolkit/static/js/chat_onboarding_button.js +97 -0
  31. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  32. iatoolkit/static/js/chat_reload_button.js +52 -0
  33. iatoolkit/static/styles/chat_iatoolkit.css +329 -507
  34. iatoolkit/static/styles/chat_modal.css +95 -56
  35. iatoolkit/static/styles/landing_page.css +182 -0
  36. iatoolkit/static/styles/onboarding.css +169 -0
  37. iatoolkit/system_prompts/query_main.prompt +3 -12
  38. iatoolkit/templates/_company_header.html +20 -0
  39. iatoolkit/templates/_login_widget.html +40 -0
  40. iatoolkit/templates/base.html +8 -3
  41. iatoolkit/templates/change_password.html +54 -37
  42. iatoolkit/templates/chat.html +149 -66
  43. iatoolkit/templates/chat_modals.html +47 -18
  44. iatoolkit/templates/error.html +41 -8
  45. iatoolkit/templates/forgot_password.html +37 -24
  46. iatoolkit/templates/index.html +140 -0
  47. iatoolkit/templates/login_simulation.html +34 -0
  48. iatoolkit/templates/onboarding_shell.html +105 -0
  49. iatoolkit/templates/signup.html +64 -66
  50. iatoolkit/views/base_login_view.py +81 -0
  51. iatoolkit/views/change_password_view.py +23 -12
  52. iatoolkit/views/external_login_view.py +61 -28
  53. iatoolkit/views/{file_store_view.py → file_store_api_view.py} +9 -2
  54. iatoolkit/views/forgot_password_view.py +23 -13
  55. iatoolkit/views/history_api_view.py +52 -0
  56. iatoolkit/views/home_view.py +58 -25
  57. iatoolkit/views/index_view.py +14 -0
  58. iatoolkit/views/init_context_api_view.py +68 -0
  59. iatoolkit/views/llmquery_api_view.py +45 -0
  60. iatoolkit/views/login_simulation_view.py +81 -0
  61. iatoolkit/views/login_view.py +118 -34
  62. iatoolkit/views/logout_api_view.py +45 -0
  63. iatoolkit/views/{prompt_view.py → prompt_api_view.py} +7 -7
  64. iatoolkit/views/signup_view.py +38 -29
  65. iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
  66. iatoolkit/views/tasks_review_api_view.py +55 -0
  67. iatoolkit/views/{user_feedback_view.py → user_feedback_api_view.py} +16 -31
  68. iatoolkit/views/verify_user_view.py +13 -8
  69. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/METADATA +2 -2
  70. iatoolkit-0.63.4.dist-info/RECORD +113 -0
  71. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/top_level.txt +0 -1
  72. iatoolkit/common/auth.py +0 -200
  73. iatoolkit/static/images/arrow_up.png +0 -0
  74. iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
  75. iatoolkit/static/images/logo_clinica.png +0 -0
  76. iatoolkit/static/images/logo_iatoolkit.png +0 -0
  77. iatoolkit/static/images/logo_maxxa.png +0 -0
  78. iatoolkit/static/images/logo_notaria.png +0 -0
  79. iatoolkit/static/images/logo_tarjeta.png +0 -0
  80. iatoolkit/static/images/logo_umayor.png +0 -0
  81. iatoolkit/static/images/upload.png +0 -0
  82. iatoolkit/static/js/chat_history.js +0 -117
  83. iatoolkit/templates/home.html +0 -201
  84. iatoolkit/templates/login.html +0 -43
  85. iatoolkit/views/chat_token_request_view.py +0 -98
  86. iatoolkit/views/chat_view.py +0 -51
  87. iatoolkit/views/download_file_view.py +0 -58
  88. iatoolkit/views/external_chat_login_view.py +0 -88
  89. iatoolkit/views/history_view.py +0 -57
  90. iatoolkit/views/llmquery_view.py +0 -65
  91. iatoolkit/views/tasks_review_view.py +0 -83
  92. iatoolkit-0.8.1.dist-info/RECORD +0 -175
  93. tests/__init__.py +0 -5
  94. tests/common/__init__.py +0 -0
  95. tests/common/test_auth.py +0 -279
  96. tests/common/test_routes.py +0 -42
  97. tests/common/test_session_manager.py +0 -59
  98. tests/common/test_util.py +0 -444
  99. tests/companies/__init__.py +0 -5
  100. tests/conftest.py +0 -36
  101. tests/infra/__init__.py +0 -5
  102. tests/infra/connectors/__init__.py +0 -5
  103. tests/infra/connectors/test_google_drive_connector.py +0 -107
  104. tests/infra/connectors/test_local_file_connector.py +0 -85
  105. tests/infra/connectors/test_s3_connector.py +0 -95
  106. tests/infra/test_call_service.py +0 -92
  107. tests/infra/test_database_manager.py +0 -59
  108. tests/infra/test_gemini_adapter.py +0 -137
  109. tests/infra/test_google_chat_app.py +0 -68
  110. tests/infra/test_llm_client.py +0 -165
  111. tests/infra/test_llm_proxy.py +0 -122
  112. tests/infra/test_mail_app.py +0 -94
  113. tests/infra/test_openai_adapter.py +0 -105
  114. tests/infra/test_redis_session_manager_service.py +0 -117
  115. tests/repositories/__init__.py +0 -5
  116. tests/repositories/test_database_manager.py +0 -87
  117. tests/repositories/test_document_repo.py +0 -76
  118. tests/repositories/test_llm_query_repo.py +0 -340
  119. tests/repositories/test_models.py +0 -38
  120. tests/repositories/test_profile_repo.py +0 -142
  121. tests/repositories/test_tasks_repo.py +0 -76
  122. tests/repositories/test_vs_repo.py +0 -107
  123. tests/services/__init__.py +0 -5
  124. tests/services/test_dispatcher_service.py +0 -274
  125. tests/services/test_document_service.py +0 -181
  126. tests/services/test_excel_service.py +0 -208
  127. tests/services/test_file_processor_service.py +0 -121
  128. tests/services/test_history_service.py +0 -164
  129. tests/services/test_jwt_service.py +0 -255
  130. tests/services/test_load_documents_service.py +0 -112
  131. tests/services/test_mail_service.py +0 -70
  132. tests/services/test_profile_service.py +0 -379
  133. tests/services/test_prompt_manager_service.py +0 -190
  134. tests/services/test_query_service.py +0 -243
  135. tests/services/test_search_service.py +0 -39
  136. tests/services/test_sql_service.py +0 -160
  137. tests/services/test_tasks_service.py +0 -252
  138. tests/services/test_user_feedback_service.py +0 -389
  139. tests/services/test_user_session_context_service.py +0 -132
  140. tests/views/__init__.py +0 -5
  141. tests/views/test_change_password_view.py +0 -191
  142. tests/views/test_chat_token_request_view.py +0 -188
  143. tests/views/test_chat_view.py +0 -98
  144. tests/views/test_download_file_view.py +0 -149
  145. tests/views/test_external_chat_login_view.py +0 -120
  146. tests/views/test_external_login_view.py +0 -102
  147. tests/views/test_file_store_view.py +0 -128
  148. tests/views/test_forgot_password_view.py +0 -142
  149. tests/views/test_history_view.py +0 -336
  150. tests/views/test_home_view.py +0 -61
  151. tests/views/test_llm_query_view.py +0 -154
  152. tests/views/test_login_view.py +0 -114
  153. tests/views/test_prompt_view.py +0 -111
  154. tests/views/test_signup_view.py +0 -140
  155. tests/views/test_tasks_review_view.py +0 -104
  156. tests/views/test_tasks_view.py +0 -130
  157. tests/views/test_user_feedback_view.py +0 -214
  158. tests/views/test_verify_user_view.py +0 -110
  159. {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