|
1 | 1 | from unittest import mock |
2 | 2 |
|
3 | 3 | import freezegun |
| 4 | +import pytest |
4 | 5 |
|
5 | 6 | from node_cli.configs import SKALE_DIR |
| 7 | +from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH |
6 | 8 | from node_cli.mirage.mirage_boot import init as init_boot |
7 | 9 | from node_cli.mirage.mirage_boot import update |
8 | | -from node_cli.mirage.mirage_node import migrate_from_boot, request_repair, restore_mirage |
| 10 | +from node_cli.mirage.mirage_node import cleanup, migrate_from_boot, request_repair, restore_mirage |
9 | 11 | from node_cli.operations.mirage import MirageUpdateType |
10 | 12 | from node_cli.utils.node_type import NodeType |
11 | 13 | from tests.helper import CURRENT_DATETIME, CURRENT_TIMESTAMP |
@@ -139,3 +141,211 @@ def test_mirage_repair(compose_node_env_mock, get_static_params_mock, redis_clie |
139 | 141 | request_repair(snapshot_from='127.0.0.1') |
140 | 142 | assert redis_client.get('test_repair_ts') == f'{CURRENT_TIMESTAMP}'.encode('utf-8') |
141 | 143 | assert redis_client.get('test_snapshot_from') == b'127.0.0.1' |
| 144 | + |
| 145 | + |
| 146 | +@mock.patch('node_cli.utils.decorators.is_user_valid', return_value=True) |
| 147 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_docker_configuration') |
| 148 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_mirage_op') |
| 149 | +@mock.patch('node_cli.mirage.mirage_node.compose_node_env') |
| 150 | +def test_cleanup_success( |
| 151 | + mock_compose_env, |
| 152 | + mock_cleanup_mirage_op, |
| 153 | + mock_cleanup_docker_config, |
| 154 | + mock_is_user_valid, |
| 155 | + inited_node, |
| 156 | + resource_alloc, |
| 157 | + meta_file_v3, |
| 158 | +): |
| 159 | + mock_env = {'ENV_TYPE': 'devnet', 'SCHAIN_NAME': 'test_chain'} |
| 160 | + mock_compose_env.return_value = mock_env |
| 161 | + |
| 162 | + cleanup() |
| 163 | + |
| 164 | + mock_compose_env.assert_called_once_with( |
| 165 | + SKALE_DIR_ENV_FILEPATH, save=False, node_type=NodeType.MIRAGE |
| 166 | + ) |
| 167 | + mock_cleanup_mirage_op.assert_called_once_with(mock_env, 'test_chain') |
| 168 | + mock_cleanup_docker_config.assert_called_once() |
| 169 | + |
| 170 | + |
| 171 | +@mock.patch('node_cli.utils.decorators.is_user_valid', return_value=True) |
| 172 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_docker_configuration') |
| 173 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_mirage_op') |
| 174 | +@mock.patch('node_cli.mirage.mirage_node.compose_node_env') |
| 175 | +def test_cleanup_calls_operations_in_correct_order( |
| 176 | + mock_compose_env, |
| 177 | + mock_cleanup_mirage_op, |
| 178 | + mock_cleanup_docker_config, |
| 179 | + mock_is_user_valid, |
| 180 | + inited_node, |
| 181 | + resource_alloc, |
| 182 | + meta_file_v3, |
| 183 | +): |
| 184 | + from node_cli.mirage.mirage_node import cleanup |
| 185 | + |
| 186 | + mock_env = {'ENV_TYPE': 'devnet', 'SCHAIN_NAME': 'test_chain'} |
| 187 | + mock_compose_env.return_value = mock_env |
| 188 | + |
| 189 | + # Create a mock manager to track call order |
| 190 | + manager = mock.Mock() |
| 191 | + manager.attach_mock(mock_compose_env, 'compose_env') |
| 192 | + manager.attach_mock(mock_cleanup_mirage_op, 'cleanup_mirage_op') |
| 193 | + manager.attach_mock(mock_cleanup_docker_config, 'cleanup_docker_config') |
| 194 | + |
| 195 | + cleanup() |
| 196 | + |
| 197 | + # Verify the order of calls |
| 198 | + expected_calls = [ |
| 199 | + mock.call.compose_env(mock.ANY, save=False, node_type=mock.ANY), |
| 200 | + mock.call.cleanup_mirage_op(mock_env, 'test_chain'), |
| 201 | + mock.call.cleanup_docker_config(), |
| 202 | + ] |
| 203 | + manager.assert_has_calls(expected_calls, any_order=False) |
| 204 | + |
| 205 | + |
| 206 | +@mock.patch('node_cli.utils.decorators.is_user_valid', return_value=True) |
| 207 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_docker_configuration') |
| 208 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_mirage_op') |
| 209 | +@mock.patch('node_cli.mirage.mirage_node.compose_node_env') |
| 210 | +def test_cleanup_with_different_chain_names( |
| 211 | + mock_compose_env, |
| 212 | + mock_cleanup_mirage_op, |
| 213 | + mock_cleanup_docker_config, |
| 214 | + mock_is_user_valid, |
| 215 | + inited_node, |
| 216 | + resource_alloc, |
| 217 | + meta_file_v3, |
| 218 | +): |
| 219 | + test_cases = [ |
| 220 | + 'simple_chain', |
| 221 | + 'chain-with-hyphens', |
| 222 | + 'chain_with_underscores', |
| 223 | + 'ChainWithMixedCase', |
| 224 | + ] |
| 225 | + |
| 226 | + for chain_name in test_cases: |
| 227 | + mock_env = {'ENV_TYPE': 'devnet', 'SCHAIN_NAME': chain_name} |
| 228 | + mock_compose_env.return_value = mock_env |
| 229 | + |
| 230 | + cleanup() |
| 231 | + |
| 232 | + mock_cleanup_mirage_op.assert_called_with(mock_env, chain_name) |
| 233 | + |
| 234 | + |
| 235 | +@mock.patch('node_cli.utils.decorators.is_user_valid', return_value=True) |
| 236 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_docker_configuration') |
| 237 | +@mock.patch( |
| 238 | + 'node_cli.mirage.mirage_node.cleanup_mirage_op', side_effect=Exception('Cleanup failed') |
| 239 | +) |
| 240 | +@mock.patch('node_cli.mirage.mirage_node.compose_node_env') |
| 241 | +def test_cleanup_continues_after_mirage_op_error( |
| 242 | + mock_compose_env, |
| 243 | + mock_cleanup_mirage_op, |
| 244 | + mock_cleanup_docker_config, |
| 245 | + mock_is_user_valid, |
| 246 | + inited_node, |
| 247 | + resource_alloc, |
| 248 | + meta_file_v3, |
| 249 | +): |
| 250 | + mock_env = {'ENV_TYPE': 'devnet', 'SCHAIN_NAME': 'test_chain'} |
| 251 | + mock_compose_env.return_value = mock_env |
| 252 | + |
| 253 | + # The function should raise the exception from cleanup_mirage_op |
| 254 | + with pytest.raises(Exception, match='Cleanup failed'): |
| 255 | + cleanup() |
| 256 | + |
| 257 | + # But we should still verify the calls were made in order |
| 258 | + mock_compose_env.assert_called_once() |
| 259 | + mock_cleanup_mirage_op.assert_called_once_with(mock_env, 'test_chain') |
| 260 | + # cleanup_docker_configuration should not be called if cleanup_mirage_op fails |
| 261 | + mock_cleanup_docker_config.assert_not_called() |
| 262 | + |
| 263 | + |
| 264 | +@mock.patch('node_cli.utils.decorators.is_user_valid', return_value=False) |
| 265 | +def test_cleanup_fails_when_user_invalid( |
| 266 | + mock_is_user_valid, |
| 267 | + inited_node, |
| 268 | + resource_alloc, |
| 269 | + meta_file_v3, |
| 270 | +): |
| 271 | + """Test that cleanup fails when user validation fails""" |
| 272 | + import pytest |
| 273 | + |
| 274 | + from node_cli.mirage.mirage_node import cleanup |
| 275 | + |
| 276 | + with pytest.raises(SystemExit): |
| 277 | + cleanup() |
| 278 | + |
| 279 | + |
| 280 | +def test_cleanup_fails_when_not_inited(ensure_meta_removed): |
| 281 | + import pytest |
| 282 | + |
| 283 | + with pytest.raises(SystemExit): |
| 284 | + cleanup() |
| 285 | + |
| 286 | + |
| 287 | +@mock.patch('node_cli.utils.decorators.is_user_valid', return_value=True) |
| 288 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_docker_configuration') |
| 289 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_mirage_op') |
| 290 | +@mock.patch('node_cli.mirage.mirage_node.compose_node_env') |
| 291 | +@mock.patch('node_cli.mirage.mirage_node.logger') |
| 292 | +def test_cleanup_logs_success_message( |
| 293 | + mock_logger, |
| 294 | + mock_compose_env, |
| 295 | + mock_cleanup_mirage_op, |
| 296 | + mock_cleanup_docker_config, |
| 297 | + mock_is_user_valid, |
| 298 | + inited_node, |
| 299 | + resource_alloc, |
| 300 | + meta_file_v3, |
| 301 | +): |
| 302 | + mock_env = {'ENV_TYPE': 'devnet', 'SCHAIN_NAME': 'test_chain'} |
| 303 | + mock_compose_env.return_value = mock_env |
| 304 | + |
| 305 | + cleanup() |
| 306 | + |
| 307 | + mock_logger.info.assert_called_once_with( |
| 308 | + 'Mirage node was cleaned up, all containers and data removed' |
| 309 | + ) |
| 310 | + |
| 311 | + |
| 312 | +@mock.patch('node_cli.utils.decorators.is_user_valid', return_value=True) |
| 313 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_docker_configuration') |
| 314 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_mirage_op') |
| 315 | +@mock.patch('node_cli.mirage.mirage_node.compose_node_env') |
| 316 | +def test_cleanup_with_missing_schain_name( |
| 317 | + mock_compose_env, |
| 318 | + mock_cleanup_mirage_op, |
| 319 | + mock_cleanup_docker_config, |
| 320 | + mock_is_user_valid, |
| 321 | + inited_node, |
| 322 | + resource_alloc, |
| 323 | + meta_file_v3, |
| 324 | +): |
| 325 | + mock_env = {'ENV_TYPE': 'devnet'} # Missing SCHAIN_NAME |
| 326 | + mock_compose_env.return_value = mock_env |
| 327 | + |
| 328 | + with pytest.raises(KeyError): |
| 329 | + cleanup() |
| 330 | + |
| 331 | + |
| 332 | +@mock.patch('node_cli.utils.decorators.is_user_valid', return_value=True) |
| 333 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_docker_configuration') |
| 334 | +@mock.patch('node_cli.mirage.mirage_node.cleanup_mirage_op') |
| 335 | +@mock.patch('node_cli.mirage.mirage_node.compose_node_env') |
| 336 | +def test_cleanup_with_empty_schain_name( |
| 337 | + mock_compose_env, |
| 338 | + mock_cleanup_mirage_op, |
| 339 | + mock_cleanup_docker_config, |
| 340 | + mock_is_user_valid, |
| 341 | + inited_node, |
| 342 | + resource_alloc, |
| 343 | + meta_file_v3, |
| 344 | +): |
| 345 | + mock_env = {'ENV_TYPE': 'devnet', 'SCHAIN_NAME': ''} |
| 346 | + mock_compose_env.return_value = mock_env |
| 347 | + |
| 348 | + cleanup() |
| 349 | + |
| 350 | + mock_cleanup_mirage_op.assert_called_once_with(mock_env, '') |
| 351 | + mock_cleanup_docker_config.assert_called_once() |
0 commit comments