-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_template_endpoint.py
More file actions
364 lines (302 loc) · 14.8 KB
/
test_template_endpoint.py
File metadata and controls
364 lines (302 loc) · 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
"""Unit tests for TemplateEndpoint class."""
import unittest
from unittest.mock import MagicMock, patch
from datetime import datetime
from pyflowintel.endpoints.templates import TemplateEndpoint
from pyflowintel.api_wrapper import PyFlowintel
from pyflowintel.commons.exceptions import (
FlowintelBadRequestError,
FlowintelNotFoundError,
PyflowintelConfigurationError,
PyflowintelValidationError
)
from pyflowintel.commons.utils import read_yaml
from pyflowintel.settings import DEFAULT_CONFIG_FILE
# Load configuration for integration tests
_test_config = {}
if DEFAULT_CONFIG_FILE.exists():
_test_config = read_yaml(DEFAULT_CONFIG_FILE).get("testing", {}) or {}
TEST_BASE_URL = _test_config.get("base_url")
TEST_API_KEY = _test_config.get("api_key")
TEST_TIMEOUT = _test_config.get("timeout", 5)
def skip_if_no_credentials(test_item):
"""Decorator to skip integration tests if credentials are not set."""
return unittest.skipIf(
not TEST_API_KEY or not TEST_BASE_URL,
f"Skipping integration test: api_key or base_url for testing not set in the configuration file"
)(test_item)
class TestTemplateEndpointValidations(unittest.TestCase):
"""Unit tests for TemplateEndpoint."""
def setUp(self):
self.mock_client = MagicMock()
self.endpoint = TemplateEndpoint(self.mock_client)
def test_init_sets_base_endpoint_path(self):
self.assertEqual(self.endpoint.base_endpoint_path, "/templating")
def test_build_endpoint_path(self):
self.assertEqual(self.endpoint.build_endpoint_path("/create_case"), "/templating/create_case")
self.assertEqual(self.endpoint.build_endpoint_path("/case/123"), "/templating/case/123")
def test_create_case_template_rejects_empty_payload(self):
'''Covers creation of case and task template as per the implementation'''
with self.assertRaises(ValueError):
self.endpoint.create_case_template({})
self.mock_client.post.assert_not_called()
def test_create_task_template_rejects_none_payload(self):
'''Covers creation of case and task template as per the implementation'''
with self.assertRaises(ValueError):
self.endpoint.create_task_template(None)
self.mock_client.post.assert_not_called()
def test_create_case_from_template_int_validation(self):
with self.assertRaises(PyflowintelValidationError) as ctx:
self.endpoint.create_case_from_template(None,"my title")
self.assertIn("integer", str(ctx.exception))
self.mock_client.post.assert_not_called()
def test_find_case_temp_by_id_success(self):
self.mock_client.get.return_value = {"id": 123, "title": "Test"}
result = self.endpoint.find_case_temp_by_id(123)
self.assertEqual(result["id"], 123)
self.mock_client.get.assert_called_once_with("/templating/case/123")
def test_find_task_temp_by_id_success(self):
self.mock_client.get.return_value = {"id": 456, "title": "Task Test"}
result = self.endpoint.find_task_temp_by_id(456)
self.assertEqual(result["id"], 456)
self.mock_client.get.assert_called_once_with("/templating/task/456")
def test_add_task_templates_to_case_template_case_id_validation(self):
with self.assertRaises(PyflowintelValidationError) as ctx:
self.endpoint.add_task_templates_to_case_template("not-int", [1, 2])
self.assertIn("integer", str(ctx.exception))
self.mock_client.post.assert_not_called()
def test_add_task_templates_to_case_template_list_validation(self):
with self.assertRaises(PyflowintelValidationError) as ctx:
self.endpoint.add_task_templates_to_case_template(123, [])
self.assertIn("must not be empty", str(ctx.exception))
self.mock_client.post.assert_not_called()
def test_add_task_templates_to_case_template_success(self):
self.mock_client.post.return_value = {"success": True}
result = self.endpoint.add_task_templates_to_case_template(123, [1, 2, 3])
self.assertEqual(result["success"], True)
self.mock_client.post.assert_called_once_with(
"/templating/case/123/add_tasks",
json_data={"tasks": [1, 2, 3]}
)
def test_find_case_temp_by_title_empty_title(self):
with self.assertRaises(PyflowintelValidationError) as ctx:
self.endpoint.find_case_temp_by_title(" ")
self.assertIn("Title must not be empty", str(ctx.exception))
self.mock_client.post.assert_not_called()
def test_find_case_temp_by_title_success(self):
self.mock_client.post.return_value = {"id": 321, "title": "My Template"}
result = self.endpoint.find_case_temp_by_title("My Template")
self.assertEqual(result["title"], "My Template")
self.mock_client.post.assert_called_once_with(
"/templating/case/title",
json_data={"title": "My Template"}
)
@patch("requests.Session.request")
def test_search_by_id_non_existing_id(self, mock_request):
client = PyFlowintel.from_args("flowintel/api","mock-api-key")
mock_response = MagicMock()
mock_response.status_code = 404
mock_response.json.return_value = {"message": "Case template not found"}
mock_request.return_value = mock_response
response = client.templates.find_case_temp_by_id(99999)
self.assertIn("message", response.keys())
mock_request.assert_called_once()
_, kwargs = mock_request.call_args
self.assertEqual(kwargs["url"], f"{client.base_url}/templating/case/99999")
@patch("requests.Session.request")
def test_search_by_id_non_existing_server_resource(self, mock_request):
client = PyFlowintel.from_args("flowintel/api","mock-api-key")
mock_response = MagicMock()
mock_response.status_code = 404
mock_response.json.side_effect = ValueError("Invalid JSON")
mock_request.return_value = mock_response
with self.assertRaises(FlowintelNotFoundError) as ctx:
client.templates.find_case_temp_by_id(99999)
mock_request.assert_called_once()
_, kwargs = mock_request.call_args
self.assertEqual(kwargs["url"], f"{client.base_url}/templating/case/99999")
@skip_if_no_credentials
class TestCaseTemplateOperations(unittest.TestCase):
"""Integration tests for case template CRUD operations."""
@classmethod
def setUpClass(cls):
cls.client = PyFlowintel.from_args(TEST_BASE_URL, TEST_API_KEY, TEST_TIMEOUT)
cls.template_payload = {
"title": "Case template for integration tests",
"time_required": "10h",
"description": "Template with fields parsed by the Flowintel API"
}
created = cls.client.templates.create_case_template(cls.template_payload)
cls.template_id = created.get("template_id", 0)
@classmethod
def tearDownClass(cls):
try:
cls.client.templates.delete_case(cls.template_id)
cls.client.close()
except Exception as e:
print(f"Cleanup failed (ignoring): {e}")
def test_create_case_template(self):
"""Verify the creation of the case template."""
self.assertIsInstance(self.template_id, int)
self.assertGreater(self.template_id, 0)
def test_create_case_template_duplicate_title_is_rejected(self):
"""Attempt to create another template with the same title.
Flowintel is expected to reject duplicates with a 400 code (BadRequest).
"""
with self.assertRaises(FlowintelBadRequestError) as ctx:
self.client.templates.create_case_template(self.template_payload)
self.assertIn("Title already exist", str(ctx.exception))
def test_find_case_temp_by_id(self):
"""Retrieve and verify that the created template has all the defined fields."""
fetched = self.client.templates.find_case_temp_by_id(self.template_id)
self.assertIsInstance(fetched, dict)
for key, value in self.template_payload.items():
self.assertEqual(fetched.get(key), value)
def test_find_case_temp_by_title_success(self):
fetched = self.client.templates.find_case_temp_by_title(self.template_payload.get("title"))
self.assertIsInstance(fetched, dict)
for key, value in self.template_payload.items():
self.assertEqual(fetched.get(key), value)
@skip_if_no_credentials
class TestTaskTemplateOperations(unittest.TestCase):
"""Integration tests for task template CRUD operations."""
@classmethod
def setUpClass(cls):
cls.client = PyFlowintel.from_args(TEST_BASE_URL, TEST_API_KEY, TEST_TIMEOUT)
cls.task_payload = {
"title": f"Test Task Template {datetime.now().strftime('%Y%m%d-%H%M%S')}",
"description": "New task for a case template"
}
created = cls.client.templates.create_task_template(cls.task_payload)
cls.task_template_id = created.get("template_id", 0)
@classmethod
def tearDownClass(cls):
try:
cls.client.templates.delete_task(cls.task_template_id)
cls.client.close()
except Exception as e:
print(f"Cleanup failed (ignoring): {e}")
def test_create_task_template(self):
"""Verify task template creation returns valid ID."""
self.assertIsInstance(self.task_template_id, int)
self.assertGreater(self.task_template_id, 0)
def test_find_task_temp_by_id(self):
"""Retrieve and verify that the created task template has all the defined fields."""
fetched = self.client.templates.find_task_temp_by_id(self.task_template_id)
self.assertIsInstance(fetched, dict)
for key, value in self.task_payload.items():
self.assertEqual(fetched.get(key), value)
@skip_if_no_credentials
class TestCaseFromTemplate(unittest.TestCase):
"""Integration tests for creating cases from templates."""
@classmethod
def setUpClass(cls):
cls.client = PyFlowintel.from_args(TEST_BASE_URL, TEST_API_KEY, TEST_TIMEOUT)
cls.template_payload = {
"title": "Integration Test Case Template",
"time_required": "10h",
"description": "Template for case creation tests"
}
case_template = cls.client.templates.create_case_template(cls.template_payload)
cls.template_id = case_template.get("template_id", 0)
cls.case_title = f"Case from template with given title"
case_w_title = cls.client.templates.create_case_from_template(cls.template_id, cls.case_title)
cls.id_case_w_title = case_w_title.get("case_id", 0)
case_no_title = cls.client.templates.create_case_from_template(cls.template_id, None)
cls.id_case_no_title = case_no_title.get("case_id", 0)
@classmethod
def tearDownClass(cls):
try:
cls.client.cases.delete(cls.id_case_w_title)
cls.client.cases.delete(cls.id_case_no_title)
cls.client.templates.delete_case(cls.template_id)
cls.client.close()
except Exception as e:
print(f"Cleanup failed (ignoring): {e}")
def test_create_case_from_template_no_title(self):
"""Verify case creation from template when no title is given.
"""
self.assertGreater(self.id_case_no_title, 0)
case = self.client.cases.search_by_id(self.id_case_no_title)
# check that the title of the template is used by default
self.assertIn(self.template_payload.get("title"), case.get("title"))
def test_created_case_contains_template_fields(self):
"""Verify case creation from template with fields inherited from the template.
NOTE: Currently, when creating a case from a template in Flowintel
the 'time_required' field is not inherited.
ToDo: report the issue in the corresponding project (Flowintel).
"""
self.assertGreater(self.id_case_w_title, 0)
fetched_case = self.client.cases.search_by_id(self.id_case_w_title)
self.assertEqual(self.case_title, fetched_case.get("title"))
# ToDo: uncomment once the error in Flowintel is fixed
# expected = self.template_payload.copy()
# expected.pop("title")
# for k, v in expected.items():
# self.assertIn(k, fetched_case)
# self.assertEqual(v, fetched_case.get(k))
@skip_if_no_credentials
class TestTemplateTasksToCases(unittest.TestCase):
"""Integration tests for adding tasks to case templates."""
@classmethod
def setUpClass(cls):
try:
cls.client = PyFlowintel.from_args(TEST_BASE_URL, TEST_API_KEY, TEST_TIMEOUT)
except PyflowintelConfigurationError as e:
print(f"Skipping task to cases tests: {e}")
return
# Create case template
case_payload = {
"title": f"Case Template",
"description": "Template for testing task relationships"
}
created_case = cls.client.templates.create_case_template(case_payload)
cls.case_template_id = created_case.get("template_id", 0)
# Create task template
task_payload = {
"title": f"Task Template",
"description": "Task template for relationship testing"
}
created_task = cls.client.templates.create_task_template(task_payload)
cls.task_template_id = created_task.get("template_id", 0)
@classmethod
def tearDownClass(cls):
try:
cls.client.templates.delete_task(cls.task_template_id)
cls.client.templates.delete_case(
cls.case_template_id,
delete_tasks=True
)
cls.client.close()
except Exception as e:
print(f"Cleanup failed (ignoring): {e}")
def test_add_task_templates_to_case_template(self):
"""Add an existing task template to a case template."""
result = self.client.templates.add_task_templates_to_case_template(
self.case_template_id,
[self.task_template_id]
)
self.assertIsInstance(result, dict)
self.assertIn("Tasks added", result.get("message"))
def test_add_tasks_to_case_template(self):
"""Create and add tasks from a dictionary list to a case template."""
tasks = [
{
"title": "On-the-fly Task 1",
"description": "First task created and added directly"
},
{
"title": "On-the-fly Task 2",
"description": "Second task created and added directly"
}
]
result = self.client.templates.add_tasks_to_case_template(
self.case_template_id,
tasks
)
self.assertIn("tasks added", result.get("message").lower())
self.assertIn("task_templates_added", result)
task_temp_ids = result.get("task_templates_added")
self.assertEqual(len(task_temp_ids),2)
if __name__ == "__main__":
unittest.main()