|
9 | 9 | from itertools import cycle |
10 | 10 | import shlex |
11 | 11 | from mock import call |
| 12 | +from requests.exceptions import ReadTimeout |
12 | 13 |
|
13 | 14 | from app.nautical_env import NauticalEnv |
14 | 15 | from app.backup import NauticalBackup |
@@ -2749,3 +2750,83 @@ def test_STOP_TIMEOUT_label( |
2749 | 2750 |
|
2750 | 2751 | # Ensure the set timeout is 20 |
2751 | 2752 | mock_container1.stop.assert_called_once_with(timeout=20) |
| 2753 | + |
| 2754 | + @mock.patch("subprocess.run") |
| 2755 | + @pytest.mark.parametrize("mock_container1", [{"name": "container1", "id": "123456789"}], indirect=True) |
| 2756 | + @pytest.mark.parametrize("mock_container2", [{"name": "container2", "id": "9876543210"}], indirect=True) |
| 2757 | + def test_stop_container_read_timeout_container_stopped( |
| 2758 | + self, |
| 2759 | + mock_subprocess_run: MagicMock, |
| 2760 | + mock_docker_client: MagicMock, |
| 2761 | + mock_container1: MagicMock, |
| 2762 | + mock_container2: MagicMock, |
| 2763 | + ): |
| 2764 | + """ReadTimeout on stop, but container actually stopped — backup should proceed normally""" |
| 2765 | + mock_subprocess_run.return_value.returncode = 0 |
| 2766 | + mock_container1.stop.side_effect = ReadTimeout() |
| 2767 | + |
| 2768 | + mock_docker_client.containers.list.return_value = [mock_container1, mock_container2] |
| 2769 | + nb = NauticalBackup(mock_docker_client) |
| 2770 | + nb.backup() |
| 2771 | + |
| 2772 | + # Both containers should be backed up (container1 stopped despite timeout) |
| 2773 | + assert mock_subprocess_run.call_count == 2 |
| 2774 | + mock_container1.start.assert_called() |
| 2775 | + mock_container2.stop.assert_called() |
| 2776 | + mock_container2.start.assert_called() |
| 2777 | + |
| 2778 | + @mock.patch("subprocess.run") |
| 2779 | + @pytest.mark.parametrize( |
| 2780 | + "mock_container1", |
| 2781 | + [ |
| 2782 | + { |
| 2783 | + "name": "container1", |
| 2784 | + "id": "123456789", |
| 2785 | + # Container never reaches exited state after the timeout |
| 2786 | + "status_side_effect": ["running", "running", "running", "running", "running"], |
| 2787 | + } |
| 2788 | + ], |
| 2789 | + indirect=True, |
| 2790 | + ) |
| 2791 | + @pytest.mark.parametrize("mock_container2", [{"name": "container2", "id": "9876543210"}], indirect=True) |
| 2792 | + def test_stop_container_read_timeout_container_not_stopped( |
| 2793 | + self, |
| 2794 | + mock_subprocess_run: MagicMock, |
| 2795 | + mock_docker_client: MagicMock, |
| 2796 | + mock_container1: MagicMock, |
| 2797 | + mock_container2: MagicMock, |
| 2798 | + ): |
| 2799 | + """ReadTimeout on stop and container never stopped — container1 fails, container2 still runs""" |
| 2800 | + mock_subprocess_run.return_value.returncode = 0 |
| 2801 | + mock_container1.stop.side_effect = ReadTimeout() |
| 2802 | + |
| 2803 | + mock_docker_client.containers.list.return_value = [mock_container1, mock_container2] |
| 2804 | + nb = NauticalBackup(mock_docker_client) |
| 2805 | + nb.backup() |
| 2806 | + |
| 2807 | + # container1 was not stopped so it should not be backed up |
| 2808 | + # container2 should still be processed |
| 2809 | + mock_container2.stop.assert_called() |
| 2810 | + mock_container2.start.assert_called() |
| 2811 | + assert mock_subprocess_run.call_count == 1 |
| 2812 | + |
| 2813 | + @mock.patch("subprocess.run") |
| 2814 | + @pytest.mark.parametrize("mock_container1", [{"name": "container1", "id": "123456789"}], indirect=True) |
| 2815 | + def test_start_container_read_timeout( |
| 2816 | + self, |
| 2817 | + mock_subprocess_run: MagicMock, |
| 2818 | + mock_docker_client: MagicMock, |
| 2819 | + mock_container1: MagicMock, |
| 2820 | + ): |
| 2821 | + """ReadTimeout on start — backup should not crash and container failure should be recorded""" |
| 2822 | + mock_subprocess_run.return_value.returncode = 0 |
| 2823 | + mock_container1.start.side_effect = ReadTimeout() |
| 2824 | + |
| 2825 | + mock_docker_client.containers.list.return_value = [mock_container1] |
| 2826 | + nb = NauticalBackup(mock_docker_client) |
| 2827 | + nb.backup() |
| 2828 | + |
| 2829 | + # Backup ran and rsync was called despite the start timeout |
| 2830 | + mock_subprocess_run.assert_called_once() |
| 2831 | + mock_container1.stop.assert_called() |
| 2832 | + mock_container1.start.assert_called() |
0 commit comments