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,76 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
from iatoolkit.repositories.tasks_repo import TaskRepo
|
|
7
|
-
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
8
|
-
from iatoolkit.repositories.models import Task, TaskType, Company, TaskStatus
|
|
9
|
-
from iatoolkit.repositories.database_manager import DatabaseManager
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestTaskRepo:
|
|
13
|
-
def setup_method(self):
|
|
14
|
-
self.db_manager = DatabaseManager('sqlite:///:memory:')
|
|
15
|
-
self.db_manager.create_all()
|
|
16
|
-
self.session = self.db_manager.get_session()
|
|
17
|
-
self.profile_repo = ProfileRepo(self.db_manager)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
self.company = Company(name='opensoft', short_name='open')
|
|
21
|
-
self.profile_repo.create_company(self.company)
|
|
22
|
-
|
|
23
|
-
self.task_type = TaskType(name="dummy_type")
|
|
24
|
-
self.task = Task(id=1, task_type_id=1, company_id=self.company.id)
|
|
25
|
-
|
|
26
|
-
self.repo = TaskRepo(self.db_manager)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def test_create_task(self):
|
|
30
|
-
result = self.repo.create_task(self.task)
|
|
31
|
-
|
|
32
|
-
task_in_db = self.session.query(Task).filter_by(id=self.task.id).first()
|
|
33
|
-
|
|
34
|
-
assert task_in_db is not None
|
|
35
|
-
assert task_in_db.id == self.task.id
|
|
36
|
-
assert task_in_db.task_type_id == self.task.task_type_id
|
|
37
|
-
assert task_in_db.company_id == self.task.company_id
|
|
38
|
-
assert result == task_in_db
|
|
39
|
-
|
|
40
|
-
def test_update_task(self):
|
|
41
|
-
self.repo.create_task(self.task)
|
|
42
|
-
|
|
43
|
-
# update value
|
|
44
|
-
self.task.client_data = {'key': 'value'}
|
|
45
|
-
result = self.repo.update_task(self.task)
|
|
46
|
-
|
|
47
|
-
task_in_db = self.session.query(Task).filter_by(id=self.task.id).first()
|
|
48
|
-
assert task_in_db.client_data == self.task.client_data
|
|
49
|
-
|
|
50
|
-
def test_create_or_update_task_type_creates_new(self):
|
|
51
|
-
result = self.repo.create_or_update_task_type(self.task_type)
|
|
52
|
-
assert result == self.task_type
|
|
53
|
-
|
|
54
|
-
def test_create_or_update_task_type_updates_existing(self):
|
|
55
|
-
existing_type = TaskType(name="dummy_type", prompt_template="old_template", template_args="old_args")
|
|
56
|
-
self.repo.create_or_update_task_type(existing_type)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
new_type = TaskType(name="dummy_type", prompt_template="new_template", template_args="new_args")
|
|
60
|
-
result = self.repo.create_or_update_task_type(new_type)
|
|
61
|
-
|
|
62
|
-
assert existing_type.prompt_template == "new_template"
|
|
63
|
-
assert existing_type.template_args == "new_args"
|
|
64
|
-
assert result == existing_type
|
|
65
|
-
|
|
66
|
-
def test_get_task_type_when_found(self):
|
|
67
|
-
result = self.repo.get_task_type("dummy_type")
|
|
68
|
-
assert result == None
|
|
69
|
-
|
|
70
|
-
def test_get_pending_tasks(self):
|
|
71
|
-
self.task.status = TaskStatus.pendiente
|
|
72
|
-
self.repo.create_task(self.task)
|
|
73
|
-
|
|
74
|
-
result = self.repo.get_pending_tasks(self.company.id)
|
|
75
|
-
assert len(result) == 1
|
|
76
|
-
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
from unittest.mock import MagicMock, patch
|
|
8
|
-
from iatoolkit.common.exceptions import IAToolkitException
|
|
9
|
-
from iatoolkit.repositories.vs_repo import VSRepo
|
|
10
|
-
from iatoolkit.repositories.models import VSDoc, Document
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class TestVSRepo:
|
|
14
|
-
@pytest.fixture
|
|
15
|
-
def mock_db_manager(self):
|
|
16
|
-
"""Fixture para mockear el DatabaseManager."""
|
|
17
|
-
mock_manager = MagicMock()
|
|
18
|
-
mock_session = MagicMock()
|
|
19
|
-
mock_manager.get_session.return_value = mock_session
|
|
20
|
-
return mock_manager
|
|
21
|
-
|
|
22
|
-
@pytest.fixture
|
|
23
|
-
def mock_embedder(self):
|
|
24
|
-
"""Fixture para mockear el modelo de SentenceTransformer."""
|
|
25
|
-
with patch('iatoolkit.repositories.vs_repo.InferenceClient') as MockEmbedder:
|
|
26
|
-
mock_instance = MockEmbedder.return_value
|
|
27
|
-
mock_instance.feature_extraction.return_value = [0.1, 0.2, 0.3] # Retorna un embedding simulado
|
|
28
|
-
yield mock_instance
|
|
29
|
-
|
|
30
|
-
@pytest.fixture
|
|
31
|
-
def vs_repo(self, mock_db_manager, mock_embedder):
|
|
32
|
-
"""Fixture para inicializar el repositorio VSRepo con dependencias mockeadas."""
|
|
33
|
-
return VSRepo(mock_db_manager)
|
|
34
|
-
|
|
35
|
-
def test_add_document_rollback_on_error(self, vs_repo, mock_db_manager, mock_embedder):
|
|
36
|
-
"""Prueba que verifica el rollback cuando ocurre un error en add_document."""
|
|
37
|
-
mock_session = mock_db_manager.get_session.return_value
|
|
38
|
-
|
|
39
|
-
mock_embedder.feature_extraction.side_effect = Exception("Error al generar embeddings")
|
|
40
|
-
vs_chunk_list = [VSDoc(id=1, text="Documento con error")]
|
|
41
|
-
|
|
42
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
43
|
-
vs_repo.add_document(vs_chunk_list)
|
|
44
|
-
|
|
45
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.VECTOR_STORE_ERROR
|
|
46
|
-
assert "Error insertando documentos en PostgreSQL" in str(excinfo.value)
|
|
47
|
-
mock_session.rollback.assert_called_once()
|
|
48
|
-
|
|
49
|
-
def test_add_document_success(self, vs_repo, mock_db_manager, mock_embedder):
|
|
50
|
-
mock_session = mock_db_manager.get_session.return_value
|
|
51
|
-
|
|
52
|
-
vs_chunk_list = [
|
|
53
|
-
VSDoc(id=1, text="Documento de prueba 1"),
|
|
54
|
-
VSDoc(id=2, text="Documento de prueba 2")
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
vs_repo.add_document(vs_chunk_list)
|
|
58
|
-
|
|
59
|
-
# Verificar que las embeddings se generaron y los documentos fueron añadidos
|
|
60
|
-
assert mock_embedder.feature_extraction.call_count == len(vs_chunk_list)
|
|
61
|
-
assert mock_session.add.call_count == len(vs_chunk_list)
|
|
62
|
-
mock_session.commit.assert_called_once()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def test_query_raises_exception_on_error(self, vs_repo, mock_db_manager, mock_embedder):
|
|
66
|
-
mock_session = mock_db_manager.get_session.return_value
|
|
67
|
-
mock_session.execute.side_effect = Exception("Error en la base de datos")
|
|
68
|
-
|
|
69
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
70
|
-
vs_repo.query(company_id=123, query_text="texto de prueba", n_results=3)
|
|
71
|
-
|
|
72
|
-
assert excinfo.value.error_type == IAToolkitException.ErrorType.VECTOR_STORE_ERROR
|
|
73
|
-
assert "Error en la consulta" in str(excinfo.value)
|
|
74
|
-
|
|
75
|
-
def test_query_success(self, vs_repo, mock_db_manager, mock_embedder):
|
|
76
|
-
mock_session = mock_db_manager.get_session.return_value
|
|
77
|
-
|
|
78
|
-
# Simular resultados de la consulta en la base de datos
|
|
79
|
-
mock_session.execute.return_value.fetchall.return_value = [
|
|
80
|
-
(1, "filename1.txt", "contenido1", 'conb64'),
|
|
81
|
-
(2, "filename2.txt", "contenido2", 'conb64')
|
|
82
|
-
]
|
|
83
|
-
|
|
84
|
-
result = vs_repo.query(company_id=123, query_text="prompt_llm.txt de prueba", n_results=2)
|
|
85
|
-
|
|
86
|
-
# Verificar resultados
|
|
87
|
-
assert len(result) == 2
|
|
88
|
-
assert result[0].id == 1
|
|
89
|
-
assert result[0].filename == "filename1.txt"
|
|
90
|
-
assert result[0].content == "contenido1"
|
|
91
|
-
mock_embedder.feature_extraction.assert_called_once_with(["prompt_llm.txt de prueba"])
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def test_remove_duplicates_by_id(self, vs_repo):
|
|
95
|
-
"""Prueba para verificar la eliminación de duplicados por ID."""
|
|
96
|
-
documents = [
|
|
97
|
-
Document(id=1, company_id=123, filename="doc1.txt", content="contenido1", content_b64=''),
|
|
98
|
-
Document(id=2, company_id=123, filename="doc2.txt", content="contenido2", content_b64=''),
|
|
99
|
-
Document(id=1, company_id=123, filename="doc1.txt", content="contenido1", content_b64=''), # Duplicado
|
|
100
|
-
]
|
|
101
|
-
|
|
102
|
-
result = vs_repo.remove_duplicates_by_id(documents)
|
|
103
|
-
|
|
104
|
-
# Verificar que solo queden 2 documentos
|
|
105
|
-
assert len(result) == 2
|
|
106
|
-
assert result[0].id == 1
|
|
107
|
-
assert result[1].id == 2
|
tests/services/__init__.py
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
from unittest.mock import MagicMock, patch
|
|
6
|
-
from injector import Injector
|
|
7
|
-
from iatoolkit.base_company import BaseCompany
|
|
8
|
-
from iatoolkit.company_registry import get_company_registry, register_company
|
|
9
|
-
from iatoolkit.services.dispatcher_service import Dispatcher
|
|
10
|
-
from iatoolkit.common.exceptions import IAToolkitException
|
|
11
|
-
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
12
|
-
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
13
|
-
from iatoolkit.repositories.models import Company, Function
|
|
14
|
-
from iatoolkit.services.excel_service import ExcelService
|
|
15
|
-
from iatoolkit.services.prompt_manager_service import PromptService
|
|
16
|
-
from iatoolkit.services.mail_service import MailService
|
|
17
|
-
from iatoolkit.common.util import Utility
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# A mock company class for testing purposes
|
|
21
|
-
class MockSampleCompany(BaseCompany):
|
|
22
|
-
def register_company(self): pass
|
|
23
|
-
|
|
24
|
-
def get_company_context(self, **kwargs) -> str: return "Company Context for Sample"
|
|
25
|
-
|
|
26
|
-
def handle_request(self, tag: str, params: dict) -> dict: return {"result": "sample_company_response"}
|
|
27
|
-
|
|
28
|
-
def start_execution(self): pass
|
|
29
|
-
|
|
30
|
-
def get_user_info(self, user_identifier: str): pass
|
|
31
|
-
|
|
32
|
-
def get_metadata_from_filename(self, filename: str) -> dict: return {}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class TestDispatcher:
|
|
36
|
-
@pytest.fixture(autouse=True)
|
|
37
|
-
def setup(self):
|
|
38
|
-
"""Set up mocks, registry, and the Dispatcher for tests."""
|
|
39
|
-
# Clean up the registry before each test to prevent interference
|
|
40
|
-
registry = get_company_registry()
|
|
41
|
-
registry.clear()
|
|
42
|
-
|
|
43
|
-
# Mocks for services that are injected into the Dispatcher
|
|
44
|
-
self.mock_prompt_manager = MagicMock()
|
|
45
|
-
self.mock_llm_query_repo = MagicMock(spec=LLMQueryRepo)
|
|
46
|
-
self.excel_service = MagicMock(spec=ExcelService)
|
|
47
|
-
self.mail_service = MagicMock(spec=MailService)
|
|
48
|
-
self.util = MagicMock(spec=Utility)
|
|
49
|
-
self.mock_profile_repo = MagicMock(spec=ProfileRepo)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
# Create a mock injector that will be used for instantiation.
|
|
53
|
-
mock_injector = Injector()
|
|
54
|
-
mock_injector.binder.bind(ProfileRepo, to=self.mock_profile_repo)
|
|
55
|
-
mock_injector.binder.bind(LLMQueryRepo, to=self.mock_llm_query_repo)
|
|
56
|
-
mock_injector.binder.bind(PromptService, to=self.mock_prompt_manager)
|
|
57
|
-
|
|
58
|
-
# Create a mock IAToolkit instance that returns our injector.
|
|
59
|
-
self.toolkit_mock = MagicMock()
|
|
60
|
-
self.toolkit_mock.get_injector.return_value = mock_injector
|
|
61
|
-
|
|
62
|
-
# Patch IAToolkit.get_instance() to return our mock toolkit. This must be active
|
|
63
|
-
# BEFORE any code that depends on the IAToolkit singleton is run.
|
|
64
|
-
self.get_instance_patcher = patch('iatoolkit.iatoolkit.IAToolkit.get_instance',
|
|
65
|
-
return_value=self.toolkit_mock)
|
|
66
|
-
self.get_instance_patcher.start()
|
|
67
|
-
|
|
68
|
-
# Now we can safely instantiate our mock company.
|
|
69
|
-
self.mock_sample_company_instance = MockSampleCompany()
|
|
70
|
-
|
|
71
|
-
# Mock methods that will be called
|
|
72
|
-
self.mock_sample_company_instance.register_company = MagicMock()
|
|
73
|
-
self.mock_sample_company_instance.handle_request = MagicMock(return_value={"result": "sample_company_response"})
|
|
74
|
-
self.mock_sample_company_instance.get_company_context = MagicMock(return_value="Company Context for Sample")
|
|
75
|
-
self.mock_sample_company_instance.start_execution = MagicMock(return_value=True)
|
|
76
|
-
self.mock_sample_company_instance.get_user_info = MagicMock(return_value={"email": "test@user.com"})
|
|
77
|
-
self.mock_sample_company_instance.get_metadata_from_filename = MagicMock(return_value={"meta": "data"})
|
|
78
|
-
|
|
79
|
-
# Register the mock company class
|
|
80
|
-
register_company("sample", MockSampleCompany)
|
|
81
|
-
|
|
82
|
-
# Bind the mock instance in our injector. When the registry asks for an instance of
|
|
83
|
-
# MockSampleCompany, the injector will return our pre-configured mock instance.
|
|
84
|
-
mock_injector.binder.bind(MockSampleCompany, to=self.mock_sample_company_instance)
|
|
85
|
-
|
|
86
|
-
# Instantiate all registered companies. The registry will use our mock_injector.
|
|
87
|
-
registry.instantiate_companies(mock_injector)
|
|
88
|
-
|
|
89
|
-
# Initialize the Dispatcher within the patched context
|
|
90
|
-
self.dispatcher = Dispatcher(
|
|
91
|
-
prompt_service=self.mock_prompt_manager,
|
|
92
|
-
llmquery_repo=self.mock_llm_query_repo,
|
|
93
|
-
util=self.util,
|
|
94
|
-
excel_service=self.excel_service,
|
|
95
|
-
mail_service=self.mail_service,
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def teardown_method(self, method):
|
|
100
|
-
"""Clean up patches after each test."""
|
|
101
|
-
if hasattr(self, 'get_instance_patcher'):
|
|
102
|
-
self.get_instance_patcher.stop()
|
|
103
|
-
|
|
104
|
-
# Clean up the registry
|
|
105
|
-
registry = get_company_registry()
|
|
106
|
-
registry.clear()
|
|
107
|
-
|
|
108
|
-
def test_dispatch_sample_company(self):
|
|
109
|
-
"""Tests that dispatch works correctly for a valid company."""
|
|
110
|
-
result = self.dispatcher.dispatch("sample", "some_data", key='a value')
|
|
111
|
-
self.mock_sample_company_instance.handle_request.assert_called_once_with("some_data", key='a value')
|
|
112
|
-
assert result == {"result": "sample_company_response"}
|
|
113
|
-
|
|
114
|
-
def test_dispatch_invalid_company(self):
|
|
115
|
-
"""Tests that dispatch raises an exception for an unconfigured company."""
|
|
116
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
117
|
-
self.dispatcher.dispatch("invalid_company", "some_tag")
|
|
118
|
-
assert "Empresa 'invalid_company' no configurada" in str(excinfo.value)
|
|
119
|
-
|
|
120
|
-
def test_dispatch_method_exception(self):
|
|
121
|
-
"""Validates that the dispatcher handles exceptions thrown by companies."""
|
|
122
|
-
self.mock_sample_company_instance.handle_request.side_effect = Exception("Method error")
|
|
123
|
-
|
|
124
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
125
|
-
self.dispatcher.dispatch("sample", "some_data")
|
|
126
|
-
|
|
127
|
-
assert "Error en function call 'some_data'" in str(excinfo.value)
|
|
128
|
-
assert "Method error" in str(excinfo.value)
|
|
129
|
-
|
|
130
|
-
def test_dispatch_system_function(self):
|
|
131
|
-
"""Tests that dispatch correctly handles system functions."""
|
|
132
|
-
self.excel_service.excel_generator.return_value = {"file": "test.xlsx"}
|
|
133
|
-
|
|
134
|
-
result = self.dispatcher.dispatch("sample", "iat_generate_excel", filename="test.xlsx")
|
|
135
|
-
|
|
136
|
-
self.excel_service.excel_generator.assert_called_once_with(filename="test.xlsx")
|
|
137
|
-
self.mock_sample_company_instance.handle_request.assert_not_called()
|
|
138
|
-
assert result == {"file": "test.xlsx"}
|
|
139
|
-
|
|
140
|
-
def test_get_company_context(self):
|
|
141
|
-
"""Tests that get_company_context works correctly."""
|
|
142
|
-
# Simulate no context files to simplify
|
|
143
|
-
self.util.get_files_by_extension.return_value = []
|
|
144
|
-
|
|
145
|
-
params = {"param1": "value1"}
|
|
146
|
-
result = self.dispatcher.get_company_context("sample", **params)
|
|
147
|
-
|
|
148
|
-
self.mock_sample_company_instance.get_company_context.assert_called_once_with(**params)
|
|
149
|
-
assert "Company Context for Sample" in result
|
|
150
|
-
|
|
151
|
-
def test_get_company_instance(self):
|
|
152
|
-
"""Tests that get_company_instance returns the correct company instance."""
|
|
153
|
-
instance = self.dispatcher.get_company_instance("sample")
|
|
154
|
-
assert instance == self.mock_sample_company_instance
|
|
155
|
-
|
|
156
|
-
instance_none = self.dispatcher.get_company_instance("non_existent")
|
|
157
|
-
assert instance_none is None
|
|
158
|
-
|
|
159
|
-
def test_get_metadata_from_filename_success(self):
|
|
160
|
-
"""Tests that get_metadata_from_filename successfully calls the company's method."""
|
|
161
|
-
filename = "test.txt"
|
|
162
|
-
expected_metadata = {"meta": "data"}
|
|
163
|
-
self.mock_sample_company_instance.get_metadata_from_filename.return_value = expected_metadata
|
|
164
|
-
|
|
165
|
-
result = self.dispatcher.get_metadata_from_filename("sample", filename)
|
|
166
|
-
|
|
167
|
-
self.mock_sample_company_instance.get_metadata_from_filename.assert_called_once_with(filename)
|
|
168
|
-
assert result == expected_metadata
|
|
169
|
-
|
|
170
|
-
def test_get_metadata_from_filename_invalid_company(self):
|
|
171
|
-
"""Tests get_metadata_from_filename with an invalid company."""
|
|
172
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
173
|
-
self.dispatcher.get_metadata_from_filename("invalid_company", "test.txt")
|
|
174
|
-
assert "Empresa no configurada: invalid_company" in str(excinfo.value)
|
|
175
|
-
|
|
176
|
-
def test_get_metadata_from_filename_company_exception(self):
|
|
177
|
-
"""Tests get_metadata_from_filename when the company method raises an exception."""
|
|
178
|
-
self.mock_sample_company_instance.get_metadata_from_filename.side_effect = Exception("Company error")
|
|
179
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
180
|
-
self.dispatcher.get_metadata_from_filename("sample", "test.txt")
|
|
181
|
-
assert "Error en get_metadata_from_filename de sample" in str(excinfo.value)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def test_get_user_info_external_user(self):
|
|
185
|
-
"""Tests get_user_info for an external user."""
|
|
186
|
-
user_identifier = "ext_user_123"
|
|
187
|
-
expected_user_data = {"email": "external@example.com"}
|
|
188
|
-
self.mock_sample_company_instance.get_user_info.return_value = expected_user_data
|
|
189
|
-
|
|
190
|
-
result = self.dispatcher.get_user_info("sample", user_identifier, is_local_user=False)
|
|
191
|
-
|
|
192
|
-
self.mock_sample_company_instance.get_user_info.assert_called_once_with(user_identifier)
|
|
193
|
-
assert result["user_email"] == "external@example.com"
|
|
194
|
-
assert not result["is_local"]
|
|
195
|
-
|
|
196
|
-
def test_get_user_info_external_user_company_exception(self):
|
|
197
|
-
"""Tests get_user_info for an external user when the company method fails."""
|
|
198
|
-
self.mock_sample_company_instance.get_user_info.side_effect = Exception("DB error")
|
|
199
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
200
|
-
self.dispatcher.get_user_info("sample", "ext_user_123", is_local_user=False)
|
|
201
|
-
assert "Error en get_user_info de sample" in str(excinfo.value)
|
|
202
|
-
|
|
203
|
-
@patch('iatoolkit.services.dispatcher_service.SessionManager')
|
|
204
|
-
def test_get_user_info_local_user(self, mock_session_manager):
|
|
205
|
-
"""Tests get_user_info for a local user from session."""
|
|
206
|
-
user_identifier = "local_user_1"
|
|
207
|
-
session_data = {"email": "local@iatoolkit.com", "user_fullname": "Local User"}
|
|
208
|
-
mock_session_manager.get.return_value = session_data
|
|
209
|
-
|
|
210
|
-
result = self.dispatcher.get_user_info("sample", user_identifier, is_local_user=True)
|
|
211
|
-
|
|
212
|
-
mock_session_manager.get.assert_called_once_with('user', {})
|
|
213
|
-
self.mock_sample_company_instance.get_user_info.assert_not_called()
|
|
214
|
-
assert result["user_email"] == "local@iatoolkit.com"
|
|
215
|
-
assert result["user_fullname"] == "Local User"
|
|
216
|
-
assert result["is_local"]
|
|
217
|
-
|
|
218
|
-
def test_get_user_info_invalid_company(self):
|
|
219
|
-
"""Tests get_user_info with an invalid company."""
|
|
220
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
221
|
-
self.dispatcher.get_user_info("invalid_company", "any_user", is_local_user=False)
|
|
222
|
-
assert "Empresa no configurada: invalid_company" in str(excinfo.value)
|
|
223
|
-
|
|
224
|
-
def test_get_company_services(self):
|
|
225
|
-
"""Tests that get_company_services returns a correctly formatted list of tools."""
|
|
226
|
-
# Mock Company and Function objects
|
|
227
|
-
mock_company = MagicMock(spec=Company)
|
|
228
|
-
mock_function = MagicMock(spec=Function)
|
|
229
|
-
mock_function.name = "test_function"
|
|
230
|
-
mock_function.description = "A test function"
|
|
231
|
-
mock_function.parameters = {"type": "object", "properties": {}}
|
|
232
|
-
|
|
233
|
-
self.mock_llm_query_repo.get_company_functions.return_value = [mock_function]
|
|
234
|
-
|
|
235
|
-
tools = self.dispatcher.get_company_services(mock_company)
|
|
236
|
-
|
|
237
|
-
self.mock_llm_query_repo.get_company_functions.assert_called_once_with(mock_company)
|
|
238
|
-
assert len(tools) == 1
|
|
239
|
-
tool = tools[0]
|
|
240
|
-
assert tool["type"] == "function"
|
|
241
|
-
assert tool["name"] == "test_function"
|
|
242
|
-
assert tool["description"] == "A test function"
|
|
243
|
-
assert tool["parameters"]["additionalProperties"] is False
|
|
244
|
-
assert tool["strict"] is True
|
|
245
|
-
|
|
246
|
-
def test_dispatcher_with_no_companies_registered(self):
|
|
247
|
-
"""Tests that the dispatcher works if no company is registered."""
|
|
248
|
-
# Stop the current patch first
|
|
249
|
-
self.get_instance_patcher.stop()
|
|
250
|
-
|
|
251
|
-
# Clean registry
|
|
252
|
-
get_company_registry().clear()
|
|
253
|
-
|
|
254
|
-
toolkit_mock = MagicMock()
|
|
255
|
-
toolkit_mock.get_injector.return_value = Injector() # Empty injector
|
|
256
|
-
|
|
257
|
-
# Start a new patch for this specific test
|
|
258
|
-
with patch('iatoolkit.iatoolkit.IAToolkit.get_instance', return_value=toolkit_mock):
|
|
259
|
-
dispatcher = Dispatcher(
|
|
260
|
-
prompt_service=self.mock_prompt_manager,
|
|
261
|
-
llmquery_repo=self.mock_llm_query_repo,
|
|
262
|
-
util=self.util,
|
|
263
|
-
excel_service=self.excel_service,
|
|
264
|
-
mail_service=self.mail_service,
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
assert len(dispatcher.company_instances) == 0
|
|
268
|
-
|
|
269
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
270
|
-
dispatcher.dispatch("any_company", "some_action")
|
|
271
|
-
assert "Empresa 'any_company' no configurada" in str(excinfo.value)
|
|
272
|
-
|
|
273
|
-
# Restart the main patch for subsequent tests
|
|
274
|
-
self.get_instance_patcher.start()
|
|
@@ -1,181 +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.services.document_service import DocumentService
|
|
9
|
-
from iatoolkit.common.exceptions import IAToolkitException
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestDocumentService:
|
|
13
|
-
|
|
14
|
-
@pytest.fixture(autouse=True)
|
|
15
|
-
def setup_env_vars(self, monkeypatch):
|
|
16
|
-
"""
|
|
17
|
-
Configura las variables de entorno necesarias para DocumentService
|
|
18
|
-
antes de cada test.
|
|
19
|
-
"""
|
|
20
|
-
monkeypatch.setenv("MAX_DOC_PAGES", "10")
|
|
21
|
-
|
|
22
|
-
def test_initialization(self):
|
|
23
|
-
"""Prueba la inicialización de DocumentService con configuración correcta."""
|
|
24
|
-
service = DocumentService()
|
|
25
|
-
assert service.max_doc_pages == 10
|
|
26
|
-
|
|
27
|
-
def test_file_txt_when_binary_content(self):
|
|
28
|
-
service = DocumentService()
|
|
29
|
-
result = service.file_to_txt("test.txt", b"dummy_content")
|
|
30
|
-
assert result == "dummy_content"
|
|
31
|
-
|
|
32
|
-
def test_file_txt_when_binary_content_and_error_decoding(self):
|
|
33
|
-
service = DocumentService()
|
|
34
|
-
with pytest.raises(IAToolkitException) as excinfo:
|
|
35
|
-
result = service.file_to_txt("test.txt", b'\xff\xfe\xff'
|
|
36
|
-
)
|
|
37
|
-
assert "FILE_FORMAT_ERROR" == excinfo.value.error_type.name
|
|
38
|
-
|
|
39
|
-
def test_file_txt_when_txt_content(self):
|
|
40
|
-
service = DocumentService()
|
|
41
|
-
result = service.file_to_txt("test.txt", "dummy_content")
|
|
42
|
-
assert result == "dummy_content"
|
|
43
|
-
|
|
44
|
-
@patch("iatoolkit.services.document_service.DocumentService.is_scanned_pdf")
|
|
45
|
-
@patch("iatoolkit.services.document_service.DocumentService.read_scanned_pdf", return_value="Scanned text")
|
|
46
|
-
@patch("iatoolkit.services.document_service.DocumentService.read_pdf", return_value="PDF text")
|
|
47
|
-
def test_extension_file_detection(
|
|
48
|
-
self, mock_read_pdf, mock_read_scanned_pdf, mock_is_scanned_pdf):
|
|
49
|
-
|
|
50
|
-
mock_is_scanned_pdf.return_value = True
|
|
51
|
-
service = DocumentService()
|
|
52
|
-
result = service.file_to_txt("test.pdf", "dummy_content")
|
|
53
|
-
|
|
54
|
-
assert result == "Scanned text"
|
|
55
|
-
|
|
56
|
-
mock_is_scanned_pdf.return_value = False
|
|
57
|
-
result = service.file_to_txt("test.pdf", "dummy_content")
|
|
58
|
-
assert result == "PDF text"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@patch("iatoolkit.services.document_service.io")
|
|
62
|
-
@patch("iatoolkit.services.document_service.Document")
|
|
63
|
-
def test_read_docx_successful(self, mock_document, mock_io):
|
|
64
|
-
"""Prueba que un archivo .docx se lea correctamente."""
|
|
65
|
-
mock_io.BytesIO.return_value = b'a file'
|
|
66
|
-
mock_doc = MagicMock()
|
|
67
|
-
mock_doc.paragraphs = [
|
|
68
|
-
MagicMock(text="First paragraph"),
|
|
69
|
-
MagicMock(text="Second paragraph"),
|
|
70
|
-
]
|
|
71
|
-
mock_document.return_value = mock_doc
|
|
72
|
-
|
|
73
|
-
service = DocumentService()
|
|
74
|
-
content = service.read_docx("dummy_docx_content")
|
|
75
|
-
assert content == "# First paragraph\n\n# Second paragraph\n\n"
|
|
76
|
-
|
|
77
|
-
@patch("iatoolkit.services.document_service.fitz.open")
|
|
78
|
-
def test_read_pdf_successful(self, mock_fitz_open):
|
|
79
|
-
"""Prueba que un archivo PDF con prompt_llm.txt se lea correctamente."""
|
|
80
|
-
mock_pdf = MagicMock()
|
|
81
|
-
mock_pdf.__enter__.return_value = mock_pdf
|
|
82
|
-
mock_pdf.__iter__.return_value = [
|
|
83
|
-
MagicMock(get_text=MagicMock(return_value="Page 1 text")),
|
|
84
|
-
MagicMock(get_text=MagicMock(return_value="Page 2 text")),
|
|
85
|
-
]
|
|
86
|
-
mock_fitz_open.return_value = mock_pdf
|
|
87
|
-
|
|
88
|
-
service = DocumentService()
|
|
89
|
-
content = service.read_pdf("dummy_pdf_content")
|
|
90
|
-
assert content == "Page 1 textPage 2 text"
|
|
91
|
-
|
|
92
|
-
@patch("iatoolkit.services.document_service.pytesseract.image_to_string", return_value="Scanned text")
|
|
93
|
-
@patch("iatoolkit.services.document_service.Image.frombytes")
|
|
94
|
-
@patch("iatoolkit.services.document_service.fitz.Pixmap")
|
|
95
|
-
@patch("iatoolkit.services.document_service.fitz.open")
|
|
96
|
-
def test_read_scanned_pdf(self, mock_fitz_open, mock_pixmap, mock_frombytes, mock_tesseract):
|
|
97
|
-
"""Prueba que un PDF escaneado convierta imágenes en prompt_llm.txt correctamente."""
|
|
98
|
-
mock_pdf = MagicMock()
|
|
99
|
-
mock_pdf.page_count = 2
|
|
100
|
-
mock_pdf.__len__.return_value = 1
|
|
101
|
-
mock_fitz_open.return_value = mock_pdf
|
|
102
|
-
|
|
103
|
-
mock_pdf.__iter__.return_value = [
|
|
104
|
-
MagicMock(get_images=MagicMock(return_value=[(1,)]))
|
|
105
|
-
]
|
|
106
|
-
mock_pdf.__getitem__.return_value = MagicMock(get_images=MagicMock(return_value=[(1,)]))
|
|
107
|
-
|
|
108
|
-
mock_pixmap_obj = MagicMock()
|
|
109
|
-
mock_pixmap.return_value = mock_pixmap_obj
|
|
110
|
-
|
|
111
|
-
# Simular retorno de atributos del Pixmap
|
|
112
|
-
mock_pixmap_obj.n = 3
|
|
113
|
-
mock_pixmap_obj.width = 100
|
|
114
|
-
mock_pixmap_obj.height = 100
|
|
115
|
-
mock_pixmap_obj.samples = b"dummy_pixels"
|
|
116
|
-
|
|
117
|
-
mock_fitz_open.return_value[0].get_images.return_value = [(1,)]
|
|
118
|
-
mock_fitz_open.return_value[0].__getitem__ = MagicMock(return_value=mock_pixmap)
|
|
119
|
-
|
|
120
|
-
service = DocumentService()
|
|
121
|
-
content = service.read_scanned_pdf(b"dummy_scanned_pdf_content")
|
|
122
|
-
assert content == "Scanned text"
|
|
123
|
-
|
|
124
|
-
@patch("iatoolkit.services.document_service.fitz.open") # Parcheamos fitz.open
|
|
125
|
-
def test_is_scanned_pdf_with_selectable_text(self, mock_fitz_open):
|
|
126
|
-
"""Prueba que un PDF con prompt_llm.txt seleccionable retorne False."""
|
|
127
|
-
# Mock del documento PDF con prompt_llm.txt seleccionable
|
|
128
|
-
mock_pdf = MagicMock()
|
|
129
|
-
mock_pdf.__len__.return_value = 1 # Simula 1 página
|
|
130
|
-
mock_page = MagicMock()
|
|
131
|
-
mock_page.get_text.return_value = "This is some selectable text"
|
|
132
|
-
mock_pdf.__getitem__.return_value = mock_page
|
|
133
|
-
mock_fitz_open.return_value = mock_pdf
|
|
134
|
-
|
|
135
|
-
service = DocumentService()
|
|
136
|
-
result = service.is_scanned_pdf(b"dummy_pdf_content")
|
|
137
|
-
assert result is False # Tiene prompt_llm.txt seleccionable, no es escaneado
|
|
138
|
-
|
|
139
|
-
@patch("iatoolkit.services.document_service.fitz.open") # Parcheamos fitz.open
|
|
140
|
-
def test_is_scanned_pdf_with_scanned_images(self, mock_fitz_open):
|
|
141
|
-
"""Prueba que un PDF con imágenes (escaneado) retorne True."""
|
|
142
|
-
# Mock del documento PDF con imágenes
|
|
143
|
-
mock_pdf = MagicMock()
|
|
144
|
-
mock_pdf.__len__.return_value = 1 # Simula 1 página
|
|
145
|
-
mock_page = MagicMock()
|
|
146
|
-
mock_page.get_text.return_value = "" # No hay prompt_llm.txt
|
|
147
|
-
mock_page.get_images.return_value = [(1,)] # Hay imágenes
|
|
148
|
-
mock_pdf.__getitem__.return_value = mock_page
|
|
149
|
-
mock_fitz_open.return_value = mock_pdf
|
|
150
|
-
|
|
151
|
-
service = DocumentService()
|
|
152
|
-
result = service.is_scanned_pdf(b"dummy_pdf_content")
|
|
153
|
-
assert result is True # Escaneado, tiene imágenes pero no prompt_llm.txt
|
|
154
|
-
|
|
155
|
-
@patch("iatoolkit.services.document_service.fitz.open") # Parcheamos fitz.open
|
|
156
|
-
def test_is_scanned_pdf_without_text_or_images(self, mock_fitz_open):
|
|
157
|
-
"""Prueba que un PDF sin prompt_llm.txt y sin imágenes retorne True."""
|
|
158
|
-
# Mock del documento PDF vacío (no tiene prompt_llm.txt ni imágenes)
|
|
159
|
-
mock_pdf = MagicMock()
|
|
160
|
-
mock_pdf.__len__.return_value = 1 # Simula 1 página
|
|
161
|
-
mock_page = MagicMock()
|
|
162
|
-
mock_page.get_text.return_value = "" # No hay prompt_llm.txt
|
|
163
|
-
mock_page.get_images.return_value = [] # No hay imágenes
|
|
164
|
-
mock_pdf.__getitem__.return_value = mock_page
|
|
165
|
-
mock_fitz_open.return_value = mock_pdf
|
|
166
|
-
|
|
167
|
-
service = DocumentService()
|
|
168
|
-
result = service.is_scanned_pdf(b"dummy_pdf_content")
|
|
169
|
-
assert result is True # No tiene prompt_llm.txt ni imágenes, probablemente escaneado
|
|
170
|
-
|
|
171
|
-
@patch("iatoolkit.services.document_service.fitz.open") # Parcheamos fitz.open
|
|
172
|
-
def test_is_scanned_pdf_with_empty_pdf(self, mock_fitz_open):
|
|
173
|
-
"""Prueba que un PDF vacío (sin páginas) retorne True."""
|
|
174
|
-
# Mock del documento PDF vacío (sin páginas)
|
|
175
|
-
mock_pdf = MagicMock()
|
|
176
|
-
mock_pdf.__len__.return_value = 0 # Sin páginas
|
|
177
|
-
mock_fitz_open.return_value = mock_pdf
|
|
178
|
-
|
|
179
|
-
service = DocumentService()
|
|
180
|
-
result = service.is_scanned_pdf(b"dummy_pdf_content")
|
|
181
|
-
assert result is True # Al no tener páginas, se considera escaneado
|