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
tests/common/test_util.py
DELETED
|
@@ -1,444 +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, mock_open
|
|
8
|
-
from iatoolkit.common.exceptions import IAToolkitException
|
|
9
|
-
import os
|
|
10
|
-
from iatoolkit.common.util import Utility
|
|
11
|
-
from datetime import datetime, date
|
|
12
|
-
from decimal import Decimal
|
|
13
|
-
from cryptography.fernet import Fernet
|
|
14
|
-
|
|
15
|
-
# Generar una clave Fernet de prueba una vez y usarla en el fixture
|
|
16
|
-
ACTUAL_FERNET_KEY_FOR_ENV = Fernet.generate_key().decode('utf-8')
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class TestUtil:
|
|
20
|
-
def setup_method(self):
|
|
21
|
-
self.util = Utility()
|
|
22
|
-
|
|
23
|
-
@patch("jinja2.Environment.get_template")
|
|
24
|
-
def test_util_when_jinja_error(self, mock_get_template):
|
|
25
|
-
mock_template = MagicMock()
|
|
26
|
-
mock_get_template.return_value = mock_template
|
|
27
|
-
mock_template.render.side_effect = Exception('jinja error')
|
|
28
|
-
|
|
29
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
30
|
-
self.util.render_prompt_from_template(
|
|
31
|
-
template_pathname='a template',
|
|
32
|
-
query='a query'
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
# Validar que la excepción es la esperada
|
|
36
|
-
assert "TEMPLATE_ERROR" == excinfo.value.error_type.name
|
|
37
|
-
|
|
38
|
-
@patch("jinja2.Environment.get_template")
|
|
39
|
-
def test_util_when_ok(self, mock_get_template):
|
|
40
|
-
mock_template = MagicMock()
|
|
41
|
-
mock_get_template.return_value = mock_template
|
|
42
|
-
mock_template.render.return_value = 'a large prompt'
|
|
43
|
-
|
|
44
|
-
prompt = self.util.render_prompt_from_template(
|
|
45
|
-
template_pathname='a template',
|
|
46
|
-
query='a query'
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
# Validar que la excepción es la esperada
|
|
50
|
-
assert "a large prompt" == prompt
|
|
51
|
-
|
|
52
|
-
def test_serialize_datetime(self):
|
|
53
|
-
"""Test serialización de datetime"""
|
|
54
|
-
test_datetime = datetime(2024, 1, 1, 12, 0, 0)
|
|
55
|
-
result = self.util.serialize(test_datetime)
|
|
56
|
-
assert result == "2024-01-01T12:00:00"
|
|
57
|
-
|
|
58
|
-
def test_serialize_date(self):
|
|
59
|
-
"""Test serialización de date"""
|
|
60
|
-
test_date = date(2024, 1, 1)
|
|
61
|
-
result = self.util.serialize(test_date)
|
|
62
|
-
assert result == "2024-01-01"
|
|
63
|
-
|
|
64
|
-
def test_serialize_decimal(self):
|
|
65
|
-
"""Test serialización de Decimal"""
|
|
66
|
-
test_decimal = Decimal('10.5')
|
|
67
|
-
result = self.util.serialize(test_decimal)
|
|
68
|
-
assert result == 10.5
|
|
69
|
-
assert isinstance(result, float)
|
|
70
|
-
|
|
71
|
-
def test_serialize_bytes(self):
|
|
72
|
-
"""Test serialización de bytes"""
|
|
73
|
-
test_bytes = b"Hello World"
|
|
74
|
-
result = self.util.serialize(test_bytes)
|
|
75
|
-
assert result == "Hello World"
|
|
76
|
-
assert isinstance(result, str)
|
|
77
|
-
|
|
78
|
-
def test_serialize_unsupported_type(self):
|
|
79
|
-
"""Test serialización de tipo no soportado"""
|
|
80
|
-
|
|
81
|
-
class UnsupportedClass:
|
|
82
|
-
pass
|
|
83
|
-
|
|
84
|
-
test_obj = UnsupportedClass()
|
|
85
|
-
with pytest.raises(TypeError) as excinfo:
|
|
86
|
-
self.util.serialize(test_obj)
|
|
87
|
-
|
|
88
|
-
assert str(excinfo.value).startswith("Type <class")
|
|
89
|
-
|
|
90
|
-
def test_load_schema_from_yaml(self):
|
|
91
|
-
mock_yaml_content = """
|
|
92
|
-
field1: "Descripción del campo 1"
|
|
93
|
-
field2: "Descripción del campo 2"
|
|
94
|
-
"""
|
|
95
|
-
with patch("builtins.open", mock_open(read_data=mock_yaml_content)) as mock_file:
|
|
96
|
-
schema = self.util.load_schema_from_yaml("fake_path/schema.yaml")
|
|
97
|
-
|
|
98
|
-
assert schema == {
|
|
99
|
-
"field1": "Descripción del campo 1",
|
|
100
|
-
"field2": "Descripción del campo 2"
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
# test file was open
|
|
104
|
-
mock_file.assert_called_once_with("fake_path/schema.yaml", 'r', encoding='utf-8')
|
|
105
|
-
|
|
106
|
-
def test_generate_llm_context(self):
|
|
107
|
-
"""Test generación de contexto LLM con schema que incluye listas y subcampos."""
|
|
108
|
-
entity_name = "test_entity"
|
|
109
|
-
client_json = {"field1": "valor1", "field2": 2}
|
|
110
|
-
schema = {
|
|
111
|
-
"field1": {
|
|
112
|
-
"type": "string",
|
|
113
|
-
"description": "Descripción del campo 1"
|
|
114
|
-
},
|
|
115
|
-
"field2": {
|
|
116
|
-
"type": "integer",
|
|
117
|
-
"description": "Descripción del campo 2",
|
|
118
|
-
"values": ["1", "2", "3"]
|
|
119
|
-
},
|
|
120
|
-
"field3": {
|
|
121
|
-
"type": "list",
|
|
122
|
-
"description": "Descripción del campo 3 (lista de objetos)",
|
|
123
|
-
"item_structure": {
|
|
124
|
-
"subfield1": {
|
|
125
|
-
"type": "string",
|
|
126
|
-
"description": "Descripción de subcampo 1"
|
|
127
|
-
},
|
|
128
|
-
"subfield2": {
|
|
129
|
-
"type": "boolean",
|
|
130
|
-
"description": "Descripción de subcampo 2"
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
expected_schema = "\n".join([
|
|
137
|
-
"- **`field1`** (string): Descripción del campo 1",
|
|
138
|
-
"- **`field2`** (integer): Descripción del campo 2",
|
|
139
|
-
'- **`field3`** (list): Descripción del campo 3 (lista de objetos)',
|
|
140
|
-
])
|
|
141
|
-
|
|
142
|
-
# now check the schema
|
|
143
|
-
schema_context = self.util.generate_context_for_schema(entity_name, schema=schema)
|
|
144
|
-
assert schema_context.strip() == expected_schema.strip()
|
|
145
|
-
|
|
146
|
-
def test_markdown_context(self):
|
|
147
|
-
md_content = "ejemplo de md context"
|
|
148
|
-
with patch("builtins.open", mock_open(read_data=md_content)) as mock_file:
|
|
149
|
-
context = self.util.load_markdown_context("company_content.md")
|
|
150
|
-
|
|
151
|
-
assert context == md_content
|
|
152
|
-
|
|
153
|
-
# test file was open
|
|
154
|
-
mock_file.assert_called_once_with("company_content.md", 'r', encoding='utf-8')
|
|
155
|
-
def test_encrypt_decrypt_key_successful(self):
|
|
156
|
-
"""Testa encriptación y desencriptación exitosa de una clave."""
|
|
157
|
-
env_vars = {'FERNET_KEY': ACTUAL_FERNET_KEY_FOR_ENV}
|
|
158
|
-
with patch.dict(os.environ, env_vars, clear=True):
|
|
159
|
-
util_with_key = Utility() # Nueva instancia que lee el FERNET_KEY mockeado
|
|
160
|
-
original_key = "mi_api_key_secreta_123_con_ñ_y_tildes_áéíóú"
|
|
161
|
-
encrypted_key = util_with_key.encrypt_key(original_key)
|
|
162
|
-
|
|
163
|
-
assert encrypted_key is not None
|
|
164
|
-
assert isinstance(encrypted_key, str)
|
|
165
|
-
assert encrypted_key != original_key
|
|
166
|
-
|
|
167
|
-
decrypted_key = util_with_key.decrypt_key(encrypted_key)
|
|
168
|
-
assert decrypted_key == original_key
|
|
169
|
-
|
|
170
|
-
def test_encrypt_key_no_fernet_key_env(self):
|
|
171
|
-
"""Testa encrypt_key cuando FERNET_KEY no está en el entorno."""
|
|
172
|
-
util_no_env_key = Utility()
|
|
173
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
174
|
-
util_no_env_key.encrypt_key("testkey")
|
|
175
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.CRYPT_ERROR
|
|
176
|
-
assert "No se pudo obtener variable de ambiente para encriptar" in excinfo.value.message
|
|
177
|
-
|
|
178
|
-
def test_encrypt_key_empty_input_key(self):
|
|
179
|
-
"""Testa encrypt_key cuando la clave de entrada está vacía, pero FERNET_KEY es válida."""
|
|
180
|
-
env_vars = {'FERNET_KEY': ACTUAL_FERNET_KEY_FOR_ENV}
|
|
181
|
-
with patch.dict(os.environ, env_vars, clear=True):
|
|
182
|
-
util_with_key = Utility() # Nueva instancia con FERNET_KEY
|
|
183
|
-
with pytest.raises(IAToolkitException) as excinfo_empty:
|
|
184
|
-
util_with_key.encrypt_key("")
|
|
185
|
-
assert excinfo_empty.value.error_type == IAToolkitException.ErrorType.CRYPT_ERROR
|
|
186
|
-
assert "falta la clave a encriptar" in excinfo_empty.value.message
|
|
187
|
-
|
|
188
|
-
def test_decrypt_key_no_fernet_key_env(self):
|
|
189
|
-
# Primero, encriptar una clave usando un Utility con FERNET_KEY
|
|
190
|
-
# (podríamos usar self.util del setup si supiéramos que no usa FERNET_KEY en __init__,
|
|
191
|
-
# pero es más seguro crear una instancia temporal con la clave para generar el dato de test)
|
|
192
|
-
encrypted_key_valid = ""
|
|
193
|
-
env_vars_for_encryption = {'FERNET_KEY': ACTUAL_FERNET_KEY_FOR_ENV}
|
|
194
|
-
with patch.dict(os.environ, env_vars_for_encryption, clear=True):
|
|
195
|
-
temp_util = Utility()
|
|
196
|
-
encrypted_key_valid = temp_util.encrypt_key("mi_api_key_secreta_123")
|
|
197
|
-
|
|
198
|
-
# Ahora, probar la desencriptación sin FERNET_KEY
|
|
199
|
-
util_no_env_key = Utility()
|
|
200
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
201
|
-
util_no_env_key.decrypt_key(encrypted_key_valid)
|
|
202
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.CRYPT_ERROR
|
|
203
|
-
assert "No se pudo obtener variable de ambiente para desencriptar" in excinfo.value.message
|
|
204
|
-
|
|
205
|
-
def test_decrypt_key_empty_input_key(self):
|
|
206
|
-
"""Testa decrypt_key cuando la clave encriptada de entrada está vacía o es None, pero FERNET_KEY es válida."""
|
|
207
|
-
env_vars = {'FERNET_KEY': ACTUAL_FERNET_KEY_FOR_ENV}
|
|
208
|
-
with patch.dict(os.environ, env_vars, clear=True):
|
|
209
|
-
util_with_key = Utility() # Nueva instancia con FERNET_KEY
|
|
210
|
-
with pytest.raises(IAToolkitException) as excinfo_empty:
|
|
211
|
-
util_with_key.decrypt_key("")
|
|
212
|
-
assert excinfo_empty.value.error_type == IAToolkitException.ErrorType.CRYPT_ERROR
|
|
213
|
-
assert "falta la clave a encriptar" in excinfo_empty.value.message # Mensaje actual de la implementación
|
|
214
|
-
|
|
215
|
-
with pytest.raises(IAToolkitException) as excinfo_none:
|
|
216
|
-
util_with_key.decrypt_key(None)
|
|
217
|
-
assert excinfo_none.value.error_type == IAToolkitException.ErrorType.CRYPT_ERROR
|
|
218
|
-
assert "falta la clave a encriptar" in excinfo_none.value.message # Mensaje actual de la implementación
|
|
219
|
-
|
|
220
|
-
def test_encrypt_key_when_exception_in_fermet(self):
|
|
221
|
-
"""Testa encrypt_key si FERNET_KEY en el entorno es inválida."""
|
|
222
|
-
invalid_env_fernet_key = "clave_no_valida_para_fernet"
|
|
223
|
-
env_vars = {'FERNET_KEY': invalid_env_fernet_key}
|
|
224
|
-
with patch.dict(os.environ, env_vars, clear=True):
|
|
225
|
-
util_invalid_env_key = Utility()
|
|
226
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
227
|
-
util_invalid_env_key.encrypt_key("testkey")
|
|
228
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.CRYPT_ERROR
|
|
229
|
-
# El mensaje exacto puede depender de cómo Utility maneje internamente el error de Fernet
|
|
230
|
-
# "No se pudo encriptar la clave" es un buen candidato si hay un try-except genérico.
|
|
231
|
-
# Si Fernet(key) se llama directamente y falla, el mensaje podría ser de Fernet/cryptography.
|
|
232
|
-
# Basado en el código original, la excepción es reenvasada.
|
|
233
|
-
assert "No se pudo encriptar la clave" in excinfo.value.message
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
def test_decrypt_key_when_exception_in_fermet(self):
|
|
237
|
-
"""Testa decrypt_key si FERNET_KEY en el entorno es inválida."""
|
|
238
|
-
# Encriptar primero con una clave válida
|
|
239
|
-
encrypted_key_valid = ""
|
|
240
|
-
valid_env_vars = {'FERNET_KEY': ACTUAL_FERNET_KEY_FOR_ENV}
|
|
241
|
-
with patch.dict(os.environ, valid_env_vars, clear=True):
|
|
242
|
-
temp_util_for_encrypt = Utility()
|
|
243
|
-
encrypted_key_valid = temp_util_for_encrypt.encrypt_key("some_secret_data")
|
|
244
|
-
|
|
245
|
-
# Ahora intentar desencriptar con una FERNET_KEY inválida en el entorno
|
|
246
|
-
invalid_env_fernet_key = "clave_no_valida_para_fernet"
|
|
247
|
-
invalid_env_vars = {'FERNET_KEY': invalid_env_fernet_key, 'PROMPTS_DIR': './prompts'}
|
|
248
|
-
with patch.dict(os.environ, invalid_env_vars, clear=True):
|
|
249
|
-
util_invalid_env_key = Utility()
|
|
250
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
251
|
-
util_invalid_env_key.decrypt_key(encrypted_key_valid)
|
|
252
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.CRYPT_ERROR
|
|
253
|
-
assert "No se pudo desencriptar la clave" in excinfo.value.message
|
|
254
|
-
|
|
255
|
-
def test_validate_rut_when_not_ok(self):
|
|
256
|
-
status = self.util.validate_rut("123456789")
|
|
257
|
-
assert status == False
|
|
258
|
-
|
|
259
|
-
def test_validate_rut_not_numeric(self):
|
|
260
|
-
status = self.util.validate_rut("opensoft")
|
|
261
|
-
assert status == False
|
|
262
|
-
|
|
263
|
-
def test_validate_rut_when_ok(self):
|
|
264
|
-
assert self.util.validate_rut("31456455-3") == True
|
|
265
|
-
|
|
266
|
-
@patch('os.path.exists')
|
|
267
|
-
@patch('os.path.isdir')
|
|
268
|
-
@patch('os.listdir')
|
|
269
|
-
@patch('os.path.isfile')
|
|
270
|
-
def test_get_files_by_extension_success_with_extension(self, mock_isfile, mock_listdir, mock_isdir, mock_exists):
|
|
271
|
-
"""Test obtener archivos con extensión, retornando nombres con extensión"""
|
|
272
|
-
# Setup mocks
|
|
273
|
-
mock_exists.return_value = True
|
|
274
|
-
mock_isdir.return_value = True
|
|
275
|
-
mock_listdir.return_value = ['file1.txt', 'file2.txt', 'file3.doc', 'subdir']
|
|
276
|
-
mock_isfile.side_effect = lambda path: not path.endswith('subdir')
|
|
277
|
-
|
|
278
|
-
result = self.util.get_files_by_extension('/test/directory', '.txt', return_extension=True)
|
|
279
|
-
|
|
280
|
-
assert result == ['file1.txt', 'file2.txt']
|
|
281
|
-
mock_exists.assert_called_once_with('/test/directory')
|
|
282
|
-
mock_isdir.assert_called_once_with('/test/directory')
|
|
283
|
-
mock_listdir.assert_called_once_with('/test/directory')
|
|
284
|
-
|
|
285
|
-
@patch('os.path.exists')
|
|
286
|
-
@patch('os.path.isdir')
|
|
287
|
-
@patch('os.listdir')
|
|
288
|
-
@patch('os.path.isfile')
|
|
289
|
-
def test_get_files_by_extension_success_without_extension(self, mock_isfile, mock_listdir, mock_isdir, mock_exists):
|
|
290
|
-
"""Test obtener archivos con extensión, retornando nombres sin extensión"""
|
|
291
|
-
# Setup mocks
|
|
292
|
-
mock_exists.return_value = True
|
|
293
|
-
mock_isdir.return_value = True
|
|
294
|
-
mock_listdir.return_value = ['file1.txt', 'file2.txt', 'file3.doc', 'subdir']
|
|
295
|
-
mock_isfile.side_effect = lambda path: not path.endswith('subdir')
|
|
296
|
-
|
|
297
|
-
result = self.util.get_files_by_extension('/test/directory', '.txt', return_extension=False)
|
|
298
|
-
|
|
299
|
-
assert result == ['file1', 'file2']
|
|
300
|
-
mock_exists.assert_called_once_with('/test/directory')
|
|
301
|
-
mock_isdir.assert_called_once_with('/test/directory')
|
|
302
|
-
mock_listdir.assert_called_once_with('/test/directory')
|
|
303
|
-
|
|
304
|
-
@patch('os.path.exists')
|
|
305
|
-
@patch('os.path.isdir')
|
|
306
|
-
@patch('os.listdir')
|
|
307
|
-
@patch('os.path.isfile')
|
|
308
|
-
def test_get_files_by_extension_success_extension_without_dot(self, mock_isfile, mock_listdir, mock_isdir, mock_exists):
|
|
309
|
-
"""Test obtener archivos con extensión sin punto, retornando nombres sin extensión"""
|
|
310
|
-
# Setup mocks
|
|
311
|
-
mock_exists.return_value = True
|
|
312
|
-
mock_isdir.return_value = True
|
|
313
|
-
mock_listdir.return_value = ['file1.txt', 'file2.txt', 'file3.doc', 'subdir']
|
|
314
|
-
mock_isfile.side_effect = lambda path: not path.endswith('subdir')
|
|
315
|
-
|
|
316
|
-
result = self.util.get_files_by_extension('/test/directory', 'txt', return_extension=False)
|
|
317
|
-
|
|
318
|
-
assert result == ['file1', 'file2']
|
|
319
|
-
mock_exists.assert_called_once_with('/test/directory')
|
|
320
|
-
mock_isdir.assert_called_once_with('/test/directory')
|
|
321
|
-
mock_listdir.assert_called_once_with('/test/directory')
|
|
322
|
-
|
|
323
|
-
@patch('os.path.exists')
|
|
324
|
-
@patch('os.path.isdir')
|
|
325
|
-
@patch('os.listdir')
|
|
326
|
-
@patch('os.path.isfile')
|
|
327
|
-
def test_get_files_by_extension_empty_result(self, mock_isfile, mock_listdir, mock_isdir, mock_exists):
|
|
328
|
-
"""Test cuando no hay archivos con la extensión especificada"""
|
|
329
|
-
# Setup mocks
|
|
330
|
-
mock_exists.return_value = True
|
|
331
|
-
mock_isdir.return_value = True
|
|
332
|
-
mock_listdir.return_value = ['file1.doc', 'file2.pdf', 'subdir']
|
|
333
|
-
mock_isfile.side_effect = lambda path: not path.endswith('subdir')
|
|
334
|
-
|
|
335
|
-
result = self.util.get_files_by_extension('/test/directory', '.txt')
|
|
336
|
-
|
|
337
|
-
assert result == []
|
|
338
|
-
mock_exists.assert_called_once_with('/test/directory')
|
|
339
|
-
mock_isdir.assert_called_once_with('/test/directory')
|
|
340
|
-
mock_listdir.assert_called_once_with('/test/directory')
|
|
341
|
-
|
|
342
|
-
@patch('os.path.exists')
|
|
343
|
-
@patch('os.path.isdir')
|
|
344
|
-
@patch('os.listdir')
|
|
345
|
-
@patch('os.path.isfile')
|
|
346
|
-
def test_get_files_by_extension_sorted_result(self, mock_isfile, mock_listdir, mock_isdir, mock_exists):
|
|
347
|
-
"""Test que los resultados estén ordenados alfabéticamente"""
|
|
348
|
-
# Setup mocks
|
|
349
|
-
mock_exists.return_value = True
|
|
350
|
-
mock_isdir.return_value = True
|
|
351
|
-
mock_listdir.return_value = ['zebra.txt', 'alpha.txt', 'beta.txt', 'subdir']
|
|
352
|
-
mock_isfile.side_effect = lambda path: not path.endswith('subdir')
|
|
353
|
-
|
|
354
|
-
result = self.util.get_files_by_extension('/test/directory', '.txt', return_extension=True)
|
|
355
|
-
|
|
356
|
-
assert result == ['alpha.txt', 'beta.txt', 'zebra.txt']
|
|
357
|
-
mock_exists.assert_called_once_with('/test/directory')
|
|
358
|
-
mock_isdir.assert_called_once_with('/test/directory')
|
|
359
|
-
mock_listdir.assert_called_once_with('/test/directory')
|
|
360
|
-
|
|
361
|
-
@patch('os.path.exists')
|
|
362
|
-
def test_get_files_by_extension_directory_not_exists(self, mock_exists):
|
|
363
|
-
"""Test cuando el directorio no existe"""
|
|
364
|
-
mock_exists.return_value = False
|
|
365
|
-
|
|
366
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
367
|
-
self.util.get_files_by_extension('/nonexistent/directory', '.txt')
|
|
368
|
-
|
|
369
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.FILE_IO_ERROR
|
|
370
|
-
assert "El directorio no existe" in excinfo.value.message
|
|
371
|
-
mock_exists.assert_called_once_with('/nonexistent/directory')
|
|
372
|
-
|
|
373
|
-
@patch('os.path.exists')
|
|
374
|
-
@patch('os.path.isdir')
|
|
375
|
-
def test_get_files_by_extension_not_a_directory(self, mock_isdir, mock_exists):
|
|
376
|
-
"""Test cuando la ruta existe pero no es un directorio"""
|
|
377
|
-
mock_exists.return_value = True
|
|
378
|
-
mock_isdir.return_value = False
|
|
379
|
-
|
|
380
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
381
|
-
self.util.get_files_by_extension('/path/to/file.txt', '.txt')
|
|
382
|
-
|
|
383
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.FILE_IO_ERROR
|
|
384
|
-
assert "La ruta no es un directorio" in excinfo.value.message
|
|
385
|
-
mock_exists.assert_called_once_with('/path/to/file.txt')
|
|
386
|
-
mock_isdir.assert_called_once_with('/path/to/file.txt')
|
|
387
|
-
|
|
388
|
-
@patch('os.path.exists')
|
|
389
|
-
@patch('os.path.isdir')
|
|
390
|
-
@patch('os.listdir')
|
|
391
|
-
def test_get_files_by_extension_os_error(self, mock_listdir, mock_isdir, mock_exists):
|
|
392
|
-
"""Test cuando hay un error del sistema operativo al listar el directorio"""
|
|
393
|
-
mock_exists.return_value = True
|
|
394
|
-
mock_isdir.return_value = True
|
|
395
|
-
mock_listdir.side_effect = OSError("Permission denied")
|
|
396
|
-
|
|
397
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
398
|
-
self.util.get_files_by_extension('/test/directory', '.txt')
|
|
399
|
-
|
|
400
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.FILE_IO_ERROR
|
|
401
|
-
assert "Error al buscar archivos en el directorio" in excinfo.value.message
|
|
402
|
-
mock_exists.assert_called_once_with('/test/directory')
|
|
403
|
-
mock_isdir.assert_called_once_with('/test/directory')
|
|
404
|
-
mock_listdir.assert_called_once_with('/test/directory')
|
|
405
|
-
|
|
406
|
-
@patch('os.path.exists')
|
|
407
|
-
@patch('os.path.isdir')
|
|
408
|
-
@patch('os.listdir')
|
|
409
|
-
@patch('os.path.isfile')
|
|
410
|
-
def test_get_files_by_extension_with_subdirectories(self, mock_isfile, mock_listdir, mock_isdir, mock_exists):
|
|
411
|
-
"""Test que solo se consideren archivos, no subdirectorios"""
|
|
412
|
-
# Setup mocks
|
|
413
|
-
mock_exists.return_value = True
|
|
414
|
-
mock_isdir.return_value = True
|
|
415
|
-
mock_listdir.return_value = ['file1.txt', 'subdir1', 'file2.txt', 'subdir2']
|
|
416
|
-
# Simular que solo los archivos son archivos, los subdirectorios no
|
|
417
|
-
mock_isfile.side_effect = lambda path: 'subdir' not in path
|
|
418
|
-
|
|
419
|
-
result = self.util.get_files_by_extension('/test/directory', '.txt', return_extension=True)
|
|
420
|
-
|
|
421
|
-
assert result == ['file1.txt', 'file2.txt']
|
|
422
|
-
mock_exists.assert_called_once_with('/test/directory')
|
|
423
|
-
mock_isdir.assert_called_once_with('/test/directory')
|
|
424
|
-
mock_listdir.assert_called_once_with('/test/directory')
|
|
425
|
-
|
|
426
|
-
@patch('os.path.exists')
|
|
427
|
-
@patch('os.path.isdir')
|
|
428
|
-
@patch('os.listdir')
|
|
429
|
-
@patch('os.path.isfile')
|
|
430
|
-
def test_get_files_by_extension_case_sensitive(self, mock_isfile, mock_listdir, mock_isdir, mock_exists):
|
|
431
|
-
"""Test que la búsqueda sea sensible a mayúsculas/minúsculas"""
|
|
432
|
-
# Setup mocks
|
|
433
|
-
mock_exists.return_value = True
|
|
434
|
-
mock_isdir.return_value = True
|
|
435
|
-
mock_listdir.return_value = ['file1.txt', 'file2.TXT', 'file3.txt', 'subdir']
|
|
436
|
-
mock_isfile.side_effect = lambda path: not path.endswith('subdir')
|
|
437
|
-
|
|
438
|
-
result = self.util.get_files_by_extension('/test/directory', '.txt', return_extension=True)
|
|
439
|
-
|
|
440
|
-
# Solo debe encontrar 'file1.txt' y 'file3.txt', no 'file2.TXT'
|
|
441
|
-
assert result == ['file1.txt', 'file3.txt']
|
|
442
|
-
mock_exists.assert_called_once_with('/test/directory')
|
|
443
|
-
mock_isdir.assert_called_once_with('/test/directory')
|
|
444
|
-
mock_listdir.assert_called_once_with('/test/directory')
|
tests/companies/__init__.py
DELETED
tests/conftest.py
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
import os
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@pytest.fixture(scope="session", autouse=True)
|
|
11
|
-
def setup_test_environment():
|
|
12
|
-
"""Configuración global de variables de ambiente para todos los tests"""
|
|
13
|
-
# Guardar estado original
|
|
14
|
-
original_environ = dict(os.environ)
|
|
15
|
-
|
|
16
|
-
# Establecer variables para tests
|
|
17
|
-
test_vars = {
|
|
18
|
-
'DATABASE_URI': 'sqlite:///:memory:',
|
|
19
|
-
'REDIS_URL': 'redis://localhost:6379',
|
|
20
|
-
'FLASK_ENV': 'testing',
|
|
21
|
-
'SECRET_KEY': 'test_secret_key',
|
|
22
|
-
'CARPETA_API_URL': 'http://test-carpeta-api.com',
|
|
23
|
-
'MIDDLEWARE_API_URL': 'http://test-middleware-api.com',
|
|
24
|
-
'BCU_API_URL': 'http://test-bcu-api.com',
|
|
25
|
-
'CONTACT_BOOK_API_URL': 'http://test-contact-book-api.com',
|
|
26
|
-
'PUBLIC_MARKET_API_URL': 'http://test-public-market-api.com',
|
|
27
|
-
'USERS_APP_API_URL': 'http://test-users-app-api.com'
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
os.environ.update(test_vars)
|
|
31
|
-
|
|
32
|
-
yield
|
|
33
|
-
|
|
34
|
-
# Restaurar estado original
|
|
35
|
-
os.environ.clear()
|
|
36
|
-
os.environ.update(original_environ)
|
tests/infra/__init__.py
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
import unittest
|
|
7
|
-
from unittest.mock import patch, MagicMock
|
|
8
|
-
from iatoolkit.infra.connectors.google_drive_connector import GoogleDriveConnector
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TestGoogleDriveConnector(unittest.TestCase):
|
|
12
|
-
def setUp(self):
|
|
13
|
-
# Parchar la función `build` de la API de Google Drive
|
|
14
|
-
self.build_patch = patch('iatoolkit.infra.connectors.google_drive_connector.build')
|
|
15
|
-
self.mock_build = self.build_patch.start()
|
|
16
|
-
|
|
17
|
-
# Mock del servicio de Google Drive
|
|
18
|
-
self.mock_drive_service = MagicMock()
|
|
19
|
-
self.mock_build.return_value = self.mock_drive_service
|
|
20
|
-
|
|
21
|
-
# Parchar las credenciales de Google
|
|
22
|
-
self.credentials_patch = patch('iatoolkit.infra.connectors.google_drive_connector.Credentials.from_service_account_file')
|
|
23
|
-
self.mock_credentials = self.credentials_patch.start()
|
|
24
|
-
|
|
25
|
-
# Datos de configuración
|
|
26
|
-
self.folder_id = "mock-folder-id"
|
|
27
|
-
self.service_account_path = "mock-service-account.json"
|
|
28
|
-
|
|
29
|
-
# Crear una instancia del conector utilizando la configuración
|
|
30
|
-
self.connector = GoogleDriveConnector(
|
|
31
|
-
folder_id=self.folder_id,
|
|
32
|
-
service_account_path=self.service_account_path
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
def tearDown(self):
|
|
36
|
-
self.build_patch.stop()
|
|
37
|
-
self.credentials_patch.stop()
|
|
38
|
-
|
|
39
|
-
def test_authenticate_when_success(self):
|
|
40
|
-
self.mock_credentials.assert_called_once_with(
|
|
41
|
-
self.service_account_path,
|
|
42
|
-
scopes=["https://www.googleapis.com/auth/drive"]
|
|
43
|
-
)
|
|
44
|
-
self.mock_build.assert_called_once_with('drive', 'v3', credentials=self.mock_credentials.return_value)
|
|
45
|
-
self.assertEqual(self.connector.drive_service, self.mock_build.return_value)
|
|
46
|
-
|
|
47
|
-
def test_list_files_empty(self):
|
|
48
|
-
self.mock_drive_service.files().list().execute.return_value = {'files': []}
|
|
49
|
-
|
|
50
|
-
result = self.connector.list_files()
|
|
51
|
-
|
|
52
|
-
self.mock_drive_service.files().list.assert_called_with(
|
|
53
|
-
q=f"'{self.folder_id}' in parents and trashed=false",
|
|
54
|
-
fields="files(id, name)"
|
|
55
|
-
)
|
|
56
|
-
self.assertEqual(result, [])
|
|
57
|
-
|
|
58
|
-
def test_list_files_success(self):
|
|
59
|
-
# Simulación de la respuesta de la API
|
|
60
|
-
fake_files = [
|
|
61
|
-
{'id': 'file1-id', 'name': 'file1.txt'},
|
|
62
|
-
{'id': 'file2-id', 'name': 'file2.txt'}
|
|
63
|
-
]
|
|
64
|
-
self.mock_drive_service.files().list().execute.return_value = {'files': fake_files}
|
|
65
|
-
|
|
66
|
-
result = self.connector.list_files()
|
|
67
|
-
|
|
68
|
-
self.mock_drive_service.files().list.assert_called_with(
|
|
69
|
-
q=f"'{self.folder_id}' in parents and trashed=false",
|
|
70
|
-
fields="files(id, name)"
|
|
71
|
-
)
|
|
72
|
-
self.assertEqual(len(result), 2)
|
|
73
|
-
self.assertEqual(result[0]['path'], 'file1-id')
|
|
74
|
-
self.assertEqual(result[0]['name'], 'file1.txt')
|
|
75
|
-
self.assertEqual(result[1]['path'], 'file2-id')
|
|
76
|
-
self.assertEqual(result[1]['name'], 'file2.txt')
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def test_get_file_content_success(self):
|
|
80
|
-
# Mock del flujo de descarga de archivo
|
|
81
|
-
mock_request = MagicMock()
|
|
82
|
-
self.mock_drive_service.files().get_media.return_value = mock_request
|
|
83
|
-
|
|
84
|
-
mock_downloader = MagicMock()
|
|
85
|
-
with patch('iatoolkit.infra.connectors.google_drive_connector.MediaIoBaseDownload', return_value=mock_downloader):
|
|
86
|
-
# Configurar el buffer simulado
|
|
87
|
-
mock_file_buffer = MagicMock()
|
|
88
|
-
mock_downloader.next_chunk.side_effect = [(None, False), (None, True)] # Simula dos "chunks"
|
|
89
|
-
mock_file_buffer.getvalue.return_value = b"mock file content"
|
|
90
|
-
|
|
91
|
-
with patch('io.BytesIO', return_value=mock_file_buffer):
|
|
92
|
-
# Llamada al método
|
|
93
|
-
result = self.connector.get_file_content('mock-file-id')
|
|
94
|
-
|
|
95
|
-
self.mock_drive_service.files().get_media.assert_called_with(fileId='mock-file-id')
|
|
96
|
-
mock_downloader.next_chunk.assert_called()
|
|
97
|
-
self.assertEqual(result, b"mock file content")
|
|
98
|
-
|
|
99
|
-
def test_get_file_content_when_error(self):
|
|
100
|
-
self.mock_drive_service.files().get_media.side_effect = Exception("Mock download error")
|
|
101
|
-
|
|
102
|
-
with self.assertRaises(Exception) as context:
|
|
103
|
-
self.connector.get_file_content('invalid-file-id')
|
|
104
|
-
|
|
105
|
-
self.mock_drive_service.files().get_media.assert_called_once_with(fileId='invalid-file-id')
|
|
106
|
-
self.assertIn("Mock download error", str(context.exception))
|
|
107
|
-
|