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,85 +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
|
-
from unittest.mock import patch, mock_open, call
|
|
9
|
-
from iatoolkit.infra.connectors.local_file_connector import LocalFileConnector
|
|
10
|
-
from iatoolkit.common.exceptions import IAToolkitException
|
|
11
|
-
from datetime import datetime
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class TestLocalFileConnector:
|
|
15
|
-
def setup_method(self):
|
|
16
|
-
self.mock_directory = "/mock/directory"
|
|
17
|
-
self.file_connector = LocalFileConnector(self.mock_directory)
|
|
18
|
-
|
|
19
|
-
@patch("os.listdir", side_effect=Exception("Error al listar directorio"))
|
|
20
|
-
def test_list_files_error(self, mock_listdir):
|
|
21
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
22
|
-
self.file_connector.list_files()
|
|
23
|
-
|
|
24
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.FILE_IO_ERROR
|
|
25
|
-
assert "Error procesando el directorio" in str(excinfo.value)
|
|
26
|
-
mock_listdir.assert_called_once_with(self.mock_directory)
|
|
27
|
-
|
|
28
|
-
@patch("os.path.getmtime")
|
|
29
|
-
@patch("os.path.getsize")
|
|
30
|
-
@patch("os.listdir")
|
|
31
|
-
@patch("os.path.isfile")
|
|
32
|
-
def test_list_files_success(self, mock_isfile, mock_listdir,
|
|
33
|
-
mock_getsize, mock_getmtime):
|
|
34
|
-
# Configurar mocks
|
|
35
|
-
mock_listdir.return_value = ["file1.txt", "file2.pdf", "subdir"]
|
|
36
|
-
mock_isfile.side_effect = lambda path: not path.endswith("subdir")
|
|
37
|
-
mock_getsize.return_value = 100
|
|
38
|
-
mock_getmtime.return_value = datetime(2024, 2, 19, 15, 30)
|
|
39
|
-
|
|
40
|
-
expected_return = [
|
|
41
|
-
{
|
|
42
|
-
'name': "file1.txt", 'path': '/mock/directory/file1.txt',
|
|
43
|
-
'metadata': {'size': 100, 'last_modified': mock_getmtime.return_value}
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
'name': "file2.pdf", 'path': '/mock/directory/file2.pdf',
|
|
47
|
-
'metadata': {'size': 100, 'last_modified': mock_getmtime.return_value}
|
|
48
|
-
}
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
result = self.file_connector.list_files()
|
|
53
|
-
|
|
54
|
-
assert result == expected_return
|
|
55
|
-
mock_listdir.assert_called_once_with(self.mock_directory)
|
|
56
|
-
mock_isfile.assert_has_calls([
|
|
57
|
-
call(os.path.join(self.mock_directory, "file1.txt")),
|
|
58
|
-
call(os.path.join(self.mock_directory, "file2.pdf")),
|
|
59
|
-
call(os.path.join(self.mock_directory, "subdir")),
|
|
60
|
-
])
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@patch("builtins.open", side_effect=Exception("Error al abrir el archivo"))
|
|
64
|
-
def test_get_file_content_error(self, mock_open_file):
|
|
65
|
-
"""Prueba para verificar que `get_file_content` lanza una excepción en caso de error."""
|
|
66
|
-
mock_file_path = os.path.join(self.mock_directory, "file1.txt")
|
|
67
|
-
|
|
68
|
-
# Verificar que se lanza la excepción esperada
|
|
69
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
70
|
-
self.file_connector.get_file_content(mock_file_path)
|
|
71
|
-
|
|
72
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.FILE_IO_ERROR
|
|
73
|
-
assert "Error leyendo el archivo" in str(excinfo.value)
|
|
74
|
-
mock_open_file.assert_called_once_with(mock_file_path, "rb")
|
|
75
|
-
|
|
76
|
-
@patch("builtins.open", new_callable=mock_open, read_data=b"file content")
|
|
77
|
-
def test_get_file_content_success(self, mock_open_file):
|
|
78
|
-
mock_file_path = os.path.join(self.mock_directory, "file1.txt")
|
|
79
|
-
|
|
80
|
-
result = self.file_connector.get_file_content(mock_file_path)
|
|
81
|
-
|
|
82
|
-
# Verificaciones
|
|
83
|
-
assert result == b"file content"
|
|
84
|
-
mock_open_file.assert_called_once_with(mock_file_path, "rb")
|
|
85
|
-
|
|
@@ -1,95 +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.s3_connector import S3Connector
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TestS3Connector(unittest.TestCase):
|
|
12
|
-
def setUp(self):
|
|
13
|
-
# Mock de `boto3.client`
|
|
14
|
-
self.boto3_client_patch = patch('iatoolkit.infra.connectors.s3_connector.boto3.client')
|
|
15
|
-
self.mock_boto3_client = self.boto3_client_patch.start()
|
|
16
|
-
|
|
17
|
-
# Mock del cliente S3
|
|
18
|
-
self.mock_s3_client = MagicMock()
|
|
19
|
-
self.mock_boto3_client.return_value = self.mock_s3_client
|
|
20
|
-
|
|
21
|
-
# Configuración básica
|
|
22
|
-
self.bucket = "test-bucket"
|
|
23
|
-
self.prefix = "test-prefix/"
|
|
24
|
-
self.auth = {
|
|
25
|
-
"aws_access_key_id": "mock-key",
|
|
26
|
-
"aws_secret_access_key": "mock-secret",
|
|
27
|
-
"region_name": "us-east-1"
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
# Crear una instancia del conector con los mocks
|
|
31
|
-
self.connector = S3Connector(bucket=self.bucket, prefix=self.prefix, auth=self.auth)
|
|
32
|
-
|
|
33
|
-
def tearDown(self):
|
|
34
|
-
self.boto3_client_patch.stop()
|
|
35
|
-
|
|
36
|
-
def test_init_when_boto3_client_error(self):
|
|
37
|
-
self.mock_boto3_client.side_effect = Exception("Failed to create boto3 client")
|
|
38
|
-
|
|
39
|
-
with self.assertRaises(Exception) as context:
|
|
40
|
-
S3Connector(bucket=self.bucket, prefix=self.prefix, auth=self.auth)
|
|
41
|
-
|
|
42
|
-
# Validar que el mensaje de la excepción coincide
|
|
43
|
-
self.assertEqual(str(context.exception), "Failed to create boto3 client")
|
|
44
|
-
|
|
45
|
-
def test_list_files_empty(self):
|
|
46
|
-
self.mock_s3_client.list_objects_v2.return_value = {}
|
|
47
|
-
|
|
48
|
-
result = self.connector.list_files()
|
|
49
|
-
self.assertEqual(result, [])
|
|
50
|
-
|
|
51
|
-
def test_list_files_success(self):
|
|
52
|
-
# Simulación de respuesta de `list_objects_v2`
|
|
53
|
-
self.mock_s3_client.list_objects_v2.return_value = {
|
|
54
|
-
"Contents": [
|
|
55
|
-
{
|
|
56
|
-
"Key": "test-prefix/file1.txt",
|
|
57
|
-
"Size": 1024,
|
|
58
|
-
"LastModified": "2023-01-01T12:00:00.000Z"
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"Key": "test-prefix/file2.txt",
|
|
62
|
-
"Size": 2048,
|
|
63
|
-
"LastModified": "2023-01-02T12:00:00.000Z"
|
|
64
|
-
}
|
|
65
|
-
]
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
result = self.connector.list_files()
|
|
69
|
-
|
|
70
|
-
self.assertEqual(len(result), 2)
|
|
71
|
-
self.assertEqual(result[0]['path'], "test-prefix/file1.txt")
|
|
72
|
-
self.assertEqual(result[0]['name'], "file1.txt")
|
|
73
|
-
self.assertEqual(result[0]['metadata']['size'], 1024)
|
|
74
|
-
self.assertEqual(result[0]['metadata']['last_modified'], "2023-01-01T12:00:00.000Z")
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def test_get_file_content_not_found(self):
|
|
78
|
-
# Simulación de excepción de archivo no encontrado
|
|
79
|
-
self.mock_s3_client.get_object.side_effect = Exception("NoSuchKey")
|
|
80
|
-
|
|
81
|
-
# Llamada al método y capturar excepción
|
|
82
|
-
with self.assertRaises(Exception) as context:
|
|
83
|
-
self.connector.get_file_content("test-prefix/nonexistent.txt")
|
|
84
|
-
|
|
85
|
-
# Validar la excepción
|
|
86
|
-
self.assertEqual(str(context.exception), "NoSuchKey")
|
|
87
|
-
|
|
88
|
-
def test_get_file_content_success(self):
|
|
89
|
-
# Simulación de respuesta de `get_object`
|
|
90
|
-
mock_body = MagicMock()
|
|
91
|
-
mock_body.read.return_value = b"mock file content"
|
|
92
|
-
self.mock_s3_client.get_object.return_value = {"Body": mock_body}
|
|
93
|
-
|
|
94
|
-
result = self.connector.get_file_content("test-prefix/file1.txt")
|
|
95
|
-
self.assertEqual(result, b"mock file content")
|
tests/infra/test_call_service.py
DELETED
|
@@ -1,92 +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 patch, MagicMock
|
|
8
|
-
from iatoolkit.infra.call_service import CallServiceClient
|
|
9
|
-
from requests import RequestException
|
|
10
|
-
from iatoolkit.common.exceptions import IAToolkitException
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class TestCallServiceClient:
|
|
14
|
-
|
|
15
|
-
def setup_method(self):
|
|
16
|
-
self.client = CallServiceClient()
|
|
17
|
-
self.endpoint = "http://fake-endpoint.com/api/test"
|
|
18
|
-
self.mock_response = MagicMock()
|
|
19
|
-
self.mock_response.json.return_value = {'result': 'ok'}
|
|
20
|
-
self.mock_response.status_code = 200
|
|
21
|
-
|
|
22
|
-
# Patch 'requests' methods
|
|
23
|
-
self.get_patcher = patch('iatoolkit.infra.call_service.requests.get', return_value=self.mock_response)
|
|
24
|
-
self.post_patcher = patch('iatoolkit.infra.call_service.requests.post', return_value=self.mock_response)
|
|
25
|
-
self.put_patcher = patch('iatoolkit.infra.call_service.requests.put', return_value=self.mock_response)
|
|
26
|
-
self.patch_patcher = patch('iatoolkit.infra.call_service.requests.patch', return_value=self.mock_response)
|
|
27
|
-
self.delete_patcher = patch('iatoolkit.infra.call_service.requests.delete', return_value=self.mock_response)
|
|
28
|
-
|
|
29
|
-
# Start patching
|
|
30
|
-
self.mock_get = self.get_patcher.start()
|
|
31
|
-
self.mock_post = self.post_patcher.start()
|
|
32
|
-
self.mock_put = self.put_patcher.start()
|
|
33
|
-
self.mock_patch = self.patch_patcher.start()
|
|
34
|
-
self.mock_delete = self.delete_patcher.start()
|
|
35
|
-
|
|
36
|
-
def teardown_method(self):
|
|
37
|
-
patch.stopall()
|
|
38
|
-
|
|
39
|
-
def test_get_success(self):
|
|
40
|
-
response, status = self.client.get(self.endpoint)
|
|
41
|
-
self.mock_get.assert_called_once_with(self.endpoint,
|
|
42
|
-
headers= {'Content-Type': 'application/json'},
|
|
43
|
-
params=None, timeout=(10, 10.0))
|
|
44
|
-
assert status == 200
|
|
45
|
-
assert response == {'result': 'ok'}
|
|
46
|
-
|
|
47
|
-
def test_get_failure(self):
|
|
48
|
-
self.mock_get.side_effect = RequestException("Failed GET")
|
|
49
|
-
|
|
50
|
-
with pytest.raises(IAToolkitException) as exc_info:
|
|
51
|
-
self.client.get(self.endpoint)
|
|
52
|
-
|
|
53
|
-
assert exc_info.value.error_type == IAToolkitException.ErrorType.REQUEST_ERROR
|
|
54
|
-
|
|
55
|
-
def test_post_success(self):
|
|
56
|
-
json_dict = {'key': 'value'}
|
|
57
|
-
response, status = self.client.post(self.endpoint, json_dict)
|
|
58
|
-
self.mock_post.assert_called_once_with(self.endpoint, json=json_dict,
|
|
59
|
-
headers=self.client.headers,
|
|
60
|
-
params=None,
|
|
61
|
-
timeout=(10, 10.0))
|
|
62
|
-
assert status == 200
|
|
63
|
-
assert response == {'result': 'ok'}
|
|
64
|
-
|
|
65
|
-
def test_post_failure(self):
|
|
66
|
-
self.mock_post.side_effect = RequestException("Failed POST")
|
|
67
|
-
|
|
68
|
-
with pytest.raises(IAToolkitException) as exc_info:
|
|
69
|
-
self.client.post(self.endpoint, {'data': 'test'})
|
|
70
|
-
|
|
71
|
-
assert exc_info.value.error_type == IAToolkitException.ErrorType.REQUEST_ERROR
|
|
72
|
-
|
|
73
|
-
def test_put_success(self):
|
|
74
|
-
json_dict = {'key': 'updated'}
|
|
75
|
-
response, status = self.client.put(self.endpoint, json_dict)
|
|
76
|
-
assert status == 200
|
|
77
|
-
assert response == {'result': 'ok'}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def test_delete_success(self):
|
|
81
|
-
json_dict = {'key': 'deleted'}
|
|
82
|
-
response, status = self.client.delete(self.endpoint, json_dict)
|
|
83
|
-
|
|
84
|
-
assert status == 200
|
|
85
|
-
assert response == {'result': 'ok'}
|
|
86
|
-
|
|
87
|
-
def test_post_files_success(self):
|
|
88
|
-
files = {'file': ('filename.txt', 'filecontent')}
|
|
89
|
-
response, status = self.client.post_files(self.endpoint, files)
|
|
90
|
-
assert status == 200
|
|
91
|
-
assert response == {'result': 'ok'}
|
|
92
|
-
|
|
@@ -1,59 +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.repositories.database_manager import DatabaseManager
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TestDatabaseManager(unittest.TestCase):
|
|
12
|
-
|
|
13
|
-
@patch('iatoolkit.repositories.database_manager.create_engine')
|
|
14
|
-
@patch('iatoolkit.repositories.database_manager.sessionmaker')
|
|
15
|
-
@patch('iatoolkit.repositories.database_manager.scoped_session')
|
|
16
|
-
def setUp(self, mock_scoped_session, mock_sessionmaker, mock_create_engine):
|
|
17
|
-
"""Configura mocks para las dependencias principales de DatabaseManager."""
|
|
18
|
-
# Mock del motor y las sesiones
|
|
19
|
-
self.mock_engine = MagicMock()
|
|
20
|
-
mock_create_engine.return_value = self.mock_engine
|
|
21
|
-
|
|
22
|
-
self.mock_sessionmaker = MagicMock()
|
|
23
|
-
mock_sessionmaker.return_value = self.mock_sessionmaker
|
|
24
|
-
|
|
25
|
-
self.mock_scoped_session = MagicMock()
|
|
26
|
-
mock_scoped_session.return_value = self.mock_scoped_session
|
|
27
|
-
|
|
28
|
-
# Instanciar el DatabaseManager con un mock de URL de base de datos
|
|
29
|
-
self.db_manager = DatabaseManager("sqlite:///:memory:")
|
|
30
|
-
|
|
31
|
-
# Verificaciones iniciales
|
|
32
|
-
mock_create_engine.assert_called_once_with("sqlite:///:memory:", echo=False)
|
|
33
|
-
mock_sessionmaker.assert_called_once_with(bind=self.mock_engine)
|
|
34
|
-
mock_scoped_session.assert_called_once_with(self.mock_sessionmaker)
|
|
35
|
-
|
|
36
|
-
def test_get_session(self):
|
|
37
|
-
"""Prueba que get_session devuelve una nueva sesión."""
|
|
38
|
-
session = self.db_manager.get_session()
|
|
39
|
-
self.mock_scoped_session.assert_called_once()
|
|
40
|
-
self.assertEqual(session, self.mock_scoped_session())
|
|
41
|
-
|
|
42
|
-
@patch('iatoolkit.repositories.database_manager.Base.metadata.create_all')
|
|
43
|
-
def test_create_all(self, mock_create_all):
|
|
44
|
-
"""Prueba que create_all crea las tablas usando la metadata de Base."""
|
|
45
|
-
self.db_manager.create_all()
|
|
46
|
-
mock_create_all.assert_called_once_with(self.mock_engine)
|
|
47
|
-
|
|
48
|
-
@patch('iatoolkit.repositories.database_manager.Base.metadata.drop_all')
|
|
49
|
-
def test_drop_all(self, mock_drop_all):
|
|
50
|
-
"""Prueba que drop_all elimina las tablas usando la metadata de Base."""
|
|
51
|
-
self.db_manager.drop_all()
|
|
52
|
-
mock_drop_all.assert_called_once_with(self.mock_engine)
|
|
53
|
-
|
|
54
|
-
def test_remove_session(self):
|
|
55
|
-
"""Prueba que remove_session limpia la sesión actual."""
|
|
56
|
-
self.db_manager.remove_session()
|
|
57
|
-
self.mock_scoped_session.remove.assert_called_once()
|
|
58
|
-
|
|
59
|
-
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
from unittest.mock import patch, MagicMock
|
|
3
|
-
import uuid
|
|
4
|
-
import json
|
|
5
|
-
|
|
6
|
-
from iatoolkit.infra.gemini_adapter import GeminiAdapter
|
|
7
|
-
from iatoolkit.infra.llm_response import LLMResponse, ToolCall
|
|
8
|
-
from iatoolkit.common.exceptions import IAToolkitException
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TestGeminiAdapter:
|
|
12
|
-
"""Tests para la clase GeminiAdapter."""
|
|
13
|
-
|
|
14
|
-
def setup_method(self):
|
|
15
|
-
"""Configura el entorno de prueba antes de cada test."""
|
|
16
|
-
self.mock_gemini_client = MagicMock()
|
|
17
|
-
self.mock_generative_model = MagicMock()
|
|
18
|
-
self.mock_gemini_client.GenerativeModel.return_value = self.mock_generative_model
|
|
19
|
-
self.adapter = GeminiAdapter(gemini_client=self.mock_gemini_client)
|
|
20
|
-
|
|
21
|
-
patch('iatoolkit.infra.gemini_adapter.uuid.uuid4', return_value=uuid.UUID('12345678-1234-5678-1234-567812345678')).start()
|
|
22
|
-
|
|
23
|
-
self.message_to_dict_patcher = patch('iatoolkit.infra.gemini_adapter.MessageToDict')
|
|
24
|
-
self.mock_message_to_dict = self.message_to_dict_patcher.start()
|
|
25
|
-
|
|
26
|
-
def teardown_method(self):
|
|
27
|
-
patch.stopall()
|
|
28
|
-
|
|
29
|
-
def _create_mock_gemini_response(self, text_content=None, function_call=None, finish_reason="STOP",
|
|
30
|
-
usage_metadata=None):
|
|
31
|
-
"""Crea un objeto de respuesta mock de Gemini de forma robusta."""
|
|
32
|
-
mock_response = MagicMock()
|
|
33
|
-
parts = []
|
|
34
|
-
|
|
35
|
-
if text_content:
|
|
36
|
-
part = MagicMock()
|
|
37
|
-
part.text = text_content
|
|
38
|
-
# Nos aseguramos que el otro atributo no exista para evitar ambigüedad
|
|
39
|
-
del part.function_call
|
|
40
|
-
parts.append(part)
|
|
41
|
-
|
|
42
|
-
if function_call:
|
|
43
|
-
# Crea un mock para el objeto `function_call` y asigna sus atributos directamente.
|
|
44
|
-
mock_fc_obj = MagicMock()
|
|
45
|
-
mock_fc_obj.name = function_call['name']
|
|
46
|
-
mock_fc_obj._pb = "mock_pb" # Simular el objeto protobuf interno
|
|
47
|
-
|
|
48
|
-
# Configura el mock del conversor para que devuelva los args esperados
|
|
49
|
-
self.mock_message_to_dict.return_value = {'args': function_call['args']}
|
|
50
|
-
|
|
51
|
-
part = MagicMock()
|
|
52
|
-
part.function_call = mock_fc_obj
|
|
53
|
-
del part.text
|
|
54
|
-
parts.append(part)
|
|
55
|
-
|
|
56
|
-
mock_candidate = MagicMock()
|
|
57
|
-
mock_candidate.content.parts = parts
|
|
58
|
-
mock_candidate.finish_reason = finish_reason
|
|
59
|
-
mock_response.candidates = [mock_candidate]
|
|
60
|
-
|
|
61
|
-
if usage_metadata:
|
|
62
|
-
mock_response.usage_metadata = MagicMock(**usage_metadata)
|
|
63
|
-
else:
|
|
64
|
-
del mock_response.usage_metadata
|
|
65
|
-
|
|
66
|
-
return mock_response
|
|
67
|
-
|
|
68
|
-
def test_create_response_text_only(self):
|
|
69
|
-
"""Prueba una llamada simple que devuelve solo texto."""
|
|
70
|
-
mock_response = self._create_mock_gemini_response(text_content="Hola mundo")
|
|
71
|
-
self.mock_generative_model.generate_content.return_value = mock_response
|
|
72
|
-
|
|
73
|
-
response = self.adapter.create_response(model="gemini-pro", input=[])
|
|
74
|
-
|
|
75
|
-
assert isinstance(response, LLMResponse)
|
|
76
|
-
assert response.output_text == "Hola mundo"
|
|
77
|
-
assert len(response.output) == 0
|
|
78
|
-
|
|
79
|
-
def test_create_response_text_with_history(self):
|
|
80
|
-
"""Prueba una llamada simple que devuelve solo texto."""
|
|
81
|
-
mock_response = self._create_mock_gemini_response(text_content="Hola mundo")
|
|
82
|
-
self.mock_generative_model.generate_content.return_value = mock_response
|
|
83
|
-
|
|
84
|
-
context_history = [{"role": "user", "content": "Pregunta"}]
|
|
85
|
-
|
|
86
|
-
response = self.adapter.create_response(model="gemini-pro",
|
|
87
|
-
input=[],
|
|
88
|
-
context_history=context_history)
|
|
89
|
-
|
|
90
|
-
assert isinstance(response, LLMResponse)
|
|
91
|
-
assert response.output_text == "Hola mundo"
|
|
92
|
-
assert len(context_history) == 2
|
|
93
|
-
|
|
94
|
-
def test_create_response_with_tool_call(self):
|
|
95
|
-
"""Prueba una llamada que devuelve una function_call."""
|
|
96
|
-
func_call_data = {'name': 'get_weather', 'args': {'location': 'Santiago'}}
|
|
97
|
-
mock_response = self._create_mock_gemini_response(function_call=func_call_data)
|
|
98
|
-
self.mock_generative_model.generate_content.return_value = mock_response
|
|
99
|
-
|
|
100
|
-
response = self.adapter.create_response(model="gemini-flash", input=[], tools=[{}])
|
|
101
|
-
|
|
102
|
-
assert len(response.output) == 1
|
|
103
|
-
tool_call = response.output[0]
|
|
104
|
-
assert isinstance(tool_call, ToolCall)
|
|
105
|
-
assert tool_call.name == "get_weather"
|
|
106
|
-
assert tool_call.arguments == json.dumps(func_call_data['args'])
|
|
107
|
-
self.mock_message_to_dict.assert_called_once_with("mock_pb")
|
|
108
|
-
|
|
109
|
-
def test_history_not_modified_if_no_content_in_response(self):
|
|
110
|
-
"""Prueba que el historial no se modifica si la respuesta está vacía."""
|
|
111
|
-
mock_response = self._create_mock_gemini_response() # Sin texto ni tool calls
|
|
112
|
-
self.mock_generative_model.generate_content.return_value = mock_response
|
|
113
|
-
|
|
114
|
-
context_history = [{"role": "user", "content": "Pregunta"}]
|
|
115
|
-
self.adapter.create_response(model="gemini-pro", input=[], context_history=context_history)
|
|
116
|
-
|
|
117
|
-
assert len(context_history) == 1 # El historial no debe cambiar
|
|
118
|
-
|
|
119
|
-
@pytest.mark.parametrize("error_msg, expected_app_msg", [
|
|
120
|
-
("Quota exceeded", "Se ha excedido la cuota de la API de Gemini"),
|
|
121
|
-
("Content blocked", "El contenido fue bloqueado"),
|
|
122
|
-
("Invalid token", "Tu consulta supera el límite de contexto de Gemini"),
|
|
123
|
-
("Other API error", "Error calling Gemini API: Other API error"),
|
|
124
|
-
])
|
|
125
|
-
def test_api_error_handling(self, error_msg, expected_app_msg):
|
|
126
|
-
self.mock_generative_model.generate_content.side_effect = Exception(error_msg)
|
|
127
|
-
with pytest.raises(IAToolkitException, match=expected_app_msg):
|
|
128
|
-
self.adapter.create_response(model="gemini-pro", input=[])
|
|
129
|
-
|
|
130
|
-
@pytest.mark.parametrize("finish_reason, expected_status", [
|
|
131
|
-
("SAFETY", "blocked"), ("MAX_TOKENS", "length_exceeded"), ("STOP", "completed")
|
|
132
|
-
])
|
|
133
|
-
def test_map_finish_reason_to_status(self, finish_reason, expected_status):
|
|
134
|
-
mock_response = self._create_mock_gemini_response(finish_reason=finish_reason)
|
|
135
|
-
self.mock_generative_model.generate_content.return_value = mock_response
|
|
136
|
-
response = self.adapter.create_response(model="gemini-pro", input=[])
|
|
137
|
-
assert response.status == expected_status
|
|
@@ -1,68 +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 Mock, patch
|
|
8
|
-
from iatoolkit.infra.google_chat_app import GoogleChatApp
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TestGoogleChatApp:
|
|
12
|
-
@pytest.fixture
|
|
13
|
-
def mock_call_service(self):
|
|
14
|
-
return Mock()
|
|
15
|
-
|
|
16
|
-
@pytest.fixture
|
|
17
|
-
def google_chat_service(self, mock_call_service):
|
|
18
|
-
return GoogleChatApp(mock_call_service)
|
|
19
|
-
|
|
20
|
-
def test_send_message_success(self, google_chat_service, mock_call_service):
|
|
21
|
-
# Configurar el mock
|
|
22
|
-
mock_call_service.post.return_value = ({"status": "sent"}, 200)
|
|
23
|
-
|
|
24
|
-
message_data = {
|
|
25
|
-
"type": "MESSAGE_TRIGGER",
|
|
26
|
-
"space": {
|
|
27
|
-
"name": "spaces/AAQAupQldd4"
|
|
28
|
-
},
|
|
29
|
-
"message": {
|
|
30
|
-
"text": "Test message"
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
with patch.dict('os.environ', {'GOOGLE_CHAT_BOT_URL': 'https://test-bot.com'}):
|
|
35
|
-
result = google_chat_service.send_message(message_data=message_data)
|
|
36
|
-
|
|
37
|
-
assert result['success'] is True
|
|
38
|
-
assert result['message'] == "Mensaje enviado correctamente"
|
|
39
|
-
mock_call_service.post.assert_called_once_with('https://test-bot.com', message_data)
|
|
40
|
-
|
|
41
|
-
def test_send_message_missing_env_var(self, google_chat_service):
|
|
42
|
-
message_data = {
|
|
43
|
-
"type": "MESSAGE_TRIGGER",
|
|
44
|
-
"space": {"name": "spaces/test"},
|
|
45
|
-
"message": {"text": "Test"}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
with patch.dict('os.environ', {}, clear=True):
|
|
49
|
-
result = google_chat_service.send_message(message_data=message_data)
|
|
50
|
-
|
|
51
|
-
assert result['success'] is False
|
|
52
|
-
assert "GOOGLE_CHAT_BOT_URL no está configurada" in result['message']
|
|
53
|
-
|
|
54
|
-
def test_send_message_api_error(self, google_chat_service, mock_call_service):
|
|
55
|
-
# Configurar el mock para simular error
|
|
56
|
-
mock_call_service.post.return_value = ({"error": "API Error"}, 500)
|
|
57
|
-
|
|
58
|
-
message_data = {
|
|
59
|
-
"type": "MESSAGE_TRIGGER",
|
|
60
|
-
"space": {"name": "spaces/test"},
|
|
61
|
-
"message": {"text": "Test"}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
with patch.dict('os.environ', {'GOOGLE_CHAT_BOT_URL': 'https://test-bot.com'}):
|
|
65
|
-
result = google_chat_service.send_message(message_data=message_data)
|
|
66
|
-
|
|
67
|
-
assert result['success'] is False
|
|
68
|
-
assert "Error al enviar mensaje" in result['message']
|