iatoolkit 0.8.1__py3-none-any.whl → 0.63.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iatoolkit might be problematic. Click here for more details.
- iatoolkit/__init__.py +8 -34
- iatoolkit/base_company.py +14 -3
- iatoolkit/common/routes.py +83 -52
- iatoolkit/common/session_manager.py +0 -1
- iatoolkit/common/util.py +0 -27
- iatoolkit/iatoolkit.py +61 -46
- iatoolkit/infra/llm_client.py +7 -8
- iatoolkit/infra/openai_adapter.py +1 -1
- iatoolkit/infra/redis_session_manager.py +48 -2
- iatoolkit/repositories/database_manager.py +17 -2
- iatoolkit/repositories/models.py +31 -6
- iatoolkit/repositories/profile_repo.py +7 -2
- iatoolkit/services/auth_service.py +188 -0
- iatoolkit/services/branding_service.py +147 -0
- iatoolkit/services/dispatcher_service.py +10 -40
- iatoolkit/services/excel_service.py +15 -15
- iatoolkit/services/history_service.py +3 -12
- iatoolkit/services/jwt_service.py +15 -24
- iatoolkit/services/onboarding_service.py +43 -0
- iatoolkit/services/profile_service.py +97 -44
- iatoolkit/services/query_service.py +124 -81
- iatoolkit/services/tasks_service.py +1 -1
- iatoolkit/services/user_feedback_service.py +67 -31
- iatoolkit/services/user_session_context_service.py +112 -54
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/js/{chat_feedback.js → chat_feedback_button.js} +6 -11
- iatoolkit/static/js/chat_history_button.js +126 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +130 -220
- iatoolkit/static/js/chat_onboarding_button.js +97 -0
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +52 -0
- iatoolkit/static/styles/chat_iatoolkit.css +329 -507
- iatoolkit/static/styles/chat_modal.css +95 -56
- iatoolkit/static/styles/landing_page.css +182 -0
- iatoolkit/static/styles/onboarding.css +169 -0
- iatoolkit/system_prompts/query_main.prompt +3 -12
- iatoolkit/templates/_company_header.html +20 -0
- iatoolkit/templates/_login_widget.html +40 -0
- iatoolkit/templates/base.html +8 -3
- iatoolkit/templates/change_password.html +54 -37
- iatoolkit/templates/chat.html +149 -66
- iatoolkit/templates/chat_modals.html +47 -18
- iatoolkit/templates/error.html +41 -8
- iatoolkit/templates/forgot_password.html +37 -24
- iatoolkit/templates/index.html +140 -0
- iatoolkit/templates/login_simulation.html +34 -0
- iatoolkit/templates/onboarding_shell.html +105 -0
- iatoolkit/templates/signup.html +64 -66
- iatoolkit/views/base_login_view.py +81 -0
- iatoolkit/views/change_password_view.py +23 -12
- iatoolkit/views/external_login_view.py +61 -28
- iatoolkit/views/{file_store_view.py → file_store_api_view.py} +9 -2
- iatoolkit/views/forgot_password_view.py +23 -13
- iatoolkit/views/history_api_view.py +52 -0
- iatoolkit/views/home_view.py +58 -25
- iatoolkit/views/index_view.py +14 -0
- iatoolkit/views/init_context_api_view.py +68 -0
- iatoolkit/views/llmquery_api_view.py +45 -0
- iatoolkit/views/login_simulation_view.py +81 -0
- iatoolkit/views/login_view.py +118 -34
- iatoolkit/views/logout_api_view.py +45 -0
- iatoolkit/views/{prompt_view.py → prompt_api_view.py} +7 -7
- iatoolkit/views/signup_view.py +38 -29
- iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
- iatoolkit/views/tasks_review_api_view.py +55 -0
- iatoolkit/views/{user_feedback_view.py → user_feedback_api_view.py} +16 -31
- iatoolkit/views/verify_user_view.py +13 -8
- {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/METADATA +2 -2
- iatoolkit-0.63.4.dist-info/RECORD +113 -0
- {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/top_level.txt +0 -1
- iatoolkit/common/auth.py +0 -200
- iatoolkit/static/images/arrow_up.png +0 -0
- iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
- iatoolkit/static/images/logo_clinica.png +0 -0
- iatoolkit/static/images/logo_iatoolkit.png +0 -0
- iatoolkit/static/images/logo_maxxa.png +0 -0
- iatoolkit/static/images/logo_notaria.png +0 -0
- iatoolkit/static/images/logo_tarjeta.png +0 -0
- iatoolkit/static/images/logo_umayor.png +0 -0
- iatoolkit/static/images/upload.png +0 -0
- iatoolkit/static/js/chat_history.js +0 -117
- iatoolkit/templates/home.html +0 -201
- iatoolkit/templates/login.html +0 -43
- iatoolkit/views/chat_token_request_view.py +0 -98
- iatoolkit/views/chat_view.py +0 -51
- iatoolkit/views/download_file_view.py +0 -58
- iatoolkit/views/external_chat_login_view.py +0 -88
- iatoolkit/views/history_view.py +0 -57
- iatoolkit/views/llmquery_view.py +0 -65
- iatoolkit/views/tasks_review_view.py +0 -83
- iatoolkit-0.8.1.dist-info/RECORD +0 -175
- tests/__init__.py +0 -5
- tests/common/__init__.py +0 -0
- tests/common/test_auth.py +0 -279
- tests/common/test_routes.py +0 -42
- tests/common/test_session_manager.py +0 -59
- tests/common/test_util.py +0 -444
- tests/companies/__init__.py +0 -5
- tests/conftest.py +0 -36
- tests/infra/__init__.py +0 -5
- tests/infra/connectors/__init__.py +0 -5
- tests/infra/connectors/test_google_drive_connector.py +0 -107
- tests/infra/connectors/test_local_file_connector.py +0 -85
- tests/infra/connectors/test_s3_connector.py +0 -95
- tests/infra/test_call_service.py +0 -92
- tests/infra/test_database_manager.py +0 -59
- tests/infra/test_gemini_adapter.py +0 -137
- tests/infra/test_google_chat_app.py +0 -68
- tests/infra/test_llm_client.py +0 -165
- tests/infra/test_llm_proxy.py +0 -122
- tests/infra/test_mail_app.py +0 -94
- tests/infra/test_openai_adapter.py +0 -105
- tests/infra/test_redis_session_manager_service.py +0 -117
- tests/repositories/__init__.py +0 -5
- tests/repositories/test_database_manager.py +0 -87
- tests/repositories/test_document_repo.py +0 -76
- tests/repositories/test_llm_query_repo.py +0 -340
- tests/repositories/test_models.py +0 -38
- tests/repositories/test_profile_repo.py +0 -142
- tests/repositories/test_tasks_repo.py +0 -76
- tests/repositories/test_vs_repo.py +0 -107
- tests/services/__init__.py +0 -5
- tests/services/test_dispatcher_service.py +0 -274
- tests/services/test_document_service.py +0 -181
- tests/services/test_excel_service.py +0 -208
- tests/services/test_file_processor_service.py +0 -121
- tests/services/test_history_service.py +0 -164
- tests/services/test_jwt_service.py +0 -255
- tests/services/test_load_documents_service.py +0 -112
- tests/services/test_mail_service.py +0 -70
- tests/services/test_profile_service.py +0 -379
- tests/services/test_prompt_manager_service.py +0 -190
- tests/services/test_query_service.py +0 -243
- tests/services/test_search_service.py +0 -39
- tests/services/test_sql_service.py +0 -160
- tests/services/test_tasks_service.py +0 -252
- tests/services/test_user_feedback_service.py +0 -389
- tests/services/test_user_session_context_service.py +0 -132
- tests/views/__init__.py +0 -5
- tests/views/test_change_password_view.py +0 -191
- tests/views/test_chat_token_request_view.py +0 -188
- tests/views/test_chat_view.py +0 -98
- tests/views/test_download_file_view.py +0 -149
- tests/views/test_external_chat_login_view.py +0 -120
- tests/views/test_external_login_view.py +0 -102
- tests/views/test_file_store_view.py +0 -128
- tests/views/test_forgot_password_view.py +0 -142
- tests/views/test_history_view.py +0 -336
- tests/views/test_home_view.py +0 -61
- tests/views/test_llm_query_view.py +0 -154
- tests/views/test_login_view.py +0 -114
- tests/views/test_prompt_view.py +0 -111
- tests/views/test_signup_view.py +0 -140
- tests/views/test_tasks_review_view.py +0 -104
- tests/views/test_tasks_view.py +0 -130
- tests/views/test_user_feedback_view.py +0 -214
- tests/views/test_verify_user_view.py +0 -110
- {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/WHEEL +0 -0
|
@@ -1,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,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"}
|
tests/views/test_chat_view.py
DELETED
|
@@ -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
|