-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_rollback_bug.py
More file actions
144 lines (118 loc) · 5.8 KB
/
Copy pathtest_rollback_bug.py
File metadata and controls
144 lines (118 loc) · 5.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
import unittest
from unittest.mock import MagicMock, patch
import docker
from main import WatcherService
from docker_handler import DockerHandler
class TestRollbackBug(unittest.TestCase):
def setUp(self):
self.mock_client = MagicMock()
with patch('docker.from_env', return_value=self.mock_client):
self.service = WatcherService()
self.service.docker.client = self.mock_client
def test_user_handling_empty_string(self):
"""Verify that empty user strings are not passed to create_args."""
mock_container = MagicMock()
mock_container.attrs = {
'Config': {'User': ''}, # Typical empty user
'HostConfig': {},
'NetworkSettings': {'Networks': {}}
}
mock_container.image.tags = ['app:latest']
plan = self.service.docker.get_recreation_plan(mock_container)
# 'user' should NOT be in create_args if it was an empty string
self.assertNotIn('user', plan['create_args'])
def test_recreate_index_error_prevention(self):
"""Verify that containers without networks don't cause IndexError."""
plan = {
"create_args": {"name": "test", "image": "img", "network_mode": "host"},
"networks": {} # Empty networks
}
# This should NOT raise IndexError
try:
self.service.docker.recreate("test", plan)
except Exception as e:
if isinstance(e, IndexError):
self.fail("recreate() raised IndexError unexpectedly!")
# Other errors are expected because we use mocks incorrectly here,
# but we only care about IndexError.
pass
def test_rollback_defensive_flow(self):
"""Verify that rollback handles missing current container and missing backup gracefully."""
self.service.client.containers.get.side_effect = docker.errors.NotFound("Not found")
# This should NOT raise IndexError
try:
self.service.perform_rollback("test", "old_id")
except Exception as e:
self.fail(f"perform_rollback() raised {type(e).__name__} unexpectedly: {e}")
def test_recreate_retry_on_user_error_success(self):
"""
Verify that recreation retries without 'user' if the first attempt fails
with a user-resolution error.
"""
plan = {
"create_args": {"name": "test", "image": "img", "user": "root"},
"networks": {}
}
# Create a mock container that fails to start once
mock_container = MagicMock()
# First call fails, second succeeds
mock_container.start.side_effect = [
docker.errors.APIError("unable to find user root: no matching entries in passwd file"),
None
]
# Setup create to return the mock container twice
self.mock_client.containers.create.return_value = mock_container
# This should call create twice and return the mock_container on the second try
result = self.service.docker.recreate("test", plan)
self.assertEqual(result, mock_container)
# Verify user was popped from the plan for the second attempt
create_calls = self.mock_client.containers.create.call_args_list
self.assertEqual(len(create_calls), 2)
# First call should have user='root'
self.assertEqual(create_calls[0][1]['user'], 'root')
# Second call should NOT have 'user'
self.assertNotIn('user', create_calls[1][1])
# Verify first container was removed
mock_container.remove.assert_called()
def test_recreate_no_retry_on_other_error(self):
"""Verify that recreation does NOT retry on non-user errors."""
plan = {
"create_args": {"name": "test", "image": "img", "user": "root"},
"networks": {}
}
mock_container = MagicMock()
mock_container.start.side_effect = Exception("Some random docker error")
self.mock_client.containers.create.return_value = mock_container
with self.assertRaisesRegex(Exception, "Some random docker error"):
self.service.docker.recreate("test", plan)
# Should only have called create once
self.assertEqual(self.mock_client.containers.create.call_count, 1)
def test_get_image_ref_from_config(self):
"""Verify that get_image_ref uses Config.Image if RepoTags are empty."""
mock_container = MagicMock()
mock_container.attrs = {'Config': {'Image': 'repo/app:latest'}}
mock_container.image.tags = [] # Empty RepoTags
ref = self.service.docker.get_image_ref(mock_container)
self.assertEqual(ref, 'repo/app:latest')
def test_get_image_ref_skip_non_latest(self):
"""Verify that get_image_ref skips images without :latest."""
mock_container = MagicMock()
mock_container.attrs = {'Config': {'Image': 'repo/app:1.2.3'}}
mock_container.image.tags = ['repo/app:1.2.3']
ref = self.service.docker.get_image_ref(mock_container)
self.assertIsNone(ref)
def test_get_watched_containers_empty_tags_but_valid_config(self):
"""Verify that containers with empty image tags but valid Config.Image are selected."""
c = MagicMock()
c.name = "crafty"
c.id = "id1"
c.labels = {}
c.attrs = {'Config': {'Image': 'crafty:latest'}}
c.image.tags = [] # The reported bug case
self.mock_client.containers.list.return_value = [c]
self.service.config.watch_by_label = False # Default mode
auto, monitor = self.service.docker.get_watched_containers()
self.assertIn(c, auto)
self.assertEqual(len(auto), 1)
if __name__ == '__main__':
unittest.main()