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,379 +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, patch
7
- from iatoolkit.repositories.profile_repo import ProfileRepo
8
- from iatoolkit.services.profile_service import ProfileService
9
- from iatoolkit.services.query_service import QueryService
10
- from iatoolkit.services.user_session_context_service import UserSessionContextService
11
- from flask_bcrypt import generate_password_hash
12
- from iatoolkit.repositories.models import User, Company
13
- from iatoolkit.infra.mail_app import MailApp
14
- import os
15
-
16
- class TestProfileService:
17
- @classmethod
18
- def setup_class(cls):
19
- cls.patcher = patch.dict(os.environ, {"USER_VERIF_KEY": "mocked_secret_key"})
20
- cls.patcher.start()
21
-
22
- def setup_method(self):
23
- self.repo = MagicMock(ProfileRepo)
24
- self.mail_app = MagicMock(MailApp)
25
- self.session_context = MagicMock(UserSessionContextService)
26
- self.query_service = MagicMock(QueryService)
27
-
28
- # init the service with the mock
29
- self.service = ProfileService(
30
- profile_repo=self.repo,
31
- session_context_service=self.session_context,
32
- query_service=self.query_service,
33
- mail_app=self.mail_app)
34
-
35
- self.mock_user = User(email='test@opensoft.cl',
36
- first_name='fernando', last_name='libe',
37
- password=generate_password_hash("password").decode("utf-8"), verified=True)
38
-
39
- self.mock_company = Company(name='my company',
40
- short_name='test_company',
41
- logo_file='company_logo.jpg')
42
- self.repo.get_company_by_short_name.return_value = self.mock_company
43
-
44
- self.mock_user.companies = [self.mock_company, Company(id=2, name="Test Company 2")]
45
-
46
- def teardown_method(self):
47
- self.patcher.stop()
48
-
49
- def test_login_when_exception(self):
50
- self.repo.get_user_by_email.side_effect = Exception('an error')
51
- response = self.service.login(self.mock_company.short_name,
52
- 'fernando',
53
- 'a password'
54
- )
55
-
56
- assert "an error" == response['error']
57
-
58
- def test_login_when_user_not_exist(self):
59
- self.repo.get_user_by_email.return_value = None
60
- response = self.service.login(self.mock_company.short_name,
61
- 'fernando',
62
- 'a password')
63
-
64
- assert "Usuario no encontrado" == response['error']
65
-
66
- def test_login_when_invalid_password(self):
67
- # Simula un usuario válido pero con una contraseña incorrecta
68
- mock_user = MagicMock()
69
- mock_user.password = generate_password_hash("correct_password").decode("utf-8")
70
- mock_user.verified = True
71
- self.repo.get_user_by_email.return_value = mock_user
72
-
73
- response = self.service.login(self.mock_company.short_name,
74
- 'fernando',
75
- 'wrong_password'
76
- )
77
-
78
- assert "Contraseña inválida" == response['error']
79
-
80
- def test_login_when_company_not_in_user_companies(self):
81
- self.repo.get_user_by_email.return_value = self.mock_user
82
- self.mock_user.password = generate_password_hash("password").decode("utf-8")
83
- self.mock_user.companies = [Company(id=2, name="Test Company 2")]
84
-
85
- response = self.service.login(self.mock_company.short_name,
86
- 'fernando',
87
- 'password'
88
- )
89
-
90
- assert "Usuario no esta autorizado para esta empresa" == response['error']
91
-
92
- def test_login_when_unverified_account(self):
93
- self.mock_user.password = generate_password_hash("password").decode("utf-8")
94
- self.mock_user.verified = False
95
- self.repo.get_user_by_email.return_value = self.mock_user
96
-
97
- response = self.service.login(self.mock_company.short_name,
98
- 'fernando',
99
- 'password'
100
- )
101
-
102
- assert "Tu cuenta no ha sido verificada" in response['error']
103
-
104
- @patch("iatoolkit.services.profile_service.SessionManager")
105
- def test_login_when_ok(self, mock_session):
106
- mock_session.set.return_value = True
107
- self.mock_user.password = generate_password_hash("password").decode("utf-8")
108
- self.repo.get_user_by_email.return_value = self.mock_user
109
-
110
- response = self.service.login(self.mock_company.short_name,
111
- 'test@email.com',
112
- 'password'
113
- )
114
-
115
- assert "Login exitoso" == response['message']
116
-
117
- def test_signup_when_invalid_company(self):
118
- self.repo.get_company_by_short_name.return_value = None
119
-
120
- response = self.service.signup(
121
- self.mock_company.short_name,
122
- email='test@email.com',
123
- first_name='Test', last_name='User',
124
- password="password", confirm_password="password",
125
- verification_url='http://verification'
126
- )
127
-
128
- assert "empresa test_company no existe" in response['error']
129
-
130
- def test_signup_when_user_exist_and_invalid_password(self):
131
- self.mock_user.password = generate_password_hash("password").decode("utf-8")
132
- self.repo.get_user_by_email.return_value = self.mock_user
133
-
134
- response = self.service.signup(
135
- self.mock_company.short_name,
136
- email='test@email.com',
137
- first_name='Test', last_name='User',
138
- password="invalid_password", confirm_password="password",
139
- verification_url='http://verification'
140
- )
141
-
142
- assert "contraseña es incorrecta" in response['error']
143
-
144
-
145
- def test_signup_when_user_exist_and_already_register(self):
146
- self.repo.get_user_by_email.return_value = self.mock_user
147
-
148
- response = self.service.signup(
149
- self.mock_company.short_name,
150
- email='test@email.com',
151
- first_name='Test', last_name='User',
152
- password="password", confirm_password="password",
153
- verification_url='http://verification'
154
- )
155
-
156
- assert "Usuario ya registrado" in response['error']
157
-
158
- def test_signup_when_user_exist_and_not_in_company(self):
159
- self.repo.get_user_by_email.return_value = self.mock_user
160
- self.mock_user.companies = []
161
-
162
- response = self.service.signup(
163
- self.mock_company.short_name,
164
- email='test@email.com',
165
- first_name='Test', last_name='User',
166
- password="password", confirm_password="password",
167
- verification_url='http://verification'
168
- )
169
-
170
- assert "Usuario asociado" in response['message']
171
- self.repo.save_user.assert_called_once()
172
-
173
- def test_signup_when_passwords_different(self):
174
- self.repo.get_user_by_email.return_value = None
175
-
176
- response = self.service.signup(
177
- self.mock_company.short_name,
178
- email='test@email.com',
179
- first_name='Test', last_name='User',
180
- password="Password1", confirm_password="Password2$1",
181
- verification_url='http://verification'
182
- )
183
-
184
- assert "contraseñas no coinciden" in response['error']
185
-
186
- def test_signup_when_passwords_incorrect2(self):
187
- self.repo.get_user_by_email.return_value = None
188
-
189
- response = self.service.signup(
190
- self.mock_company.short_name,
191
- email='test@email.com',
192
- first_name='Test', last_name='User',
193
- password="Password", confirm_password="Password",
194
- verification_url='http://verification'
195
- )
196
-
197
- assert "número" in response['error']
198
-
199
- def test_signup_when_passwords_incorrect3(self):
200
- self.repo.get_user_by_email.return_value = None
201
-
202
- response = self.service.signup(
203
- self.mock_company.short_name,
204
- email='test@email.com',
205
- first_name='Test', last_name='User',
206
- password="Passw1", confirm_password="Passw1",
207
- verification_url='http://verification'
208
- )
209
-
210
- assert "8 caracteres" in response['error']
211
-
212
- def test_signup_when_passwords_incorrect4(self):
213
- self.repo.get_user_by_email.return_value = None
214
-
215
- response = self.service.signup(
216
- self.mock_company.short_name,
217
- email='test@email.com',
218
- first_name='Test', last_name='User',
219
- password="password123", confirm_password="password123",
220
- verification_url='http://verification'
221
- )
222
-
223
- assert "mayúscula" in response['error']
224
-
225
- def test_signup_when_passwords_incorrect5(self):
226
- self.repo.get_user_by_email.return_value = None
227
-
228
- response = self.service.signup(
229
- self.mock_company.short_name,
230
- email='test@email.com',
231
- first_name='Test', last_name='User',
232
- password="Password123", confirm_password="Password123",
233
- verification_url='http://verification'
234
- )
235
-
236
- assert "especial" in response['error']
237
-
238
- def test_signup_when_ok(self):
239
- self.repo.get_user_by_email.return_value = None
240
- self.mail_app.send_email.return_value = True
241
-
242
- response = self.service.signup(
243
- self.mock_company.short_name,
244
- email='test@email.com',
245
- first_name='Test', last_name='User',
246
- password="Password$1", confirm_password="Password$1",
247
- verification_url='http://verification'
248
- )
249
-
250
- assert "Registro exitoso" in response['message']
251
- self.mail_app.send_email.assert_called()
252
-
253
- def test_signup_when_exception(self):
254
- self.repo.get_user_by_email.side_effect = Exception('an error')
255
- response = self.service.signup(
256
- self.mock_company.short_name,
257
- email='test@email.com',
258
- first_name='Test', last_name='User',
259
- password="password", confirm_password="password",
260
- verification_url='http://verification'
261
- )
262
-
263
- assert "an error" == response['error']
264
-
265
- def test_get_companies_when_ok(self):
266
- self.repo.get_companies.return_value = [self.mock_company]
267
- companies = self.service.get_companies()
268
- assert companies == [self.mock_company]
269
-
270
- def test_get_company_by_short_name_when_ok(self):
271
- company = self.service.get_company_by_short_name('test_company')
272
- assert company == self.mock_company
273
-
274
- def test_update_user(self):
275
- self.repo.update_user.return_value = self.mock_user
276
- user = self.service.update_user('fl@opensoft.cl', first_name='fernando')
277
-
278
- assert user == self.mock_user
279
-
280
- def test_verify_account_when_user_not_exist(self):
281
- self.repo.get_user_by_email.return_value = None
282
-
283
- response = self.service.verify_account(email='test@email.com')
284
-
285
- assert "El usuario no existe." in response['error']
286
-
287
- def test_verify_account_when_exception(self):
288
- self.repo.get_user_by_email.side_effect = Exception('an error')
289
- response = self.service.verify_account(email='test@email.com')
290
-
291
- assert "an error" == response['error']
292
-
293
- def test_verify_account_when_ok(self):
294
- self.repo.get_user_by_email.return_value = self.mock_user
295
- response = self.service.verify_account(email='test@email.com')
296
-
297
- assert "cuenta ha sido verificada" in response['message']
298
-
299
- def test_change_password_when_password_mismatch(self):
300
- response = self.service.change_password(
301
- email='test@email.com',
302
- temp_code='ABC',
303
- new_password='pass1',
304
- confirm_password='pass2'
305
- )
306
- assert "contraseñas no coinciden" in response['error']
307
-
308
- def test_change_passworwd_when_invalid_code(self):
309
- self.repo.get_user_by_email.return_value = self.mock_user
310
- self.mock_user.temp_code = 'xYhvt'
311
- response = self.service.change_password(
312
- email='test@email.com',
313
- temp_code='ABC',
314
- new_password='pass1',
315
- confirm_password='pass1'
316
- )
317
- assert "código temporal no es válido" in response['error']
318
-
319
- def test_change_password_when_ok(self):
320
- self.repo.get_user_by_email.return_value = self.mock_user
321
- self.mock_user.temp_code = 'ABC'
322
- response = self.service.change_password(
323
- email='test@email.com',
324
- temp_code=self.mock_user.temp_code,
325
- new_password='pass1',
326
- confirm_password='pass1'
327
- )
328
- assert "clave se cambio correctamente" in response['message']
329
-
330
- def test_change_password_when_exception(self):
331
- self.repo.get_user_by_email.return_value = self.mock_user
332
- self.repo.update_password.side_effect = Exception('db error')
333
- response = self.service.change_password(
334
- email='test@email.com',
335
- temp_code=self.mock_user.temp_code,
336
- new_password='pass1',
337
- confirm_password='pass1'
338
- )
339
- assert "db error" == response['error']
340
-
341
- def test_forgot_password_when_user_not_exist(self):
342
- self.repo.get_user_by_email.return_value = None
343
- response = self.service.forgot_password(
344
- email='test@email.com',
345
- reset_url='http://a_reset_utl'
346
- )
347
- assert "El usuario no existe" in response['error']
348
-
349
- def test_forgot_password_when_ok(self):
350
- self.repo.get_user_by_email.return_value = self.mock_user
351
- response = self.service.forgot_password(
352
- email='test@email.com',
353
- reset_url='http://a_reset_utl'
354
- )
355
- assert "se envio mail para cambio de clave" in response['message']
356
- self.mail_app.send_email.assert_called()
357
-
358
- def test_forgot_password_when_exception(self):
359
- self.repo.get_user_by_email.return_value = self.mock_user
360
- self.mail_app.send_email.side_effect = Exception('mail error')
361
- response = self.service.forgot_password(
362
- email='test@email.com',
363
- reset_url='http://a_reset_utl'
364
- )
365
-
366
- assert "mail error" == response['error']
367
-
368
- def test_new_api_key_when_not_company(self):
369
- self.repo.get_company_by_short_name.return_value = None
370
- response = self.service.new_api_key(company_short_name='test_company')
371
- assert "test_company no existe" in response['error']
372
-
373
- def test_new_api_key_when_ok(self):
374
- self.repo.get_company_by_short_name.return_value = self.mock_company
375
- response = self.service.new_api_key(company_short_name='test_company')
376
-
377
- self.repo.create_api_key.assert_called()
378
- assert response['api-key'] != ''
379
-
@@ -1,190 +0,0 @@
1
- import pytest
2
- from unittest.mock import MagicMock, patch, mock_open
3
-
4
- # Asegúrate de que todas las importaciones necesarias estén presentes y correctas
5
- from iatoolkit.services.prompt_manager_service import PromptService
6
- from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
7
- from iatoolkit.repositories.profile_repo import ProfileRepo
8
- from iatoolkit.repositories.models import Prompt, PromptCategory, Company
9
- from iatoolkit.common.exceptions import IAToolkitException
10
-
11
-
12
- class TestPromptService:
13
- @pytest.fixture(autouse=True)
14
- def setup(self):
15
- """Configura mocks y la instancia del servicio para cada test."""
16
- self.llm_query_repo = MagicMock(spec=LLMQueryRepo)
17
- self.profile_repo = MagicMock(spec=ProfileRepo)
18
- self.prompt_service = PromptService(
19
- llm_query_repo=self.llm_query_repo,
20
- profile_repo=self.profile_repo
21
- )
22
- self.mock_company = MagicMock(spec=Company)
23
- self.mock_company.id = 1
24
- self.mock_company.name = 'Test Company'
25
- self.mock_company.short_name = 'test_co'
26
-
27
- # --- Tests para get_user_prompts ---
28
-
29
- def test_get_user_prompts_company_not_found(self):
30
- """Prueba que se devuelve un error cuando la empresa no existe."""
31
- self.profile_repo.get_company_by_short_name.return_value = None
32
- result = self.prompt_service.get_user_prompts(company_short_name='nonexistent_company')
33
- assert result == {'error': 'No existe la empresa: nonexistent_company'}
34
-
35
- def test_get_user_prompts_no_prompts_exist(self):
36
- """Prueba que se devuelve una lista vacía cuando la empresa no tiene prompts."""
37
- self.profile_repo.get_company_by_short_name.return_value = self.mock_company
38
- self.llm_query_repo.get_prompts.return_value = []
39
- result = self.prompt_service.get_user_prompts(company_short_name='test_company')
40
- assert result == {'message': []}
41
-
42
- def test_get_user_prompts_filters_inactive_and_groups_correctly(self):
43
- """Prueba que los prompts inactivos se filtran y que los activos se agrupan correctamente."""
44
- # Usamos instancias reales de los modelos en lugar de Mocks para los datos.
45
- category = PromptCategory(name='General', order=1)
46
- active_prompt = Prompt(name='active_prompt', description='Active', active=True, order=1, category=category)
47
- inactive_prompt = Prompt(name='inactive_prompt', description='Inactive', active=False, order=2,
48
- category=category)
49
-
50
- self.profile_repo.get_company_by_short_name.return_value = self.mock_company
51
- self.llm_query_repo.get_prompts.return_value = [active_prompt, inactive_prompt]
52
-
53
- result = self.prompt_service.get_user_prompts(company_short_name='test_company')
54
-
55
- # Verificar que solo hay una categoría en el resultado
56
- assert len(result['message']) == 1
57
- # Verificar que dentro de esa categoría, solo hay un prompt (el activo)
58
- assert len(result['message'][0]['prompts']) == 1
59
- # Verificar que el prompt es el correcto
60
- assert result['message'][0]['prompts'][0]['prompt'] == 'active_prompt'
61
-
62
- # --- Tests para get_system_prompt ---
63
-
64
- @patch('iatoolkit.services.prompt_manager_service.importlib.resources.read_text')
65
- def test_get_system_prompt_success(self, mock_read_text):
66
- """Prueba la obtención exitosa de los prompts de sistema concatenados."""
67
- prompt1 = Prompt(filename='system1.prompt')
68
- prompt2 = Prompt(filename='system2.prompt')
69
- self.llm_query_repo.get_system_prompts.return_value = [prompt1, prompt2]
70
-
71
- # Configurar el mock para devolver diferentes contenidos en cada llamada
72
- mock_read_text.side_effect = [
73
- 'Contenido 1',
74
- 'Contenido 2'
75
- ]
76
-
77
- result = self.prompt_service.get_system_prompt()
78
-
79
- assert result == "Contenido 1\nContenido 2"
80
- assert mock_read_text.call_count == 2
81
-
82
- @patch('iatoolkit.services.prompt_manager_service.os.path.exists', return_value=False)
83
- @patch('iatoolkit.services.prompt_manager_service.logging')
84
- def test_get_system_prompt_file_not_found(self, mock_logging, mock_exists):
85
- """Prueba que se loguea una advertencia si un archivo de prompt no existe."""
86
- prompt1 = Prompt(filename='missing.prompt')
87
- self.llm_query_repo.get_system_prompts.return_value = [prompt1]
88
-
89
- result = self.prompt_service.get_system_prompt()
90
-
91
- assert result == ""
92
- mock_logging.warning.assert_called_once()
93
- assert "no existe" in mock_logging.warning.call_args[0][0]
94
-
95
- def test_get_system_prompt_handles_repo_exception(self):
96
- """Prueba que se maneja una excepción del repositorio."""
97
- self.llm_query_repo.get_system_prompts.side_effect = Exception("DB Connection Error")
98
-
99
- with pytest.raises(IAToolkitException) as exc_info:
100
- self.prompt_service.get_system_prompt()
101
-
102
- assert exc_info.value.error_type == IAToolkitException.ErrorType.PROMPT_ERROR
103
- assert "DB Connection Error" in str(exc_info.value)
104
-
105
- # --- Tests para get_prompt_content ---
106
-
107
- @patch('iatoolkit.services.prompt_manager_service.os.path.exists', return_value=True)
108
- @patch('builtins.open', new_callable=mock_open, read_data='Contenido específico del prompt.')
109
- def test_get_prompt_content_success(self, mock_file, mock_exists):
110
- """Prueba la obtención exitosa del contenido de un prompt específico."""
111
- mock_prompt = Prompt(filename='my_prompt.prompt')
112
- self.llm_query_repo.get_prompt_by_name.return_value = mock_prompt
113
-
114
- result = self.prompt_service.get_prompt_content(self.mock_company, 'my_prompt')
115
-
116
- assert result == 'Contenido específico del prompt.'
117
- self.llm_query_repo.get_prompt_by_name.assert_called_once_with(self.mock_company, 'my_prompt')
118
-
119
- def test_get_prompt_content_prompt_not_in_db(self):
120
- """Prueba que se lanza una excepción si el prompt no se encuentra en la BD."""
121
- self.llm_query_repo.get_prompt_by_name.return_value = None
122
-
123
- with pytest.raises(IAToolkitException) as exc_info:
124
- self.prompt_service.get_prompt_content(self.mock_company, 'non_existent_prompt')
125
-
126
- assert exc_info.value.error_type == IAToolkitException.ErrorType.DOCUMENT_NOT_FOUND
127
-
128
- # --- Tests para create_prompt ---
129
-
130
- @patch('iatoolkit.services.prompt_manager_service.os.path.exists', return_value=True)
131
- def test_create_prompt_success_for_company(self, mock_exists):
132
- """Prueba la creación exitosa de un prompt para una compañía."""
133
- self.prompt_service.create_prompt(
134
- prompt_name='new_prompt',
135
- description='A new prompt',
136
- order=1,
137
- company=self.mock_company,
138
- custom_fields = [{'data_key': 'key', 'label': ' a label'}]
139
- )
140
-
141
- self.llm_query_repo.create_or_update_prompt.assert_called_once()
142
- call_args = self.llm_query_repo.create_or_update_prompt.call_args[0]
143
- prompt_object = call_args[0]
144
-
145
- assert isinstance(prompt_object, Prompt)
146
- assert prompt_object.name == 'new_prompt'
147
- assert prompt_object.company_id == self.mock_company.id
148
- assert not prompt_object.is_system_prompt
149
- assert 'new_prompt.prompt' in prompt_object.filename
150
- assert prompt_object.custom_fields == [{'data_key': 'key', 'label': ' a label', 'type': 'text'}]
151
-
152
- @patch('iatoolkit.services.prompt_manager_service.os.path.exists', return_value=True)
153
- def test_create_prompt_when_invalid_custom_fields(self, mock_exists):
154
- with pytest.raises(IAToolkitException) as exc_info:
155
- self.prompt_service.create_prompt(
156
- prompt_name='new_prompt',
157
- description='A new prompt',
158
- order=1,
159
- company=self.mock_company,
160
- custom_fields=[{'label': ' a label'}]
161
- )
162
-
163
- assert exc_info.value.error_type == IAToolkitException.ErrorType.INVALID_PARAMETER
164
-
165
- @patch('iatoolkit.services.prompt_manager_service.os.path.exists', return_value=False)
166
- def test_create_prompt_fails_if_file_does_not_exist(self, mock_exists):
167
- """Prueba que la creación falla si el archivo de plantilla no existe."""
168
- with pytest.raises(IAToolkitException) as exc_info:
169
- self.prompt_service.create_prompt(
170
- prompt_name='prompt_with_missing_file',
171
- description='Desc',
172
- order=1,
173
- company=self.mock_company
174
- )
175
- assert exc_info.value.error_type == IAToolkitException.ErrorType.INVALID_NAME
176
- assert "No existe el archivo de prompt" in str(exc_info.value)
177
-
178
- @patch('iatoolkit.services.prompt_manager_service.os.path.exists', return_value=True)
179
- def test_create_prompt_handles_db_exception(self, mock_exists):
180
- """Prueba que se maneja una excepción de la base de datos al guardar."""
181
- self.llm_query_repo.create_or_update_prompt.side_effect = Exception("DB Unique Constraint Failed")
182
-
183
- with pytest.raises(IAToolkitException) as exc_info:
184
- self.prompt_service.create_prompt(
185
- prompt_name='any_prompt',
186
- description='Desc',
187
- order=1,
188
- company=self.mock_company
189
- )
190
- assert exc_info.value.error_type == IAToolkitException.ErrorType.DATABASE_ERROR