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,149 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
|
-
import shutil
|
|
8
|
-
import tempfile
|
|
9
|
-
from unittest.mock import MagicMock, patch
|
|
10
|
-
|
|
11
|
-
import pytest
|
|
12
|
-
from flask import Flask, jsonify
|
|
13
|
-
|
|
14
|
-
from iatoolkit.common.auth import IAuthentication
|
|
15
|
-
from iatoolkit.repositories.models import Company
|
|
16
|
-
from iatoolkit.services.excel_service import ExcelService
|
|
17
|
-
from iatoolkit.services.profile_service import ProfileService
|
|
18
|
-
from iatoolkit.views.download_file_view import DownloadFileView
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class TestDownloadFileView:
|
|
22
|
-
@pytest.fixture(autouse=True)
|
|
23
|
-
def setup(self):
|
|
24
|
-
self.app = Flask(__name__)
|
|
25
|
-
self.app.testing = True
|
|
26
|
-
|
|
27
|
-
# Create a real temporary directory for static/temp
|
|
28
|
-
self.temp_dir_base = tempfile.mkdtemp()
|
|
29
|
-
self.app.root_path = self.temp_dir_base
|
|
30
|
-
temp_dir_path = os.path.join(self.app.root_path, 'static', 'temp')
|
|
31
|
-
os.makedirs(temp_dir_path)
|
|
32
|
-
|
|
33
|
-
# Create a real test file
|
|
34
|
-
self.test_filename = 'test_file.xlsx'
|
|
35
|
-
self.test_file_path = os.path.join(temp_dir_path, self.test_filename)
|
|
36
|
-
self.test_content = b'test file content'
|
|
37
|
-
with open(self.test_file_path, 'wb') as f:
|
|
38
|
-
f.write(self.test_content)
|
|
39
|
-
|
|
40
|
-
# Mocks of services
|
|
41
|
-
self.iauthentication = MagicMock(spec=IAuthentication)
|
|
42
|
-
self.profile_service = MagicMock(spec=ProfileService)
|
|
43
|
-
self.excel_service = MagicMock(spec=ExcelService)
|
|
44
|
-
|
|
45
|
-
# Default mock configuration for successful case
|
|
46
|
-
self.iauthentication.verify.return_value = {'success': True}
|
|
47
|
-
self.test_company = Company(id=1, name="Test Company", short_name="test_company")
|
|
48
|
-
self.profile_service.get_company_by_short_name.return_value = self.test_company
|
|
49
|
-
self.excel_service.validate_file_access.return_value = None
|
|
50
|
-
|
|
51
|
-
# Inject mocks and register the route
|
|
52
|
-
view = DownloadFileView.as_view(
|
|
53
|
-
'download_file',
|
|
54
|
-
iauthentication=self.iauthentication,
|
|
55
|
-
profile_service=self.profile_service,
|
|
56
|
-
excel_service=self.excel_service,
|
|
57
|
-
)
|
|
58
|
-
self.app.add_url_rule(
|
|
59
|
-
'/<string:company_short_name>/<string:external_user_id>/download-file/<path:filename>',
|
|
60
|
-
view_func=view
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
self.client = self.app.test_client()
|
|
64
|
-
|
|
65
|
-
yield
|
|
66
|
-
|
|
67
|
-
# Cleanup after test
|
|
68
|
-
shutil.rmtree(self.temp_dir_base)
|
|
69
|
-
|
|
70
|
-
def test_successful_download(self):
|
|
71
|
-
with self.app.app_context():
|
|
72
|
-
response = self.client.get(f'/test_company/user_123/download-file/{self.test_filename}')
|
|
73
|
-
|
|
74
|
-
assert response.status_code == 200
|
|
75
|
-
assert response.data == self.test_content
|
|
76
|
-
assert response.headers['Content-Disposition'] == f'attachment; filename={self.test_filename}'
|
|
77
|
-
|
|
78
|
-
self.iauthentication.verify.assert_called_once_with('test_company', body_external_user_id='user_123')
|
|
79
|
-
self.profile_service.get_company_by_short_name.assert_called_once_with('test_company')
|
|
80
|
-
self.excel_service.validate_file_access.assert_called_once_with(self.test_filename)
|
|
81
|
-
|
|
82
|
-
def test_missing_external_user_id(self):
|
|
83
|
-
# Test with empty external_user_id
|
|
84
|
-
with self.app.app_context():
|
|
85
|
-
response = self.client.get('/test_company//download-file/file.xlsx')
|
|
86
|
-
|
|
87
|
-
view = DownloadFileView(
|
|
88
|
-
iauthentication=self.iauthentication,
|
|
89
|
-
profile_service=self.profile_service,
|
|
90
|
-
excel_service=self.excel_service
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
with self.app.test_request_context():
|
|
94
|
-
response = view.get('test_company', '', 'file.xlsx')
|
|
95
|
-
|
|
96
|
-
assert response[1] == 400
|
|
97
|
-
data = response[0].get_json()
|
|
98
|
-
assert data['error'] == 'Falta external_user_id'
|
|
99
|
-
|
|
100
|
-
def test_authentication_failure(self):
|
|
101
|
-
self.iauthentication.verify.return_value = {'success': False, 'error': 'Token inválido'}
|
|
102
|
-
|
|
103
|
-
with self.app.app_context():
|
|
104
|
-
response = self.client.get(f'/test_company/user_123/download-file/{self.test_filename}')
|
|
105
|
-
|
|
106
|
-
assert response.status_code == 401
|
|
107
|
-
data = response.get_json()
|
|
108
|
-
assert data['success'] is False
|
|
109
|
-
assert data['error'] == 'Token inválido'
|
|
110
|
-
|
|
111
|
-
def test_company_not_found(self):
|
|
112
|
-
self.profile_service.get_company_by_short_name.return_value = None
|
|
113
|
-
|
|
114
|
-
with self.app.app_context():
|
|
115
|
-
response = self.client.get(f'/test_company/user_123/download-file/{self.test_filename}')
|
|
116
|
-
|
|
117
|
-
assert response.status_code == 404
|
|
118
|
-
data = response.get_json()
|
|
119
|
-
assert data['error'] == 'Empresa no encontrada'
|
|
120
|
-
|
|
121
|
-
def test_file_validation_error(self):
|
|
122
|
-
with self.app.app_context():
|
|
123
|
-
self.excel_service.validate_file_access.return_value = (jsonify({"error": "Acceso denegado"}), 403)
|
|
124
|
-
response = self.client.get('/test_company/user_123/download-file/unauthorized_file.xlsx')
|
|
125
|
-
|
|
126
|
-
assert response.status_code == 403
|
|
127
|
-
data = response.get_json()
|
|
128
|
-
assert data['error'] == 'Acceso denegado'
|
|
129
|
-
|
|
130
|
-
def test_file_not_found_on_disk(self):
|
|
131
|
-
with self.app.app_context():
|
|
132
|
-
response = self.client.get('/test_company/user_123/download-file/non_existent_file.xlsx')
|
|
133
|
-
|
|
134
|
-
assert response.status_code == 500
|
|
135
|
-
data = response.get_json()
|
|
136
|
-
assert data['error'] == 'Error descargando archivo'
|
|
137
|
-
|
|
138
|
-
@patch('iatoolkit.views.download_file_view.logging')
|
|
139
|
-
def test_logging_on_success_and_error(self, mock_logging):
|
|
140
|
-
with self.app.app_context():
|
|
141
|
-
# Success case
|
|
142
|
-
self.client.get(f'/test_company/user_123/download-file/{self.test_filename}')
|
|
143
|
-
mock_logging.info.assert_called_with(f'Archivo descargado via API: {self.test_filename}')
|
|
144
|
-
|
|
145
|
-
# Error case
|
|
146
|
-
self.client.get('/test_company/user_123/download-file/non_existent_file.xlsx')
|
|
147
|
-
assert mock_logging.error.call_count == 1
|
|
148
|
-
args, _ = mock_logging.error.call_args
|
|
149
|
-
assert 'Error descargando archivo non_existent_file.xlsx' in args[0]
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from flask import Flask
|
|
3
|
-
from unittest.mock import MagicMock, patch
|
|
4
|
-
from iatoolkit.views.external_chat_login_view import ExternalChatLoginView
|
|
5
|
-
from iatoolkit.services.profile_service import ProfileService
|
|
6
|
-
from iatoolkit.services.query_service import QueryService
|
|
7
|
-
from iatoolkit.services.prompt_manager_service import PromptService
|
|
8
|
-
from iatoolkit.services.jwt_service import JWTService # <-- Importar JWTService
|
|
9
|
-
from iatoolkit.common.auth import IAuthentication
|
|
10
|
-
from iatoolkit.repositories.models import Company
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# --- Constantes para los Tests ---
|
|
14
|
-
MOCK_COMPANY_SHORT_NAME = "test-comp"
|
|
15
|
-
MOCK_EXTERNAL_USER_ID = "ext-user-123"
|
|
16
|
-
MOCK_API_KEY = "super-secret-api-key"
|
|
17
|
-
MOCK_JWT_TOKEN = "a-fake-but-valid-jwt-token"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class TestExternalChatLoginView:
|
|
21
|
-
|
|
22
|
-
@pytest.fixture(autouse=True)
|
|
23
|
-
def setup_method(self):
|
|
24
|
-
"""Configura la aplicación Flask y los mocks para cada test."""
|
|
25
|
-
self.app = Flask(__name__)
|
|
26
|
-
self.app.testing = True
|
|
27
|
-
|
|
28
|
-
# Mocks de todos los servicios inyectados
|
|
29
|
-
self.mock_profile_service = MagicMock(spec=ProfileService)
|
|
30
|
-
self.mock_query_service = MagicMock(spec=QueryService)
|
|
31
|
-
self.mock_prompt_service = MagicMock(spec=PromptService)
|
|
32
|
-
self.mock_iauthentication = MagicMock(spec=IAuthentication)
|
|
33
|
-
self.mock_jwt_service = MagicMock(spec=JWTService)
|
|
34
|
-
|
|
35
|
-
# Configurar el mock de la compañía que se devolverá
|
|
36
|
-
self.mock_company = Company(id=1, name="Test Company", short_name=MOCK_COMPANY_SHORT_NAME)
|
|
37
|
-
self.mock_profile_service.get_company_by_short_name.return_value = self.mock_company
|
|
38
|
-
|
|
39
|
-
# Registrar la vista en la app de Flask con TODAS las dependencias
|
|
40
|
-
view_func = ExternalChatLoginView.as_view(
|
|
41
|
-
'external_chat_login',
|
|
42
|
-
profile_service=self.mock_profile_service,
|
|
43
|
-
query_service=self.mock_query_service,
|
|
44
|
-
prompt_service=self.mock_prompt_service,
|
|
45
|
-
iauthentication=self.mock_iauthentication,
|
|
46
|
-
jwt_service=self.mock_jwt_service # <-- 2. Inyectar el mock de JWTService
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
self.app.add_url_rule('/<company_short_name>/chat_login', view_func=view_func, methods=['POST'])
|
|
50
|
-
self.client = self.app.test_client()
|
|
51
|
-
|
|
52
|
-
def test_login_success(self):
|
|
53
|
-
"""
|
|
54
|
-
Prueba el flujo exitoso, incluyendo la generación del JWT.
|
|
55
|
-
"""
|
|
56
|
-
# Configurar mocks para el caso de éxito
|
|
57
|
-
self.mock_iauthentication.verify.return_value = {"success": True}
|
|
58
|
-
self.mock_prompt_service.get_user_prompts.return_value = []
|
|
59
|
-
# 3. Configurar el mock para que devuelva un token
|
|
60
|
-
self.mock_jwt_service.generate_chat_jwt.return_value = MOCK_JWT_TOKEN
|
|
61
|
-
|
|
62
|
-
with patch('iatoolkit.views.external_chat_login_view.render_template') as mock_render:
|
|
63
|
-
mock_render.return_value = "<html>Chat Page</html>"
|
|
64
|
-
|
|
65
|
-
response = self.client.post(
|
|
66
|
-
f'/{MOCK_COMPANY_SHORT_NAME}/chat_login',
|
|
67
|
-
headers={'Authorization': f'Bearer {MOCK_API_KEY}'},
|
|
68
|
-
json={'external_user_id': MOCK_EXTERNAL_USER_ID}
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
# Verificar que la respuesta es exitosa
|
|
72
|
-
assert response.status_code == 200
|
|
73
|
-
assert response.data == b"<html>Chat Page</html>"
|
|
74
|
-
|
|
75
|
-
# Verificar que se llamó a la autenticación
|
|
76
|
-
self.mock_iauthentication.verify.assert_called_once_with(
|
|
77
|
-
MOCK_COMPANY_SHORT_NAME,
|
|
78
|
-
body_external_user_id=MOCK_EXTERNAL_USER_ID
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
# 4. Verificar que se intentó generar el JWT con los datos correctos
|
|
82
|
-
self.mock_jwt_service.generate_chat_jwt.assert_called_once_with(
|
|
83
|
-
company_id=self.mock_company.id,
|
|
84
|
-
company_short_name=self.mock_company.short_name,
|
|
85
|
-
external_user_id=MOCK_EXTERNAL_USER_ID,
|
|
86
|
-
expires_delta_seconds=3600 * 8
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
# Verificar que se inicializó el contexto y se obtuvieron los prompts
|
|
90
|
-
self.mock_query_service.llm_init_context.assert_called_once()
|
|
91
|
-
self.mock_prompt_service.get_user_prompts.assert_called_once()
|
|
92
|
-
|
|
93
|
-
# 5. Verificar que se renderizó la plantilla con TODOS los datos correctos
|
|
94
|
-
mock_render.assert_called_once()
|
|
95
|
-
call_kwargs = mock_render.call_args[1]
|
|
96
|
-
assert call_kwargs['company_short_name'] == MOCK_COMPANY_SHORT_NAME
|
|
97
|
-
assert call_kwargs['external_user_id'] == MOCK_EXTERNAL_USER_ID
|
|
98
|
-
assert call_kwargs['auth_method'] == 'jwt'
|
|
99
|
-
assert call_kwargs['session_jwt'] == MOCK_JWT_TOKEN # <-- Verificar que el token se pasa
|
|
100
|
-
|
|
101
|
-
def test_login_fails_if_jwt_generation_fails(self):
|
|
102
|
-
"""
|
|
103
|
-
Prueba que la vista maneja un error si el JWTService no puede generar un token.
|
|
104
|
-
"""
|
|
105
|
-
self.mock_iauthentication.verify.return_value = {"success": True}
|
|
106
|
-
# Simular fallo en la generación del token
|
|
107
|
-
self.mock_jwt_service.generate_chat_jwt.return_value = None
|
|
108
|
-
|
|
109
|
-
response = self.client.post(
|
|
110
|
-
f'/{MOCK_COMPANY_SHORT_NAME}/chat_login',
|
|
111
|
-
headers={'Authorization': f'Bearer {MOCK_API_KEY}'},
|
|
112
|
-
json={'external_user_id': MOCK_EXTERNAL_USER_ID}
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
# La vista debería devolver un error 500
|
|
116
|
-
assert response.status_code == 500
|
|
117
|
-
assert response.is_json
|
|
118
|
-
assert 'Error interno' in response.json['error']
|
|
119
|
-
|
|
120
|
-
# ... (el resto de los tests, como el de fallo de autenticación, permanecen igual y deberían seguir funcionando) ...
|
|
@@ -1,102 +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.common.auth import IAuthentication
|
|
10
|
-
from iatoolkit.views.external_login_view import ExternalLoginView
|
|
11
|
-
from iatoolkit.services.query_service import QueryService
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class TestExternalLoginView:
|
|
15
|
-
@staticmethod
|
|
16
|
-
def create_app():
|
|
17
|
-
"""Configura la aplicación Flask para pruebas."""
|
|
18
|
-
app = Flask(__name__)
|
|
19
|
-
app.testing = True
|
|
20
|
-
return app
|
|
21
|
-
|
|
22
|
-
@pytest.fixture(autouse=True)
|
|
23
|
-
def setup(self):
|
|
24
|
-
"""Configura el cliente y los mocks antes de cada test."""
|
|
25
|
-
self.app = self.create_app()
|
|
26
|
-
self.client = self.app.test_client()
|
|
27
|
-
self.iauthentication = MagicMock(spec=IAuthentication)
|
|
28
|
-
self.query_service = MagicMock(spec=QueryService)
|
|
29
|
-
|
|
30
|
-
self.iauthentication.verify.return_value = {"success": True}
|
|
31
|
-
|
|
32
|
-
# Registrar la vista
|
|
33
|
-
view = ExternalLoginView.as_view("external_login",
|
|
34
|
-
query_service=self.query_service,
|
|
35
|
-
iauthentication=self.iauthentication)
|
|
36
|
-
self.app.add_url_rule("/<company_short_name>/external_login/<external_user_id>",
|
|
37
|
-
view_func=view, methods=["GET"])
|
|
38
|
-
|
|
39
|
-
def test_get_with_valid_parameters(self):
|
|
40
|
-
"""Test que verifica que la vista funciona con parámetros válidos"""
|
|
41
|
-
response = self.client.get("/test_company/external_login/user123")
|
|
42
|
-
|
|
43
|
-
assert response.status_code == 200
|
|
44
|
-
self.iauthentication.verify.assert_called_once_with("test_company", "user123")
|
|
45
|
-
self.query_service.llm_init_context.assert_called_once_with(
|
|
46
|
-
company_short_name="test_company",
|
|
47
|
-
external_user_id="user123"
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
def test_get_with_authentication_failure(self):
|
|
51
|
-
"""Test que verifica el manejo de fallos de autenticación"""
|
|
52
|
-
self.iauthentication.verify.return_value = {"success": False, "error": "Invalid credentials"}
|
|
53
|
-
|
|
54
|
-
response = self.client.get("/test_company/external_login/user123")
|
|
55
|
-
|
|
56
|
-
assert response.status_code == 401
|
|
57
|
-
assert b"Invalid credentials" in response.data
|
|
58
|
-
|
|
59
|
-
def test_get_with_service_exception(self):
|
|
60
|
-
"""Test que verifica el manejo de excepciones del servicio"""
|
|
61
|
-
self.query_service.llm_init_context.side_effect = Exception("Service error")
|
|
62
|
-
|
|
63
|
-
response = self.client.get("/test_company/external_login/user123")
|
|
64
|
-
|
|
65
|
-
assert response.status_code == 500
|
|
66
|
-
assert b"Service error" in response.data
|
|
67
|
-
|
|
68
|
-
def test_get_with_different_company_and_user(self):
|
|
69
|
-
"""Test que verifica que funciona con diferentes combinaciones de company y user"""
|
|
70
|
-
response = self.client.get("/my_company/external_login/employee456")
|
|
71
|
-
|
|
72
|
-
assert response.status_code == 200
|
|
73
|
-
self.iauthentication.verify.assert_called_once_with("my_company", "employee456")
|
|
74
|
-
self.query_service.llm_init_context.assert_called_once_with(
|
|
75
|
-
company_short_name="my_company",
|
|
76
|
-
external_user_id="employee456"
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
def test_get_with_special_characters_in_parameters(self):
|
|
80
|
-
"""Test que verifica el manejo de caracteres especiales en los parámetros"""
|
|
81
|
-
response = self.client.get("/test-company/external_login/user-123")
|
|
82
|
-
|
|
83
|
-
assert response.status_code == 200
|
|
84
|
-
self.iauthentication.verify.assert_called_once_with("test-company", "user-123")
|
|
85
|
-
self.query_service.llm_init_context.assert_called_once_with(
|
|
86
|
-
company_short_name="test-company",
|
|
87
|
-
external_user_id="user-123"
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
@patch('iatoolkit.views.external_login_view.logging')
|
|
91
|
-
def test_get_logs_exception_when_service_fails(self, mock_logging):
|
|
92
|
-
"""Test que verifica que se registra la excepción cuando falla el servicio"""
|
|
93
|
-
self.query_service.llm_init_context.side_effect = Exception("Database connection failed")
|
|
94
|
-
|
|
95
|
-
response = self.client.get("/test_company/external_login/user123")
|
|
96
|
-
|
|
97
|
-
assert response.status_code == 500
|
|
98
|
-
mock_logging.exception.assert_called_once()
|
|
99
|
-
# Verificar que el mensaje de log contiene la información correcta
|
|
100
|
-
call_args = mock_logging.exception.call_args[0][0]
|
|
101
|
-
assert "test_company" in call_args
|
|
102
|
-
assert "Database connection failed" in call_args
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
from unittest.mock import MagicMock
|
|
8
|
-
from flask import Flask
|
|
9
|
-
from iatoolkit.views.file_store_view import FileStoreView
|
|
10
|
-
from iatoolkit.services.load_documents_service import LoadDocumentsService
|
|
11
|
-
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
12
|
-
import base64
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TestFileStoreView:
|
|
16
|
-
|
|
17
|
-
def setup_method(self):
|
|
18
|
-
self.app = Flask(__name__)
|
|
19
|
-
self.client = self.app.test_client()
|
|
20
|
-
|
|
21
|
-
# Mock the services
|
|
22
|
-
self.mock_doc_service = MagicMock(spec=LoadDocumentsService)
|
|
23
|
-
self.mock_profile_repo = MagicMock(spec=ProfileRepo)
|
|
24
|
-
|
|
25
|
-
# Instantiate the view with mocked services
|
|
26
|
-
self.file_store_view = FileStoreView.as_view("load",
|
|
27
|
-
doc_service=self.mock_doc_service,
|
|
28
|
-
profile_repo=self.mock_profile_repo)
|
|
29
|
-
self.app.add_url_rule('/load', view_func=self.file_store_view, methods=["POST"])
|
|
30
|
-
|
|
31
|
-
@pytest.mark.parametrize("missing_field", ["company", "filename", "content"])
|
|
32
|
-
def test_post_when_missing_required_fields(self, missing_field):
|
|
33
|
-
payload = {
|
|
34
|
-
"company": "test_company",
|
|
35
|
-
"filename": "test_file.txt",
|
|
36
|
-
"content": base64.b64encode(b"test content").decode('utf-8'),
|
|
37
|
-
"metadata": {"key": "value"}
|
|
38
|
-
}
|
|
39
|
-
payload.pop(missing_field)
|
|
40
|
-
|
|
41
|
-
response = self.client.post('/load', json=payload)
|
|
42
|
-
|
|
43
|
-
assert response.status_code == 400
|
|
44
|
-
assert response.get_json() == {
|
|
45
|
-
"error": f"El campo {missing_field} es requerido"
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
self.mock_doc_service.load_file_callback.assert_not_called()
|
|
49
|
-
|
|
50
|
-
def test_post_when_company_not_found(self):
|
|
51
|
-
# Mock the profile repo to return None for the company
|
|
52
|
-
self.mock_profile_repo.get_company_by_short_name.return_value = None
|
|
53
|
-
|
|
54
|
-
payload = {
|
|
55
|
-
"company": "nonexistent_company",
|
|
56
|
-
"filename": "test_file.txt",
|
|
57
|
-
"content": base64.b64encode(b"test content").decode('utf-8'),
|
|
58
|
-
"metadata": {"key": "value"}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
response = self.client.post('/load', json=payload)
|
|
62
|
-
|
|
63
|
-
assert response.status_code == 400
|
|
64
|
-
assert response.get_json() == {
|
|
65
|
-
"error": "La empresa nonexistent_company no existe"
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
self.mock_profile_repo.get_company_by_short_name.assert_called_once_with("nonexistent_company")
|
|
69
|
-
self.mock_doc_service.load_file_callback.assert_not_called()
|
|
70
|
-
|
|
71
|
-
def test_post_when_internal_exception_error(self):
|
|
72
|
-
# Mock the profile repo to return a company
|
|
73
|
-
mock_company = MagicMock()
|
|
74
|
-
self.mock_profile_repo.get_company_by_short_name.return_value = mock_company
|
|
75
|
-
|
|
76
|
-
# Mock the doc service to raise an exception
|
|
77
|
-
self.mock_doc_service.load_file_callback.side_effect = Exception("Internal Error")
|
|
78
|
-
|
|
79
|
-
payload = {
|
|
80
|
-
"company": "test_company",
|
|
81
|
-
"filename": "test_file.txt",
|
|
82
|
-
"content": base64.b64encode(b"test content").decode('utf-8'),
|
|
83
|
-
"metadata": {"key": "value"}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
response = self.client.post('/load', json=payload)
|
|
87
|
-
|
|
88
|
-
assert response.status_code == 500
|
|
89
|
-
response_json = response.get_json()
|
|
90
|
-
assert response_json is not None, "Expected JSON response, got None"
|
|
91
|
-
|
|
92
|
-
assert "error" in response_json
|
|
93
|
-
assert response_json["error"] == "Internal Error"
|
|
94
|
-
|
|
95
|
-
self.mock_profile_repo.get_company_by_short_name.assert_called_once_with("test_company")
|
|
96
|
-
self.mock_doc_service.load_file_callback.assert_called_once()
|
|
97
|
-
|
|
98
|
-
def test_post_when_successful_file_storage(self):
|
|
99
|
-
# Mock the profile repo to return a company
|
|
100
|
-
mock_company = MagicMock()
|
|
101
|
-
self.mock_profile_repo.get_company_by_short_name.return_value = mock_company
|
|
102
|
-
|
|
103
|
-
# Mock the document returned by the service
|
|
104
|
-
mock_document = MagicMock()
|
|
105
|
-
mock_document.id = 123
|
|
106
|
-
self.mock_doc_service.load_file_callback.return_value = mock_document
|
|
107
|
-
|
|
108
|
-
payload = {
|
|
109
|
-
"company": "test_company",
|
|
110
|
-
"filename": "test_file.txt",
|
|
111
|
-
"content": base64.b64encode(b"test content").decode('utf-8'),
|
|
112
|
-
"metadata": {"key": "value"}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
response = self.client.post('/load', json=payload)
|
|
116
|
-
|
|
117
|
-
assert response.status_code == 200
|
|
118
|
-
assert response.get_json() == {
|
|
119
|
-
"document_id": 123
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
self.mock_profile_repo.get_company_by_short_name.assert_called_once_with("test_company")
|
|
123
|
-
self.mock_doc_service.load_file_callback.assert_called_once_with(
|
|
124
|
-
filename="test_file.txt",
|
|
125
|
-
content=b"test content",
|
|
126
|
-
company=mock_company,
|
|
127
|
-
context={'metadata':{"key": "value"}}
|
|
128
|
-
)
|
|
@@ -1,142 +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.forgot_password_view import ForgotPasswordView
|
|
12
|
-
import os
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TestForgotPasswordView:
|
|
16
|
-
@classmethod
|
|
17
|
-
def setup_class(cls):
|
|
18
|
-
cls.patcher = patch.dict(os.environ, {"PASS_RESET_KEY": "mocked_reset_key"})
|
|
19
|
-
cls.patcher.start()
|
|
20
|
-
|
|
21
|
-
@classmethod
|
|
22
|
-
def teardown_class(cls):
|
|
23
|
-
cls.patcher.stop()
|
|
24
|
-
|
|
25
|
-
@staticmethod
|
|
26
|
-
def create_app():
|
|
27
|
-
"""Configura la aplicación Flask para pruebas."""
|
|
28
|
-
app = Flask(__name__)
|
|
29
|
-
app.testing = True
|
|
30
|
-
return app
|
|
31
|
-
|
|
32
|
-
@pytest.fixture(autouse=True)
|
|
33
|
-
def setup(self):
|
|
34
|
-
"""Configura el cliente y los mocks antes de cada test."""
|
|
35
|
-
self.app = self.create_app()
|
|
36
|
-
self.client = self.app.test_client()
|
|
37
|
-
self.profile_service = MagicMock(spec=ProfileService)
|
|
38
|
-
self.test_company = Company(
|
|
39
|
-
id=1,
|
|
40
|
-
name="Empresa de Prueba",
|
|
41
|
-
short_name="test_company",
|
|
42
|
-
logo_file="test_logo.png"
|
|
43
|
-
)
|
|
44
|
-
self.profile_service.get_company_by_short_name.return_value = self.test_company
|
|
45
|
-
|
|
46
|
-
# Registrar la vista
|
|
47
|
-
view = ForgotPasswordView.as_view("forgot_password", profile_service=self.profile_service)
|
|
48
|
-
self.app.add_url_rule("/<company_short_name>/forgot_password", view_func=view, methods=["GET", "POST"])
|
|
49
|
-
|
|
50
|
-
@patch("iatoolkit.views.forgot_password_view.render_template")
|
|
51
|
-
def test_get_when_invalid_company(self, mock_render):
|
|
52
|
-
self.profile_service.get_company_by_short_name.return_value = None
|
|
53
|
-
response = self.client.get("/test_company/forgot_password")
|
|
54
|
-
assert response.status_code == 404
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@patch("iatoolkit.views.forgot_password_view.render_template")
|
|
58
|
-
def test_post_when_invalid_company(self, mock_render):
|
|
59
|
-
self.profile_service.get_company_by_short_name.return_value = None
|
|
60
|
-
response = self.client.post("/test_company/forgot_password",
|
|
61
|
-
data={"email": "nonexistent@email.com"},
|
|
62
|
-
content_type="application/x-www-form-urlencoded")
|
|
63
|
-
|
|
64
|
-
assert response.status_code == 404
|
|
65
|
-
|
|
66
|
-
@patch("iatoolkit.views.forgot_password_view.render_template")
|
|
67
|
-
def test_get_forgot_password_page(self, mock_render_template):
|
|
68
|
-
mock_render_template.return_value = "<html><body><h1>Forgot Password</h1></body></html>"
|
|
69
|
-
response = self.client.get("/test_company/forgot_password")
|
|
70
|
-
|
|
71
|
-
mock_render_template.assert_called_once_with(
|
|
72
|
-
"forgot_password.html",
|
|
73
|
-
company=self.test_company,
|
|
74
|
-
company_short_name='test_company',
|
|
75
|
-
)
|
|
76
|
-
assert response.status_code == 200
|
|
77
|
-
|
|
78
|
-
@patch("iatoolkit.views.forgot_password_view.url_for")
|
|
79
|
-
@patch("iatoolkit.views.forgot_password_view.render_template")
|
|
80
|
-
@patch("iatoolkit.views.forgot_password_view.URLSafeTimedSerializer")
|
|
81
|
-
def test_post_with_error(self,
|
|
82
|
-
mock_serializer,
|
|
83
|
-
mock_render_template,
|
|
84
|
-
mock_url_for):
|
|
85
|
-
mock_serializer.return_value.loads.return_value = "nonexistent@email.com"
|
|
86
|
-
mock_render_template.return_value = "<html><body><h1>Signup Page</h1></body></html>"
|
|
87
|
-
mock_url_for.return_value = 'http://verification'
|
|
88
|
-
self.profile_service.forgot_password.return_value = {'error': 'invalid email'}
|
|
89
|
-
|
|
90
|
-
response = self.client.post("/test_company/forgot_password",
|
|
91
|
-
data={"email": "nonexistent@email.com"},
|
|
92
|
-
content_type="application/x-www-form-urlencoded")
|
|
93
|
-
|
|
94
|
-
assert response.status_code == 400
|
|
95
|
-
mock_render_template.assert_called_once_with(
|
|
96
|
-
"forgot_password.html",
|
|
97
|
-
company=self.test_company,
|
|
98
|
-
company_short_name='test_company',
|
|
99
|
-
form_data={"email": "nonexistent@email.com"},
|
|
100
|
-
alert_message='invalid email'
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
@patch("iatoolkit.views.forgot_password_view.url_for")
|
|
104
|
-
@patch("iatoolkit.views.forgot_password_view.render_template")
|
|
105
|
-
@patch("iatoolkit.views.forgot_password_view.URLSafeTimedSerializer")
|
|
106
|
-
def test_post_ok(self,
|
|
107
|
-
mock_serializer,
|
|
108
|
-
mock_render_template,
|
|
109
|
-
mock_url_for):
|
|
110
|
-
mock_serializer.return_value.loads.return_value = "nonexistent@email.com"
|
|
111
|
-
mock_render_template.return_value = "<html><body><h1>Signup Page</h1></body></html>"
|
|
112
|
-
mock_url_for.return_value = 'http://verification'
|
|
113
|
-
self.profile_service.forgot_password.return_value = {"message": "link sent"}
|
|
114
|
-
|
|
115
|
-
response = self.client.post("/test_company/forgot_password",
|
|
116
|
-
data={"email": "nonexistent@email.com"},
|
|
117
|
-
content_type="application/x-www-form-urlencoded")
|
|
118
|
-
|
|
119
|
-
assert response.status_code == 200
|
|
120
|
-
mock_render_template.assert_called_once_with(
|
|
121
|
-
"login.html",
|
|
122
|
-
company=self.test_company,
|
|
123
|
-
company_short_name='test_company',
|
|
124
|
-
alert_icon='success',
|
|
125
|
-
alert_message="Hemos enviado un enlace a tu correo para restablecer la contraseña."
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
@patch("iatoolkit.views.forgot_password_view.render_template")
|
|
129
|
-
@patch("iatoolkit.views.forgot_password_view.URLSafeTimedSerializer")
|
|
130
|
-
def test_post_unexpected_error(self, mock_serializer, mock_render_template):
|
|
131
|
-
mock_serializer.return_value.loads.side_effect = Exception('an error')
|
|
132
|
-
response = self.client.post("/test_company/forgot_password",
|
|
133
|
-
data={"email": "nonexistent@email.com"},
|
|
134
|
-
content_type="application/x-www-form-urlencoded")
|
|
135
|
-
|
|
136
|
-
mock_render_template.assert_called_once_with(
|
|
137
|
-
"error.html",
|
|
138
|
-
company=self.test_company,
|
|
139
|
-
company_short_name='test_company',
|
|
140
|
-
message="Ha ocurrido un error inesperado."
|
|
141
|
-
)
|
|
142
|
-
assert response.status_code == 500
|