Skip to content

Commit 9eb85c8

Browse files
Migrate integration tests to unit tests
1 parent df1209f commit 9eb85c8

File tree

3 files changed

+315
-248
lines changed

3 files changed

+315
-248
lines changed

linodecli/plugins/obj/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,7 @@ def _cleanup_keys(client: CLI, options) -> None:
641641
"""
642642
Cleans up stale linode-cli generated object storage keys.
643643
"""
644+
644645
try:
645646
current_timestamp = int(time.time())
646647
if not _should_perform_key_cleanup(client, options, current_timestamp):
@@ -670,6 +671,7 @@ def _cleanup_keys(client: CLI, options) -> None:
670671
linode_cli_keys = _get_linode_cli_keys(
671672
keys["data"], key_lifespan, key_rotation_period, current_timestamp
672673
)
674+
673675
_rotate_current_key_if_needed(client, linode_cli_keys)
674676
_delete_stale_keys(client, linode_cli_keys, cleanup_batch_size)
675677

@@ -692,6 +694,7 @@ def _should_perform_key_cleanup(
692694
return True
693695
if not _is_key_cleanup_enabled(client, options):
694696
return False
697+
695698
last_cleanup = client.config.plugin_get_value(
696699
LAST_KEY_CLEANUP_TIMESTAMP_KEY
697700
)
@@ -733,17 +736,18 @@ def _get_linode_cli_keys(
733736
key_rotation_period: str,
734737
current_timestamp: int,
735738
) -> list:
736-
737739
stale_threshold = current_timestamp - parse_time(key_lifespan)
738740
rotation_threshold = current_timestamp - parse_time(key_rotation_period)
739741

740742
def extract_key_info(key: dict) -> Optional[dict]:
741743
match = re.match(r"^linode-cli-.+@.+-(\d{10,})$", key["label"])
742744
if not match:
743745
return None
746+
744747
created_timestamp = int(match.group(1))
745748
is_stale = created_timestamp < stale_threshold
746749
needs_rotation = is_stale or created_timestamp <= rotation_threshold
750+
747751
return {
748752
"id": key["id"],
749753
"label": key["label"],
@@ -761,6 +765,7 @@ def extract_key_info(key: dict) -> Optional[dict]:
761765

762766
def _rotate_current_key_if_needed(client: CLI, linode_cli_keys: list) -> None:
763767
current_access_key = client.config.plugin_get_value(ACCESS_KEY_KEY)
768+
764769
key_to_rotate = next(
765770
(
766771
key_info

tests/integration/obj/test_obj_plugin.py

Lines changed: 0 additions & 245 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import time
21
from concurrent.futures import ThreadPoolExecutor, wait
32
from typing import Callable, Optional
4-
from unittest.mock import patch
53

64
import pytest
75
import requests
@@ -349,246 +347,3 @@ def test_generate_url(
349347
response = requests.get(url.strip("\n"))
350348
assert response.text == content
351349
assert response.status_code == 200
352-
353-
354-
def test_obj_action_triggers_key_cleanup_and_deletes_stale_key(
355-
keys: Keys,
356-
monkeypatch: MonkeyPatch,
357-
create_bucket: callable,
358-
):
359-
patch_keys(keys, monkeypatch)
360-
bucket = create_bucket()
361-
362-
now = int(time.time())
363-
stale_timestamp = (
364-
now - 31 * 24 * 60 * 60
365-
) # 31 days ago (assuming 30d lifespan)
366-
fresh_timestamp = now
367-
368-
stale_key = {
369-
"id": "stale-id",
370-
"label": f"linode-cli-testuser@localhost-{stale_timestamp}",
371-
"access_key": "STALEKEY",
372-
}
373-
fresh_key = {
374-
"id": "fresh-id",
375-
"label": f"linode-cli-testuser@localhost-{fresh_timestamp}",
376-
"access_key": "FRESHKEY",
377-
}
378-
379-
def call_operation_side_effect(resource, action, *args, **kwargs):
380-
if resource == "object-storage" and action == "keys-list":
381-
return 200, {"data": [stale_key, fresh_key]}
382-
if resource == "object-storage" and action == "keys-delete":
383-
return 200, {}
384-
if resource == "object-storage" and action == "keys-create":
385-
return 200, {"access_key": "NEWKEY", "secret_key": "NEWSECRET"}
386-
if resource == "account" and action == "view":
387-
return 200, {}
388-
return 200, {}
389-
390-
with patch("linodecli.plugins.obj.__init__.CLI") as MockCLI:
391-
mock_client = MockCLI.return_value
392-
mock_client.config.plugin_get_value.side_effect = (
393-
lambda k, d=None, t=None: {
394-
"key-cleanup-enabled": True,
395-
"key-lifespan": "30d",
396-
"key-rotation-period": "10d",
397-
"key-cleanup-batch-size": 10,
398-
}[k]
399-
)
400-
mock_client.call_operation.side_effect = call_operation_side_effect
401-
mock_client.config.plugin_set_value.return_value = None
402-
mock_client.config.write_config.return_value = None
403-
404-
# Execute the ls command
405-
exec_test_command(BASE_CMD + ["ls", bucket])
406-
407-
# Check that keys-delete was called for the stale key only
408-
delete_calls = [
409-
c
410-
for c in mock_client.call_operation.mock_calls
411-
if c[1][1] == "keys-delete"
412-
]
413-
assert any(
414-
c[1][2][0] == "stale-id" for c in delete_calls
415-
), "Stale key was not deleted"
416-
assert not any(
417-
c[1][2][0] == "fresh-id" for c in delete_calls
418-
), "Fresh key should not be deleted"
419-
420-
421-
def test_obj_action_triggers_key_rotation(
422-
keys: Keys,
423-
monkeypatch: MonkeyPatch,
424-
create_bucket: callable,
425-
):
426-
patch_keys(keys, monkeypatch)
427-
bucket = create_bucket()
428-
429-
now = int(time.time())
430-
# Key created 31 days ago, rotation period is 30 days
431-
old_timestamp = now - 60 * 60 * 24 * 31
432-
433-
key_due_for_rotation = {
434-
"id": "rotate-id",
435-
"label": f"linode-cli-testuser@localhost-{old_timestamp}",
436-
"access_key": "ROTATEKEY",
437-
}
438-
439-
def call_operation_side_effect(resource, action, *args, **kwargs):
440-
if resource == "object-storage" and action == "keys-list":
441-
return 200, {"data": [key_due_for_rotation]}
442-
if resource == "object-storage" and action == "keys-create":
443-
return 200, {"access_key": "NEWKEY", "secret_key": "NEWSECRET"}
444-
if resource == "object-storage" and action == "keys-delete":
445-
return 200, {}
446-
if resource == "account" and action == "view":
447-
return 200, {}
448-
return 200, {}
449-
450-
with patch("linodecli.plugins.obj.__init__.CLI") as MockCLI:
451-
mock_client = MockCLI.return_value
452-
mock_client.config.plugin_get_value.side_effect = (
453-
lambda k, d=None, t=None: {
454-
"key-cleanup-enabled": True,
455-
"key-lifespan": "90d",
456-
"key-rotation-period": "30d",
457-
"key-cleanup-batch-size": 10,
458-
}[k]
459-
)
460-
mock_client.call_operation.side_effect = call_operation_side_effect
461-
mock_client.config.plugin_set_value.return_value = None
462-
mock_client.config.write_config.return_value = None
463-
464-
exec_test_command(BASE_CMD + ["ls", bucket])
465-
466-
# Check that keys-create (rotation) was called
467-
create_calls = [
468-
c
469-
for c in mock_client.call_operation.mock_calls
470-
if c[1][1] == "keys-create"
471-
]
472-
assert create_calls, "Key rotation (keys-create) was not triggered"
473-
474-
# Check that keys-delete was called for the old key
475-
delete_calls = [
476-
c
477-
for c in mock_client.call_operation.mock_calls
478-
if c[1][1] == "keys-delete"
479-
]
480-
assert any(
481-
c[1][2][0] == "rotate-id" for c in delete_calls
482-
), "Old key was not deleted after rotation"
483-
484-
485-
def test_obj_action_does_not_trigger_cleanup_if_recent(
486-
keys: Keys,
487-
monkeypatch: MonkeyPatch,
488-
create_bucket: callable,
489-
):
490-
patch_keys(keys, monkeypatch)
491-
bucket = create_bucket()
492-
493-
now = int(time.time())
494-
# Set last cleanup to 1 hour ago (less than 24h)
495-
last_cleanup = now - 60 * 60
496-
497-
stale_timestamp = now - 31 * 24 * 60 * 60
498-
stale_key = {
499-
"id": "stale-id",
500-
"label": f"linode-cli-testuser@localhost-{stale_timestamp}",
501-
"access_key": "STALEKEY",
502-
}
503-
504-
def call_operation_side_effect(resource, action, *args, **kwargs):
505-
if resource == "object-storage" and action == "keys-list":
506-
return 200, {"data": [stale_key]}
507-
if resource == "object-storage" and action == "keys-delete":
508-
return 200, {}
509-
if resource == "object-storage" and action == "keys-create":
510-
return 200, {"access_key": "NEWKEY", "secret_key": "NEWSECRET"}
511-
if resource == "account" and action == "view":
512-
return 200, {}
513-
return 200, {}
514-
515-
with patch("linodecli.plugins.obj.__init__.CLI") as MockCLI:
516-
mock_client = MockCLI.return_value
517-
mock_client.config.plugin_get_value.side_effect = (
518-
lambda k, d=None, t=None: {
519-
"key-cleanup-enabled": True,
520-
"key-lifespan": "30d",
521-
"key-rotation-period": "10d",
522-
"key-cleanup-batch-size": 10,
523-
"last-key-cleanup-timestamp": str(last_cleanup),
524-
}[k]
525-
)
526-
mock_client.call_operation.side_effect = call_operation_side_effect
527-
mock_client.config.plugin_set_value.return_value = None
528-
mock_client.config.write_config.return_value = None
529-
530-
exec_test_command(BASE_CMD + ["ls", bucket])
531-
532-
# Check that keys-delete was NOT called
533-
delete_calls = [
534-
c
535-
for c in mock_client.call_operation.mock_calls
536-
if c[1][1] == "keys-delete"
537-
]
538-
assert (
539-
not delete_calls
540-
), "Cleanup should not be performed if it was done in the last 24 hours"
541-
542-
543-
def test_obj_action_does_not_trigger_cleanup_if_disabled(
544-
keys: Keys,
545-
monkeypatch: MonkeyPatch,
546-
create_bucket: callable,
547-
):
548-
patch_keys(keys, monkeypatch)
549-
bucket = create_bucket()
550-
551-
now = int(time.time())
552-
stale_timestamp = now - 31 * 24 * 60 * 60
553-
stale_key = {
554-
"id": "stale-id",
555-
"label": f"linode-cli-testuser@localhost-{stale_timestamp}",
556-
"access_key": "STALEKEY",
557-
}
558-
559-
def call_operation_side_effect(resource, action, *args, **kwargs):
560-
if resource == "object-storage" and action == "keys-list":
561-
return 200, {"data": [stale_key]}
562-
if resource == "object-storage" and action == "keys-delete":
563-
return 200, {}
564-
if resource == "object-storage" and action == "keys-create":
565-
return 200, {"access_key": "NEWKEY", "secret_key": "NEWSECRET"}
566-
if resource == "account" and action == "view":
567-
return 200, {}
568-
return 200, {}
569-
570-
with patch("linodecli.plugins.obj.__init__.CLI") as MockCLI:
571-
mock_client = MockCLI.return_value
572-
mock_client.config.plugin_get_value.side_effect = (
573-
lambda k, d=None, t=None: {
574-
"key-cleanup-enabled": False, # Cleanup disabled
575-
"key-lifespan": "30d",
576-
"key-rotation-period": "10d",
577-
"key-cleanup-batch-size": 10,
578-
}[k]
579-
)
580-
mock_client.config.plugin_set_value.return_value = None
581-
mock_client.config.write_config.return_value = None
582-
mock_client.call_operation.side_effect = call_operation_side_effect
583-
584-
exec_test_command(BASE_CMD + ["ls", bucket])
585-
586-
# Check that keys-delete was NOT called
587-
delete_calls = [
588-
c
589-
for c in mock_client.call_operation.mock_calls
590-
if c[1][1] == "keys-delete"
591-
]
592-
assert (
593-
not delete_calls
594-
), "Cleanup should not be performed when key-cleanup-enabled is False"

0 commit comments

Comments
 (0)