Skip to content

Commit fb35efb

Browse files
committed
increase coverage
1 parent df435d8 commit fb35efb

File tree

2 files changed

+312
-0
lines changed

2 files changed

+312
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright 2025 IBM, Red Hat
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
Tests for demos module.
17+
"""
18+
19+
import pytest
20+
import tempfile
21+
from pathlib import Path
22+
from unittest.mock import patch, MagicMock
23+
from codeflare_sdk.common.utils.demos import copy_demo_nbs
24+
25+
26+
class TestCopyDemoNbs:
27+
"""Test cases for copy_demo_nbs function."""
28+
29+
def test_copy_demo_nbs_directory_exists_error(self):
30+
"""Test that FileExistsError is raised when directory exists and overwrite=False."""
31+
with tempfile.TemporaryDirectory() as temp_dir:
32+
# Create a subdirectory that will conflict
33+
conflict_dir = Path(temp_dir) / "demo-notebooks"
34+
conflict_dir.mkdir()
35+
36+
with pytest.raises(FileExistsError, match="Directory.*already exists"):
37+
copy_demo_nbs(dir=str(conflict_dir), overwrite=False)
38+
39+
def test_copy_demo_nbs_overwrite_true(self):
40+
"""Test that overwrite=True allows copying to existing directory."""
41+
with tempfile.TemporaryDirectory() as temp_dir:
42+
# Create a subdirectory that will conflict
43+
conflict_dir = Path(temp_dir) / "demo-notebooks"
44+
conflict_dir.mkdir()
45+
46+
# Mock the demo_dir to point to a real directory
47+
with patch("codeflare_sdk.common.utils.demos.demo_dir", temp_dir):
48+
# Should not raise an error with overwrite=True
49+
copy_demo_nbs(dir=str(conflict_dir), overwrite=True)
50+
51+
def test_copy_demo_nbs_default_parameters(self):
52+
"""Test copy_demo_nbs with default parameters."""
53+
with tempfile.TemporaryDirectory() as temp_dir:
54+
# Mock the demo_dir to point to a real directory
55+
with patch("codeflare_sdk.common.utils.demos.demo_dir", temp_dir):
56+
# Should work with default parameters
57+
copy_demo_nbs(dir=temp_dir, overwrite=True)
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
# Copyright 2025 IBM, Red Hat
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
Tests for k8s_utils module.
17+
"""
18+
19+
import pytest
20+
from unittest.mock import mock_open, patch, MagicMock
21+
from codeflare_sdk.common.utils.k8s_utils import get_current_namespace
22+
23+
24+
class TestGetCurrentNamespace:
25+
"""Test cases for get_current_namespace function."""
26+
27+
def test_get_current_namespace_incluster_success(self):
28+
"""Test successful namespace detection from in-cluster service account."""
29+
mock_file_content = "test-namespace\n"
30+
31+
with patch("os.path.isfile", return_value=True):
32+
with patch("builtins.open", mock_open(read_data=mock_file_content)):
33+
result = get_current_namespace()
34+
35+
assert result == "test-namespace"
36+
37+
def test_get_current_namespace_incluster_file_read_error(self):
38+
"""Test handling of file read errors when reading service account namespace."""
39+
with patch("os.path.isfile", return_value=True):
40+
with patch("builtins.open", side_effect=IOError("File read error")):
41+
with patch("builtins.print") as mock_print:
42+
# Mock config_check to avoid kubeconfig fallback
43+
with patch(
44+
"codeflare_sdk.common.utils.k8s_utils.config_check",
45+
side_effect=Exception("Config error"),
46+
):
47+
with patch(
48+
"codeflare_sdk.common.utils.k8s_utils._kube_api_error_handling",
49+
return_value=None,
50+
):
51+
result = get_current_namespace()
52+
53+
assert result is None
54+
# Should see both error messages: in-cluster failure and kubeconfig fallback
55+
mock_print.assert_any_call("Unable to find current namespace")
56+
mock_print.assert_any_call("trying to gather from current context")
57+
58+
def test_get_current_namespace_incluster_file_open_error(self):
59+
"""Test handling of file open errors when reading service account namespace."""
60+
with patch("os.path.isfile", return_value=True):
61+
with patch(
62+
"builtins.open", side_effect=PermissionError("Permission denied")
63+
):
64+
with patch("builtins.print") as mock_print:
65+
# Mock config_check to avoid kubeconfig fallback
66+
with patch(
67+
"codeflare_sdk.common.utils.k8s_utils.config_check",
68+
side_effect=Exception("Config error"),
69+
):
70+
with patch(
71+
"codeflare_sdk.common.utils.k8s_utils._kube_api_error_handling",
72+
return_value=None,
73+
):
74+
result = get_current_namespace()
75+
76+
assert result is None
77+
# Should see both error messages: in-cluster failure and kubeconfig fallback
78+
mock_print.assert_any_call("Unable to find current namespace")
79+
mock_print.assert_any_call("trying to gather from current context")
80+
81+
def test_get_current_namespace_kubeconfig_success(self):
82+
"""Test successful namespace detection from kubeconfig context."""
83+
mock_contexts = [
84+
{"name": "context1", "context": {"namespace": "default"}},
85+
{"name": "context2", "context": {"namespace": "test-namespace"}},
86+
]
87+
mock_active_context = {
88+
"name": "context2",
89+
"context": {"namespace": "test-namespace"},
90+
}
91+
92+
with patch("os.path.isfile", return_value=False):
93+
with patch("builtins.print") as mock_print:
94+
with patch(
95+
"codeflare_sdk.common.utils.k8s_utils.config_check",
96+
return_value="~/.kube/config",
97+
):
98+
with patch(
99+
"kubernetes.config.list_kube_config_contexts",
100+
return_value=(mock_contexts, mock_active_context),
101+
):
102+
result = get_current_namespace()
103+
104+
assert result == "test-namespace"
105+
mock_print.assert_called_with("trying to gather from current context")
106+
107+
def test_get_current_namespace_kubeconfig_no_namespace_in_context(self):
108+
"""Test handling when kubeconfig context has no namespace field."""
109+
mock_contexts = [
110+
{"name": "context1", "context": {}},
111+
{"name": "context2", "context": {"cluster": "test-cluster"}},
112+
]
113+
mock_active_context = {
114+
"name": "context2",
115+
"context": {"cluster": "test-cluster"},
116+
}
117+
118+
with patch("os.path.isfile", return_value=False):
119+
with patch("builtins.print") as mock_print:
120+
with patch(
121+
"codeflare_sdk.common.utils.k8s_utils.config_check",
122+
return_value="~/.kube/config",
123+
):
124+
with patch(
125+
"kubernetes.config.list_kube_config_contexts",
126+
return_value=(mock_contexts, mock_active_context),
127+
):
128+
result = get_current_namespace()
129+
130+
assert result is None
131+
mock_print.assert_called_with("trying to gather from current context")
132+
133+
def test_get_current_namespace_kubeconfig_config_check_error(self):
134+
"""Test handling when config_check raises an exception."""
135+
with patch("os.path.isfile", return_value=False):
136+
with patch("builtins.print") as mock_print:
137+
with patch(
138+
"codeflare_sdk.common.utils.k8s_utils.config_check",
139+
side_effect=Exception("Config error"),
140+
):
141+
with patch(
142+
"codeflare_sdk.common.utils.k8s_utils._kube_api_error_handling",
143+
return_value=None,
144+
) as mock_error_handler:
145+
result = get_current_namespace()
146+
147+
assert result is None
148+
mock_print.assert_called_with("trying to gather from current context")
149+
mock_error_handler.assert_called_once()
150+
151+
def test_get_current_namespace_kubeconfig_list_contexts_error(self):
152+
"""Test handling when list_kube_config_contexts raises an exception."""
153+
with patch("os.path.isfile", return_value=False):
154+
with patch("builtins.print") as mock_print:
155+
with patch(
156+
"codeflare_sdk.common.utils.k8s_utils.config_check",
157+
return_value="~/.kube/config",
158+
):
159+
with patch(
160+
"kubernetes.config.list_kube_config_contexts",
161+
side_effect=Exception("Context error"),
162+
):
163+
with patch(
164+
"codeflare_sdk.common.utils.k8s_utils._kube_api_error_handling",
165+
return_value=None,
166+
) as mock_error_handler:
167+
result = get_current_namespace()
168+
169+
assert result is None
170+
mock_print.assert_called_with("trying to gather from current context")
171+
mock_error_handler.assert_called_once()
172+
173+
def test_get_current_namespace_kubeconfig_key_error(self):
174+
"""Test handling when accessing context namespace raises KeyError."""
175+
mock_contexts = [{"name": "context1", "context": {"namespace": "default"}}]
176+
mock_active_context = {"name": "context1"} # Missing 'context' key
177+
178+
with patch("os.path.isfile", return_value=False):
179+
with patch("builtins.print") as mock_print:
180+
with patch(
181+
"codeflare_sdk.common.utils.k8s_utils.config_check",
182+
return_value="~/.kube/config",
183+
):
184+
with patch(
185+
"kubernetes.config.list_kube_config_contexts",
186+
return_value=(mock_contexts, mock_active_context),
187+
):
188+
result = get_current_namespace()
189+
190+
assert result is None
191+
mock_print.assert_called_with("trying to gather from current context")
192+
193+
def test_get_current_namespace_fallback_flow(self):
194+
"""Test the complete fallback flow from in-cluster to kubeconfig."""
195+
# First attempt: in-cluster file doesn't exist
196+
# Second attempt: kubeconfig context has namespace
197+
mock_contexts = [
198+
{"name": "context1", "context": {"namespace": "fallback-namespace"}}
199+
]
200+
mock_active_context = {
201+
"name": "context1",
202+
"context": {"namespace": "fallback-namespace"},
203+
}
204+
205+
with patch("os.path.isfile", return_value=False):
206+
with patch("builtins.print") as mock_print:
207+
with patch(
208+
"codeflare_sdk.common.utils.k8s_utils.config_check",
209+
return_value="~/.kube/config",
210+
):
211+
with patch(
212+
"kubernetes.config.list_kube_config_contexts",
213+
return_value=(mock_contexts, mock_active_context),
214+
):
215+
result = get_current_namespace()
216+
217+
assert result == "fallback-namespace"
218+
mock_print.assert_called_with("trying to gather from current context")
219+
220+
def test_get_current_namespace_complete_failure(self):
221+
"""Test complete failure scenario where no namespace can be detected."""
222+
with patch("os.path.isfile", return_value=False):
223+
with patch("builtins.print") as mock_print:
224+
with patch(
225+
"codeflare_sdk.common.utils.k8s_utils.config_check",
226+
side_effect=Exception("Config error"),
227+
):
228+
with patch(
229+
"codeflare_sdk.common.utils.k8s_utils._kube_api_error_handling",
230+
return_value=None,
231+
):
232+
result = get_current_namespace()
233+
234+
assert result is None
235+
mock_print.assert_called_with("trying to gather from current context")
236+
237+
def test_get_current_namespace_mixed_errors(self):
238+
"""Test scenario with mixed error conditions."""
239+
# In-cluster file exists but read fails, then kubeconfig also fails
240+
with patch("os.path.isfile", return_value=True):
241+
with patch("builtins.open", side_effect=IOError("File read error")):
242+
with patch("builtins.print") as mock_print:
243+
with patch(
244+
"codeflare_sdk.common.utils.k8s_utils.config_check",
245+
side_effect=Exception("Config error"),
246+
):
247+
with patch(
248+
"codeflare_sdk.common.utils.k8s_utils._kube_api_error_handling",
249+
return_value=None,
250+
):
251+
result = get_current_namespace()
252+
253+
assert result is None
254+
# Should see both error messages
255+
assert mock_print.call_count >= 2

0 commit comments

Comments
 (0)