Skip to content

Commit 97354d4

Browse files
committed
Append WCA suffix to project title based on service configuration
1 parent 81847dc commit 97354d4

File tree

7 files changed

+435
-4
lines changed

7 files changed

+435
-4
lines changed

ansible_ai_connect/main/settings/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
ANSIBLE_AI_CHATBOT_NAME = (
4747
os.getenv("ANSIBLE_AI_CHATBOT_NAME") or "Ansible Lightspeed Intelligent Assistant"
4848
)
49+
ANSIBLE_AI_PROJECT_WCA_SUFFIX = (
50+
os.getenv("ANSIBLE_AI_PROJECT_WCA_SUFFIX") or " with IBM watsonx Code Assistant"
51+
)
4952

5053
# Quick-start development settings - unsuitable for production
5154
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
# Copyright 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+
# from unittest.mock import patch
16+
17+
# from django.test import RequestFactory, override_settings
18+
19+
# from ansible_ai_connect.ai.api.model_pipelines.tests import mock_config
20+
# from ansible_ai_connect.main.utils import (
21+
# get_project_name_with_wca_suffix,
22+
# has_wca_providers,
23+
# )
24+
# from ansible_ai_connect.test_utils import WisdomServiceLogAwareTestCase
25+
26+
from unittest.mock import patch
27+
28+
from django.test import RequestFactory
29+
30+
from ansible_ai_connect.ai.api.model_pipelines.factory import ModelPipelineFactory
31+
from ansible_ai_connect.ai.api.model_pipelines.tests import mock_config
32+
from ansible_ai_connect.main.utils import (
33+
get_project_name_with_wca_suffix,
34+
has_wca_providers,
35+
)
36+
from ansible_ai_connect.test_utils import WisdomServiceLogAwareTestCase
37+
38+
39+
class TestHasWCAProvidersWithWCAProvider(WisdomServiceLogAwareTestCase):
40+
def test_returns_true_with_wca_provider(self):
41+
"""Test that has_wca_providers returns True when WCA provider is configured."""
42+
# Create a factory with WCA config directly, without affecting global state
43+
config_path = (
44+
"ansible_ai_connect.ai.api.model_pipelines.config_loader.settings."
45+
"ANSIBLE_AI_MODEL_MESH_CONFIG"
46+
)
47+
with patch(config_path, mock_config("wca")):
48+
factory = ModelPipelineFactory()
49+
with patch("ansible_ai_connect.main.utils.apps.get_app_config") as mock_app:
50+
mock_app.return_value._pipeline_factory = factory
51+
self.assertTrue(has_wca_providers())
52+
53+
54+
class TestHasWCAProvidersWithWCAOnPremProvider(WisdomServiceLogAwareTestCase):
55+
def test_returns_true_with_wca_onprem_provider(self):
56+
"""Test that has_wca_providers returns True when WCA-onprem provider is configured."""
57+
config_path = (
58+
"ansible_ai_connect.ai.api.model_pipelines.config_loader.settings."
59+
"ANSIBLE_AI_MODEL_MESH_CONFIG"
60+
)
61+
with patch(config_path, mock_config("wca-onprem")):
62+
factory = ModelPipelineFactory()
63+
with patch("ansible_ai_connect.main.utils.apps.get_app_config") as mock_app:
64+
mock_app.return_value._pipeline_factory = factory
65+
self.assertTrue(has_wca_providers())
66+
67+
68+
class TestHasWCAProvidersWithHttpProvider(WisdomServiceLogAwareTestCase):
69+
def test_returns_false_with_http_provider(self):
70+
"""Test that has_wca_providers returns False when HTTP provider is configured."""
71+
config_path = (
72+
"ansible_ai_connect.ai.api.model_pipelines.config_loader.settings."
73+
"ANSIBLE_AI_MODEL_MESH_CONFIG"
74+
)
75+
with patch(config_path, mock_config("http")):
76+
factory = ModelPipelineFactory()
77+
with patch("ansible_ai_connect.main.utils.apps.get_app_config") as mock_app:
78+
mock_app.return_value._pipeline_factory = factory
79+
self.assertFalse(has_wca_providers())
80+
81+
82+
class TestHasWCAProvidersWithEmptyConfig(WisdomServiceLogAwareTestCase):
83+
def test_returns_false_with_empty_config(self):
84+
"""Test that has_wca_providers returns False with empty configuration."""
85+
config_path = (
86+
"ansible_ai_connect.ai.api.model_pipelines.config_loader.settings."
87+
"ANSIBLE_AI_MODEL_MESH_CONFIG"
88+
)
89+
with patch(config_path, "{}"):
90+
factory = ModelPipelineFactory()
91+
with patch("ansible_ai_connect.main.utils.apps.get_app_config") as mock_app:
92+
mock_app.return_value._pipeline_factory = factory
93+
self.assertFalse(has_wca_providers())
94+
95+
96+
class TestGetProjectNameWithWCASuffixWithRealWCAConfig(WisdomServiceLogAwareTestCase):
97+
def test_integration_with_real_wca_config(self):
98+
"""Integration test using real configuration."""
99+
config_path = (
100+
"ansible_ai_connect.ai.api.model_pipelines.config_loader.settings."
101+
"ANSIBLE_AI_MODEL_MESH_CONFIG"
102+
)
103+
with patch(config_path, mock_config("wca")):
104+
factory = ModelPipelineFactory()
105+
with patch("ansible_ai_connect.main.utils.apps.get_app_config") as mock_app:
106+
mock_app.return_value._pipeline_factory = factory
107+
result = get_project_name_with_wca_suffix("Ansible Lightspeed")
108+
self.assertEqual(result, "Ansible Lightspeed with IBM watsonx Code Assistant")
109+
110+
111+
class TestGetProjectNameWithWCASuffixWithRealNonWCAConfig(WisdomServiceLogAwareTestCase):
112+
def test_integration_with_real_non_wca_config(self):
113+
"""Integration test using real non-WCA configuration."""
114+
config_path = (
115+
"ansible_ai_connect.ai.api.model_pipelines.config_loader.settings."
116+
"ANSIBLE_AI_MODEL_MESH_CONFIG"
117+
)
118+
with patch(config_path, mock_config("dummy")):
119+
factory = ModelPipelineFactory()
120+
with patch("ansible_ai_connect.main.utils.apps.get_app_config") as mock_app:
121+
mock_app.return_value._pipeline_factory = factory
122+
result = get_project_name_with_wca_suffix("Ansible Lightspeed")
123+
self.assertEqual(result, "Ansible Lightspeed")
124+
125+
126+
class TestGetProjectNameWithWCASuffix(WisdomServiceLogAwareTestCase):
127+
def test_adds_suffix_when_wca_providers_exist(self):
128+
"""Test that suffix is added when WCA providers are detected."""
129+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
130+
result = get_project_name_with_wca_suffix("Ansible Lightspeed")
131+
self.assertEqual(result, "Ansible Lightspeed with IBM watsonx Code Assistant")
132+
133+
def test_no_suffix_when_no_wca_providers(self):
134+
"""Test that no suffix is added when no WCA providers are detected."""
135+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=False):
136+
result = get_project_name_with_wca_suffix("Ansible Lightspeed")
137+
self.assertEqual(result, "Ansible Lightspeed")
138+
139+
def test_handles_empty_base_name(self):
140+
"""Test that function handles empty base project name."""
141+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
142+
result = get_project_name_with_wca_suffix("")
143+
self.assertEqual(result, " with IBM watsonx Code Assistant")
144+
145+
def test_handles_none_base_name(self):
146+
"""Test that function handles None as base project name."""
147+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
148+
result = get_project_name_with_wca_suffix(None)
149+
self.assertEqual(result, "None with IBM watsonx Code Assistant")
150+
151+
def test_preserves_base_name_when_no_wca(self):
152+
"""Test that base name is preserved exactly when no WCA providers."""
153+
test_names = [
154+
"Ansible AI Connect",
155+
"Custom Project Name",
156+
"Project with Special Characters !@#",
157+
" Padded Name ",
158+
]
159+
160+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=False):
161+
for base_name in test_names:
162+
with self.subTest(base_name=base_name):
163+
result = get_project_name_with_wca_suffix(base_name)
164+
self.assertEqual(result, base_name)
165+
166+
def test_idempotent_with_existing_suffix_and_wca_providers(self):
167+
"""Test function is idempotent when suffix exists and WCA providers present."""
168+
base_name = "Ansible Lightspeed with IBM watsonx Code Assistant"
169+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
170+
result = get_project_name_with_wca_suffix(base_name)
171+
self.assertEqual(result, "Ansible Lightspeed with IBM watsonx Code Assistant")
172+
173+
def test_idempotent_with_existing_suffix_and_no_wca_providers(self):
174+
"""Test that function preserves existing suffix even when no WCA providers."""
175+
base_name = "Ansible Lightspeed with IBM watsonx Code Assistant"
176+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=False):
177+
result = get_project_name_with_wca_suffix(base_name)
178+
self.assertEqual(result, "Ansible Lightspeed with IBM watsonx Code Assistant")
179+
180+
def test_handles_partial_suffix_match(self):
181+
"""Test that function doesn't get confused by partial suffix matches."""
182+
test_cases = [
183+
"Ansible with IBM",
184+
"Project with IBM watsonx",
185+
"Ansible IBM watsonx Code Assistant",
186+
"with IBM watsonx Code Assistant prefix",
187+
]
188+
189+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
190+
for base_name in test_cases:
191+
with self.subTest(base_name=base_name):
192+
result = get_project_name_with_wca_suffix(base_name)
193+
expected = f"{base_name} with IBM watsonx Code Assistant"
194+
self.assertEqual(result, expected)
195+
196+
def test_case_sensitive_suffix_matching(self):
197+
"""Test that suffix matching is case sensitive."""
198+
test_cases = [
199+
"Ansible Lightspeed with ibm watsonx code assistant", # lowercase
200+
"Ansible Lightspeed with IBM WATSONX CODE ASSISTANT", # uppercase
201+
"Ansible Lightspeed With IBM Watsonx Code Assistant", # mixed case
202+
]
203+
204+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
205+
for base_name in test_cases:
206+
with self.subTest(base_name=base_name):
207+
result = get_project_name_with_wca_suffix(base_name)
208+
expected = f"{base_name} with IBM watsonx Code Assistant"
209+
self.assertEqual(result, expected)
210+
211+
def test_multiple_calls_are_idempotent(self):
212+
"""Test that calling the function multiple times produces the same result."""
213+
base_name = "Ansible Lightspeed"
214+
215+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
216+
# First call
217+
result1 = get_project_name_with_wca_suffix(base_name)
218+
# Second call with result of first call
219+
result2 = get_project_name_with_wca_suffix(result1)
220+
# Third call with result of second call
221+
result3 = get_project_name_with_wca_suffix(result2)
222+
223+
expected = "Ansible Lightspeed with IBM watsonx Code Assistant"
224+
self.assertEqual(result1, expected)
225+
self.assertEqual(result2, expected)
226+
self.assertEqual(result3, expected)
227+
228+
def test_empty_string_with_existing_suffix_check(self):
229+
"""Test behavior with empty string (edge case for endswith check)."""
230+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
231+
result = get_project_name_with_wca_suffix("")
232+
self.assertEqual(result, " with IBM watsonx Code Assistant")
233+
234+
def test_none_value_with_existing_suffix_check(self):
235+
"""Test behavior with None value (edge case for endswith check)."""
236+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
237+
result = get_project_name_with_wca_suffix(None)
238+
self.assertEqual(result, "None with IBM watsonx Code Assistant")
239+
240+
def test_no_suffix_for_chatbot_route_with_wca_providers(self):
241+
"""Test that no suffix is added when next param starts with /chatbot."""
242+
request = RequestFactory().get("/login?next=/chatbot")
243+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
244+
result = get_project_name_with_wca_suffix("Ansible Lightspeed", request)
245+
self.assertEqual(result, "Ansible Lightspeed")
246+
247+
def test_no_suffix_for_chatbot_route_with_path(self):
248+
"""Test that no suffix is added when next param is /chatbot/something."""
249+
request = RequestFactory().get("/login?next=/chatbot/")
250+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
251+
result = get_project_name_with_wca_suffix("Ansible Lightspeed", request)
252+
self.assertEqual(result, "Ansible Lightspeed")
253+
254+
def test_suffix_added_for_non_chatbot_routes(self):
255+
"""Test that suffix is added for non-chatbot routes when WCA providers exist."""
256+
request = RequestFactory().get("/login?next=/home")
257+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
258+
result = get_project_name_with_wca_suffix("Ansible Lightspeed", request)
259+
self.assertEqual(result, "Ansible Lightspeed with IBM watsonx Code Assistant")
260+
261+
def test_suffix_added_for_empty_next_param(self):
262+
"""Test that suffix is added when next param is empty."""
263+
request = RequestFactory().get("/login?next=")
264+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
265+
result = get_project_name_with_wca_suffix("Ansible Lightspeed", request)
266+
self.assertEqual(result, "Ansible Lightspeed with IBM watsonx Code Assistant")
267+
268+
def test_suffix_added_when_no_next_param(self):
269+
"""Test that suffix is added when there is no next param."""
270+
request = RequestFactory().get("/login")
271+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
272+
result = get_project_name_with_wca_suffix("Ansible Lightspeed", request)
273+
self.assertEqual(result, "Ansible Lightspeed with IBM watsonx Code Assistant")
274+
275+
def test_backward_compatibility_without_request(self):
276+
"""Test that function works without request parameter for backward compatibility."""
277+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
278+
result = get_project_name_with_wca_suffix("Ansible Lightspeed")
279+
self.assertEqual(result, "Ansible Lightspeed with IBM watsonx Code Assistant")
280+
281+
def test_chatbot_route_case_sensitivity(self):
282+
"""Test that chatbot route detection is case sensitive."""
283+
request = RequestFactory().get("/login?next=/CHATBOT")
284+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
285+
result = get_project_name_with_wca_suffix("Ansible Lightspeed", request)
286+
# Should add suffix because /CHATBOT != /chatbot
287+
self.assertEqual(result, "Ansible Lightspeed with IBM watsonx Code Assistant")

