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,208 +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
|
|
13
|
-
|
|
14
|
-
from iatoolkit.services.excel_service import ExcelService
|
|
15
|
-
from iatoolkit.common.util import Utility
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class TestExcelService:
|
|
19
|
-
@pytest.fixture(autouse=True)
|
|
20
|
-
def setup(self):
|
|
21
|
-
self.app = Flask(__name__)
|
|
22
|
-
self.app.testing = True
|
|
23
|
-
|
|
24
|
-
# Create real temporary directory structure
|
|
25
|
-
self.temp_dir_base = tempfile.mkdtemp()
|
|
26
|
-
self.app.root_path = self.temp_dir_base
|
|
27
|
-
self.temp_dir = os.path.join(self.temp_dir_base, 'static', 'temp')
|
|
28
|
-
os.makedirs(self.temp_dir, exist_ok=True)
|
|
29
|
-
|
|
30
|
-
# Mocks of services
|
|
31
|
-
self.util = MagicMock(spec=Utility)
|
|
32
|
-
self.excel_service = ExcelService(util=self.util)
|
|
33
|
-
|
|
34
|
-
yield
|
|
35
|
-
|
|
36
|
-
# Cleanup after test
|
|
37
|
-
shutil.rmtree(self.temp_dir_base)
|
|
38
|
-
|
|
39
|
-
def create_test_file(self, filename, content=b'test content'):
|
|
40
|
-
file_path = os.path.join(self.temp_dir, filename)
|
|
41
|
-
with open(file_path, 'wb') as f:
|
|
42
|
-
f.write(content)
|
|
43
|
-
return file_path
|
|
44
|
-
|
|
45
|
-
def test_validate_file_access_valid_file(self):
|
|
46
|
-
filename = 'valid_file.xlsx'
|
|
47
|
-
self.create_test_file(filename)
|
|
48
|
-
|
|
49
|
-
with self.app.app_context():
|
|
50
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
51
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
52
|
-
result = self.excel_service.validate_file_access(filename)
|
|
53
|
-
|
|
54
|
-
assert result is None
|
|
55
|
-
|
|
56
|
-
def test_validate_file_access_path_traversal_dotdot(self):
|
|
57
|
-
filename = '../../../etc/passwd'
|
|
58
|
-
|
|
59
|
-
with self.app.app_context():
|
|
60
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
61
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
62
|
-
result = self.excel_service.validate_file_access(filename)
|
|
63
|
-
|
|
64
|
-
assert result is not None
|
|
65
|
-
data = result.get_json()
|
|
66
|
-
assert data['error'] == 'Nombre de archivo inválido'
|
|
67
|
-
|
|
68
|
-
def test_validate_file_access_path_traversal_absolute_unix(self):
|
|
69
|
-
filename = '/etc/passwd'
|
|
70
|
-
|
|
71
|
-
with self.app.app_context():
|
|
72
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
73
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
74
|
-
result = self.excel_service.validate_file_access(filename)
|
|
75
|
-
|
|
76
|
-
assert result is not None
|
|
77
|
-
data = result.get_json()
|
|
78
|
-
assert data['error'] == 'Nombre de archivo inválido'
|
|
79
|
-
|
|
80
|
-
def test_validate_file_access_path_traversal_backslash(self):
|
|
81
|
-
filename = 'folder\\..\\sensitive_file.txt'
|
|
82
|
-
|
|
83
|
-
with self.app.app_context():
|
|
84
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
85
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
86
|
-
result = self.excel_service.validate_file_access(filename)
|
|
87
|
-
|
|
88
|
-
assert result is not None
|
|
89
|
-
data = result.get_json()
|
|
90
|
-
assert data['error'] == 'Nombre de archivo inválido'
|
|
91
|
-
|
|
92
|
-
def test_validate_file_access_file_not_found(self):
|
|
93
|
-
filename = 'non_existent_file.xlsx'
|
|
94
|
-
|
|
95
|
-
with self.app.app_context():
|
|
96
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
97
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
98
|
-
result = self.excel_service.validate_file_access(filename)
|
|
99
|
-
|
|
100
|
-
assert result is not None
|
|
101
|
-
data = result.get_json()
|
|
102
|
-
assert data['error'] == 'Archivo no encontrado'
|
|
103
|
-
|
|
104
|
-
def test_validate_file_access_is_directory(self):
|
|
105
|
-
dirname = 'test_directory'
|
|
106
|
-
dir_path = os.path.join(self.temp_dir, dirname)
|
|
107
|
-
os.makedirs(dir_path)
|
|
108
|
-
|
|
109
|
-
with self.app.app_context():
|
|
110
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
111
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
112
|
-
result = self.excel_service.validate_file_access(dirname)
|
|
113
|
-
|
|
114
|
-
assert result is not None
|
|
115
|
-
data = result.get_json()
|
|
116
|
-
assert data['error'] == 'La ruta no corresponde a un archivo'
|
|
117
|
-
|
|
118
|
-
def test_validate_file_access_exception_handling(self):
|
|
119
|
-
filename = 'test_file.xlsx'
|
|
120
|
-
|
|
121
|
-
with self.app.app_context():
|
|
122
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
123
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
124
|
-
with patch('iatoolkit.services.excel_service.os.path.exists', side_effect=Exception("Test exception")):
|
|
125
|
-
result = self.excel_service.validate_file_access(filename)
|
|
126
|
-
|
|
127
|
-
assert result is not None
|
|
128
|
-
data = result.get_json()
|
|
129
|
-
assert data['error'] == 'Error validando archivo'
|
|
130
|
-
|
|
131
|
-
def test_validate_file_access_logs_exception(self):
|
|
132
|
-
filename = 'test_file.xlsx'
|
|
133
|
-
|
|
134
|
-
with patch('iatoolkit.services.excel_service.logging') as mock_logging:
|
|
135
|
-
with patch('iatoolkit.services.excel_service.os.path.exists', side_effect=Exception("Test exception")):
|
|
136
|
-
with self.app.app_context():
|
|
137
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
138
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
139
|
-
self.excel_service.validate_file_access(filename)
|
|
140
|
-
|
|
141
|
-
mock_logging.error.assert_called_once()
|
|
142
|
-
error_msg = mock_logging.error.call_args[0][0]
|
|
143
|
-
assert 'Error validando acceso al archivo test_file.xlsx' in error_msg
|
|
144
|
-
assert 'Test exception' in error_msg
|
|
145
|
-
|
|
146
|
-
def test_validate_file_access_various_valid_filenames(self):
|
|
147
|
-
with self.app.app_context():
|
|
148
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
149
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
150
|
-
|
|
151
|
-
valid_filenames = [
|
|
152
|
-
'simple.xlsx',
|
|
153
|
-
'file_with_underscores.xlsx',
|
|
154
|
-
'file-with-dashes.xlsx',
|
|
155
|
-
'file with spaces.xlsx',
|
|
156
|
-
'file123.xlsx',
|
|
157
|
-
'UPPERCASE.XLSX',
|
|
158
|
-
'file.with.dots.xlsx'
|
|
159
|
-
]
|
|
160
|
-
|
|
161
|
-
for filename in valid_filenames:
|
|
162
|
-
self.create_test_file(filename)
|
|
163
|
-
result = self.excel_service.validate_file_access(filename)
|
|
164
|
-
assert result is None, f"Filename '{filename}' should be valid"
|
|
165
|
-
|
|
166
|
-
def test_validate_file_access_various_invalid_filenames(self):
|
|
167
|
-
with self.app.app_context():
|
|
168
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
169
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
170
|
-
|
|
171
|
-
invalid_filenames = [
|
|
172
|
-
'../file.xlsx',
|
|
173
|
-
'../../file.xlsx',
|
|
174
|
-
'/absolute/path/file.xlsx',
|
|
175
|
-
'folder\\file.xlsx',
|
|
176
|
-
'..\\file.xlsx',
|
|
177
|
-
'file..xlsx/../other.xlsx',
|
|
178
|
-
'/etc/passwd',
|
|
179
|
-
'C:\\Windows\\System32\\config'
|
|
180
|
-
]
|
|
181
|
-
|
|
182
|
-
for filename in invalid_filenames:
|
|
183
|
-
result = self.excel_service.validate_file_access(filename)
|
|
184
|
-
assert result is not None, f"Filename '{filename}' should be invalid"
|
|
185
|
-
data = result.get_json()
|
|
186
|
-
assert data['error'] == 'Nombre de archivo inválido'
|
|
187
|
-
|
|
188
|
-
def test_validate_file_access_empty_filename(self):
|
|
189
|
-
filename = ''
|
|
190
|
-
|
|
191
|
-
with self.app.app_context():
|
|
192
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
193
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
194
|
-
result = self.excel_service.validate_file_access(filename)
|
|
195
|
-
|
|
196
|
-
assert result is not None
|
|
197
|
-
data = result.get_json()
|
|
198
|
-
assert data['error'] == 'Nombre de archivo inválido'
|
|
199
|
-
|
|
200
|
-
def test_validate_file_access_none_filename(self):
|
|
201
|
-
with self.app.app_context():
|
|
202
|
-
with patch('iatoolkit.services.excel_service.current_app') as mock_current_app:
|
|
203
|
-
mock_current_app.root_path = self.temp_dir_base
|
|
204
|
-
result = self.excel_service.validate_file_access(None)
|
|
205
|
-
|
|
206
|
-
assert result is not None
|
|
207
|
-
data = result.get_json()
|
|
208
|
-
assert data['error'] == 'Nombre de archivo inválido'
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
from unittest.mock import MagicMock, patch
|
|
8
|
-
from iatoolkit.services.file_processor_service import FileProcessor, FileProcessorConfig
|
|
9
|
-
from iatoolkit.infra.connectors.file_connector import FileConnector
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestFileProcessor:
|
|
13
|
-
def setup_method(self):
|
|
14
|
-
# Mock del conector de archivos
|
|
15
|
-
self.mock_connector = MagicMock(spec=FileConnector)
|
|
16
|
-
|
|
17
|
-
# Acción de ejemplo para la configuración
|
|
18
|
-
self.mock_callback = MagicMock()
|
|
19
|
-
self.mock_company = MagicMock()
|
|
20
|
-
|
|
21
|
-
# Configuración del FileProcessorConfig con validaciones y mocks
|
|
22
|
-
self.mock_config = FileProcessorConfig(
|
|
23
|
-
log_file="mock_log_file.log", # Archivo de log ficticio
|
|
24
|
-
callback=self.mock_callback, # Acción mockeada
|
|
25
|
-
filters={
|
|
26
|
-
"filename_contains": "test",
|
|
27
|
-
"custom_filter": lambda x: x.endswith(".txt") # Solo procesar archivos .txt
|
|
28
|
-
},
|
|
29
|
-
echo=True,
|
|
30
|
-
continue_on_error=True,
|
|
31
|
-
context={'company': self.mock_company,}
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
# Creamos la instancia de FileProcessor con los mocks
|
|
35
|
-
self.processor = FileProcessor(
|
|
36
|
-
self.mock_connector,
|
|
37
|
-
self.mock_config
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
self.mock_connector.list_files.return_value = [
|
|
41
|
-
{'path': "/mock/directory/test_file.txt",
|
|
42
|
-
'name': 'test_file.txt'}
|
|
43
|
-
]
|
|
44
|
-
|
|
45
|
-
def test_process_files_when_exception_on_list_files(self):
|
|
46
|
-
# Generar un error intencional al abrir un archivo
|
|
47
|
-
self.mock_connector.list_files.side_effect = Exception("list file error")
|
|
48
|
-
|
|
49
|
-
status = self.processor.process_files()
|
|
50
|
-
assert status == False
|
|
51
|
-
|
|
52
|
-
def test_process_files_when_exception_on_get_content(self):
|
|
53
|
-
self.mock_connector.get_file_content.side_effect = Exception("Mocked error")
|
|
54
|
-
|
|
55
|
-
# Mock del logger para capturar errores
|
|
56
|
-
with patch.object(self.processor, "logger") as mock_logger:
|
|
57
|
-
# Ejecutar el método y permitir que continúe tras el error
|
|
58
|
-
self.processor.process_files()
|
|
59
|
-
|
|
60
|
-
# Verificar que no se ejecutó la acción debido al error
|
|
61
|
-
self.mock_callback.assert_not_called()
|
|
62
|
-
|
|
63
|
-
# Verificar que el logger capture el error
|
|
64
|
-
mock_logger.error.assert_called_once_with("Error processing /mock/directory/test_file.txt: Mocked error")
|
|
65
|
-
|
|
66
|
-
def test_process_files_filtered_out(self):
|
|
67
|
-
"""Prueba que se filtren los archivos que no coincidan con los filtros."""
|
|
68
|
-
self.mock_connector.list_files.return_value = [
|
|
69
|
-
{'path': "/mock/directory/test_file1.doc",
|
|
70
|
-
'name': 'test_file1.doc'},
|
|
71
|
-
{'path': "/mock/directory/test_file2.txt",
|
|
72
|
-
'name': 'test_file2.txt'}
|
|
73
|
-
]
|
|
74
|
-
|
|
75
|
-
self.mock_connector.get_file_content.return_value = b"content of file2"
|
|
76
|
-
|
|
77
|
-
self.processor.process_files()
|
|
78
|
-
|
|
79
|
-
# Solo debe procesarse test_file2.txt
|
|
80
|
-
self.mock_callback.assert_called_once_with(
|
|
81
|
-
company=self.mock_company,
|
|
82
|
-
filename="test_file2.txt",
|
|
83
|
-
content=b"content of file2",
|
|
84
|
-
context={'company': self.mock_company})
|
|
85
|
-
assert self.mock_callback.call_count == 1
|
|
86
|
-
assert self.mock_connector.get_file_content.call_count == 1
|
|
87
|
-
|
|
88
|
-
def test_process_files_stop_on_error(self):
|
|
89
|
-
"""Prueba que se detenga el procesamiento si `continue_on_error` es False."""
|
|
90
|
-
self.mock_config.continue_on_error = False # Configuración para detenerse en errores
|
|
91
|
-
self.mock_connector.get_file_content.side_effect = Exception("Mocked error")
|
|
92
|
-
|
|
93
|
-
# Verificar que se lanza la excepción y no continúa procesando
|
|
94
|
-
with pytest.raises(Exception, match="Mocked error"):
|
|
95
|
-
self.processor.process_files()
|
|
96
|
-
|
|
97
|
-
# No se debe haber realizado ninguna acción
|
|
98
|
-
self.mock_callback.assert_not_called()
|
|
99
|
-
|
|
100
|
-
def test_process_files_success(self):
|
|
101
|
-
self.mock_connector.list_files.return_value = [
|
|
102
|
-
{'path': "/mock/directory/test_file1.txt",
|
|
103
|
-
'name': 'test_file1.txt'},
|
|
104
|
-
{'path': "/mock/directory/test_file2.txt",
|
|
105
|
-
'name': 'test_file2.txt'}
|
|
106
|
-
]
|
|
107
|
-
self.mock_connector.get_file_content.side_effect = [
|
|
108
|
-
b"content of file1",
|
|
109
|
-
b"content of file2",
|
|
110
|
-
]
|
|
111
|
-
|
|
112
|
-
self.processor.process_files()
|
|
113
|
-
|
|
114
|
-
# Verificar que se llamaron las acciones con los archivos correctos
|
|
115
|
-
assert self.mock_callback.call_count == 2 # Solo archivos filtrados
|
|
116
|
-
|
|
117
|
-
# Verificar numero de archivos procesados
|
|
118
|
-
assert self.processor.processed_files == 2
|
|
119
|
-
|
|
120
|
-
# Verificar las llamadas al logger (si es necesario)
|
|
121
|
-
self.mock_connector.list_files.assert_called_once()
|
|
@@ -1,164 +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 iatoolkit.services.history_service import HistoryService
|
|
9
|
-
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
10
|
-
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
11
|
-
from iatoolkit.repositories.models import LLMQuery, Company
|
|
12
|
-
from iatoolkit.common.util import Utility
|
|
13
|
-
from iatoolkit.common.session_manager import SessionManager
|
|
14
|
-
from unittest.mock import patch
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class TestHistoryService:
|
|
18
|
-
@pytest.fixture(autouse=True)
|
|
19
|
-
def setup(self):
|
|
20
|
-
self.llm_query_repo = MagicMock(spec=LLMQueryRepo)
|
|
21
|
-
self.profile_repo = MagicMock(spec=ProfileRepo)
|
|
22
|
-
# Usamos la clase Utility real para asegurar que la lógica de resolución de ID es correcta
|
|
23
|
-
self.util = Utility()
|
|
24
|
-
self.history_service = HistoryService(
|
|
25
|
-
llm_query_repo=self.llm_query_repo,
|
|
26
|
-
profile_repo=self.profile_repo,
|
|
27
|
-
util=self.util
|
|
28
|
-
)
|
|
29
|
-
# Mock común para la compañía
|
|
30
|
-
self.mock_company = MagicMock(spec=Company)
|
|
31
|
-
self.mock_company.id = 1
|
|
32
|
-
self.mock_company.name = 'Test Company'
|
|
33
|
-
self.profile_repo.get_company_by_short_name.return_value = self.mock_company
|
|
34
|
-
|
|
35
|
-
def test_get_history_company_not_found(self):
|
|
36
|
-
"""Prueba que el servicio devuelve un error si la empresa no se encuentra."""
|
|
37
|
-
self.profile_repo.get_company_by_short_name.return_value = None
|
|
38
|
-
|
|
39
|
-
result = self.history_service.get_history(
|
|
40
|
-
company_short_name='nonexistent_company',
|
|
41
|
-
external_user_id='test_user'
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
assert result == {'error': 'No existe la empresa: nonexistent_company'}
|
|
45
|
-
self.profile_repo.get_company_by_short_name.assert_called_once_with('nonexistent_company')
|
|
46
|
-
self.llm_query_repo.get_history.assert_not_called()
|
|
47
|
-
|
|
48
|
-
def test_get_history_no_history_found(self):
|
|
49
|
-
"""Prueba que el servicio devuelve un error si no se encuentra historial."""
|
|
50
|
-
self.llm_query_repo.get_history.return_value = []
|
|
51
|
-
user_identifier = 'test_user'
|
|
52
|
-
|
|
53
|
-
result = self.history_service.get_history(
|
|
54
|
-
company_short_name='test_company',
|
|
55
|
-
external_user_id=user_identifier
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
assert result == {'error': 'No se pudo obtener el historial'}
|
|
59
|
-
self.profile_repo.get_company_by_short_name.assert_called_once_with('test_company')
|
|
60
|
-
self.llm_query_repo.get_history.assert_called_once_with(self.mock_company, user_identifier)
|
|
61
|
-
|
|
62
|
-
def test_get_history_success_with_external_user_id(self):
|
|
63
|
-
"""Prueba la recuperación exitosa del historial usando un external_user_id."""
|
|
64
|
-
user_identifier = 'external_user_123'
|
|
65
|
-
|
|
66
|
-
mock_query1 = MagicMock(spec=LLMQuery)
|
|
67
|
-
mock_query1.to_dict.return_value = {'id': 1, 'query': 'q1', 'answer': 'a1', 'created_at': 't1'}
|
|
68
|
-
mock_query2 = MagicMock(spec=LLMQuery)
|
|
69
|
-
mock_query2.to_dict.return_value = {'id': 2, 'query': 'q2', 'answer': 'a2', 'created_at': 't2'}
|
|
70
|
-
|
|
71
|
-
self.llm_query_repo.get_history.return_value = [mock_query1, mock_query2]
|
|
72
|
-
|
|
73
|
-
result = self.history_service.get_history(
|
|
74
|
-
company_short_name='test_company',
|
|
75
|
-
external_user_id=user_identifier
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
assert result['message'] == 'Historial obtenido correctamente'
|
|
79
|
-
assert len(result['history']) == 2
|
|
80
|
-
assert result['history'][0]['id'] == 1
|
|
81
|
-
|
|
82
|
-
self.llm_query_repo.get_history.assert_called_once_with(self.mock_company, user_identifier)
|
|
83
|
-
|
|
84
|
-
def test_get_history_success_with_local_user_id_and_external_id_takes_precedence(self):
|
|
85
|
-
"""Prueba que external_user_id tiene prioridad sobre local_user_id."""
|
|
86
|
-
external_user_identifier = 'external_user_abc'
|
|
87
|
-
|
|
88
|
-
mock_query = MagicMock(spec=LLMQuery)
|
|
89
|
-
mock_query.to_dict.return_value = {'id': 1}
|
|
90
|
-
self.llm_query_repo.get_history.return_value = [mock_query]
|
|
91
|
-
|
|
92
|
-
result = self.history_service.get_history(
|
|
93
|
-
company_short_name='test_company',
|
|
94
|
-
external_user_id=external_user_identifier,
|
|
95
|
-
local_user_id=123 # Este debería ser ignorado
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
assert 'history' in result
|
|
99
|
-
# El user_identifier resuelto debe ser el ID externo.
|
|
100
|
-
self.llm_query_repo.get_history.assert_called_once_with(self.mock_company, external_user_identifier)
|
|
101
|
-
|
|
102
|
-
def test_get_history_success_with_local_user_id_only(self):
|
|
103
|
-
local_id = 456
|
|
104
|
-
resolved_user_identifier = 'fl@gmail.com'
|
|
105
|
-
|
|
106
|
-
with patch('iatoolkit.common.session_manager.session', new={}) as fake_session:
|
|
107
|
-
SessionManager.set('user', {'id': local_id, 'email': resolved_user_identifier})
|
|
108
|
-
|
|
109
|
-
mock_query = MagicMock(spec=LLMQuery)
|
|
110
|
-
mock_query.to_dict.return_value = {'id': 1, 'query': 'q1'}
|
|
111
|
-
self.llm_query_repo.get_history.return_value = [mock_query]
|
|
112
|
-
|
|
113
|
-
result = self.history_service.get_history(
|
|
114
|
-
company_short_name='test_company',
|
|
115
|
-
local_user_id=local_id
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
assert result['message'] == 'Historial obtenido correctamente'
|
|
119
|
-
self.llm_query_repo.get_history.assert_called_once_with(self.mock_company, resolved_user_identifier)
|
|
120
|
-
|
|
121
|
-
def test_get_history_fails_when_no_user_identifier_is_provided(self):
|
|
122
|
-
"""Prueba que falla si no se proporciona ningún identificador de usuario."""
|
|
123
|
-
result = self.history_service.get_history(company_short_name='test_company')
|
|
124
|
-
|
|
125
|
-
assert result == {'error': 'No se pudo resolver el identificador del usuario'}
|
|
126
|
-
self.llm_query_repo.get_history.assert_not_called()
|
|
127
|
-
|
|
128
|
-
def test_get_history_propagates_exception_from_company_lookup(self):
|
|
129
|
-
"""Prueba que las excepciones de la capa de repositorio se propagan."""
|
|
130
|
-
self.profile_repo.get_company_by_short_name.side_effect = Exception('Database error')
|
|
131
|
-
|
|
132
|
-
result = self.history_service.get_history(
|
|
133
|
-
company_short_name='test_company',
|
|
134
|
-
external_user_id='test_user'
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
assert result == {'error': 'Database error'}
|
|
138
|
-
self.llm_query_repo.get_history.assert_not_called()
|
|
139
|
-
|
|
140
|
-
def test_get_history_propagates_exception_from_history_lookup(self):
|
|
141
|
-
"""Prueba que las excepciones de la capa de repositorio se propagan."""
|
|
142
|
-
user_identifier = 'test_user'
|
|
143
|
-
self.llm_query_repo.get_history.side_effect = Exception('History lookup error')
|
|
144
|
-
|
|
145
|
-
result = self.history_service.get_history(
|
|
146
|
-
company_short_name='test_company',
|
|
147
|
-
external_user_id=user_identifier
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
assert result == {'error': 'History lookup error'}
|
|
151
|
-
self.llm_query_repo.get_history.assert_called_once_with(self.mock_company, user_identifier)
|
|
152
|
-
|
|
153
|
-
def test_get_history_none_returned_from_repo(self):
|
|
154
|
-
"""Prueba el caso en que el repositorio devuelve None."""
|
|
155
|
-
self.llm_query_repo.get_history.return_value = None
|
|
156
|
-
user_identifier = 'test_user'
|
|
157
|
-
|
|
158
|
-
result = self.history_service.get_history(
|
|
159
|
-
company_short_name='test_company',
|
|
160
|
-
external_user_id=user_identifier
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
assert result == {'error': 'No se pudo obtener el historial'}
|
|
164
|
-
self.llm_query_repo.get_history.assert_called_once_with(self.mock_company, user_identifier)
|