Link Here
|
1 |
Sync PYPI tests with GH released version |
2 |
|
3 |
diff -ruN tests/__init__.py proxmoxer-2.2.0/tests/__init__.py |
4 |
--- tests/__init__.py 1970-01-01 01:00:00.000000000 +0100 |
5 |
+++ proxmoxer-2.2.0/tests/__init__.py 2024-12-15 02:12:42.000000000 +0000 |
6 |
@@ -0,0 +1,3 @@ |
7 |
+__author__ = "John Hollowell" |
8 |
+__copyright__ = "(c) John Hollowell 2022" |
9 |
+__license__ = "MIT" |
10 |
diff -ruN tests/api_mock.py proxmoxer-2.2.0/tests/api_mock.py |
11 |
--- tests/api_mock.py 1970-01-01 01:00:00.000000000 +0100 |
12 |
+++ proxmoxer-2.2.0/tests/api_mock.py 2024-12-15 02:12:42.000000000 +0000 |
13 |
@@ -0,0 +1,360 @@ |
14 |
+__author__ = "John Hollowell" |
15 |
+__copyright__ = "(c) John Hollowell 2022" |
16 |
+__license__ = "MIT" |
17 |
+ |
18 |
+import json |
19 |
+import re |
20 |
+from urllib.parse import parse_qsl, urlparse |
21 |
+ |
22 |
+import pytest |
23 |
+import responses |
24 |
+from requests_toolbelt import MultipartEncoder |
25 |
+ |
26 |
+ |
27 |
+@pytest.fixture() |
28 |
+def mock_pve(): |
29 |
+ with responses.RequestsMock(registry=PVERegistry, assert_all_requests_are_fired=False) as rsps: |
30 |
+ yield rsps |
31 |
+ |
32 |
+ |
33 |
+class PVERegistry(responses.registries.FirstMatchRegistry): |
34 |
+ base_url = "https://1.2.3.4:1234/api2/json" |
35 |
+ |
36 |
+ common_headers = { |
37 |
+ "Cache-Control": "max-age=0", |
38 |
+ "Connection": "close, Keep-Alive", |
39 |
+ "Pragma": "no-cache", |
40 |
+ "Server": "pve-api-daemon/3.0", |
41 |
+ "Content-Type": "application/json;charset=UTF-8", |
42 |
+ } |
43 |
+ |
44 |
+ def __init__(self): |
45 |
+ super().__init__() |
46 |
+ for resp in self._generate_static_responses(): |
47 |
+ self.add(resp) |
48 |
+ |
49 |
+ for resp in self._generate_dynamic_responses(): |
50 |
+ self.add(resp) |
51 |
+ |
52 |
+ def _generate_static_responses(self): |
53 |
+ resps = [] |
54 |
+ |
55 |
+ # Basic GET requests |
56 |
+ resps.append( |
57 |
+ responses.Response( |
58 |
+ method="GET", |
59 |
+ url=self.base_url + "/version", |
60 |
+ json={"data": {"version": "7.2-3", "release": "7.2", "repoid": "c743d6c1"}}, |
61 |
+ ) |
62 |
+ ) |
63 |
+ |
64 |
+ resps.append( |
65 |
+ responses.Response( |
66 |
+ method="POST", |
67 |
+ url=re.compile(self.base_url + r"/nodes/[^/]+/storage/[^/]+/download-url"), |
68 |
+ # "done" added to UPID so polling will terminate (status checking is tested elsewhere) |
69 |
+ json={ |
70 |
+ "data": "UPID:node:003094EA:095F1EFE:63E88772:download:file.iso:root@pam:done", |
71 |
+ "success": 1, |
72 |
+ }, |
73 |
+ ) |
74 |
+ ) |
75 |
+ |
76 |
+ resps.append( |
77 |
+ responses.Response( |
78 |
+ method="POST", |
79 |
+ url=re.compile(self.base_url + r"/nodes/[^/]+/storage/storage1/upload"), |
80 |
+ # "done" added to UPID so polling will terminate (status checking is tested elsewhere) |
81 |
+ json={"data": "UPID:node:0017C594:0ADB2769:63EC5455:imgcopy::root@pam:done"}, |
82 |
+ ) |
83 |
+ ) |
84 |
+ resps.append( |
85 |
+ responses.Response( |
86 |
+ method="POST", |
87 |
+ url=re.compile(self.base_url + r"/nodes/[^/]+/storage/missing/upload"), |
88 |
+ status=500, |
89 |
+ body="storage 'missing' does not exist", |
90 |
+ ) |
91 |
+ ) |
92 |
+ |
93 |
+ return resps |
94 |
+ |
95 |
+ def _generate_dynamic_responses(self): |
96 |
+ resps = [] |
97 |
+ |
98 |
+ # Authentication |
99 |
+ resps.append( |
100 |
+ responses.CallbackResponse( |
101 |
+ method="POST", |
102 |
+ url=self.base_url + "/access/ticket", |
103 |
+ callback=self._cb_password_auth, |
104 |
+ ) |
105 |
+ ) |
106 |
+ |
107 |
+ # Session testing |
108 |
+ resps.append( |
109 |
+ responses.CallbackResponse( |
110 |
+ method="GET", |
111 |
+ url=self.base_url + "/fake/echo", |
112 |
+ callback=self._cb_echo, |
113 |
+ ) |
114 |
+ ) |
115 |
+ |
116 |
+ resps.append( |
117 |
+ responses.CallbackResponse( |
118 |
+ method="GET", |
119 |
+ url=re.compile(self.base_url + r"/nodes/[^/]+/qemu/[^/]+/agent/exec"), |
120 |
+ callback=self._cb_echo, |
121 |
+ ) |
122 |
+ ) |
123 |
+ |
124 |
+ resps.append( |
125 |
+ responses.CallbackResponse( |
126 |
+ method="GET", |
127 |
+ url=re.compile(self.base_url + r"/nodes/[^/]+/qemu/[^/]+/monitor"), |
128 |
+ callback=self._cb_qemu_monitor, |
129 |
+ ) |
130 |
+ ) |
131 |
+ |
132 |
+ resps.append( |
133 |
+ responses.CallbackResponse( |
134 |
+ method="GET", |
135 |
+ url=re.compile(self.base_url + r"/nodes/[^/]+/tasks/[^/]+/status"), |
136 |
+ callback=self._cb_task_status, |
137 |
+ ) |
138 |
+ ) |
139 |
+ |
140 |
+ resps.append( |
141 |
+ responses.CallbackResponse( |
142 |
+ method="GET", |
143 |
+ url=re.compile(self.base_url + r"/nodes/[^/]+/query-url-metadata.*"), |
144 |
+ callback=self._cb_url_metadata, |
145 |
+ ) |
146 |
+ ) |
147 |
+ |
148 |
+ return resps |
149 |
+ |
150 |
+ ################################### |
151 |
+ # Callbacks for Dynamic Responses # |
152 |
+ ################################### |
153 |
+ |
154 |
+ def _cb_echo(self, request): |
155 |
+ body = request.body |
156 |
+ if body is not None: |
157 |
+ if isinstance(body, MultipartEncoder): |
158 |
+ body = body.to_string() # really, to byte string |
159 |
+ body = body if isinstance(body, str) else str(body, "utf-8") |
160 |
+ |
161 |
+ resp = { |
162 |
+ "method": request.method, |
163 |
+ "url": request.url, |
164 |
+ "headers": dict(request.headers), |
165 |
+ "cookies": request._cookies.get_dict(), |
166 |
+ "body": body, |
167 |
+ # "body_json": dict(parse_qsl(request.body)), |
168 |
+ } |
169 |
+ return (200, self.common_headers, json.dumps(resp)) |
170 |
+ |
171 |
+ def _cb_password_auth(self, request): |
172 |
+ form_data_dict = dict(parse_qsl(request.body)) |
173 |
+ |
174 |
+ # if this user should not be authenticated |
175 |
+ if form_data_dict.get("username") == "bad_auth": |
176 |
+ return ( |
177 |
+ 401, |
178 |
+ self.common_headers, |
179 |
+ json.dumps({"data": None}), |
180 |
+ ) |
181 |
+ # if this user requires OTP and it is not included |
182 |
+ if form_data_dict.get("username") == "otp" and form_data_dict.get("otp") is None: |
183 |
+ return ( |
184 |
+ 200, |
185 |
+ self.common_headers, |
186 |
+ json.dumps( |
187 |
+ { |
188 |
+ "data": { |
189 |
+ "ticket": "otp_ticket", |
190 |
+ "CSRFPreventionToken": "CSRFPreventionToken", |
191 |
+ "NeedTFA": 1, |
192 |
+ } |
193 |
+ } |
194 |
+ ), |
195 |
+ ) |
196 |
+ |
197 |
+ # if this is the first ticket |
198 |
+ if form_data_dict.get("password") != "ticket": |
199 |
+ return ( |
200 |
+ 200, |
201 |
+ self.common_headers, |
202 |
+ json.dumps( |
203 |
+ {"data": {"ticket": "ticket", "CSRFPreventionToken": "CSRFPreventionToken"}} |
204 |
+ ), |
205 |
+ ) |
206 |
+ # if this is refreshing the ticket, return new ticket |
207 |
+ else: |
208 |
+ return ( |
209 |
+ 200, |
210 |
+ self.common_headers, |
211 |
+ json.dumps( |
212 |
+ { |
213 |
+ "data": { |
214 |
+ "ticket": "new_ticket", |
215 |
+ "CSRFPreventionToken": "CSRFPreventionToken_2", |
216 |
+ } |
217 |
+ } |
218 |
+ ), |
219 |
+ ) |
220 |
+ |
221 |
+ def _cb_task_status(self, request): |
222 |
+ resp = {} |
223 |
+ if "keep-running" in request.url: |
224 |
+ resp = { |
225 |
+ "data": { |
226 |
+ "id": "110", |
227 |
+ "pid": 1044989, |
228 |
+ "node": "node1", |
229 |
+ "pstart": 284768076, |
230 |
+ "status": "running", |
231 |
+ "upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:keep-running", |
232 |
+ "starttime": 1661825068, |
233 |
+ "user": "root@pam", |
234 |
+ "type": "vzdump", |
235 |
+ } |
236 |
+ } |
237 |
+ |
238 |
+ elif "stopped" in request.url: |
239 |
+ resp = { |
240 |
+ "data": { |
241 |
+ "upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:stopped", |
242 |
+ "starttime": 1661825068, |
243 |
+ "user": "root@pam", |
244 |
+ "type": "vzdump", |
245 |
+ "pstart": 284768076, |
246 |
+ "status": "stopped", |
247 |
+ "exitstatus": "interrupted by signal", |
248 |
+ "pid": 1044989, |
249 |
+ "id": "110", |
250 |
+ "node": "node1", |
251 |
+ } |
252 |
+ } |
253 |
+ |
254 |
+ elif "done" in request.url: |
255 |
+ resp = { |
256 |
+ "data": { |
257 |
+ "upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:done", |
258 |
+ "starttime": 1661825068, |
259 |
+ "user": "root@pam", |
260 |
+ "type": "vzdump", |
261 |
+ "pstart": 284768076, |
262 |
+ "status": "stopped", |
263 |
+ "exitstatus": "OK", |
264 |
+ "pid": 1044989, |
265 |
+ "id": "110", |
266 |
+ "node": "node1", |
267 |
+ } |
268 |
+ } |
269 |
+ |
270 |
+ elif "comment" in request.url: |
271 |
+ resp = { |
272 |
+ "data": { |
273 |
+ "upid": "UPID:node:00000000:00000000:00000000:task:id:root@pam:comment", |
274 |
+ "node": "node", |
275 |
+ "pid": 0, |
276 |
+ "pstart": 0, |
277 |
+ "starttime": 0, |
278 |
+ "type": "task", |
279 |
+ "id": "id", |
280 |
+ "user": "root@pam", |
281 |
+ "status": "stopped", |
282 |
+ "exitstatus": "OK", |
283 |
+ } |
284 |
+ } |
285 |
+ |
286 |
+ return (200, self.common_headers, json.dumps(resp)) |
287 |
+ |
288 |
+ def _cb_url_metadata(self, request): |
289 |
+ form_data_dict = dict(parse_qsl((urlparse(request.url)).query)) |
290 |
+ |
291 |
+ if "file.iso" in form_data_dict.get("url", ""): |
292 |
+ return ( |
293 |
+ 200, |
294 |
+ self.common_headers, |
295 |
+ json.dumps( |
296 |
+ { |
297 |
+ "data": { |
298 |
+ "size": 123456, |
299 |
+ "filename": "file.iso", |
300 |
+ "mimetype": "application/x-iso9660-image", |
301 |
+ # "mimetype": "application/octet-stream", |
302 |
+ }, |
303 |
+ "success": 1, |
304 |
+ } |
305 |
+ ), |
306 |
+ ) |
307 |
+ elif "invalid.iso" in form_data_dict.get("url", ""): |
308 |
+ return ( |
309 |
+ 500, |
310 |
+ self.common_headers, |
311 |
+ json.dumps( |
312 |
+ { |
313 |
+ "status": 500, |
314 |
+ "message": "invalid server response: '500 Can't connect to sub.domain.tld:443 (certificate verify failed)'\n", |
315 |
+ "success": 0, |
316 |
+ "data": None, |
317 |
+ } |
318 |
+ ), |
319 |
+ ) |
320 |
+ elif "missing.iso" in form_data_dict.get("url", ""): |
321 |
+ return ( |
322 |
+ 500, |
323 |
+ self.common_headers, |
324 |
+ json.dumps( |
325 |
+ { |
326 |
+ "status": 500, |
327 |
+ "success": 0, |
328 |
+ "message": "invalid server response: '404 Not Found'\n", |
329 |
+ "data": None, |
330 |
+ } |
331 |
+ ), |
332 |
+ ) |
333 |
+ |
334 |
+ elif "index.html" in form_data_dict.get("url", ""): |
335 |
+ return ( |
336 |
+ 200, |
337 |
+ self.common_headers, |
338 |
+ json.dumps( |
339 |
+ { |
340 |
+ "success": 1, |
341 |
+ "data": {"filename": "index.html", "mimetype": "text/html", "size": 17664}, |
342 |
+ } |
343 |
+ ), |
344 |
+ ) |
345 |
+ |
346 |
+ def _cb_qemu_monitor(self, request): |
347 |
+ body = request.body |
348 |
+ if body is not None: |
349 |
+ body = body if isinstance(body, str) else str(body, "utf-8") |
350 |
+ |
351 |
+ # if the command is an array, throw the type error PVE would throw |
352 |
+ if "&" in body: |
353 |
+ return ( |
354 |
+ 400, |
355 |
+ self.common_headers, |
356 |
+ json.dumps( |
357 |
+ { |
358 |
+ "data": None, |
359 |
+ "errors": {"command": "type check ('string') failed - got ARRAY"}, |
360 |
+ } |
361 |
+ ), |
362 |
+ ) |
363 |
+ else: |
364 |
+ resp = { |
365 |
+ "method": request.method, |
366 |
+ "url": request.url, |
367 |
+ "headers": dict(request.headers), |
368 |
+ "cookies": request._cookies.get_dict(), |
369 |
+ "body": body, |
370 |
+ # "body_json": dict(parse_qsl(request.body)), |
371 |
+ } |
372 |
+ print(resp) |
373 |
+ return (200, self.common_headers, json.dumps(resp)) |
374 |
diff -ruN tests/files_mock.py proxmoxer-2.2.0/tests/files_mock.py |
375 |
--- tests/files_mock.py 1970-01-01 01:00:00.000000000 +0100 |
376 |
+++ proxmoxer-2.2.0/tests/files_mock.py 2024-12-15 02:12:42.000000000 +0000 |
377 |
@@ -0,0 +1,127 @@ |
378 |
+__author__ = "John Hollowell" |
379 |
+__copyright__ = "(c) John Hollowell 2022" |
380 |
+__license__ = "MIT" |
381 |
+ |
382 |
+import re |
383 |
+ |
384 |
+import pytest |
385 |
+import responses |
386 |
+from requests import exceptions |
387 |
+ |
388 |
+from .api_mock import PVERegistry |
389 |
+ |
390 |
+ |
391 |
+@pytest.fixture() |
392 |
+def mock_files(): |
393 |
+ with responses.RequestsMock( |
394 |
+ registry=FilesRegistry, assert_all_requests_are_fired=False |
395 |
+ ) as rsps: |
396 |
+ yield rsps |
397 |
+ |
398 |
+ |
399 |
+class FilesRegistry(responses.registries.FirstMatchRegistry): |
400 |
+ base_url = "https://sub.domain.tld" |
401 |
+ |
402 |
+ common_headers = { |
403 |
+ "Cache-Control": "max-age=0", |
404 |
+ "Connection": "close, Keep-Alive", |
405 |
+ "Pragma": "no-cache", |
406 |
+ "Server": "pve-api-daemon/3.0", |
407 |
+ "Content-Type": "application/json;charset=UTF-8", |
408 |
+ } |
409 |
+ |
410 |
+ def __init__(self): |
411 |
+ super().__init__() |
412 |
+ for resp in self._generate_static_responses(): |
413 |
+ self.add(resp) |
414 |
+ |
415 |
+ def _generate_static_responses(self): |
416 |
+ resps = [] |
417 |
+ |
418 |
+ # Basic GET requests |
419 |
+ resps.append(responses.Response(method="GET", url=self.base_url, body="hello world")) |
420 |
+ resps.append( |
421 |
+ responses.Response(method="GET", url=self.base_url + "/file.iso", body="CONTENTS") |
422 |
+ ) |
423 |
+ |
424 |
+ # sibling |
425 |
+ resps.append( |
426 |
+ responses.Response( |
427 |
+ method="GET", url=self.base_url + "/sibling/file.iso", body="CONTENTS\n" |
428 |
+ ) |
429 |
+ ) |
430 |
+ resps.append( |
431 |
+ responses.Response( |
432 |
+ method="GET", |
433 |
+ url=self.base_url + "/sibling/TESTINGSUMS", |
434 |
+ body="this_is_the_hash file.iso", |
435 |
+ ) |
436 |
+ ) |
437 |
+ |
438 |
+ # extension |
439 |
+ resps.append( |
440 |
+ responses.Response( |
441 |
+ method="GET", url=self.base_url + "/extension/file.iso", body="CONTENTS\n" |
442 |
+ ) |
443 |
+ ) |
444 |
+ resps.append( |
445 |
+ responses.Response( |
446 |
+ method="GET", |
447 |
+ url=self.base_url + "/extension/file.iso.testing", |
448 |
+ body="this_is_the_hash file.iso", |
449 |
+ ) |
450 |
+ ) |
451 |
+ resps.append( |
452 |
+ responses.Response( |
453 |
+ method="GET", |
454 |
+ url=self.base_url + "/extension/connectionerror.iso.testing", |
455 |
+ body=exceptions.ConnectionError(), |
456 |
+ ) |
457 |
+ ) |
458 |
+ resps.append( |
459 |
+ responses.Response( |
460 |
+ method="GET", |
461 |
+ url=self.base_url + "/extension/readtimeout.iso.testing", |
462 |
+ body=exceptions.ReadTimeout(), |
463 |
+ ) |
464 |
+ ) |
465 |
+ |
466 |
+ # extension upper |
467 |
+ resps.append( |
468 |
+ responses.Response( |
469 |
+ method="GET", url=self.base_url + "/upper/file.iso", body="CONTENTS\n" |
470 |
+ ) |
471 |
+ ) |
472 |
+ resps.append( |
473 |
+ responses.Response( |
474 |
+ method="GET", |
475 |
+ url=self.base_url + "/upper/file.iso.TESTING", |
476 |
+ body="this_is_the_hash file.iso", |
477 |
+ ) |
478 |
+ ) |
479 |
+ |
480 |
+ resps.append( |
481 |
+ responses.Response( |
482 |
+ method="GET", |
483 |
+ url=re.compile(self.base_url + r"/checksums/file.iso.\w+"), |
484 |
+ body="1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 file.iso", |
485 |
+ ) |
486 |
+ ) |
487 |
+ |
488 |
+ return resps |
489 |
+ |
490 |
+ |
491 |
+@pytest.fixture() |
492 |
+def mock_files_and_pve(): |
493 |
+ with responses.RequestsMock(registry=BothRegistry, assert_all_requests_are_fired=False) as rsps: |
494 |
+ yield rsps |
495 |
+ |
496 |
+ |
497 |
+class BothRegistry(responses.registries.FirstMatchRegistry): |
498 |
+ def __init__(self): |
499 |
+ super().__init__() |
500 |
+ registries = [FilesRegistry(), PVERegistry()] |
501 |
+ |
502 |
+ for reg in registries: |
503 |
+ for resp in reg.registered: |
504 |
+ self.add(resp) |
505 |
diff -ruN tests/known_issues.json proxmoxer-2.2.0/tests/known_issues.json |
506 |
--- tests/known_issues.json 1970-01-01 01:00:00.000000000 +0100 |
507 |
+++ proxmoxer-2.2.0/tests/known_issues.json 2024-12-15 02:12:42.000000000 +0000 |
508 |
@@ -0,0 +1,520 @@ |
509 |
+{ |
510 |
+ "errors": [], |
511 |
+ "generated_at": "2022-08-25T03:08:48Z", |
512 |
+ "metrics": { |
513 |
+ "_totals": { |
514 |
+ "CONFIDENCE.HIGH": 3, |
515 |
+ "CONFIDENCE.LOW": 0, |
516 |
+ "CONFIDENCE.MEDIUM": 11, |
517 |
+ "CONFIDENCE.UNDEFINED": 0, |
518 |
+ "SEVERITY.HIGH": 0, |
519 |
+ "SEVERITY.LOW": 3, |
520 |
+ "SEVERITY.MEDIUM": 11, |
521 |
+ "SEVERITY.UNDEFINED": 0, |
522 |
+ "loc": 1947, |
523 |
+ "nosec": 0, |
524 |
+ "skipped_tests": 0 |
525 |
+ }, |
526 |
+ "proxmoxer/__init__.py": { |
527 |
+ "CONFIDENCE.HIGH": 0, |
528 |
+ "CONFIDENCE.LOW": 0, |
529 |
+ "CONFIDENCE.MEDIUM": 0, |
530 |
+ "CONFIDENCE.UNDEFINED": 0, |
531 |
+ "SEVERITY.HIGH": 0, |
532 |
+ "SEVERITY.LOW": 0, |
533 |
+ "SEVERITY.MEDIUM": 0, |
534 |
+ "SEVERITY.UNDEFINED": 0, |
535 |
+ "loc": 5, |
536 |
+ "nosec": 0, |
537 |
+ "skipped_tests": 0 |
538 |
+ }, |
539 |
+ "proxmoxer/backends/__init__.py": { |
540 |
+ "CONFIDENCE.HIGH": 0, |
541 |
+ "CONFIDENCE.LOW": 0, |
542 |
+ "CONFIDENCE.MEDIUM": 0, |
543 |
+ "CONFIDENCE.UNDEFINED": 0, |
544 |
+ "SEVERITY.HIGH": 0, |
545 |
+ "SEVERITY.LOW": 0, |
546 |
+ "SEVERITY.MEDIUM": 0, |
547 |
+ "SEVERITY.UNDEFINED": 0, |
548 |
+ "loc": 3, |
549 |
+ "nosec": 0, |
550 |
+ "skipped_tests": 0 |
551 |
+ }, |
552 |
+ "proxmoxer/backends/command_base.py": { |
553 |
+ "CONFIDENCE.HIGH": 0, |
554 |
+ "CONFIDENCE.LOW": 0, |
555 |
+ "CONFIDENCE.MEDIUM": 0, |
556 |
+ "CONFIDENCE.UNDEFINED": 0, |
557 |
+ "SEVERITY.HIGH": 0, |
558 |
+ "SEVERITY.LOW": 0, |
559 |
+ "SEVERITY.MEDIUM": 0, |
560 |
+ "SEVERITY.UNDEFINED": 0, |
561 |
+ "loc": 115, |
562 |
+ "nosec": 0, |
563 |
+ "skipped_tests": 0 |
564 |
+ }, |
565 |
+ "proxmoxer/backends/https.py": { |
566 |
+ "CONFIDENCE.HIGH": 1, |
567 |
+ "CONFIDENCE.LOW": 0, |
568 |
+ "CONFIDENCE.MEDIUM": 0, |
569 |
+ "CONFIDENCE.UNDEFINED": 0, |
570 |
+ "SEVERITY.HIGH": 0, |
571 |
+ "SEVERITY.LOW": 1, |
572 |
+ "SEVERITY.MEDIUM": 0, |
573 |
+ "SEVERITY.UNDEFINED": 0, |
574 |
+ "loc": 286, |
575 |
+ "nosec": 0, |
576 |
+ "skipped_tests": 0 |
577 |
+ }, |
578 |
+ "proxmoxer/backends/local.py": { |
579 |
+ "CONFIDENCE.HIGH": 2, |
580 |
+ "CONFIDENCE.LOW": 0, |
581 |
+ "CONFIDENCE.MEDIUM": 0, |
582 |
+ "CONFIDENCE.UNDEFINED": 0, |
583 |
+ "SEVERITY.HIGH": 0, |
584 |
+ "SEVERITY.LOW": 2, |
585 |
+ "SEVERITY.MEDIUM": 0, |
586 |
+ "SEVERITY.UNDEFINED": 0, |
587 |
+ "loc": 14, |
588 |
+ "nosec": 0, |
589 |
+ "skipped_tests": 0 |
590 |
+ }, |
591 |
+ "proxmoxer/backends/openssh.py": { |
592 |
+ "CONFIDENCE.HIGH": 0, |
593 |
+ "CONFIDENCE.LOW": 0, |
594 |
+ "CONFIDENCE.MEDIUM": 0, |
595 |
+ "CONFIDENCE.UNDEFINED": 0, |
596 |
+ "SEVERITY.HIGH": 0, |
597 |
+ "SEVERITY.LOW": 0, |
598 |
+ "SEVERITY.MEDIUM": 0, |
599 |
+ "SEVERITY.UNDEFINED": 0, |
600 |
+ "loc": 53, |
601 |
+ "nosec": 0, |
602 |
+ "skipped_tests": 0 |
603 |
+ }, |
604 |
+ "proxmoxer/backends/ssh_paramiko.py": { |
605 |
+ "CONFIDENCE.HIGH": 0, |
606 |
+ "CONFIDENCE.LOW": 0, |
607 |
+ "CONFIDENCE.MEDIUM": 1, |
608 |
+ "CONFIDENCE.UNDEFINED": 0, |
609 |
+ "SEVERITY.HIGH": 0, |
610 |
+ "SEVERITY.LOW": 0, |
611 |
+ "SEVERITY.MEDIUM": 1, |
612 |
+ "SEVERITY.UNDEFINED": 0, |
613 |
+ "loc": 58, |
614 |
+ "nosec": 0, |
615 |
+ "skipped_tests": 0 |
616 |
+ }, |
617 |
+ "proxmoxer/core.py": { |
618 |
+ "CONFIDENCE.HIGH": 0, |
619 |
+ "CONFIDENCE.LOW": 0, |
620 |
+ "CONFIDENCE.MEDIUM": 0, |
621 |
+ "CONFIDENCE.UNDEFINED": 0, |
622 |
+ "SEVERITY.HIGH": 0, |
623 |
+ "SEVERITY.LOW": 0, |
624 |
+ "SEVERITY.MEDIUM": 0, |
625 |
+ "SEVERITY.UNDEFINED": 0, |
626 |
+ "loc": 155, |
627 |
+ "nosec": 0, |
628 |
+ "skipped_tests": 0 |
629 |
+ }, |
630 |
+ "tests/api_mock.py": { |
631 |
+ "CONFIDENCE.HIGH": 0, |
632 |
+ "CONFIDENCE.LOW": 0, |
633 |
+ "CONFIDENCE.MEDIUM": 0, |
634 |
+ "CONFIDENCE.UNDEFINED": 0, |
635 |
+ "SEVERITY.HIGH": 0, |
636 |
+ "SEVERITY.LOW": 0, |
637 |
+ "SEVERITY.MEDIUM": 0, |
638 |
+ "SEVERITY.UNDEFINED": 0, |
639 |
+ "loc": 108, |
640 |
+ "nosec": 0, |
641 |
+ "skipped_tests": 0 |
642 |
+ }, |
643 |
+ "tests/test_command_base.py": { |
644 |
+ "CONFIDENCE.HIGH": 0, |
645 |
+ "CONFIDENCE.LOW": 0, |
646 |
+ "CONFIDENCE.MEDIUM": 2, |
647 |
+ "CONFIDENCE.UNDEFINED": 0, |
648 |
+ "SEVERITY.HIGH": 0, |
649 |
+ "SEVERITY.LOW": 0, |
650 |
+ "SEVERITY.MEDIUM": 2, |
651 |
+ "SEVERITY.UNDEFINED": 0, |
652 |
+ "loc": 195, |
653 |
+ "nosec": 0, |
654 |
+ "skipped_tests": 0 |
655 |
+ }, |
656 |
+ "tests/test_core.py": { |
657 |
+ "CONFIDENCE.HIGH": 0, |
658 |
+ "CONFIDENCE.LOW": 0, |
659 |
+ "CONFIDENCE.MEDIUM": 0, |
660 |
+ "CONFIDENCE.UNDEFINED": 0, |
661 |
+ "SEVERITY.HIGH": 0, |
662 |
+ "SEVERITY.LOW": 0, |
663 |
+ "SEVERITY.MEDIUM": 0, |
664 |
+ "SEVERITY.UNDEFINED": 0, |
665 |
+ "loc": 241, |
666 |
+ "nosec": 0, |
667 |
+ "skipped_tests": 0 |
668 |
+ }, |
669 |
+ "tests/test_https.py": { |
670 |
+ "CONFIDENCE.HIGH": 0, |
671 |
+ "CONFIDENCE.LOW": 0, |
672 |
+ "CONFIDENCE.MEDIUM": 0, |
673 |
+ "CONFIDENCE.UNDEFINED": 0, |
674 |
+ "SEVERITY.HIGH": 0, |
675 |
+ "SEVERITY.LOW": 0, |
676 |
+ "SEVERITY.MEDIUM": 0, |
677 |
+ "SEVERITY.UNDEFINED": 0, |
678 |
+ "loc": 362, |
679 |
+ "nosec": 0, |
680 |
+ "skipped_tests": 0 |
681 |
+ }, |
682 |
+ "tests/test_https_helpers.py": { |
683 |
+ "CONFIDENCE.HIGH": 0, |
684 |
+ "CONFIDENCE.LOW": 0, |
685 |
+ "CONFIDENCE.MEDIUM": 0, |
686 |
+ "CONFIDENCE.UNDEFINED": 0, |
687 |
+ "SEVERITY.HIGH": 0, |
688 |
+ "SEVERITY.LOW": 0, |
689 |
+ "SEVERITY.MEDIUM": 0, |
690 |
+ "SEVERITY.UNDEFINED": 0, |
691 |
+ "loc": 62, |
692 |
+ "nosec": 0, |
693 |
+ "skipped_tests": 0 |
694 |
+ }, |
695 |
+ "tests/test_imports.py": { |
696 |
+ "CONFIDENCE.HIGH": 0, |
697 |
+ "CONFIDENCE.LOW": 0, |
698 |
+ "CONFIDENCE.MEDIUM": 0, |
699 |
+ "CONFIDENCE.UNDEFINED": 0, |
700 |
+ "SEVERITY.HIGH": 0, |
701 |
+ "SEVERITY.LOW": 0, |
702 |
+ "SEVERITY.MEDIUM": 0, |
703 |
+ "SEVERITY.UNDEFINED": 0, |
704 |
+ "loc": 80, |
705 |
+ "nosec": 0, |
706 |
+ "skipped_tests": 0 |
707 |
+ }, |
708 |
+ "tests/test_local.py": { |
709 |
+ "CONFIDENCE.HIGH": 0, |
710 |
+ "CONFIDENCE.LOW": 0, |
711 |
+ "CONFIDENCE.MEDIUM": 0, |
712 |
+ "CONFIDENCE.UNDEFINED": 0, |
713 |
+ "SEVERITY.HIGH": 0, |
714 |
+ "SEVERITY.LOW": 0, |
715 |
+ "SEVERITY.MEDIUM": 0, |
716 |
+ "SEVERITY.UNDEFINED": 0, |
717 |
+ "loc": 35, |
718 |
+ "nosec": 0, |
719 |
+ "skipped_tests": 0 |
720 |
+ }, |
721 |
+ "tests/test_openssh.py": { |
722 |
+ "CONFIDENCE.HIGH": 0, |
723 |
+ "CONFIDENCE.LOW": 0, |
724 |
+ "CONFIDENCE.MEDIUM": 2, |
725 |
+ "CONFIDENCE.UNDEFINED": 0, |
726 |
+ "SEVERITY.HIGH": 0, |
727 |
+ "SEVERITY.LOW": 0, |
728 |
+ "SEVERITY.MEDIUM": 2, |
729 |
+ "SEVERITY.UNDEFINED": 0, |
730 |
+ "loc": 62, |
731 |
+ "nosec": 0, |
732 |
+ "skipped_tests": 0 |
733 |
+ }, |
734 |
+ "tests/test_paramiko.py": { |
735 |
+ "CONFIDENCE.HIGH": 0, |
736 |
+ "CONFIDENCE.LOW": 0, |
737 |
+ "CONFIDENCE.MEDIUM": 6, |
738 |
+ "CONFIDENCE.UNDEFINED": 0, |
739 |
+ "SEVERITY.HIGH": 0, |
740 |
+ "SEVERITY.LOW": 0, |
741 |
+ "SEVERITY.MEDIUM": 6, |
742 |
+ "SEVERITY.UNDEFINED": 0, |
743 |
+ "loc": 113, |
744 |
+ "nosec": 0, |
745 |
+ "skipped_tests": 0 |
746 |
+ } |
747 |
+ }, |
748 |
+ "results": [ |
749 |
+ { |
750 |
+ "code": "332 def get_serializer(self):\n333 assert self.mode == \"json\"\n334 return JsonSerializer()\n", |
751 |
+ "col_offset": 8, |
752 |
+ "filename": "proxmoxer/backends/https.py", |
753 |
+ "issue_confidence": "HIGH", |
754 |
+ "issue_cwe": { |
755 |
+ "id": 703, |
756 |
+ "link": "https://cwe.mitre.org/data/definitions/703.html" |
757 |
+ }, |
758 |
+ "issue_severity": "LOW", |
759 |
+ "issue_text": "Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.", |
760 |
+ "line_number": 333, |
761 |
+ "line_range": [ |
762 |
+ 333 |
763 |
+ ], |
764 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b101_assert_used.html", |
765 |
+ "test_id": "B101", |
766 |
+ "test_name": "assert_used" |
767 |
+ }, |
768 |
+ { |
769 |
+ "code": "1 import shutil\n2 from subprocess import PIPE, Popen\n3 \n4 from proxmoxer.backends.command_base import CommandBaseBackend, CommandBaseSession\n", |
770 |
+ "col_offset": 0, |
771 |
+ "filename": "proxmoxer/backends/local.py", |
772 |
+ "issue_confidence": "HIGH", |
773 |
+ "issue_cwe": { |
774 |
+ "id": 78, |
775 |
+ "link": "https://cwe.mitre.org/data/definitions/78.html" |
776 |
+ }, |
777 |
+ "issue_severity": "LOW", |
778 |
+ "issue_text": "Consider possible security implications associated with the subprocess module.", |
779 |
+ "line_number": 2, |
780 |
+ "line_range": [ |
781 |
+ 2, |
782 |
+ 3 |
783 |
+ ], |
784 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/blacklists/blacklist_imports.html#b404-import-subprocess", |
785 |
+ "test_id": "B404", |
786 |
+ "test_name": "blacklist" |
787 |
+ }, |
788 |
+ { |
789 |
+ "code": "8 def _exec(self, cmd):\n9 proc = Popen(cmd, stdout=PIPE, stderr=PIPE)\n10 stdout, stderr = proc.communicate(timeout=self.timeout)\n", |
790 |
+ "col_offset": 15, |
791 |
+ "filename": "proxmoxer/backends/local.py", |
792 |
+ "issue_confidence": "HIGH", |
793 |
+ "issue_cwe": { |
794 |
+ "id": 78, |
795 |
+ "link": "https://cwe.mitre.org/data/definitions/78.html" |
796 |
+ }, |
797 |
+ "issue_severity": "LOW", |
798 |
+ "issue_text": "subprocess call - check for execution of untrusted input.", |
799 |
+ "line_number": 9, |
800 |
+ "line_range": [ |
801 |
+ 9 |
802 |
+ ], |
803 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b603_subprocess_without_shell_equals_true.html", |
804 |
+ "test_id": "B603", |
805 |
+ "test_name": "subprocess_without_shell_equals_true" |
806 |
+ }, |
807 |
+ { |
808 |
+ "code": "62 session = self.ssh_client.get_transport().open_session()\n63 session.exec_command(shell_join(cmd))\n64 stdout = session.makefile(\"rb\", -1).read().decode()\n", |
809 |
+ "col_offset": 8, |
810 |
+ "filename": "proxmoxer/backends/ssh_paramiko.py", |
811 |
+ "issue_confidence": "MEDIUM", |
812 |
+ "issue_cwe": { |
813 |
+ "id": 78, |
814 |
+ "link": "https://cwe.mitre.org/data/definitions/78.html" |
815 |
+ }, |
816 |
+ "issue_severity": "MEDIUM", |
817 |
+ "issue_text": "Possible shell injection via Paramiko call, check inputs are properly sanitized.", |
818 |
+ "line_number": 63, |
819 |
+ "line_range": [ |
820 |
+ 63 |
821 |
+ ], |
822 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b601_paramiko_calls.html", |
823 |
+ "test_id": "B601", |
824 |
+ "test_name": "paramiko_calls" |
825 |
+ }, |
826 |
+ { |
827 |
+ "code": "39 with pytest.raises(NotImplementedError), tempfile.TemporaryFile(\"w+b\") as f_obj:\n40 self._session.upload_file_obj(f_obj, \"/tmp/file.iso\")\n41 \n", |
828 |
+ "col_offset": 49, |
829 |
+ "filename": "tests/test_command_base.py", |
830 |
+ "issue_confidence": "MEDIUM", |
831 |
+ "issue_cwe": { |
832 |
+ "id": 377, |
833 |
+ "link": "https://cwe.mitre.org/data/definitions/377.html" |
834 |
+ }, |
835 |
+ "issue_severity": "MEDIUM", |
836 |
+ "issue_text": "Probable insecure usage of temp file/directory.", |
837 |
+ "line_number": 40, |
838 |
+ "line_range": [ |
839 |
+ 40 |
840 |
+ ], |
841 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b108_hardcoded_tmp_directory.html", |
842 |
+ "test_id": "B108", |
843 |
+ "test_name": "hardcoded_tmp_directory" |
844 |
+ }, |
845 |
+ { |
846 |
+ "code": "160 \"-tmpfilename\",\n161 \"/tmp/tmpasdfasdf\",\n162 \"--output-format\",\n163 \"json\",\n164 ]\n165 \n166 \n167 class TestJsonSimpleSerializer:\n168 _serializer = command_base.JsonSimpleSerializer()\n169 \n170 def test_loads_pass(self):\n171 input_str = '{\"key1\": \"value1\", \"key2\": \"value2\"}'\n172 exp_output = {\"key1\": \"value1\", \"key2\": \"value2\"}\n173 \n", |
847 |
+ "col_offset": 16, |
848 |
+ "filename": "tests/test_command_base.py", |
849 |
+ "issue_confidence": "MEDIUM", |
850 |
+ "issue_cwe": { |
851 |
+ "id": 377, |
852 |
+ "link": "https://cwe.mitre.org/data/definitions/377.html" |
853 |
+ }, |
854 |
+ "issue_severity": "MEDIUM", |
855 |
+ "issue_text": "Probable insecure usage of temp file/directory.", |
856 |
+ "line_number": 161, |
857 |
+ "line_range": [ |
858 |
+ 152, |
859 |
+ 153, |
860 |
+ 154, |
861 |
+ 155, |
862 |
+ 156, |
863 |
+ 157, |
864 |
+ 158, |
865 |
+ 159, |
866 |
+ 160, |
867 |
+ 161, |
868 |
+ 162, |
869 |
+ 163 |
870 |
+ ], |
871 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b108_hardcoded_tmp_directory.html", |
872 |
+ "test_id": "B108", |
873 |
+ "test_name": "hardcoded_tmp_directory" |
874 |
+ }, |
875 |
+ { |
876 |
+ "code": "61 with tempfile.NamedTemporaryFile(\"r\") as f_obj:\n62 mock_session.upload_file_obj(f_obj, \"/tmp/file\")\n63 \n", |
877 |
+ "col_offset": 48, |
878 |
+ "filename": "tests/test_openssh.py", |
879 |
+ "issue_confidence": "MEDIUM", |
880 |
+ "issue_cwe": { |
881 |
+ "id": 377, |
882 |
+ "link": "https://cwe.mitre.org/data/definitions/377.html" |
883 |
+ }, |
884 |
+ "issue_severity": "MEDIUM", |
885 |
+ "issue_text": "Probable insecure usage of temp file/directory.", |
886 |
+ "line_number": 62, |
887 |
+ "line_range": [ |
888 |
+ 62 |
889 |
+ ], |
890 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b108_hardcoded_tmp_directory.html", |
891 |
+ "test_id": "B108", |
892 |
+ "test_name": "hardcoded_tmp_directory" |
893 |
+ }, |
894 |
+ { |
895 |
+ "code": "65 (f_obj,),\n66 target=\"/tmp/file\",\n67 )\n", |
896 |
+ "col_offset": 23, |
897 |
+ "filename": "tests/test_openssh.py", |
898 |
+ "issue_confidence": "MEDIUM", |
899 |
+ "issue_cwe": { |
900 |
+ "id": 377, |
901 |
+ "link": "https://cwe.mitre.org/data/definitions/377.html" |
902 |
+ }, |
903 |
+ "issue_severity": "MEDIUM", |
904 |
+ "issue_text": "Probable insecure usage of temp file/directory.", |
905 |
+ "line_number": 66, |
906 |
+ "line_range": [ |
907 |
+ 66 |
908 |
+ ], |
909 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b108_hardcoded_tmp_directory.html", |
910 |
+ "test_id": "B108", |
911 |
+ "test_name": "hardcoded_tmp_directory" |
912 |
+ }, |
913 |
+ { |
914 |
+ "code": "23 sess = ssh_paramiko.SshParamikoSession(\n24 \"host\", \"user\", password=\"password\", private_key_file=\"/tmp/key_file\", port=1234\n25 )\n", |
915 |
+ "col_offset": 66, |
916 |
+ "filename": "tests/test_paramiko.py", |
917 |
+ "issue_confidence": "MEDIUM", |
918 |
+ "issue_cwe": { |
919 |
+ "id": 377, |
920 |
+ "link": "https://cwe.mitre.org/data/definitions/377.html" |
921 |
+ }, |
922 |
+ "issue_severity": "MEDIUM", |
923 |
+ "issue_text": "Probable insecure usage of temp file/directory.", |
924 |
+ "line_number": 24, |
925 |
+ "line_range": [ |
926 |
+ 24 |
927 |
+ ], |
928 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b108_hardcoded_tmp_directory.html", |
929 |
+ "test_id": "B108", |
930 |
+ "test_name": "hardcoded_tmp_directory" |
931 |
+ }, |
932 |
+ { |
933 |
+ "code": "29 assert sess.password == \"password\"\n30 assert sess.private_key_file == \"/tmp/key_file\"\n31 assert sess.port == 1234\n", |
934 |
+ "col_offset": 40, |
935 |
+ "filename": "tests/test_paramiko.py", |
936 |
+ "issue_confidence": "MEDIUM", |
937 |
+ "issue_cwe": { |
938 |
+ "id": 377, |
939 |
+ "link": "https://cwe.mitre.org/data/definitions/377.html" |
940 |
+ }, |
941 |
+ "issue_severity": "MEDIUM", |
942 |
+ "issue_text": "Probable insecure usage of temp file/directory.", |
943 |
+ "line_number": 30, |
944 |
+ "line_range": [ |
945 |
+ 30 |
946 |
+ ], |
947 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b108_hardcoded_tmp_directory.html", |
948 |
+ "test_id": "B108", |
949 |
+ "test_name": "hardcoded_tmp_directory" |
950 |
+ }, |
951 |
+ { |
952 |
+ "code": "55 sess = ssh_paramiko.SshParamikoSession(\n56 \"host\", \"user\", password=\"password\", private_key_file=\"/tmp/key_file\", port=1234\n57 )\n", |
953 |
+ "col_offset": 66, |
954 |
+ "filename": "tests/test_paramiko.py", |
955 |
+ "issue_confidence": "MEDIUM", |
956 |
+ "issue_cwe": { |
957 |
+ "id": 377, |
958 |
+ "link": "https://cwe.mitre.org/data/definitions/377.html" |
959 |
+ }, |
960 |
+ "issue_severity": "MEDIUM", |
961 |
+ "issue_text": "Probable insecure usage of temp file/directory.", |
962 |
+ "line_number": 56, |
963 |
+ "line_range": [ |
964 |
+ 56 |
965 |
+ ], |
966 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b108_hardcoded_tmp_directory.html", |
967 |
+ "test_id": "B108", |
968 |
+ "test_name": "hardcoded_tmp_directory" |
969 |
+ }, |
970 |
+ { |
971 |
+ "code": "63 look_for_keys=True,\n64 key_filename=\"/tmp/key_file\",\n65 password=\"password\",\n", |
972 |
+ "col_offset": 25, |
973 |
+ "filename": "tests/test_paramiko.py", |
974 |
+ "issue_confidence": "MEDIUM", |
975 |
+ "issue_cwe": { |
976 |
+ "id": 377, |
977 |
+ "link": "https://cwe.mitre.org/data/definitions/377.html" |
978 |
+ }, |
979 |
+ "issue_severity": "MEDIUM", |
980 |
+ "issue_text": "Probable insecure usage of temp file/directory.", |
981 |
+ "line_number": 64, |
982 |
+ "line_range": [ |
983 |
+ 64 |
984 |
+ ], |
985 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b108_hardcoded_tmp_directory.html", |
986 |
+ "test_id": "B108", |
987 |
+ "test_name": "hardcoded_tmp_directory" |
988 |
+ }, |
989 |
+ { |
990 |
+ "code": "110 with tempfile.NamedTemporaryFile(\"r\") as f_obj:\n111 sess.upload_file_obj(f_obj, \"/tmp/file\")\n112 \n", |
991 |
+ "col_offset": 40, |
992 |
+ "filename": "tests/test_paramiko.py", |
993 |
+ "issue_confidence": "MEDIUM", |
994 |
+ "issue_cwe": { |
995 |
+ "id": 377, |
996 |
+ "link": "https://cwe.mitre.org/data/definitions/377.html" |
997 |
+ }, |
998 |
+ "issue_severity": "MEDIUM", |
999 |
+ "issue_text": "Probable insecure usage of temp file/directory.", |
1000 |
+ "line_number": 111, |
1001 |
+ "line_range": [ |
1002 |
+ 111 |
1003 |
+ ], |
1004 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b108_hardcoded_tmp_directory.html", |
1005 |
+ "test_id": "B108", |
1006 |
+ "test_name": "hardcoded_tmp_directory" |
1007 |
+ }, |
1008 |
+ { |
1009 |
+ "code": "112 \n113 mock_sftp.putfo.assert_called_once_with(f_obj, \"/tmp/file\")\n114 \n", |
1010 |
+ "col_offset": 59, |
1011 |
+ "filename": "tests/test_paramiko.py", |
1012 |
+ "issue_confidence": "MEDIUM", |
1013 |
+ "issue_cwe": { |
1014 |
+ "id": 377, |
1015 |
+ "link": "https://cwe.mitre.org/data/definitions/377.html" |
1016 |
+ }, |
1017 |
+ "issue_severity": "MEDIUM", |
1018 |
+ "issue_text": "Probable insecure usage of temp file/directory.", |
1019 |
+ "line_number": 113, |
1020 |
+ "line_range": [ |
1021 |
+ 113 |
1022 |
+ ], |
1023 |
+ "more_info": "https://bandit.readthedocs.io/en/1.7.4/plugins/b108_hardcoded_tmp_directory.html", |
1024 |
+ "test_id": "B108", |
1025 |
+ "test_name": "hardcoded_tmp_directory" |
1026 |
+ } |
1027 |
+ ] |
1028 |
+} |
1029 |
\ No newline at end of file |
1030 |
diff -ruN tests/tools/__init__.py proxmoxer-2.2.0/tests/tools/__init__.py |
1031 |
--- tests/tools/__init__.py 1970-01-01 01:00:00.000000000 +0100 |
1032 |
+++ proxmoxer-2.2.0/tests/tools/__init__.py 2024-12-15 02:12:42.000000000 +0000 |
1033 |
@@ -0,0 +1,3 @@ |
1034 |
+__author__ = "John Hollowell" |
1035 |
+__copyright__ = "(c) John Hollowell 2022" |
1036 |
+__license__ = "MIT" |
1037 |
diff -ruN tests/tools/test_files.py proxmoxer-2.2.0/tests/tools/test_files.py |
1038 |
--- tests/tools/test_files.py 1970-01-01 01:00:00.000000000 +0100 |
1039 |
+++ proxmoxer-2.2.0/tests/tools/test_files.py 2024-12-15 02:12:42.000000000 +0000 |
1040 |
@@ -0,0 +1,375 @@ |
1041 |
+__author__ = "John Hollowell" |
1042 |
+__copyright__ = "(c) John Hollowell 2023" |
1043 |
+__license__ = "MIT" |
1044 |
+ |
1045 |
+import logging |
1046 |
+import tempfile |
1047 |
+from unittest import mock |
1048 |
+ |
1049 |
+import pytest |
1050 |
+ |
1051 |
+from proxmoxer import ProxmoxAPI, core |
1052 |
+from proxmoxer.tools import ChecksumInfo, Files, SupportedChecksums |
1053 |
+ |
1054 |
+from ..api_mock import mock_pve # pylint: disable=unused-import # noqa: F401 |
1055 |
+from ..files_mock import ( # pylint: disable=unused-import # noqa: F401 |
1056 |
+ mock_files, |
1057 |
+ mock_files_and_pve, |
1058 |
+) |
1059 |
+ |
1060 |
+MODULE_LOGGER_NAME = "proxmoxer.tools.files" |
1061 |
+ |
1062 |
+ |
1063 |
+class TestChecksumInfo: |
1064 |
+ def test_basic(self): |
1065 |
+ info = ChecksumInfo("name", 123) |
1066 |
+ |
1067 |
+ assert info.name == "name" |
1068 |
+ assert info.hex_size == 123 |
1069 |
+ |
1070 |
+ def test_str(self): |
1071 |
+ info = ChecksumInfo("name", 123) |
1072 |
+ |
1073 |
+ assert str(info) == "name" |
1074 |
+ |
1075 |
+ def test_repr(self): |
1076 |
+ info = ChecksumInfo("name", 123) |
1077 |
+ |
1078 |
+ assert repr(info) == "name (123 digits)" |
1079 |
+ |
1080 |
+ |
1081 |
+class TestGetChecksum: |
1082 |
+ def test_get_checksum_from_sibling_file_success(self, mock_files): |
1083 |
+ url = "https://sub.domain.tld/sibling/file.iso" |
1084 |
+ exp_hash = "this_is_the_hash" |
1085 |
+ info = ChecksumInfo("testing", 16) |
1086 |
+ res1 = Files._get_checksum_from_sibling_file(url, checksum_info=info) |
1087 |
+ res2 = Files._get_checksum_from_sibling_file(url, checksum_info=info, filename="file.iso") |
1088 |
+ |
1089 |
+ assert res1 == exp_hash |
1090 |
+ assert res2 == exp_hash |
1091 |
+ |
1092 |
+ def test_get_checksum_from_sibling_file_fail(self, mock_files): |
1093 |
+ url = "https://sub.domain.tld/sibling/missing.iso" |
1094 |
+ info = ChecksumInfo("testing", 16) |
1095 |
+ res1 = Files._get_checksum_from_sibling_file(url, checksum_info=info) |
1096 |
+ res2 = Files._get_checksum_from_sibling_file( |
1097 |
+ url, checksum_info=info, filename="missing.iso" |
1098 |
+ ) |
1099 |
+ |
1100 |
+ assert res1 is None |
1101 |
+ assert res2 is None |
1102 |
+ |
1103 |
+ def test_get_checksum_from_extension_success(self, mock_files): |
1104 |
+ url = "https://sub.domain.tld/extension/file.iso" |
1105 |
+ exp_hash = "this_is_the_hash" |
1106 |
+ info = ChecksumInfo("testing", 16) |
1107 |
+ res1 = Files._get_checksum_from_extension(url, checksum_info=info) |
1108 |
+ res2 = Files._get_checksum_from_extension(url, checksum_info=info, filename="file.iso") |
1109 |
+ |
1110 |
+ assert res1 == exp_hash |
1111 |
+ assert res2 == exp_hash |
1112 |
+ |
1113 |
+ def test_get_checksum_from_extension_fail(self, mock_files): |
1114 |
+ url = "https://sub.domain.tld/extension/missing.iso" |
1115 |
+ |
1116 |
+ info = ChecksumInfo("testing", 16) |
1117 |
+ res1 = Files._get_checksum_from_extension(url, checksum_info=info) |
1118 |
+ res2 = Files._get_checksum_from_extension( |
1119 |
+ url, checksum_info=info, filename="connectionerror.iso" |
1120 |
+ ) |
1121 |
+ res3 = Files._get_checksum_from_extension( |
1122 |
+ url, checksum_info=info, filename="readtimeout.iso" |
1123 |
+ ) |
1124 |
+ |
1125 |
+ assert res1 is None |
1126 |
+ assert res2 is None |
1127 |
+ assert res3 is None |
1128 |
+ |
1129 |
+ def test_get_checksum_from_extension_upper_success(self, mock_files): |
1130 |
+ url = "https://sub.domain.tld/upper/file.iso" |
1131 |
+ exp_hash = "this_is_the_hash" |
1132 |
+ info = ChecksumInfo("testing", 16) |
1133 |
+ res1 = Files._get_checksum_from_extension_upper(url, checksum_info=info) |
1134 |
+ res2 = Files._get_checksum_from_extension_upper( |
1135 |
+ url, checksum_info=info, filename="file.iso" |
1136 |
+ ) |
1137 |
+ |
1138 |
+ assert res1 == exp_hash |
1139 |
+ assert res2 == exp_hash |
1140 |
+ |
1141 |
+ def test_get_checksum_from_extension_upper_fail(self, mock_files): |
1142 |
+ url = "https://sub.domain.tld/upper/missing.iso" |
1143 |
+ info = ChecksumInfo("testing", 16) |
1144 |
+ res1 = Files._get_checksum_from_extension_upper(url, checksum_info=info) |
1145 |
+ res2 = Files._get_checksum_from_extension_upper( |
1146 |
+ url, checksum_info=info, filename="missing.iso" |
1147 |
+ ) |
1148 |
+ |
1149 |
+ assert res1 is None |
1150 |
+ assert res2 is None |
1151 |
+ |
1152 |
+ def test_get_checksums_from_file_url_all_checksums(self, mock_files): |
1153 |
+ base_url = "https://sub.domain.tld/checksums/file.iso" |
1154 |
+ full_checksum_string = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" |
1155 |
+ for types_enum in SupportedChecksums: |
1156 |
+ checksum_info = types_enum.value |
1157 |
+ |
1158 |
+ data = Files.get_checksums_from_file_url(base_url, preferred_type=checksum_info) |
1159 |
+ |
1160 |
+ assert data[0] == full_checksum_string[0 : checksum_info.hex_size] |
1161 |
+ assert data[1] == checksum_info |
1162 |
+ |
1163 |
+ def test_get_checksums_from_file_url_missing(self, mock_files): |
1164 |
+ url = "https://sub.domain.tld/missing.iso" |
1165 |
+ |
1166 |
+ data = Files.get_checksums_from_file_url(url) |
1167 |
+ |
1168 |
+ assert data[0] is None |
1169 |
+ assert data[1] is None |
1170 |
+ |
1171 |
+ |
1172 |
+class TestFiles: |
1173 |
+ prox = ProxmoxAPI("1.2.3.4:1234", token_name="name", token_value="value") |
1174 |
+ |
1175 |
+ def test_init_basic(self): |
1176 |
+ f = Files(self.prox, "node1", "storage1") |
1177 |
+ |
1178 |
+ assert f._prox == self.prox |
1179 |
+ assert f._node == "node1" |
1180 |
+ assert f._storage == "storage1" |
1181 |
+ |
1182 |
+ def test_repr(self): |
1183 |
+ f = Files(self.prox, "node1", "storage1") |
1184 |
+ assert ( |
1185 |
+ repr(f) |
1186 |
+ == "Files (node1/storage1 at ProxmoxAPI (https backend for https://1.2.3.4:1234/api2/json))" |
1187 |
+ ) |
1188 |
+ |
1189 |
+ def test_get_file_info_pass(self, mock_pve): |
1190 |
+ f = Files(self.prox, "node1", "storage1") |
1191 |
+ info = f.get_file_info("https://sub.domain.tld/file.iso") |
1192 |
+ |
1193 |
+ assert info["filename"] == "file.iso" |
1194 |
+ assert info["mimetype"] == "application/x-iso9660-image" |
1195 |
+ assert info["size"] == 123456 |
1196 |
+ |
1197 |
+ def test_get_file_info_fail(self, mock_pve): |
1198 |
+ f = Files(self.prox, "node1", "storage1") |
1199 |
+ info = f.get_file_info("https://sub.domain.tld/invalid.iso") |
1200 |
+ |
1201 |
+ assert info is None |
1202 |
+ |
1203 |
+ |
1204 |
+class TestFilesDownload: |
1205 |
+ prox = ProxmoxAPI("1.2.3.4:1234", token_name="name", token_value="value") |
1206 |
+ f = Files(prox, "node1", "storage1") |
1207 |
+ |
1208 |
+ def test_download_discover_checksum(self, mock_files_and_pve, caplog): |
1209 |
+ status = self.f.download_file_to_storage("https://sub.domain.tld/checksums/file.iso") |
1210 |
+ |
1211 |
+ # this is the default "done" task mock information |
1212 |
+ assert status == { |
1213 |
+ "upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:done", |
1214 |
+ "starttime": 1661825068, |
1215 |
+ "user": "root@pam", |
1216 |
+ "type": "vzdump", |
1217 |
+ "pstart": 284768076, |
1218 |
+ "status": "stopped", |
1219 |
+ "exitstatus": "OK", |
1220 |
+ "pid": 1044989, |
1221 |
+ "id": "110", |
1222 |
+ "node": "node1", |
1223 |
+ } |
1224 |
+ assert caplog.record_tuples == [] |
1225 |
+ |
1226 |
+ def test_download_no_blocking(self, mock_files_and_pve, caplog): |
1227 |
+ status = self.f.download_file_to_storage( |
1228 |
+ "https://sub.domain.tld/checksums/file.iso", blocking_status=False |
1229 |
+ ) |
1230 |
+ |
1231 |
+ # this is the default "done" task mock information |
1232 |
+ assert status == { |
1233 |
+ "upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:done", |
1234 |
+ "starttime": 1661825068, |
1235 |
+ "user": "root@pam", |
1236 |
+ "type": "vzdump", |
1237 |
+ "pstart": 284768076, |
1238 |
+ "status": "stopped", |
1239 |
+ "exitstatus": "OK", |
1240 |
+ "pid": 1044989, |
1241 |
+ "id": "110", |
1242 |
+ "node": "node1", |
1243 |
+ } |
1244 |
+ assert caplog.record_tuples == [] |
1245 |
+ |
1246 |
+ def test_download_no_discover_checksum(self, mock_files_and_pve, caplog): |
1247 |
+ caplog.set_level(logging.WARNING, logger=MODULE_LOGGER_NAME) |
1248 |
+ |
1249 |
+ status = self.f.download_file_to_storage("https://sub.domain.tld/file.iso") |
1250 |
+ |
1251 |
+ # this is the default "stopped" task mock information |
1252 |
+ assert status == { |
1253 |
+ "upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:done", |
1254 |
+ "starttime": 1661825068, |
1255 |
+ "user": "root@pam", |
1256 |
+ "type": "vzdump", |
1257 |
+ "pstart": 284768076, |
1258 |
+ "status": "stopped", |
1259 |
+ "exitstatus": "OK", |
1260 |
+ "pid": 1044989, |
1261 |
+ "id": "110", |
1262 |
+ "node": "node1", |
1263 |
+ } |
1264 |
+ assert caplog.record_tuples == [ |
1265 |
+ ( |
1266 |
+ MODULE_LOGGER_NAME, |
1267 |
+ logging.WARNING, |
1268 |
+ "Unable to discover checksum. Will not do checksum validation", |
1269 |
+ ), |
1270 |
+ ] |
1271 |
+ |
1272 |
+ def test_uneven_checksum(self, caplog, mock_files_and_pve): |
1273 |
+ caplog.set_level(logging.DEBUG, logger=MODULE_LOGGER_NAME) |
1274 |
+ status = self.f.download_file_to_storage("https://sub.domain.tld/file.iso", checksum="asdf") |
1275 |
+ |
1276 |
+ assert status is None |
1277 |
+ |
1278 |
+ assert caplog.record_tuples == [ |
1279 |
+ ( |
1280 |
+ MODULE_LOGGER_NAME, |
1281 |
+ logging.ERROR, |
1282 |
+ "Must pass both checksum and checksum_type or leave both None for auto-discovery", |
1283 |
+ ), |
1284 |
+ ] |
1285 |
+ |
1286 |
+ def test_uneven_checksum_type(self, caplog, mock_files_and_pve): |
1287 |
+ caplog.set_level(logging.DEBUG, logger=MODULE_LOGGER_NAME) |
1288 |
+ status = self.f.download_file_to_storage( |
1289 |
+ "https://sub.domain.tld/file.iso", checksum_type="asdf" |
1290 |
+ ) |
1291 |
+ |
1292 |
+ assert status is None |
1293 |
+ |
1294 |
+ assert caplog.record_tuples == [ |
1295 |
+ ( |
1296 |
+ MODULE_LOGGER_NAME, |
1297 |
+ logging.ERROR, |
1298 |
+ "Must pass both checksum and checksum_type or leave both None for auto-discovery", |
1299 |
+ ), |
1300 |
+ ] |
1301 |
+ |
1302 |
+ def test_get_file_info_missing(self, mock_pve): |
1303 |
+ f = Files(self.prox, "node1", "storage1") |
1304 |
+ info = f.get_file_info("https://sub.domain.tld/missing.iso") |
1305 |
+ |
1306 |
+ assert info is None |
1307 |
+ |
1308 |
+ def test_get_file_info_non_iso(self, mock_pve): |
1309 |
+ f = Files(self.prox, "node1", "storage1") |
1310 |
+ info = f.get_file_info("https://sub.domain.tld/index.html") |
1311 |
+ |
1312 |
+ assert info["filename"] == "index.html" |
1313 |
+ assert info["mimetype"] == "text/html" |
1314 |
+ |
1315 |
+ |
1316 |
+class TestFilesUpload: |
1317 |
+ prox = ProxmoxAPI("1.2.3.4:1234", token_name="name", token_value="value") |
1318 |
+ f = Files(prox, "node1", "storage1") |
1319 |
+ |
1320 |
+ def test_upload_no_file(self, mock_files_and_pve, caplog): |
1321 |
+ status = self.f.upload_local_file_to_storage("/does-not-exist.iso") |
1322 |
+ |
1323 |
+ assert status is None |
1324 |
+ assert caplog.record_tuples == [ |
1325 |
+ ( |
1326 |
+ MODULE_LOGGER_NAME, |
1327 |
+ logging.ERROR, |
1328 |
+ '"/does-not-exist.iso" does not exist or is not a file', |
1329 |
+ ), |
1330 |
+ ] |
1331 |
+ |
1332 |
+ def test_upload_dir(self, mock_files_and_pve, caplog): |
1333 |
+ with tempfile.TemporaryDirectory() as tmp_dir: |
1334 |
+ status = self.f.upload_local_file_to_storage(tmp_dir) |
1335 |
+ |
1336 |
+ assert status is None |
1337 |
+ assert caplog.record_tuples == [ |
1338 |
+ ( |
1339 |
+ MODULE_LOGGER_NAME, |
1340 |
+ logging.ERROR, |
1341 |
+ f'"{tmp_dir}" does not exist or is not a file', |
1342 |
+ ), |
1343 |
+ ] |
1344 |
+ |
1345 |
+ def test_upload_empty_file(self, mock_files_and_pve, caplog): |
1346 |
+ with tempfile.NamedTemporaryFile("rb") as f_obj: |
1347 |
+ status = self.f.upload_local_file_to_storage(filename=f_obj.name) |
1348 |
+ |
1349 |
+ assert status is not None |
1350 |
+ assert caplog.record_tuples == [] |
1351 |
+ |
1352 |
+ def test_upload_non_empty_file(self, mock_files_and_pve, caplog): |
1353 |
+ with tempfile.NamedTemporaryFile("w+b") as f_obj: |
1354 |
+ f_obj.write(b"a" * 100) |
1355 |
+ f_obj.seek(0) |
1356 |
+ status = self.f.upload_local_file_to_storage(filename=f_obj.name) |
1357 |
+ |
1358 |
+ assert status is not None |
1359 |
+ assert caplog.record_tuples == [] |
1360 |
+ |
1361 |
+ def test_upload_no_checksum(self, mock_files_and_pve, caplog): |
1362 |
+ with tempfile.NamedTemporaryFile("rb") as f_obj: |
1363 |
+ status = self.f.upload_local_file_to_storage( |
1364 |
+ filename=f_obj.name, do_checksum_check=False |
1365 |
+ ) |
1366 |
+ |
1367 |
+ assert status is not None |
1368 |
+ assert caplog.record_tuples == [] |
1369 |
+ |
1370 |
+ def test_upload_checksum_unavailable(self, mock_files_and_pve, caplog, apply_no_checksums): |
1371 |
+ with tempfile.NamedTemporaryFile("rb") as f_obj: |
1372 |
+ status = self.f.upload_local_file_to_storage(filename=f_obj.name) |
1373 |
+ |
1374 |
+ assert status is not None |
1375 |
+ assert caplog.record_tuples == [ |
1376 |
+ ( |
1377 |
+ MODULE_LOGGER_NAME, |
1378 |
+ logging.WARNING, |
1379 |
+ "There are no Proxmox supported checksums which are supported by hashlib. Skipping checksum validation", |
1380 |
+ ) |
1381 |
+ ] |
1382 |
+ |
1383 |
+ def test_upload_non_blocking(self, mock_files_and_pve, caplog): |
1384 |
+ with tempfile.NamedTemporaryFile("rb") as f_obj: |
1385 |
+ status = self.f.upload_local_file_to_storage(filename=f_obj.name, blocking_status=False) |
1386 |
+ |
1387 |
+ assert status is not None |
1388 |
+ assert caplog.record_tuples == [] |
1389 |
+ |
1390 |
+ def test_upload_proxmox_error(self, mock_files_and_pve, caplog): |
1391 |
+ with tempfile.NamedTemporaryFile("rb") as f_obj: |
1392 |
+ f_copy = Files(self.f._prox, self.f._node, "missing") |
1393 |
+ |
1394 |
+ with pytest.raises(core.ResourceException) as exc_info: |
1395 |
+ f_copy.upload_local_file_to_storage(filename=f_obj.name) |
1396 |
+ |
1397 |
+ assert exc_info.value.status_code == 500 |
1398 |
+ assert exc_info.value.status_message == "Internal Server Error" |
1399 |
+ # assert exc_info.value.content == "storage 'missing' does not exist" |
1400 |
+ |
1401 |
+ def test_upload_io_error(self, mock_files_and_pve, caplog): |
1402 |
+ with tempfile.NamedTemporaryFile("rb") as f_obj: |
1403 |
+ mo = mock.mock_open() |
1404 |
+ mo.side_effect = IOError("ERROR MESSAGE") |
1405 |
+ with mock.patch("builtins.open", mo): |
1406 |
+ status = self.f.upload_local_file_to_storage(filename=f_obj.name) |
1407 |
+ |
1408 |
+ assert status is None |
1409 |
+ assert caplog.record_tuples == [(MODULE_LOGGER_NAME, logging.ERROR, "ERROR MESSAGE")] |
1410 |
+ |
1411 |
+ |
1412 |
+@pytest.fixture |
1413 |
+def apply_no_checksums(): |
1414 |
+ with mock.patch("hashlib.algorithms_available", set()): |
1415 |
+ yield |
1416 |
diff -ruN tests/tools/test_tasks.py proxmoxer-2.2.0/tests/tools/test_tasks.py |
1417 |
--- tests/tools/test_tasks.py 1970-01-01 01:00:00.000000000 +0100 |
1418 |
+++ proxmoxer-2.2.0/tests/tools/test_tasks.py 2024-12-15 02:12:42.000000000 +0000 |
1419 |
@@ -0,0 +1,223 @@ |
1420 |
+__author__ = "John Hollowell" |
1421 |
+__copyright__ = "(c) John Hollowell 2022" |
1422 |
+__license__ = "MIT" |
1423 |
+ |
1424 |
+import logging |
1425 |
+ |
1426 |
+import pytest |
1427 |
+ |
1428 |
+from proxmoxer import ProxmoxAPI |
1429 |
+from proxmoxer.tools import Tasks |
1430 |
+ |
1431 |
+from ..api_mock import mock_pve # pylint: disable=unused-import # noqa: F401 |
1432 |
+ |
1433 |
+ |
1434 |
+class TestBlockingStatus: |
1435 |
+ def test_basic(self, mocked_prox, caplog): |
1436 |
+ caplog.set_level(logging.DEBUG, logger="proxmoxer.core") |
1437 |
+ |
1438 |
+ status = Tasks.blocking_status( |
1439 |
+ mocked_prox, "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:done" |
1440 |
+ ) |
1441 |
+ |
1442 |
+ assert status == { |
1443 |
+ "upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:done", |
1444 |
+ "starttime": 1661825068, |
1445 |
+ "user": "root@pam", |
1446 |
+ "type": "vzdump", |
1447 |
+ "pstart": 284768076, |
1448 |
+ "status": "stopped", |
1449 |
+ "exitstatus": "OK", |
1450 |
+ "pid": 1044989, |
1451 |
+ "id": "110", |
1452 |
+ "node": "node1", |
1453 |
+ } |
1454 |
+ assert caplog.record_tuples == [ |
1455 |
+ ( |
1456 |
+ "proxmoxer.core", |
1457 |
+ 20, |
1458 |
+ "GET https://1.2.3.4:1234/api2/json/nodes/node1/tasks/UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:done/status", |
1459 |
+ ), |
1460 |
+ ( |
1461 |
+ "proxmoxer.core", |
1462 |
+ 10, |
1463 |
+ 'Status code: 200, output: b\'{"data": {"upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:done", "starttime": 1661825068, "user": "root@pam", "type": "vzdump", "pstart": 284768076, "status": "stopped", "exitstatus": "OK", "pid": 1044989, "id": "110", "node": "node1"}}\'', |
1464 |
+ ), |
1465 |
+ ] |
1466 |
+ |
1467 |
+ def test_zeroed(self, mocked_prox, caplog): |
1468 |
+ caplog.set_level(logging.DEBUG, logger="proxmoxer.core") |
1469 |
+ |
1470 |
+ status = Tasks.blocking_status( |
1471 |
+ mocked_prox, "UPID:node:00000000:00000000:00000000:task:id:root@pam:comment" |
1472 |
+ ) |
1473 |
+ |
1474 |
+ assert status == { |
1475 |
+ "upid": "UPID:node:00000000:00000000:00000000:task:id:root@pam:comment", |
1476 |
+ "node": "node", |
1477 |
+ "pid": 0, |
1478 |
+ "pstart": 0, |
1479 |
+ "starttime": 0, |
1480 |
+ "type": "task", |
1481 |
+ "id": "id", |
1482 |
+ "user": "root@pam", |
1483 |
+ "status": "stopped", |
1484 |
+ "exitstatus": "OK", |
1485 |
+ } |
1486 |
+ assert caplog.record_tuples == [ |
1487 |
+ ( |
1488 |
+ "proxmoxer.core", |
1489 |
+ 20, |
1490 |
+ "GET https://1.2.3.4:1234/api2/json/nodes/node/tasks/UPID:node:00000000:00000000:00000000:task:id:root@pam:comment/status", |
1491 |
+ ), |
1492 |
+ ( |
1493 |
+ "proxmoxer.core", |
1494 |
+ 10, |
1495 |
+ 'Status code: 200, output: b\'{"data": {"upid": "UPID:node:00000000:00000000:00000000:task:id:root@pam:comment", "node": "node", "pid": 0, "pstart": 0, "starttime": 0, "type": "task", "id": "id", "user": "root@pam", "status": "stopped", "exitstatus": "OK"}}\'', |
1496 |
+ ), |
1497 |
+ ] |
1498 |
+ |
1499 |
+ def test_killed(self, mocked_prox, caplog): |
1500 |
+ caplog.set_level(logging.DEBUG, logger="proxmoxer.core") |
1501 |
+ |
1502 |
+ status = Tasks.blocking_status( |
1503 |
+ mocked_prox, "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:stopped" |
1504 |
+ ) |
1505 |
+ |
1506 |
+ assert status == { |
1507 |
+ "upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:stopped", |
1508 |
+ "starttime": 1661825068, |
1509 |
+ "user": "root@pam", |
1510 |
+ "type": "vzdump", |
1511 |
+ "pstart": 284768076, |
1512 |
+ "status": "stopped", |
1513 |
+ "exitstatus": "interrupted by signal", |
1514 |
+ "pid": 1044989, |
1515 |
+ "id": "110", |
1516 |
+ "node": "node1", |
1517 |
+ } |
1518 |
+ assert caplog.record_tuples == [ |
1519 |
+ ( |
1520 |
+ "proxmoxer.core", |
1521 |
+ 20, |
1522 |
+ "GET https://1.2.3.4:1234/api2/json/nodes/node1/tasks/UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:stopped/status", |
1523 |
+ ), |
1524 |
+ ( |
1525 |
+ "proxmoxer.core", |
1526 |
+ 10, |
1527 |
+ 'Status code: 200, output: b\'{"data": {"upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:stopped", "starttime": 1661825068, "user": "root@pam", "type": "vzdump", "pstart": 284768076, "status": "stopped", "exitstatus": "interrupted by signal", "pid": 1044989, "id": "110", "node": "node1"}}\'', |
1528 |
+ ), |
1529 |
+ ] |
1530 |
+ |
1531 |
+ def test_timeout(self, mocked_prox, caplog): |
1532 |
+ caplog.set_level(logging.DEBUG, logger="proxmoxer.core") |
1533 |
+ |
1534 |
+ status = Tasks.blocking_status( |
1535 |
+ mocked_prox, |
1536 |
+ "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:keep-running", |
1537 |
+ timeout=0.021, |
1538 |
+ polling_interval=0.01, |
1539 |
+ ) |
1540 |
+ |
1541 |
+ assert status is None |
1542 |
+ assert caplog.record_tuples == [ |
1543 |
+ ( |
1544 |
+ "proxmoxer.core", |
1545 |
+ 20, |
1546 |
+ "GET https://1.2.3.4:1234/api2/json/nodes/node1/tasks/UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:keep-running/status", |
1547 |
+ ), |
1548 |
+ ( |
1549 |
+ "proxmoxer.core", |
1550 |
+ 10, |
1551 |
+ 'Status code: 200, output: b\'{"data": {"id": "110", "pid": 1044989, "node": "node1", "pstart": 284768076, "status": "running", "upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:keep-running", "starttime": 1661825068, "user": "root@pam", "type": "vzdump"}}\'', |
1552 |
+ ), |
1553 |
+ ( |
1554 |
+ "proxmoxer.core", |
1555 |
+ 20, |
1556 |
+ "GET https://1.2.3.4:1234/api2/json/nodes/node1/tasks/UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:keep-running/status", |
1557 |
+ ), |
1558 |
+ ( |
1559 |
+ "proxmoxer.core", |
1560 |
+ 10, |
1561 |
+ 'Status code: 200, output: b\'{"data": {"id": "110", "pid": 1044989, "node": "node1", "pstart": 284768076, "status": "running", "upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:keep-running", "starttime": 1661825068, "user": "root@pam", "type": "vzdump"}}\'', |
1562 |
+ ), |
1563 |
+ ( |
1564 |
+ "proxmoxer.core", |
1565 |
+ 20, |
1566 |
+ "GET https://1.2.3.4:1234/api2/json/nodes/node1/tasks/UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:keep-running/status", |
1567 |
+ ), |
1568 |
+ ( |
1569 |
+ "proxmoxer.core", |
1570 |
+ 10, |
1571 |
+ 'Status code: 200, output: b\'{"data": {"id": "110", "pid": 1044989, "node": "node1", "pstart": 284768076, "status": "running", "upid": "UPID:node1:000FF1FD:10F9374C:630D702C:vzdump:110:root@pam:keep-running", "starttime": 1661825068, "user": "root@pam", "type": "vzdump"}}\'', |
1572 |
+ ), |
1573 |
+ ] |
1574 |
+ |
1575 |
+ |
1576 |
+class TestDecodeUpid: |
1577 |
+ def test_basic(self): |
1578 |
+ upid = "UPID:node:000CFC5C:03E8D0C3:6194806C:aptupdate::root@pam:" |
1579 |
+ decoded = Tasks.decode_upid(upid) |
1580 |
+ |
1581 |
+ assert decoded["upid"] == upid |
1582 |
+ assert decoded["node"] == "node" |
1583 |
+ assert decoded["pid"] == 851036 |
1584 |
+ assert decoded["pstart"] == 65589443 |
1585 |
+ assert decoded["starttime"] == 1637122156 |
1586 |
+ assert decoded["type"] == "aptupdate" |
1587 |
+ assert decoded["id"] == "" |
1588 |
+ assert decoded["user"] == "root@pam" |
1589 |
+ assert decoded["comment"] == "" |
1590 |
+ |
1591 |
+ def test_all_values(self): |
1592 |
+ upid = "UPID:node1:000CFFFA:03E8EF53:619480BA:vzdump:103:root@pam:local" |
1593 |
+ decoded = Tasks.decode_upid(upid) |
1594 |
+ |
1595 |
+ assert decoded["upid"] == upid |
1596 |
+ assert decoded["node"] == "node1" |
1597 |
+ assert decoded["pid"] == 851962 |
1598 |
+ assert decoded["pstart"] == 65597267 |
1599 |
+ assert decoded["starttime"] == 1637122234 |
1600 |
+ assert decoded["type"] == "vzdump" |
1601 |
+ assert decoded["id"] == "103" |
1602 |
+ assert decoded["user"] == "root@pam" |
1603 |
+ assert decoded["comment"] == "local" |
1604 |
+ |
1605 |
+ def test_invalid_length(self): |
1606 |
+ upid = "UPID:node1:000CFFFA:03E8EF53:619480BA:vzdump:103:root@pam" |
1607 |
+ with pytest.raises(AssertionError) as exc_info: |
1608 |
+ Tasks.decode_upid(upid) |
1609 |
+ |
1610 |
+ assert str(exc_info.value) == "UPID is not in the correct format" |
1611 |
+ |
1612 |
+ def test_invalid_start(self): |
1613 |
+ upid = "ASDF:node1:000CFFFA:03E8EF53:619480BA:vzdump:103:root@pam:" |
1614 |
+ with pytest.raises(AssertionError) as exc_info: |
1615 |
+ Tasks.decode_upid(upid) |
1616 |
+ |
1617 |
+ assert str(exc_info.value) == "UPID is not in the correct format" |
1618 |
+ |
1619 |
+ |
1620 |
+class TestDecodeLog: |
1621 |
+ def test_basic(self): |
1622 |
+ log_list = [{"n": 1, "t": "client connection: 127.0.0.1:49608"}, {"t": "TASK OK", "n": 2}] |
1623 |
+ log_str = Tasks.decode_log(log_list) |
1624 |
+ |
1625 |
+ assert log_str == "client connection: 127.0.0.1:49608\nTASK OK" |
1626 |
+ |
1627 |
+ def test_empty(self): |
1628 |
+ log_list = [] |
1629 |
+ log_str = Tasks.decode_log(log_list) |
1630 |
+ |
1631 |
+ assert log_str == "" |
1632 |
+ |
1633 |
+ def test_unordered(self): |
1634 |
+ log_list = [{"n": 3, "t": "third"}, {"t": "first", "n": 1}, {"t": "second", "n": 2}] |
1635 |
+ log_str = Tasks.decode_log(log_list) |
1636 |
+ |
1637 |
+ assert log_str == "first\nsecond\nthird" |
1638 |
+ |
1639 |
+ |
1640 |
+@pytest.fixture |
1641 |
+def mocked_prox(mock_pve): |
1642 |
+ return ProxmoxAPI("1.2.3.4:1234", user="user", password="password") |