ansible_ai_connect/main/tests/test_views.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,66 @@ class MockUser:
151151
self.assertEqual(response.url, "/")
152152

153153

154+
@override_settings(ANSIBLE_AI_PROJECT_NAME="Ansible Lightspeed")
155+
class LoginViewProjectNameTest(TestCase):
156+
def test_project_name_with_wca_provider(self):
157+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
158+
request = RequestFactory().get("/login")
159+
request.user = AnonymousUser()
160+
response = LoginView.as_view()(request)
161+
response.render()
162+
contents = response.content.decode()
163+
self.assertIn("Log in to Ansible Lightspeed with IBM watsonx Code Assistant", contents)
164+
165+
def test_project_name_without_wca_provider(self):
166+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=False):
167+
request = RequestFactory().get("/login")
168+
request.user = AnonymousUser()
169+
response = LoginView.as_view()(request)
170+
response.render()
171+
contents = response.content.decode()
172+
self.assertIn("Log in to Ansible Lightspeed", contents)
173+
self.assertNotIn(
174+
"Log in to Ansible Lightspeed with IBM watsonx Code Assistant", contents
175+
)
176+
177+
def test_project_name_no_suffix_for_chatbot_redirect(self):
178+
"""Test that WCA suffix is not added when redirecting to chatbot route."""
179+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
180+
request = RequestFactory().get("/login?next=/chatbot")
181+
request.user = AnonymousUser()
182+
response = LoginView.as_view()(request)
183+
response.render()
184+
contents = response.content.decode()
185+
self.assertIn("Log in to Ansible Lightspeed", contents)
186+
self.assertNotIn(
187+
"Log in to Ansible Lightspeed with IBM watsonx Code Assistant", contents
188+
)
189+
190+
def test_project_name_no_suffix_for_chatbot_subpath_redirect(self):
191+
"""Test that WCA suffix is not added when redirecting to chatbot subpath."""
192+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
193+
request = RequestFactory().get("/login?next=/chatbot/")
194+
request.user = AnonymousUser()
195+
response = LoginView.as_view()(request)
196+
response.render()
197+
contents = response.content.decode()
198+
self.assertIn("Log in to Ansible Lightspeed", contents)
199+
self.assertNotIn(
200+
"Log in to Ansible Lightspeed with IBM watsonx Code Assistant", contents
201+
)
202+
203+
def test_project_name_with_suffix_for_non_chatbot_redirect(self):
204+
"""Test that WCA suffix is added when redirecting to non-chatbot route."""
205+
with patch("ansible_ai_connect.main.utils.has_wca_providers", return_value=True):
206+
request = RequestFactory().get("/login?next=/home")
207+
request.user = AnonymousUser()
208+
response = LoginView.as_view()(request)
209+
response.render()
210+
contents = response.content.decode()
211+
self.assertIn("Log in to Ansible Lightspeed with IBM watsonx Code Assistant", contents)
212+
213+
154214
@override_settings(ALLOW_METRICS_FOR_ANONYMOUS_USERS=False)
155215
class TestMetricsView(APITransactionTestCase):
156216

0 commit comments

Comments
 (0)