iatoolkit 0.8.1__py3-none-any.whl → 0.63.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of iatoolkit might be problematic. Click here for more details.

Files changed (159) hide show
  1. iatoolkit/__init__.py +8 -34
  2. iatoolkit/base_company.py +14 -3
  3. iatoolkit/common/routes.py +83 -52
  4. iatoolkit/common/session_manager.py +0 -1
  5. iatoolkit/common/util.py +0 -27
  6. iatoolkit/iatoolkit.py +61 -46
  7. iatoolkit/infra/llm_client.py +7 -8
  8. iatoolkit/infra/openai_adapter.py +1 -1
  9. iatoolkit/infra/redis_session_manager.py +48 -2
  10. iatoolkit/repositories/database_manager.py +17 -2
  11. iatoolkit/repositories/models.py +31 -6
  12. iatoolkit/repositories/profile_repo.py +7 -2
  13. iatoolkit/services/auth_service.py +188 -0
  14. iatoolkit/services/branding_service.py +147 -0
  15. iatoolkit/services/dispatcher_service.py +10 -40
  16. iatoolkit/services/excel_service.py +15 -15
  17. iatoolkit/services/history_service.py +3 -12
  18. iatoolkit/services/jwt_service.py +15 -24
  19. iatoolkit/services/onboarding_service.py +43 -0
  20. iatoolkit/services/profile_service.py +97 -44
  21. iatoolkit/services/query_service.py +124 -81
  22. iatoolkit/services/tasks_service.py +1 -1
  23. iatoolkit/services/user_feedback_service.py +67 -31
  24. iatoolkit/services/user_session_context_service.py +112 -54
  25. iatoolkit/static/images/fernando.jpeg +0 -0
  26. iatoolkit/static/js/{chat_feedback.js → chat_feedback_button.js} +6 -11
  27. iatoolkit/static/js/chat_history_button.js +126 -0
  28. iatoolkit/static/js/chat_logout_button.js +36 -0
  29. iatoolkit/static/js/chat_main.js +130 -220
  30. iatoolkit/static/js/chat_onboarding_button.js +97 -0
  31. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  32. iatoolkit/static/js/chat_reload_button.js +52 -0
  33. iatoolkit/static/styles/chat_iatoolkit.css +329 -507
  34. iatoolkit/static/styles/chat_modal.css +95 -56
  35. iatoolkit/static/styles/landing_page.css +182 -0
  36. iatoolkit/static/styles/onboarding.css +169 -0
  37. iatoolkit/system_prompts/query_main.prompt +3 -12
  38. iatoolkit/templates/_company_header.html +20 -0
  39. iatoolkit/templates/_login_widget.html +40 -0
  40. iatoolkit/templates/base.html +8 -3
  41. iatoolkit/templates/change_password.html +54 -37
  42. iatoolkit/templates/chat.html +149 -66
  43. iatoolkit/templates/chat_modals.html +47 -18
  44. iatoolkit/templates/error.html +41 -8
  45. iatoolkit/templates/forgot_password.html +37 -24
  46. iatoolkit/templates/index.html +140 -0
  47. iatoolkit/templates/login_simulation.html +34 -0
  48. iatoolkit/templates/onboarding_shell.html +105 -0
  49. iatoolkit/templates/signup.html +64 -66
  50. iatoolkit/views/base_login_view.py +81 -0
  51. iatoolkit/views/change_password_view.py +23 -12
  52. iatoolkit/views/external_login_view.py +61 -28
  53. iatoolkit/views/{file_store_view.py → file_store_api_view.py} +9 -2
  54. iatoolkit/views/forgot_password_view.py +23 -13
  55. iatoolkit/views/history_api_view.py +52 -0
  56. iatoolkit/views/home_view.py +58 -25
  57. iatoolkit/views/index_view.py +14 -0
  58. iatoolkit/views/init_context_api_view.py +68 -0
  59. iatoolkit/views/llmquery_api_view.py +45 -0
  60. iatoolkit/views/login_simulation_view.py +81 -0
  61. iatoolkit/views/login_view.py +118 -34
  62. iatoolkit/views/logout_api_view.py +45 -0
  63. iatoolkit/views/{prompt_view.py → prompt_api_view.py} +7 -7
  64. iatoolkit/views/signup_view.py +38 -29
  65. iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
  66. iatoolkit/views/tasks_review_api_view.py +55 -0
  67. iatoolkit/views/{user_feedback_view.py → user_feedback_api_view.py} +16 -31
  68. iatoolkit/views/verify_user_view.py +13 -8
  69. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/METADATA +2 -2
  70. iatoolkit-0.63.4.dist-info/RECORD +113 -0
  71. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/top_level.txt +0 -1
  72. iatoolkit/common/auth.py +0 -200
  73. iatoolkit/static/images/arrow_up.png +0 -0
  74. iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
  75. iatoolkit/static/images/logo_clinica.png +0 -0
  76. iatoolkit/static/images/logo_iatoolkit.png +0 -0
  77. iatoolkit/static/images/logo_maxxa.png +0 -0
  78. iatoolkit/static/images/logo_notaria.png +0 -0
  79. iatoolkit/static/images/logo_tarjeta.png +0 -0
  80. iatoolkit/static/images/logo_umayor.png +0 -0
  81. iatoolkit/static/images/upload.png +0 -0
  82. iatoolkit/static/js/chat_history.js +0 -117
  83. iatoolkit/templates/home.html +0 -201
  84. iatoolkit/templates/login.html +0 -43
  85. iatoolkit/views/chat_token_request_view.py +0 -98
  86. iatoolkit/views/chat_view.py +0 -51
  87. iatoolkit/views/download_file_view.py +0 -58
  88. iatoolkit/views/external_chat_login_view.py +0 -88
  89. iatoolkit/views/history_view.py +0 -57
  90. iatoolkit/views/llmquery_view.py +0 -65
  91. iatoolkit/views/tasks_review_view.py +0 -83
  92. iatoolkit-0.8.1.dist-info/RECORD +0 -175
  93. tests/__init__.py +0 -5
  94. tests/common/__init__.py +0 -0
  95. tests/common/test_auth.py +0 -279
  96. tests/common/test_routes.py +0 -42
  97. tests/common/test_session_manager.py +0 -59
  98. tests/common/test_util.py +0 -444
  99. tests/companies/__init__.py +0 -5
  100. tests/conftest.py +0 -36
  101. tests/infra/__init__.py +0 -5
  102. tests/infra/connectors/__init__.py +0 -5
  103. tests/infra/connectors/test_google_drive_connector.py +0 -107
  104. tests/infra/connectors/test_local_file_connector.py +0 -85
  105. tests/infra/connectors/test_s3_connector.py +0 -95
  106. tests/infra/test_call_service.py +0 -92
  107. tests/infra/test_database_manager.py +0 -59
  108. tests/infra/test_gemini_adapter.py +0 -137
  109. tests/infra/test_google_chat_app.py +0 -68
  110. tests/infra/test_llm_client.py +0 -165
  111. tests/infra/test_llm_proxy.py +0 -122
  112. tests/infra/test_mail_app.py +0 -94
  113. tests/infra/test_openai_adapter.py +0 -105
  114. tests/infra/test_redis_session_manager_service.py +0 -117
  115. tests/repositories/__init__.py +0 -5
  116. tests/repositories/test_database_manager.py +0 -87
  117. tests/repositories/test_document_repo.py +0 -76
  118. tests/repositories/test_llm_query_repo.py +0 -340
  119. tests/repositories/test_models.py +0 -38
  120. tests/repositories/test_profile_repo.py +0 -142
  121. tests/repositories/test_tasks_repo.py +0 -76
  122. tests/repositories/test_vs_repo.py +0 -107
  123. tests/services/__init__.py +0 -5
  124. tests/services/test_dispatcher_service.py +0 -274
  125. tests/services/test_document_service.py +0 -181
  126. tests/services/test_excel_service.py +0 -208
  127. tests/services/test_file_processor_service.py +0 -121
  128. tests/services/test_history_service.py +0 -164
  129. tests/services/test_jwt_service.py +0 -255
  130. tests/services/test_load_documents_service.py +0 -112
  131. tests/services/test_mail_service.py +0 -70
  132. tests/services/test_profile_service.py +0 -379
  133. tests/services/test_prompt_manager_service.py +0 -190
  134. tests/services/test_query_service.py +0 -243
  135. tests/services/test_search_service.py +0 -39
  136. tests/services/test_sql_service.py +0 -160
  137. tests/services/test_tasks_service.py +0 -252
  138. tests/services/test_user_feedback_service.py +0 -389
  139. tests/services/test_user_session_context_service.py +0 -132
  140. tests/views/__init__.py +0 -5
  141. tests/views/test_change_password_view.py +0 -191
  142. tests/views/test_chat_token_request_view.py +0 -188
  143. tests/views/test_chat_view.py +0 -98
  144. tests/views/test_download_file_view.py +0 -149
  145. tests/views/test_external_chat_login_view.py +0 -120
  146. tests/views/test_external_login_view.py +0 -102
  147. tests/views/test_file_store_view.py +0 -128
  148. tests/views/test_forgot_password_view.py +0 -142
  149. tests/views/test_history_view.py +0 -336
  150. tests/views/test_home_view.py +0 -61
  151. tests/views/test_llm_query_view.py +0 -154
  152. tests/views/test_login_view.py +0 -114
  153. tests/views/test_prompt_view.py +0 -111
  154. tests/views/test_signup_view.py +0 -140
  155. tests/views/test_tasks_review_view.py +0 -104
  156. tests/views/test_tasks_view.py +0 -130
  157. tests/views/test_user_feedback_view.py +0 -214
  158. tests/views/test_verify_user_view.py +0 -110
  159. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/WHEEL +0 -0
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')
@@ -1,5 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
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,5 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
@@ -1,5 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
@@ -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
-