Coverage for ivatar / test_no_opentelemetry.py: 95%
156 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-03 00:09 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-03 00:09 +0000
1"""
2Tests to verify the application works properly without OpenTelemetry packages installed.
4This test simulates the ImportError scenario by mocking the import failure
5and ensures all functionality continues to work normally.
6"""
8import sys
9import unittest
10from unittest.mock import patch
11from io import BytesIO
13from django.test import TestCase, RequestFactory, Client
14from django.contrib.auth.models import User
15from django.core.files.uploadedfile import SimpleUploadedFile
16from django.urls import reverse
17from PIL import Image
20class NoOpenTelemetryTestCase(TestCase):
21 """Test application functionality when OpenTelemetry is not available"""
23 def setUp(self):
24 self.factory = RequestFactory()
25 self.client = Client()
27 # Create test user
28 self.user = User.objects.create_user(
29 username="testuser", email="test@example.com", password="testpass123"
30 )
32 # Store original modules for restoration
33 self.original_modules = {}
34 for module_name in list(sys.modules.keys()):
35 if "telemetry" in module_name:
36 self.original_modules[module_name] = sys.modules[module_name]
38 def tearDown(self):
39 """Restore original module state to prevent test isolation issues"""
40 # Remove any modules that were added during testing
41 modules_to_remove = [k for k in sys.modules.keys() if "telemetry" in k]
42 for module in modules_to_remove:
43 if module in sys.modules:
44 del sys.modules[module]
46 # Restore original modules
47 for module_name, module in self.original_modules.items():
48 sys.modules[module_name] = module
50 # Force reload of telemetry_utils to restore proper state
51 if "ivatar.telemetry_utils" in self.original_modules:
52 import importlib
54 importlib.reload(self.original_modules["ivatar.telemetry_utils"])
56 def _mock_import_error(self, name, *args, **kwargs):
57 """Mock function to simulate ImportError for OpenTelemetry packages"""
58 if "opentelemetry" in name:
59 raise ImportError(f"No module named '{name}'")
60 return self._original_import(name, *args, **kwargs)
62 def _create_test_image(self, format="PNG", size=(100, 100)):
63 """Create a test image for upload testing"""
64 image = Image.new("RGB", size, color="red")
65 image_io = BytesIO()
66 image.save(image_io, format=format)
67 image_io.seek(0)
68 return image_io
70 def test_telemetry_utils_without_opentelemetry(self):
71 """Test that telemetry_utils works when OpenTelemetry is not installed"""
72 # Create a mock module that simulates ImportError for OpenTelemetry
73 original_import = __builtins__["__import__"]
75 def mock_import(name, *args, **kwargs):
76 if "opentelemetry" in name:
77 raise ImportError(f"No module named '{name}'")
78 return original_import(name, *args, **kwargs)
80 # Patch the import and force module reload
81 with patch("builtins.__import__", side_effect=mock_import):
82 # Remove from cache to force reimport
83 modules_to_remove = [k for k in sys.modules.keys() if "telemetry" in k]
84 for module in modules_to_remove:
85 if module in sys.modules:
86 del sys.modules[module]
88 # Import should trigger the ImportError path
89 import importlib
90 import ivatar.telemetry_utils
92 importlib.reload(ivatar.telemetry_utils)
94 from ivatar.telemetry_utils import (
95 get_telemetry_decorators,
96 get_telemetry_metrics,
97 is_telemetry_available,
98 )
100 # Should indicate telemetry is not available
101 self.assertFalse(is_telemetry_available())
103 # Should get no-op decorators
104 trace_avatar, trace_file, trace_auth = get_telemetry_decorators()
106 # Test decorators work as no-op
107 @trace_avatar("test")
108 def test_func():
109 return "success"
111 self.assertEqual(test_func(), "success")
113 # Should get no-op metrics
114 metrics = get_telemetry_metrics()
116 # These should not raise exceptions
117 metrics.record_avatar_generated(size="80", format_type="png", source="test")
118 metrics.record_cache_hit(size="80", format_type="png")
119 metrics.record_external_request("test", 200)
120 metrics.record_file_upload(1024, "image/png", True)
122 @patch.dict(
123 "sys.modules",
124 {
125 "opentelemetry": None,
126 "opentelemetry.trace": None,
127 "opentelemetry.metrics": None,
128 },
129 )
130 def test_views_work_without_opentelemetry(self):
131 """Test that views work when OpenTelemetry is not installed"""
132 # Force reimport to trigger ImportError path
133 modules_to_reload = [
134 "ivatar.telemetry_utils",
135 "ivatar.views",
136 "ivatar.ivataraccount.views",
137 ]
139 for module in modules_to_reload:
140 if module in sys.modules:
141 del sys.modules[module]
143 # Import views - this should work without OpenTelemetry
144 from ivatar.views import AvatarImageView
145 from ivatar.ivataraccount.views import UploadPhotoView
147 # Create instances - should not raise exceptions
148 avatar_view = AvatarImageView()
149 upload_view = UploadPhotoView()
151 # Views should have the no-op metrics
152 self.assertTrue(hasattr(avatar_view, "__class__"))
153 self.assertTrue(hasattr(upload_view, "__class__"))
155 def test_avatar_generation_without_opentelemetry(self):
156 """Test avatar generation works without OpenTelemetry"""
157 # Test default avatar generation (should work without telemetry)
158 response = self.client.get("/avatar/nonexistent@example.com?d=identicon&s=80")
160 # Should get a redirect or image response, not an error
161 self.assertIn(response.status_code, [200, 302, 404])
163 def test_file_upload_without_opentelemetry(self):
164 """Test file upload works without OpenTelemetry"""
165 # Login user
166 self.client.login(username="testuser", password="testpass123")
168 # Create test image
169 test_image = self._create_test_image()
170 uploaded_file = SimpleUploadedFile(
171 "test.png", test_image.getvalue(), content_type="image/png"
172 )
174 # Test upload (should work without telemetry)
175 response = self.client.post(
176 reverse("upload_photo"), {"photo": uploaded_file}, follow=True
177 )
179 # Should not get a server error
180 self.assertNotEqual(response.status_code, 500)
182 def test_authentication_without_opentelemetry(self):
183 """Test authentication works without OpenTelemetry"""
184 # Test login page access
185 response = self.client.get(reverse("login"))
186 self.assertEqual(response.status_code, 200)
188 # Test login submission
189 response = self.client.post(
190 reverse("login"), {"username": "testuser", "password": "testpass123"}
191 )
193 # Should not get a server error
194 self.assertNotEqual(response.status_code, 500)
196 def test_user_registration_without_opentelemetry(self):
197 """Test user registration works without OpenTelemetry"""
198 # Check if the 'new' URL exists, skip if not
199 try:
200 url = reverse("new")
201 except Exception:
202 # URL doesn't exist, skip this test
203 self.skipTest("User registration URL 'new' not found")
205 response = self.client.post(
206 url,
207 {
208 "username": "newuser",
209 "password1": "newpass123",
210 "password2": "newpass123",
211 },
212 )
214 # Should not get a server error
215 self.assertNotEqual(response.status_code, 500)
217 @patch.dict(
218 "sys.modules",
219 {
220 "opentelemetry": None,
221 "opentelemetry.trace": None,
222 "opentelemetry.metrics": None,
223 },
224 )
225 def test_decorated_functions_work_without_opentelemetry(self):
226 """Test that decorated functions work when OpenTelemetry is not available"""
227 # Force reimport to get no-op decorators
228 if "ivatar.telemetry_utils" in sys.modules:
229 del sys.modules["ivatar.telemetry_utils"]
231 from ivatar.telemetry_utils import (
232 trace_avatar_operation,
233 trace_file_upload,
234 trace_authentication,
235 )
237 # Test each decorator type
238 @trace_avatar_operation("test_avatar")
239 def avatar_function():
240 return "avatar_success"
242 @trace_file_upload("test_upload")
243 def upload_function():
244 return "upload_success"
246 @trace_authentication("test_auth")
247 def auth_function():
248 return "auth_success"
250 # All should work normally
251 self.assertEqual(avatar_function(), "avatar_success")
252 self.assertEqual(upload_function(), "upload_success")
253 self.assertEqual(auth_function(), "auth_success")
255 def test_metrics_recording_without_opentelemetry(self):
256 """Test that metrics recording works (as no-op) without OpenTelemetry"""
257 # Test the no-op metrics class directly
258 from ivatar.telemetry_utils import NoOpMetrics
260 metrics = NoOpMetrics()
262 # These should all work without exceptions
263 metrics.record_avatar_generated(size="80", format_type="png", source="test")
264 metrics.record_avatar_request(size="80", format_type="png")
265 metrics.record_cache_hit(size="80", format_type="png")
266 metrics.record_cache_miss(size="80", format_type="png")
267 metrics.record_external_request("gravatar", 200)
268 metrics.record_file_upload(1024, "image/png", True)
270 # Verify it's the no-op implementation
271 self.assertEqual(metrics.__class__.__name__, "NoOpMetrics")
273 def test_application_startup_without_opentelemetry(self):
274 """Test that Django can start without OpenTelemetry packages"""
275 # This test verifies that the settings.py OpenTelemetry setup
276 # handles ImportError gracefully
278 # The fact that this test runs means Django started successfully
279 # even if OpenTelemetry packages were missing during import
281 from django.conf import settings
283 # Django should be configured
284 self.assertTrue(settings.configured)
286 # Middleware should be loaded (even if OpenTelemetry middleware failed to load)
287 self.assertIsInstance(settings.MIDDLEWARE, list)
289 def test_views_import_safely_without_opentelemetry(self):
290 """Test that all views can be imported without OpenTelemetry"""
291 # These imports should not raise ImportError even without OpenTelemetry
292 try:
293 from ivatar import views
294 from ivatar.ivataraccount import views as account_views
296 # Should be able to access the views
297 self.assertTrue(hasattr(views, "AvatarImageView"))
298 self.assertTrue(hasattr(account_views, "UploadPhotoView"))
300 except ImportError as e:
301 self.fail(f"Views failed to import without OpenTelemetry: {e}")
303 def test_middleware_handles_missing_opentelemetry(self):
304 """Test that middleware handles missing OpenTelemetry gracefully"""
305 # Test a simple request to ensure middleware doesn't break
306 response = self.client.get("/")
308 # Should get some response, not a server error
309 self.assertNotEqual(response.status_code, 500)
312class OpenTelemetryFallbackIntegrationTest(TestCase):
313 """Integration tests for OpenTelemetry fallback behavior"""
315 def setUp(self):
316 self.client = Client()
318 def test_full_avatar_workflow_without_opentelemetry(self):
319 """Test complete avatar workflow works without OpenTelemetry"""
320 # Test various avatar generation methods
321 test_cases = [
322 "/avatar/test@example.com?d=identicon&s=80",
323 "/avatar/test@example.com?d=monsterid&s=80",
324 "/avatar/test@example.com?d=robohash&s=80",
325 "/avatar/test@example.com?d=retro&s=80",
326 "/avatar/test@example.com?d=pagan&s=80",
327 "/avatar/test@example.com?d=mm&s=80",
328 ]
330 for url in test_cases:
331 with self.subTest(url=url):
332 response = self.client.get(url)
333 # Should not get server error
334 self.assertNotEqual(
335 response.status_code, 500, f"Server error for {url}"
336 )
338 def test_stats_endpoint_without_opentelemetry(self):
339 """Test stats endpoint works without OpenTelemetry"""
340 response = self.client.get("/stats/")
342 # Should not get server error
343 self.assertNotEqual(response.status_code, 500)
345 def test_version_endpoint_without_opentelemetry(self):
346 """Test version endpoint works without OpenTelemetry"""
347 response = self.client.get("/version/")
349 # Should not get server error
350 self.assertNotEqual(response.status_code, 500)
353if __name__ == "__main__":
354 unittest.main()