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,132 +0,0 @@
1
- import unittest
2
- from unittest.mock import patch
3
- from iatoolkit.services.user_session_context_service import UserSessionContextService
4
-
5
-
6
- class TestUserSessionContextService(unittest.TestCase):
7
-
8
- def setUp(self):
9
- """Configura el servicio y los mocks antes de cada test."""
10
- self.service = UserSessionContextService()
11
- self.company_short_name = "test_company"
12
- self.user_identifier = "test_user"
13
-
14
- # Definir las claves esperadas para reutilizarlas en los tests
15
- self.history_key = f"llm_history:{self.company_short_name}/{self.user_identifier}"
16
- self.data_key = f"user_data:{self.company_short_name}/{self.user_identifier}"
17
-
18
- # Patchear la dependencia RedisSessionManager para aislar el servicio
19
- self.redis_patcher = patch("iatoolkit.services.user_session_context_service.RedisSessionManager")
20
- self.mock_redis_manager = self.redis_patcher.start()
21
-
22
- def tearDown(self):
23
- """Limpia los patches después de cada test."""
24
- patch.stopall()
25
-
26
- def test_save_last_response_id(self):
27
- """Prueba que se guarda correctamente el ID de la última respuesta."""
28
- response_id = "resp_xyz"
29
- self.service.save_last_response_id(self.company_short_name, self.user_identifier, response_id)
30
- # Verificar que se llamó a Redis con la clave y valor correctos
31
- self.mock_redis_manager.set.assert_called_once_with(self.history_key, response_id)
32
-
33
- def test_get_last_response_id(self):
34
- """Prueba que se obtiene correctamente el ID de la última respuesta."""
35
- self.mock_redis_manager.get.return_value = "resp_abc"
36
- result = self.service.get_last_response_id(self.company_short_name, self.user_identifier)
37
-
38
- # Verificar que se llamó a Redis con la clave y el default correctos
39
- self.mock_redis_manager.get.assert_called_once_with(self.history_key, '')
40
- self.assertEqual(result, "resp_abc")
41
-
42
- def test_save_user_session_data(self):
43
- """Prueba que los datos de sesión del usuario se guardan como JSON."""
44
- data = {"role": "admin", "theme": "dark"}
45
- self.service.save_user_session_data(self.company_short_name, self.user_identifier, data)
46
- # Verificar que se llamó a set_json con la clave y los datos correctos
47
- self.mock_redis_manager.set_json.assert_called_once_with(self.data_key, data)
48
-
49
- def test_get_user_session_data(self):
50
- """Prueba que los datos de sesión del usuario se recuperan correctamente."""
51
- expected_data = {"role": "admin", "theme": "dark"}
52
- self.mock_redis_manager.get_json.return_value = expected_data
53
-
54
- result = self.service.get_user_session_data(self.company_short_name, self.user_identifier)
55
-
56
- # Verificar que se llamó a get_json con la clave y el default correctos
57
- self.mock_redis_manager.get_json.assert_called_once_with(self.data_key, {})
58
- self.assertEqual(result, expected_data)
59
-
60
- def test_clear_llm_history(self):
61
- """Prueba que el historial del LLM se limpia correctamente."""
62
- self.service.clear_llm_history(self.company_short_name, self.user_identifier)
63
- # Verificar que se llamó a remove con la clave del historial
64
- self.mock_redis_manager.remove.assert_called_once_with(self.history_key)
65
-
66
- def test_clear_user_session_data(self):
67
- """Prueba que los datos de sesión del usuario se limpian correctamente."""
68
- self.service.clear_user_session_data(self.company_short_name, self.user_identifier)
69
- # Verificar que se llamó a remove con la clave de datos de usuario
70
- self.mock_redis_manager.remove.assert_called_once_with(self.data_key)
71
-
72
- def test_clear_all_context(self):
73
- """Prueba que se limpian ambos contextos (historial y datos de usuario)."""
74
- self.service.clear_all_context(self.company_short_name, self.user_identifier)
75
-
76
- # Verificar que se llamó a remove para ambas claves
77
- self.mock_redis_manager.remove.assert_any_call(self.history_key)
78
- self.mock_redis_manager.remove.assert_any_call(self.data_key)
79
- # Asegurarse de que se hicieron exactamente dos llamadas a remove
80
- self.assertEqual(self.mock_redis_manager.remove.call_count, 2)
81
-
82
- def test_methods_do_nothing_with_none_user_identifier(self):
83
- """
84
- Prueba que ningún método interactúa con Redis si el user_identifier es None.
85
- """
86
- user_id = None
87
-
88
- # Probar métodos de escritura
89
- self.service.save_last_response_id(self.company_short_name, user_id, "id_1")
90
- self.service.save_user_session_data(self.company_short_name, user_id, {"data": "value"})
91
- self.service.clear_all_context(self.company_short_name, user_id)
92
-
93
- # Probar métodos de lectura
94
- get_id_result = self.service.get_last_response_id(self.company_short_name, user_id)
95
- get_data_result = self.service.get_user_session_data(self.company_short_name, user_id)
96
-
97
- # Verificar que NUNCA se llamó a Redis
98
- self.mock_redis_manager.set.assert_not_called()
99
- self.mock_redis_manager.get.assert_not_called()
100
- self.mock_redis_manager.set_json.assert_not_called()
101
- self.mock_redis_manager.get_json.assert_not_called()
102
- self.mock_redis_manager.remove.assert_not_called()
103
-
104
- # Verificar que los métodos de lectura devuelven valores seguros/por defecto
105
- self.assertIsNone(get_id_result)
106
- self.assertEqual(get_data_result, {})
107
-
108
- def test_methods_do_nothing_with_empty_user_identifier(self):
109
- """
110
- Prueba que ningún método interactúa con Redis si el user_identifier es una cadena vacía o espacios.
111
- """
112
- for user_id in ["", " "]:
113
- # Probar métodos de escritura
114
- self.service.save_last_response_id(self.company_short_name, user_id, "id_1")
115
- self.service.save_user_session_data(self.company_short_name, user_id, {"data": "value"})
116
- self.service.clear_all_context(self.company_short_name, user_id)
117
-
118
- # Probar métodos de lectura
119
- get_id_result = self.service.get_last_response_id(self.company_short_name, user_id)
120
- get_data_result = self.service.get_user_session_data(self.company_short_name, user_id)
121
-
122
- # Verificar que los métodos de lectura devuelven valores seguros/por defecto
123
- self.assertIsNone(get_id_result)
124
- self.assertEqual(get_data_result, {})
125
-
126
- # Al final del bucle, verificar que NUNCA se llamó a Redis
127
- self.mock_redis_manager.set.assert_not_called()
128
- self.mock_redis_manager.get.assert_not_called()
129
- self.mock_redis_manager.set_json.assert_not_called()
130
- self.mock_redis_manager.get_json.assert_not_called()
131
- self.mock_redis_manager.remove.assert_not_called()
132
-
tests/views/__init__.py DELETED
@@ -1,5 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
@@ -1,191 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- import pytest
7
- from flask import Flask
8
- from unittest.mock import MagicMock, patch
9
- from iatoolkit.services.profile_service import ProfileService
10
- from iatoolkit.views.change_password_view import ChangePasswordView
11
- from itsdangerous import SignatureExpired
12
- import os
13
- from iatoolkit.repositories.models import Company
14
-
15
-
16
-
17
- class TestChangePasswordView:
18
- @classmethod
19
- def setup_class(cls):
20
- cls.patcher = patch.dict(os.environ, {"PASS_RESET_KEY": "mocked_reset_key"})
21
- cls.patcher.start()
22
-
23
- @classmethod
24
- def teardown_class(cls):
25
- cls.patcher.stop()
26
-
27
- @staticmethod
28
- def create_app():
29
- """Configura la aplicación Flask para pruebas."""
30
- app = Flask(__name__)
31
- app.testing = True
32
- return app
33
-
34
- @pytest.fixture(autouse=True)
35
- def setup(self):
36
- """Configura el cliente y los mocks antes de cada test."""
37
- self.app = self.create_app()
38
- self.client = self.app.test_client()
39
- self.profile_service = MagicMock(spec=ProfileService)
40
- self.test_company = Company(
41
- id=1,
42
- name="Empresa de Prueba",
43
- short_name="test_company",
44
- logo_file="test_logo.png"
45
- )
46
- self.profile_service.get_company_by_short_name.return_value = self.test_company
47
-
48
- # Registrar la vista
49
- view = ChangePasswordView.as_view("change_password", profile_service=self.profile_service)
50
- self.app.add_url_rule("/<company_short_name>/change_password/<token>", view_func=view, methods=["GET", "POST"])
51
-
52
-
53
- @patch("iatoolkit.views.change_password_view.render_template")
54
- def test_get_and_post_invalid_company(self, mock_render):
55
- self.profile_service.get_company_by_short_name.return_value = None
56
- response = self.client.get("/test_company/change_password/valid_token")
57
- assert response.status_code == 404
58
-
59
- response = self.client.post("/test_company/change_password/valid_token",
60
- data={
61
- "temp_code": "123456",
62
- "new_password": "password123",
63
- "confirm_password": "password456"
64
- },
65
- content_type="application/x-www-form-urlencoded")
66
-
67
- assert response.status_code == 404
68
-
69
- @patch("iatoolkit.views.change_password_view.render_template")
70
- def test_get_with_expired_token(self, mock_render_template):
71
- """Prueba GET con un token expirado."""
72
- # Configura el serializer para que lance una excepción SignatureExpired
73
- with patch("iatoolkit.views.change_password_view.URLSafeTimedSerializer") as mock_serializer_class:
74
- mock_serializer = mock_serializer_class.return_value
75
- mock_serializer.loads.side_effect = SignatureExpired('error')
76
-
77
- mock_render_template.return_value = "<html><body><h1>Forgot Password</h1></body></html>"
78
- response = self.client.get("/test_company/change_password/expired_token")
79
-
80
- mock_render_template.assert_called_once_with(
81
- "forgot_password.html",
82
- alert_message="El enlace de cambio de contraseña ha expirado. Por favor, solicita uno nuevo."
83
- )
84
- assert response.status_code == 200
85
-
86
- @patch("iatoolkit.views.change_password_view.render_template")
87
- def test_get_with_valid_token(self, mock_render_template):
88
- """Prueba GET con un token válido."""
89
- with patch("iatoolkit.views.change_password_view.URLSafeTimedSerializer") as mock_serializer_class:
90
- mock_serializer = mock_serializer_class.return_value
91
- mock_serializer.loads.return_value = "valid@email.com"
92
-
93
- mock_render_template.return_value = "<html><body><h1>Change Password</h1></body></html>"
94
- response = self.client.get("/test_company/change_password/valid_token")
95
-
96
- mock_render_template.assert_called_once_with(
97
- "change_password.html",
98
- company=self.test_company,
99
- company_short_name='test_company',
100
- token="valid_token",
101
- email="valid@email.com"
102
- )
103
- assert response.status_code == 200
104
-
105
- @patch("iatoolkit.views.change_password_view.render_template")
106
- @patch("iatoolkit.views.change_password_view.URLSafeTimedSerializer")
107
- def test_post_with_expired_token(self, mock_serializer, mock_render_template):
108
- # Configura el serializer para que lance una excepción SignatureExpired
109
- mock_serializer.return_value.loads.side_effect = SignatureExpired('error')
110
-
111
- mock_render_template.return_value = "<html><body><h1>Forgot Password</h1></body></html>"
112
- response = self.client.post("/test_company/change_password/valid_token",
113
- data={
114
- "temp_code": "123456",
115
- "new_password": "password123",
116
- "confirm_password": "password456"
117
- },
118
- content_type="application/x-www-form-urlencoded")
119
-
120
- mock_render_template.assert_called_once_with(
121
- "forgot_password.html",
122
- company=self.test_company,
123
- company_short_name='test_company',
124
- alert_message="El enlace de cambio de contraseña ha expirado. Por favor, solicita uno nuevo."
125
- )
126
- assert response.status_code == 200
127
-
128
- @patch("iatoolkit.views.change_password_view.render_template")
129
- @patch("iatoolkit.views.change_password_view.URLSafeTimedSerializer")
130
- def test_post_with_error(self, mock_serializer, mock_render_template):
131
- mock_serializer.return_value.return_value = "valid@email.com"
132
- mock_render_template.return_value = "<html><body></body></html>"
133
- self.profile_service.change_password.return_value = \
134
- {'error': 'password missmatch'}
135
- response = self.client.post("/test_company/change_password/valid_token",
136
- data={
137
- "temp_code": "123456",
138
- "new_password": "password123",
139
- "confirm_password": "password456"
140
- },
141
- content_type="application/x-www-form-urlencoded")
142
-
143
- mock_render_template.assert_called_once_with(
144
- "change_password.html",
145
- company=self.test_company,
146
- company_short_name='test_company',
147
- form_data={"temp_code": "123456", "new_password": "password123", "confirm_password": "password456"},
148
- alert_message='password missmatch',
149
- token='valid_token'
150
- )
151
- assert response.status_code == 400
152
-
153
- @patch("iatoolkit.views.change_password_view.render_template")
154
- @patch("iatoolkit.views.change_password_view.URLSafeTimedSerializer")
155
- def test_post_ok(self, mock_serializer, mock_render_template):
156
- mock_serializer.return_value.return_value = "valid@email.com"
157
- mock_render_template.return_value = "<html><body></body></html>"
158
- self.profile_service.change_password.return_value = \
159
- {'message': 'password changed'}
160
-
161
- response = self.client.post("/test_company/change_password/valid_token",
162
- data={
163
- "temp_code": "123456",
164
- "new_password": "password123",
165
- "confirm_password": "password456"
166
- },
167
- content_type="application/x-www-form-urlencoded")
168
-
169
- mock_render_template.assert_called_once_with(
170
- "login.html",
171
- company=self.test_company,
172
- company_short_name='test_company',
173
- alert_icon='success',
174
- alert_message="Tu contraseña ha sido restablecida exitosamente. Ahora puedes iniciar sesión."
175
- )
176
- assert response.status_code == 200
177
-
178
- @patch("iatoolkit.views.change_password_view.render_template")
179
- @patch("iatoolkit.views.change_password_view.URLSafeTimedSerializer")
180
- def test_post_unexpected_error(self, mock_serializer, mock_render_template):
181
- mock_serializer.return_value.loads.return_value ='123'
182
- self.profile_service.change_password.side_effect = Exception('an error')
183
- response = self.client.post("/test_company/change_password/valid_token",
184
- data={
185
- "temp_code": "123456",
186
- "new_password": "password123",
187
- "confirm_password": "password456"
188
- },
189
- content_type="application/x-www-form-urlencoded")
190
-
191
- assert response.status_code == 500
@@ -1,188 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- import pytest
7
- from flask import Flask
8
- from unittest.mock import MagicMock
9
- from iatoolkit.views.chat_token_request_view import ChatTokenRequestView
10
- from iatoolkit.services.jwt_service import JWTService
11
- from iatoolkit.repositories.profile_repo import ProfileRepo
12
-
13
- # --- Constantes de Prueba ---
14
- VALID_API_KEY_VALUE = "test-api-key-123"
15
- MOCK_AUTH_COMPANY_ID = 100
16
- MOCK_AUTH_COMPANY_SHORT_NAME = "authcomp"
17
- MOCK_EXTERNAL_USER_ID = "ext-user-456"
18
- GENERATED_TEST_JWT = "test.jwt.token.string"
19
- JWT_EXPIRATION_CONFIG_VALUE = 1800 # 30 minutos para test
20
-
21
-
22
- class TestChatTokenRequestView:
23
- @staticmethod
24
- def create_app():
25
- """Configura la aplicación Flask para pruebas."""
26
- app = Flask(__name__)
27
- app.testing = True
28
- app.config['JWT_EXPIRATION_SECONDS_CHAT'] = JWT_EXPIRATION_CONFIG_VALUE
29
- # Aseguramos que current_app.config esté disponible en el init de la vista
30
- # app.app_context().push() # No es necesario aquí si la vista se instancia dentro del contexto en setup
31
- return app
32
-
33
- @pytest.fixture(autouse=True)
34
- def setup(self):
35
- self.app = self.create_app()
36
- # Es importante que la vista se registre y se cree el cliente de prueba
37
- # dentro de un contexto de aplicación para que current_app esté disponible.
38
- with self.app.app_context():
39
- self.profile_repo = MagicMock(spec=ProfileRepo)
40
- self.jwt_service = MagicMock(spec=JWTService)
41
-
42
- # register the view
43
- view_func = ChatTokenRequestView.as_view(
44
- 'chat-token', # Nombre del endpoint para el test
45
- profile_repo=self.profile_repo,
46
- jwt_service=self.jwt_service
47
- )
48
- self.app.add_url_rule('/auth/chat_token', view_func=view_func, methods=['POST'])
49
-
50
- self.client = self.app.test_client()
51
-
52
- def test_post_no_api_key_header(self):
53
- response = self.client.post('/auth/chat_token', json={
54
- "company_short_name": MOCK_AUTH_COMPANY_SHORT_NAME,
55
- "external_user_id": MOCK_EXTERNAL_USER_ID
56
- })
57
- assert response.status_code == 401
58
- assert response.json == {"error": "API Key faltante o mal formada en el header Authorization"}
59
-
60
- def test_post_malformed_api_key_header(self):
61
- """Prueba POST con header de Authorization mal formado."""
62
- response = self.client.post('/auth/chat_token',
63
- headers={"Authorization": "NotBearerKey"},
64
- json={
65
- "company_short_name": MOCK_AUTH_COMPANY_SHORT_NAME,
66
- "external_user_id": MOCK_EXTERNAL_USER_ID
67
- })
68
- assert response.status_code == 401
69
- assert response.json == {"error": "API Key faltante o mal formada en el header Authorization"}
70
-
71
- def test_post_invalid_or_inactive_api_key(self):
72
- """Prueba POST con una API Key inválida o inactiva."""
73
- self.profile_repo.get_active_api_key_entry.return_value = None
74
- response = self.client.post('/auth/chat_token', headers={"Authorization": f"Bearer {VALID_API_KEY_VALUE}"},
75
- json={
76
- "company_short_name": MOCK_AUTH_COMPANY_SHORT_NAME,
77
- "external_user_id": MOCK_EXTERNAL_USER_ID
78
- })
79
- assert response.status_code == 401
80
- assert response.json == {"error": "API Key inválida o inactiva"}
81
- self.profile_repo.get_active_api_key_entry.assert_called_once_with(VALID_API_KEY_VALUE)
82
-
83
- def test_post_api_key_without_company_association(self):
84
- """Prueba POST con API Key válida pero sin compañía asociada (error de datos)."""
85
- mock_api_key_entry = MagicMock()
86
- mock_api_key_entry.company = None
87
- mock_api_key_entry.company_id = None
88
- self.profile_repo.get_active_api_key_entry.return_value = mock_api_key_entry
89
-
90
- response = self.client.post('/auth/chat_token', headers={"Authorization": f"Bearer {VALID_API_KEY_VALUE}"},
91
- json={
92
- "company_short_name": MOCK_AUTH_COMPANY_SHORT_NAME,
93
- "external_user_id": MOCK_EXTERNAL_USER_ID
94
- })
95
- assert response.status_code == 500
96
- assert response.json == {"error": "Error interno del servidor al verificar API Key"}
97
-
98
- def test_post_api_key_validation_internal_exception(self):
99
- """Prueba POST donde la validación de API Key lanza una excepción."""
100
- self.profile_repo.get_active_api_key_entry.side_effect = Exception("Database connection error")
101
- response = self.client.post('/auth/chat_token', headers={"Authorization": f"Bearer {VALID_API_KEY_VALUE}"},
102
- json={
103
- "company_short_name": MOCK_AUTH_COMPANY_SHORT_NAME,
104
- "external_user_id": MOCK_EXTERNAL_USER_ID
105
- })
106
- assert response.status_code == 500
107
- assert response.json == {"error": "Error interno del servidor al validar API Key"}
108
-
109
- def _setup_successful_auth(self):
110
- """Helper para configurar una autenticación de API Key exitosa."""
111
- mock_company_obj = MagicMock()
112
- mock_company_obj.short_name = MOCK_AUTH_COMPANY_SHORT_NAME
113
-
114
- mock_api_key_entry_obj = MagicMock()
115
- mock_api_key_entry_obj.company_id = MOCK_AUTH_COMPANY_ID
116
- mock_api_key_entry_obj.company = mock_company_obj
117
- self.profile_repo.get_active_api_key_entry.return_value = mock_api_key_entry_obj
118
-
119
- def test_post_missing_json_body(self):
120
- """Prueba POST con header de autenticación válido pero sin cuerpo JSON."""
121
- self._setup_successful_auth()
122
- response = self.client.post('/auth/chat_token',
123
- headers={"Authorization": f"Bearer {VALID_API_KEY_VALUE}"},
124
- json={},
125
- content_type="application/json")
126
- assert response.status_code == 400
127
- assert response.json == {"error": "Cuerpo de la solicitud JSON faltante"}
128
-
129
- @pytest.mark.parametrize("payload", [
130
- {"external_user_id": MOCK_EXTERNAL_USER_ID}, # Falta company_short_name
131
- {"company_short_name": MOCK_AUTH_COMPANY_SHORT_NAME}, # Falta external_user_id
132
- ])
133
- def test_post_missing_fields_in_json_payload(self, payload):
134
- """Prueba POST con campos faltantes en el payload JSON."""
135
- self._setup_successful_auth()
136
- response = self.client.post('/auth/chat_token',
137
- headers={"Authorization": f"Bearer {VALID_API_KEY_VALUE}"},
138
- json=payload)
139
- assert response.status_code == 401
140
- assert response.json['error'] == "Faltan 'company_short_name' o 'external_user_id' en el cuerpo de la solicitud"
141
-
142
- def test_post_api_key_company_mismatch_with_payload(self):
143
- """Prueba POST donde la compañía de la API Key no coincide con la del payload."""
144
- self._setup_successful_auth() # API Key es de MOCK_AUTH_COMPANY_SHORT_NAME
145
-
146
- mismatched_company_short_name = "othercomp"
147
- response = self.client.post('/auth/chat_token',
148
- headers={"Authorization": f"Bearer {VALID_API_KEY_VALUE}"},
149
- json={
150
- "company_short_name": mismatched_company_short_name,
151
- "external_user_id": MOCK_EXTERNAL_USER_ID
152
- })
153
- assert response.status_code == 403
154
- assert response.json == {
155
- "error": f"API Key no autorizada para generar tokens para la compañía '{mismatched_company_short_name}'"}
156
-
157
- def test_post_successful_token_generation(self):
158
- """Prueba una generación de token exitosa."""
159
- self._setup_successful_auth()
160
- self.jwt_service.generate_chat_jwt.return_value = GENERATED_TEST_JWT
161
-
162
- response = self.client.post('/auth/chat_token', headers={"Authorization": f"Bearer {VALID_API_KEY_VALUE}"},
163
- json={
164
- "company_short_name": MOCK_AUTH_COMPANY_SHORT_NAME,
165
- "external_user_id": MOCK_EXTERNAL_USER_ID
166
- })
167
-
168
- assert response.status_code == 200
169
- assert response.json == {"chat_jwt": GENERATED_TEST_JWT}
170
- self.jwt_service.generate_chat_jwt.assert_called_once_with(
171
- company_id=MOCK_AUTH_COMPANY_ID,
172
- company_short_name=MOCK_AUTH_COMPANY_SHORT_NAME,
173
- external_user_id=MOCK_EXTERNAL_USER_ID,
174
- expires_delta_seconds=JWT_EXPIRATION_CONFIG_VALUE
175
- )
176
-
177
- def test_post_jwt_service_fails_to_generate_token(self):
178
- """Prueba el caso donde JWTService no puede generar un token."""
179
- self._setup_successful_auth()
180
- self.jwt_service.generate_chat_jwt.return_value = None # Simula fallo en la generación
181
-
182
- response = self.client.post('/auth/chat_token', headers={"Authorization": f"Bearer {VALID_API_KEY_VALUE}"},
183
- json={
184
- "company_short_name": MOCK_AUTH_COMPANY_SHORT_NAME,
185
- "external_user_id": MOCK_EXTERNAL_USER_ID
186
- })
187
- assert response.status_code == 500
188
- assert response.json == {"error": "No se pudo generar el token de chat"}
@@ -1,98 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- import pytest
7
- from flask import Flask
8
- from unittest.mock import MagicMock, patch
9
- from iatoolkit.services.profile_service import ProfileService
10
- from iatoolkit.repositories.models import Company
11
- from iatoolkit.views.chat_view import ChatView
12
- from iatoolkit.common.session_manager import SessionManager
13
- from datetime import datetime, timezone
14
- from iatoolkit.common.auth import IAuthentication
15
- from iatoolkit.services.prompt_manager_service import PromptService
16
-
17
-
18
- class TestChatView:
19
- @staticmethod
20
- def create_app():
21
- """Configura la aplicación Flask para pruebas."""
22
- app = Flask(__name__)
23
- app.testing = True
24
- return app
25
-
26
- @pytest.fixture(autouse=True)
27
- def setup(self):
28
- """Configura el cliente y los mocks antes de cada test."""
29
- self.app = self.create_app()
30
- self.client = self.app.test_client()
31
- self.profile_service = MagicMock(spec=ProfileService)
32
- self.iauthentication = MagicMock(spec=IAuthentication)
33
- self.prompt_service = MagicMock(spec=PromptService)
34
-
35
- self.iauthentication.verify.return_value = {
36
- 'success': True,
37
- 'company_id': 101,
38
- 'external_user_id': 'test_user_id'
39
- }
40
-
41
- @self.app.route('/login')
42
- def login():
43
- return "Login Page", 200
44
-
45
- self.test_company = Company(
46
- id=1,
47
- name="Empresa de Prueba",
48
- short_name="test_company",
49
- logo_file="test_logo.png"
50
- )
51
- self.profile_service.get_company_by_short_name.return_value = self.test_company
52
-
53
- # Registrar la vista
54
- view = ChatView.as_view("chat",
55
- profile_service=self.profile_service,
56
- iauthentication=self.iauthentication,
57
- prompt_service=self.prompt_service,
58
- )
59
- self.app.add_url_rule("/<company_short_name>/chat", view_func=view, methods=["GET"])
60
-
61
- # Mock values
62
- mock_values = {
63
- 'user': {'id': 1, 'username': 'test_user'},
64
- 'user_id': 1,
65
- 'company_id': 100,
66
- 'company_short_name': 'test_company',
67
- 'last_activity': datetime.now(timezone.utc).timestamp()
68
- }
69
-
70
- # Mockear SessionManager.get
71
- mock_session_manager = MagicMock(spec=SessionManager) # <- Mock de la clase
72
- mock_session_manager.get.side_effect = lambda key, default=None: mock_values.get(key, default)
73
-
74
- with patch('iatoolkit.common.auth.SessionManager', new=mock_session_manager): # <- Aplicar el mock
75
- with self.app.test_request_context(): # Necesario para Flask
76
- yield
77
-
78
- def test_get_missing_auth(self):
79
- self.iauthentication.verify.return_value = {'error_message': 'error in authentication'}
80
- response = self.client.get("/test_company/chat")
81
- assert response.status_code == 401
82
-
83
- @patch("iatoolkit.views.chat_view.render_template")
84
- def test_get_invalid_company(self, mock_render):
85
- self.profile_service.get_company_by_short_name.return_value = None
86
- response = self.client.get("/test_company/chat")
87
- assert response.status_code == 404
88
-
89
-
90
- @patch("iatoolkit.views.chat_view.render_template")
91
- def test_get_chat(self, mock_render_template):
92
- mock_render_template.return_value = "<html><body><h1>Home Page</h1></body></html>"
93
- response = self.client.get("/test_company/chat")
94
-
95
- # Asegúrate de que se llame a render_template correctamente
96
- mock_render_template.assert_called_once()
97
- assert response.status_code == 200
98
- assert b"<h1>Home Page</h1>" in response.data