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
@@ -1,111 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- import pytest
7
- from unittest.mock import MagicMock
8
- from flask import Flask
9
- from iatoolkit.views.prompt_view import PromptView
10
- from iatoolkit.services.prompt_manager_service import PromptService
11
- from iatoolkit.common.auth import IAuthentication
12
-
13
-
14
- class TestPromptView:
15
- @staticmethod
16
- def create_app():
17
- """Creates a Flask app instance for testing."""
18
- app = Flask(__name__)
19
- app.testing = True
20
- return app
21
-
22
- @pytest.fixture(autouse=True)
23
- def setup(self):
24
- """Set up the test client and mock dependencies for each test."""
25
- self.app = self.create_app()
26
- self.client = self.app.test_client()
27
- self.prompt_service = MagicMock(spec=PromptService)
28
- self.iauthentication = MagicMock(spec=IAuthentication)
29
-
30
- # Default to successful authentication
31
- self.iauthentication.verify.return_value = {'success': True}
32
-
33
- # Register the view with mocked dependencies
34
- prompt_view = PromptView.as_view("prompt",
35
- iauthentication=self.iauthentication,
36
- prompt_service=self.prompt_service)
37
- self.app.add_url_rule('/<company_short_name>/prompts',
38
- view_func=prompt_view,
39
- methods=["GET"])
40
-
41
- def test_get_when_auth_error(self):
42
- """Test response when authentication fails."""
43
- self.iauthentication.verify.return_value = {
44
- 'success': False,
45
- 'error_message': 'Authentication token is invalid'
46
- }
47
-
48
- response = self.client.get('/test_company/prompts')
49
-
50
- assert response.status_code == 401
51
- assert response.json['error_message'] == 'Authentication token is invalid'
52
- self.prompt_service.get_user_prompts.assert_not_called()
53
-
54
- def test_get_when_service_returns_error(self):
55
- """Test response when the prompt service returns a logical error."""
56
- self.prompt_service.get_user_prompts.return_value = {
57
- 'error': 'Company not configured for prompts'
58
- }
59
-
60
- response = self.client.get('/test_company/prompts')
61
-
62
- assert response.status_code == 402
63
- assert response.json['error_message'] == 'Company not configured for prompts'
64
- self.iauthentication.verify.assert_called_once_with('test_company')
65
- self.prompt_service.get_user_prompts.assert_called_once_with('test_company')
66
-
67
- def test_get_when_service_raises_exception(self):
68
- """Test response when the prompt service raises an unhandled exception."""
69
- self.prompt_service.get_user_prompts.side_effect = Exception('Unexpected database error')
70
-
71
- response = self.client.get('/test_company/prompts')
72
-
73
- assert response.status_code == 500
74
- assert response.json['error_message'] == 'Unexpected database error'
75
-
76
- def test_get_success(self):
77
- """Test a successful request to retrieve prompts."""
78
- mock_response = {
79
- 'message': [
80
- {'prompt': 'sales_prompt', 'description': 'A prompt for sales questions'},
81
- {'prompt': 'support_prompt', 'description': 'A prompt for support inquiries'}
82
- ]
83
- }
84
- self.prompt_service.get_user_prompts.return_value = mock_response
85
-
86
- response = self.client.get('/test_company/prompts')
87
-
88
- assert response.status_code == 200
89
- assert response.json == mock_response
90
- self.iauthentication.verify.assert_called_once_with('test_company')
91
- self.prompt_service.get_user_prompts.assert_called_once_with('test_company')
92
-
93
- def test_get_success_with_empty_list(self):
94
- """Test a successful request that results in an empty list of prompts."""
95
- mock_response = {'message': []}
96
- self.prompt_service.get_user_prompts.return_value = mock_response
97
-
98
- response = self.client.get('/test_company/prompts')
99
-
100
- assert response.status_code == 200
101
- assert response.json == mock_response
102
-
103
- def test_get_calls_services_with_correct_company(self):
104
- """Test that services are called with the correct company name from the URL."""
105
- self.prompt_service.get_user_prompts.return_value = {'message': []}
106
-
107
- company_name = 'another-company'
108
- self.client.get(f'/{company_name}/prompts')
109
-
110
- self.iauthentication.verify.assert_called_once_with(company_name)
111
- self.prompt_service.get_user_prompts.assert_called_once_with(company_name)
@@ -1,140 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- import pytest
7
- from flask import Flask
8
- from unittest.mock import MagicMock, patch
9
- from iatoolkit.services.profile_service import ProfileService
10
- from iatoolkit.views.signup_view import SignupView
11
- from iatoolkit.repositories.models import Company
12
-
13
-
14
- class TestSignupView:
15
- @staticmethod
16
- def create_app():
17
- """Configura la aplicación Flask para pruebas."""
18
- app = Flask(__name__)
19
- app.testing = True
20
-
21
- return app
22
-
23
- @pytest.fixture(autouse=True)
24
- def setup(self):
25
- """Configura el cliente y los mocks antes de cada test."""
26
- self.app = self.create_app()
27
- self.client = self.app.test_client()
28
- self.profile_service = MagicMock(spec=ProfileService)
29
- self.test_company = Company(
30
- id=1,
31
- name="Empresa de Prueba",
32
- short_name="test_company",
33
- logo_file="test_logo.png"
34
- )
35
- self.profile_service.get_company_by_short_name.return_value = self.test_company
36
-
37
- # Registrar la vista
38
- view = SignupView.as_view("signup", profile_service=self.profile_service)
39
- self.app.add_url_rule("/<company_short_name>/signup", view_func=view, methods=["GET", "POST"])
40
-
41
- @patch("iatoolkit.views.signup_view.render_template")
42
- @patch("iatoolkit.views.signup_view.URLSafeTimedSerializer")
43
- def test_get_when_invalid_company(self, mock_serializer, mock_render):
44
- mock_serializer.return_value.loads.return_value = "nonexistent@email.com"
45
- self.profile_service.get_company_by_short_name.return_value = None
46
- response = self.client.get("/test_company/signup")
47
- assert response.status_code == 404
48
-
49
- @patch("iatoolkit.views.signup_view.render_template")
50
- @patch("iatoolkit.views.signup_view.URLSafeTimedSerializer")
51
- def test_post_when_invalid_company(self, mock_serializer, mock_render):
52
- mock_serializer.return_value.loads.return_value = "nonexistent@email.com"
53
- self.profile_service.get_company_by_short_name.return_value = None
54
- response = self.client.post("/test_company/signup",
55
- data={},
56
- content_type="application/x-www-form-urlencoded")
57
-
58
- assert response.status_code == 404
59
-
60
- @patch("iatoolkit.views.signup_view.render_template")
61
- @patch("iatoolkit.views.signup_view.URLSafeTimedSerializer")
62
- def test_get_signup_page(self, mock_serializer, mock_render_template):
63
- mock_serializer.return_value.loads.return_value = "nonexistent@email.com"
64
- mock_render_template.return_value = "<html><body><h1>Signup Page</h1></body></html>"
65
- response = self.client.get("/test_company/signup")
66
-
67
- assert response.status_code == 200
68
-
69
- @patch("iatoolkit.views.signup_view.render_template")
70
- @patch("iatoolkit.views.signup_view.url_for")
71
- @patch("iatoolkit.views.signup_view.URLSafeTimedSerializer")
72
- def test_post_with_error(self, mock_serializer, mock_url_for, mock_render_template):
73
- mock_serializer.return_value.loads.return_value = "nonexistent@email.com"
74
- mock_url_for.return_value = 'http://verification'
75
- mock_render_template.return_value = "<html><body></body></html>"
76
- self.profile_service.signup.return_value = \
77
- {'error': 'user exists'}
78
-
79
- response = self.client.post("/test_company/signup",
80
- data={
81
- "first_name": "Juan",
82
- "last_name": "Perez",
83
- "email": "test@email.com",
84
- "password": "password123",
85
- "confirm_password": "password123"
86
- },
87
- content_type="application/x-www-form-urlencoded")
88
-
89
- assert response.status_code == 400
90
-
91
- @patch("iatoolkit.views.signup_view.render_template")
92
- @patch("iatoolkit.views.signup_view.url_for")
93
- @patch("iatoolkit.views.signup_view.URLSafeTimedSerializer")
94
- def test_post_when_ok(self, mock_serializer, mock_url_for, mock_render_template):
95
- mock_render_template.return_value = "<html><body></body></html>"
96
- mock_serializer.return_value.loads.return_value = "nonexistent@email.com"
97
- mock_url_for.return_value = 'http://verification'
98
- self.profile_service.signup.return_value = \
99
- {"message": "User created"}
100
-
101
- response = self.client.post("/test_company/signup",
102
- data={
103
- "first_name": "Juan",
104
- "last_name": "Perez",
105
- "email": "juan@email.com",
106
- "password": "password123",
107
- "confirm_password": "password123"
108
- },
109
- content_type="application/x-www-form-urlencoded")
110
-
111
- assert response.status_code == 200
112
- mock_render_template.assert_called_once_with(
113
- "login.html",
114
- company=self.test_company,
115
- company_short_name='test_company',
116
- alert_message="User created",
117
- alert_icon='success'
118
- )
119
-
120
- @patch("iatoolkit.views.signup_view.render_template")
121
- @patch("iatoolkit.views.signup_view.URLSafeTimedSerializer")
122
- def test_post_unexpected_error(self, mock_serializer, mock_render_template):
123
- mock_serializer.return_value.loads.side_effect = Exception('an error')
124
- response = self.client.post("/test_company/signup",
125
- data={
126
- "first_name": "Juan",
127
- "last_name": "Perez",
128
- "email": "juan@email.com",
129
- "password": "password123",
130
- "confirm_password": "password123"
131
- },
132
- content_type="application/x-www-form-urlencoded")
133
-
134
- mock_render_template.assert_called_once_with(
135
- "error.html",
136
- company=self.test_company,
137
- company_short_name='test_company',
138
- message="Ha ocurrido un error inesperado."
139
- )
140
- assert response.status_code == 500
@@ -1,104 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- import pytest
7
- from unittest.mock import MagicMock
8
- from flask import Flask
9
- from iatoolkit.repositories.profile_repo import ProfileRepo
10
- from iatoolkit.views.tasks_review_view import TaskReviewView
11
- from iatoolkit.services.tasks_service import TaskService, TaskStatus
12
- from iatoolkit.repositories.models import Company, ApiKey
13
-
14
-
15
-
16
- class TestTaskReviewView:
17
-
18
- def setup_method(self):
19
- self.app = Flask(__name__)
20
- self.client = self.app.test_client()
21
-
22
- self.mock_task_service = MagicMock(spec=TaskService)
23
- self.mock_profile_repo = MagicMock(spec=ProfileRepo)
24
-
25
- # Instanciamos la vista con el mock del servicio
26
- self.task_review_view = TaskReviewView.as_view("tasks-review",
27
- task_service=self.mock_task_service,
28
- profile_repo=self.mock_profile_repo)
29
- self.app.add_url_rule('/tasks/review/<int:task_id>', view_func=self.task_review_view, methods=["POST"])
30
-
31
- # API Key exitosa
32
- self.api_key = ApiKey(key="test_key", company_id=100)
33
- self.api_key.company = Company(id=100, name="Test Company", short_name="test_company")
34
- self.mock_profile_repo.get_active_api_key_entry.return_value = self.api_key
35
- self.valid_header = {"Authorization": f"Bearer {self.api_key.key}"}
36
-
37
- @pytest.mark.parametrize("missing_field", ["review_user", "approved"])
38
- def test_post_when_missing_required_fields(self, missing_field):
39
- payload = {
40
- "review_user": "test_username",
41
- "approved": True,
42
- "comment": "this is a comment",
43
- }
44
- payload.pop(missing_field)
45
-
46
- response = self.client.post('/tasks/review/1',
47
- headers=self.valid_header,
48
- json=payload)
49
-
50
- assert response.status_code == 400
51
- assert response.get_json() == {
52
- "error": f"El campo {missing_field} es requerido"
53
- }
54
-
55
- self.mock_task_service.create_task.assert_not_called()
56
-
57
- def test_post_when_internal_exception_error(self):
58
- self.mock_task_service.review_task.side_effect = Exception("Internal Error")
59
-
60
- payload = {
61
- "review_user": "test_username",
62
- "approved": True,
63
- "comment": "this is a comment",
64
- }
65
-
66
- response = self.client.post('/tasks/review/1',
67
- headers=self.valid_header,
68
- json=payload)
69
-
70
- assert response.status_code == 500
71
- assert response.get_json() == {
72
- "error": "Internal Error"
73
- }
74
-
75
- self.mock_task_service.review_task.assert_called_once()
76
-
77
- def test_post_when_successful_creation(self):
78
- mocked_task = MagicMock()
79
- mocked_task.id = 123
80
- mocked_task.status = TaskStatus.aprobada
81
- self.mock_task_service.review_task.return_value = mocked_task
82
-
83
- payload = {
84
- "review_user": "test_username",
85
- "approved": True,
86
- "comment": "this is a comment",
87
- }
88
-
89
- response = self.client.post('/tasks/review/1',
90
- headers=self.valid_header,
91
- json=payload)
92
-
93
- assert response.status_code == 200
94
- assert response.get_json() == {
95
- "task_id": 123,
96
- "status": "aprobada"
97
- }
98
-
99
- self.mock_task_service.review_task.assert_called_once_with(
100
- task_id=1,
101
- review_user="test_username",
102
- approved=True,
103
- comment="this is a comment"
104
- )
@@ -1,130 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Product: IAToolkit
3
- #
4
- # IAToolkit is open source software.
5
-
6
- import pytest
7
- from unittest.mock import MagicMock
8
- from flask import Flask
9
- from iatoolkit.views.tasks_view import TaskView
10
- from iatoolkit.services.tasks_service import TaskService
11
- from iatoolkit.repositories.profile_repo import ProfileRepo
12
- from iatoolkit.repositories.models import Company, ApiKey
13
- from datetime import datetime
14
-
15
-
16
- class TestTaskView:
17
-
18
- def setup_method(self):
19
- self.app = Flask(__name__)
20
- self.client = self.app.test_client()
21
-
22
- # Mock del TaskService
23
- self.mock_task_service = MagicMock(spec=TaskService)
24
- self.mock_profile_repo = MagicMock(spec=ProfileRepo)
25
-
26
-
27
- # Instanciamos la vista con el mock del servicio
28
- self.task_view = TaskView.as_view("tasks",
29
- task_service=self.mock_task_service,
30
- profile_repo=self.mock_profile_repo)
31
- self.app.add_url_rule('/tasks', view_func=self.task_view, methods=["POST"])
32
-
33
- # API Key exitosa
34
- self.api_key = ApiKey(key="test_key", company_id=100)
35
- self.api_key.company = Company(id=100, name="Test Company", short_name="test_company")
36
- self.mock_profile_repo.get_active_api_key_entry.return_value = self.api_key
37
- self.valid_header = {"Authorization": f"Bearer {self.api_key.key}"}
38
-
39
- @pytest.mark.parametrize("missing_field", ["company", "task_type", "client_data"])
40
- def test_post_when_missing_required_fields(self, missing_field):
41
- payload = {
42
- "company": "test_company",
43
- "task_type": "test_type",
44
- "client_data": {"key": "value"},
45
- }
46
- payload.pop(missing_field)
47
-
48
- response = self.client.post('/tasks',
49
- headers=self.valid_header,
50
- json=payload)
51
-
52
- assert response.status_code == 400
53
- assert response.get_json() == {
54
- "error": f"El campo {missing_field} es requerido"
55
- }
56
-
57
- self.mock_task_service.create_task.assert_not_called()
58
-
59
- def test_post_when_invalid_execute_at_format(self):
60
- payload = {
61
- "company": "test_company",
62
- "task_type": "test_type",
63
- "client_data": {"key": "value"},
64
- "execute_at": "fecha-invalida"
65
- }
66
-
67
- response = self.client.post('/tasks',
68
- headers=self.valid_header,
69
- json=payload)
70
-
71
- assert response.status_code == 400
72
- assert response.get_json() == {
73
- "error": "El formato de execute_at debe ser YYYY-MM-DD HH:MM:SS"
74
- }
75
-
76
- self.mock_task_service.create_task.assert_not_called()
77
-
78
- def test_post_when_internal_exception_error(self):
79
- self.mock_task_service.create_task.side_effect = Exception("Internal Error")
80
-
81
- payload = {
82
- "company": "test_company",
83
- "task_type": "test_type",
84
- "client_data": {"key": "value"}
85
- }
86
-
87
- response = self.client.post('/tasks',
88
- headers=self.valid_header,
89
- json=payload)
90
-
91
- assert response.status_code == 500
92
- assert response.get_json() == {
93
- "error": "Internal Error"
94
- }
95
-
96
- self.mock_task_service.create_task.assert_called_once()
97
-
98
- def test_post_when_successful_creation(self):
99
- mocked_task = MagicMock()
100
- mocked_task.id = 123
101
- mocked_task.status.name = "CREATED"
102
- self.mock_task_service.create_task.return_value = mocked_task
103
-
104
- payload = {
105
- "company": "test_company",
106
- "company_task_id": 100,
107
- "task_type": "test_type",
108
- "client_data": {"key": "value"},
109
- "execute_at": "2024-04-17 10:00:00"
110
- }
111
-
112
- response = self.client.post('/tasks', headers=self.valid_header,
113
- json=payload)
114
-
115
- assert response.status_code == 201
116
- assert response.get_json() == {
117
- "task_id": 123,
118
- "status": "CREATED"
119
- }
120
-
121
- execute_datetime = datetime.fromisoformat(payload["execute_at"])
122
-
123
- self.mock_task_service.create_task.assert_called_once_with(
124
- company_short_name="test_company",
125
- task_type_name="test_type",
126
- client_data={"key": "value"},
127
- company_task_id=100,
128
- execute_at=execute_datetime,
129
- files=[]
130
- )