authentik.admin.files.tests.test_manager

Test file service layer

  1"""Test file service layer"""
  2
  3from unittest import skipUnless
  4from unittest.mock import Mock
  5from urllib.parse import urlparse
  6
  7from django.http import HttpRequest
  8from django.test import TestCase
  9
 10from authentik.admin.files.manager import FileManager
 11from authentik.admin.files.tests.utils import (
 12    FileTestFileBackendMixin,
 13    FileTestS3BackendMixin,
 14    s3_test_server_available,
 15)
 16from authentik.admin.files.usage import FileUsage
 17from authentik.lib.config import CONFIG
 18
 19
 20class TestResolveFileUrlBasic(TestCase):
 21    def test_resolve_empty_path(self):
 22        """Test resolving empty file path"""
 23        manager = FileManager(FileUsage.MEDIA)
 24        result = manager.file_url("")
 25        self.assertEqual(result, "")
 26
 27    def test_resolve_none_path(self):
 28        """Test resolving None file path"""
 29        manager = FileManager(FileUsage.MEDIA)
 30        result = manager.file_url(None)
 31        self.assertEqual(result, "")
 32
 33    def test_resolve_font_awesome(self):
 34        """Test resolving Font Awesome icon"""
 35        manager = FileManager(FileUsage.MEDIA)
 36        result = manager.file_url("fa://fa-check")
 37        self.assertEqual(result, "fa://fa-check")
 38
 39    def test_resolve_http_url(self):
 40        """Test resolving HTTP URL"""
 41        manager = FileManager(FileUsage.MEDIA)
 42        result = manager.file_url("http://example.com/icon.png")
 43        self.assertEqual(result, "http://example.com/icon.png")
 44
 45    def test_resolve_https_url(self):
 46        """Test resolving HTTPS URL"""
 47        manager = FileManager(FileUsage.MEDIA)
 48        result = manager.file_url("https://example.com/icon.png")
 49        self.assertEqual(result, "https://example.com/icon.png")
 50
 51    def test_resolve_static_path(self):
 52        """Test resolving static file path"""
 53        manager = FileManager(FileUsage.MEDIA)
 54        result = manager.file_url("/static/authentik/sources/icon.svg")
 55        self.assertEqual(result, "/static/authentik/sources/icon.svg")
 56
 57    def test_file_url_forwards_use_cache(self):
 58        """Test file_url forwards use_cache to backend."""
 59        manager = FileManager(FileUsage.MEDIA)
 60        backend = Mock()
 61        backend.supports_file.return_value = True
 62        backend.file_url.return_value = "/files/media/public/test.png?token=fresh"
 63        manager.backends = [backend]
 64
 65        result = manager.file_url("test.png", use_cache=False)
 66
 67        self.assertEqual(result, "/files/media/public/test.png?token=fresh")
 68        backend.file_url.assert_called_once_with("test.png", None, use_cache=False)
 69
 70
 71class TestResolveFileUrlFileBackend(FileTestFileBackendMixin, TestCase):
 72    def test_resolve_storage_file(self):
 73        """Test resolving uploaded storage file"""
 74        manager = FileManager(FileUsage.MEDIA)
 75        result = manager.file_url("test.png").split("?")[0]
 76        self.assertEqual(result, "/files/media/public/test.png")
 77
 78    def test_resolve_full_static_with_request(self):
 79        """Test resolving static file with request builds absolute URI"""
 80        mock_request = HttpRequest()
 81        mock_request.META = {
 82            "HTTP_HOST": "example.com",
 83            "SERVER_NAME": "example.com",
 84        }
 85
 86        manager = FileManager(FileUsage.MEDIA)
 87        result = manager.file_url("/static/icon.svg", mock_request)
 88
 89        self.assertEqual(result, "http://example.com/static/icon.svg")
 90
 91    def test_resolve_full_file_backend_with_request(self):
 92        """Test resolving FileBackend file with request"""
 93        mock_request = HttpRequest()
 94        mock_request.META = {
 95            "HTTP_HOST": "example.com",
 96            "SERVER_NAME": "example.com",
 97        }
 98
 99        manager = FileManager(FileUsage.MEDIA)
100        result = manager.file_url("test.png", mock_request).split("?")[0]
101
102        self.assertEqual(result, "http://example.com/files/media/public/test.png")
103
104
105@skipUnless(s3_test_server_available(), "S3 test server not available")
106class TestResolveFileUrlS3Backend(FileTestS3BackendMixin, TestCase):
107    @CONFIG.patch("storage.media.s3.custom_domain", "s3.test:8080/test")
108    @CONFIG.patch("storage.media.s3.secure_urls", False)
109    def test_resolve_full_s3_backend(self):
110        """Test resolving S3Backend returns presigned URL as-is"""
111        mock_request = HttpRequest()
112        mock_request.META = {
113            "HTTP_HOST": "example.com",
114            "SERVER_NAME": "example.com",
115        }
116
117        manager = FileManager(FileUsage.MEDIA)
118        result = manager.file_url("test.png", mock_request)
119
120        # S3 URLs should be returned as-is (already absolute)
121        self.assertTrue(result.startswith("http://s3.test:8080/test"))
122
123
124class TestThemedUrls(FileTestFileBackendMixin, TestCase):
125    """Test FileManager.themed_urls method"""
126
127    def test_themed_urls_none_path(self):
128        """Test themed_urls returns None for None path"""
129        manager = FileManager(FileUsage.MEDIA)
130        result = manager.themed_urls(None)
131        self.assertIsNone(result)
132
133    def test_themed_urls_empty_path(self):
134        """Test themed_urls returns None for empty path"""
135        manager = FileManager(FileUsage.MEDIA)
136        result = manager.themed_urls("")
137        self.assertIsNone(result)
138
139    def test_themed_urls_no_theme_variable(self):
140        """Test themed_urls returns None when no %(theme)s in path"""
141        manager = FileManager(FileUsage.MEDIA)
142        result = manager.themed_urls("logo.png")
143        self.assertIsNone(result)
144
145    def test_themed_urls_with_theme_variable(self):
146        """Test themed_urls returns dict of URLs for each theme"""
147        manager = FileManager(FileUsage.MEDIA)
148        result = manager.themed_urls("logo-%(theme)s.png")
149
150        self.assertIsInstance(result, dict)
151        self.assertIn("light", result)
152        self.assertIn("dark", result)
153        self.assertIn("logo-light.png", result["light"])
154        self.assertIn("logo-dark.png", result["dark"])
155
156    def test_themed_urls_with_request(self):
157        """Test themed_urls builds absolute URLs with request"""
158        mock_request = HttpRequest()
159        mock_request.META = {
160            "HTTP_HOST": "example.com",
161            "SERVER_NAME": "example.com",
162        }
163
164        manager = FileManager(FileUsage.MEDIA)
165        result = manager.themed_urls("logo-%(theme)s.svg", mock_request)
166
167        self.assertIsInstance(result, dict)
168        light_url = urlparse(result["light"])
169        dark_url = urlparse(result["dark"])
170        self.assertEqual(light_url.scheme, "http")
171        self.assertEqual(light_url.netloc, "example.com")
172        self.assertEqual(dark_url.scheme, "http")
173        self.assertEqual(dark_url.netloc, "example.com")
174
175    def test_themed_urls_passthrough_with_theme_variable(self):
176        """Test themed_urls returns dict for passthrough URLs with %(theme)s"""
177        manager = FileManager(FileUsage.MEDIA)
178        # External URLs with %(theme)s should return themed URLs
179        result = manager.themed_urls("https://example.com/logo-%(theme)s.png")
180        self.assertIsInstance(result, dict)
181        self.assertEqual(result["light"], "https://example.com/logo-light.png")
182        self.assertEqual(result["dark"], "https://example.com/logo-dark.png")
183
184    def test_themed_urls_passthrough_without_theme_variable(self):
185        """Test themed_urls returns None for passthrough URLs without %(theme)s"""
186        manager = FileManager(FileUsage.MEDIA)
187        # External URLs without %(theme)s should return None
188        result = manager.themed_urls("https://example.com/logo.png")
189        self.assertIsNone(result)
class TestResolveFileUrlBasic(django.test.testcases.TestCase):
21class TestResolveFileUrlBasic(TestCase):
22    def test_resolve_empty_path(self):
23        """Test resolving empty file path"""
24        manager = FileManager(FileUsage.MEDIA)
25        result = manager.file_url("")
26        self.assertEqual(result, "")
27
28    def test_resolve_none_path(self):
29        """Test resolving None file path"""
30        manager = FileManager(FileUsage.MEDIA)
31        result = manager.file_url(None)
32        self.assertEqual(result, "")
33
34    def test_resolve_font_awesome(self):
35        """Test resolving Font Awesome icon"""
36        manager = FileManager(FileUsage.MEDIA)
37        result = manager.file_url("fa://fa-check")
38        self.assertEqual(result, "fa://fa-check")
39
40    def test_resolve_http_url(self):
41        """Test resolving HTTP URL"""
42        manager = FileManager(FileUsage.MEDIA)
43        result = manager.file_url("http://example.com/icon.png")
44        self.assertEqual(result, "http://example.com/icon.png")
45
46    def test_resolve_https_url(self):
47        """Test resolving HTTPS URL"""
48        manager = FileManager(FileUsage.MEDIA)
49        result = manager.file_url("https://example.com/icon.png")
50        self.assertEqual(result, "https://example.com/icon.png")
51
52    def test_resolve_static_path(self):
53        """Test resolving static file path"""
54        manager = FileManager(FileUsage.MEDIA)
55        result = manager.file_url("/static/authentik/sources/icon.svg")
56        self.assertEqual(result, "/static/authentik/sources/icon.svg")
57
58    def test_file_url_forwards_use_cache(self):
59        """Test file_url forwards use_cache to backend."""
60        manager = FileManager(FileUsage.MEDIA)
61        backend = Mock()
62        backend.supports_file.return_value = True
63        backend.file_url.return_value = "/files/media/public/test.png?token=fresh"
64        manager.backends = [backend]
65
66        result = manager.file_url("test.png", use_cache=False)
67
68        self.assertEqual(result, "/files/media/public/test.png?token=fresh")
69        backend.file_url.assert_called_once_with("test.png", None, use_cache=False)

Similar to TransactionTestCase, but use transaction.atomic() to achieve test isolation.

In most situations, TestCase should be preferred to TransactionTestCase as it allows faster execution. However, there are some situations where using TransactionTestCase might be necessary (e.g. testing some transactional behavior).

On database backends with no transaction support, TestCase behaves as TransactionTestCase.

def test_resolve_empty_path(self):
22    def test_resolve_empty_path(self):
23        """Test resolving empty file path"""
24        manager = FileManager(FileUsage.MEDIA)
25        result = manager.file_url("")
26        self.assertEqual(result, "")

Test resolving empty file path

def test_resolve_none_path(self):
28    def test_resolve_none_path(self):
29        """Test resolving None file path"""
30        manager = FileManager(FileUsage.MEDIA)
31        result = manager.file_url(None)
32        self.assertEqual(result, "")

Test resolving None file path

def test_resolve_font_awesome(self):
34    def test_resolve_font_awesome(self):
35        """Test resolving Font Awesome icon"""
36        manager = FileManager(FileUsage.MEDIA)
37        result = manager.file_url("fa://fa-check")
38        self.assertEqual(result, "fa://fa-check")

Test resolving Font Awesome icon

def test_resolve_http_url(self):
40    def test_resolve_http_url(self):
41        """Test resolving HTTP URL"""
42        manager = FileManager(FileUsage.MEDIA)
43        result = manager.file_url("http://example.com/icon.png")
44        self.assertEqual(result, "http://example.com/icon.png")

Test resolving HTTP URL

def test_resolve_https_url(self):
46    def test_resolve_https_url(self):
47        """Test resolving HTTPS URL"""
48        manager = FileManager(FileUsage.MEDIA)
49        result = manager.file_url("https://example.com/icon.png")
50        self.assertEqual(result, "https://example.com/icon.png")

Test resolving HTTPS URL

def test_resolve_static_path(self):
52    def test_resolve_static_path(self):
53        """Test resolving static file path"""
54        manager = FileManager(FileUsage.MEDIA)
55        result = manager.file_url("/static/authentik/sources/icon.svg")
56        self.assertEqual(result, "/static/authentik/sources/icon.svg")

Test resolving static file path

def test_file_url_forwards_use_cache(self):
58    def test_file_url_forwards_use_cache(self):
59        """Test file_url forwards use_cache to backend."""
60        manager = FileManager(FileUsage.MEDIA)
61        backend = Mock()
62        backend.supports_file.return_value = True
63        backend.file_url.return_value = "/files/media/public/test.png?token=fresh"
64        manager.backends = [backend]
65
66        result = manager.file_url("test.png", use_cache=False)
67
68        self.assertEqual(result, "/files/media/public/test.png?token=fresh")
69        backend.file_url.assert_called_once_with("test.png", None, use_cache=False)

Test file_url forwards use_cache to backend.

class TestResolveFileUrlFileBackend(authentik.admin.files.tests.utils.FileTestFileBackendMixin, django.test.testcases.TestCase):
 72class TestResolveFileUrlFileBackend(FileTestFileBackendMixin, TestCase):
 73    def test_resolve_storage_file(self):
 74        """Test resolving uploaded storage file"""
 75        manager = FileManager(FileUsage.MEDIA)
 76        result = manager.file_url("test.png").split("?")[0]
 77        self.assertEqual(result, "/files/media/public/test.png")
 78
 79    def test_resolve_full_static_with_request(self):
 80        """Test resolving static file with request builds absolute URI"""
 81        mock_request = HttpRequest()
 82        mock_request.META = {
 83            "HTTP_HOST": "example.com",
 84            "SERVER_NAME": "example.com",
 85        }
 86
 87        manager = FileManager(FileUsage.MEDIA)
 88        result = manager.file_url("/static/icon.svg", mock_request)
 89
 90        self.assertEqual(result, "http://example.com/static/icon.svg")
 91
 92    def test_resolve_full_file_backend_with_request(self):
 93        """Test resolving FileBackend file with request"""
 94        mock_request = HttpRequest()
 95        mock_request.META = {
 96            "HTTP_HOST": "example.com",
 97            "SERVER_NAME": "example.com",
 98        }
 99
100        manager = FileManager(FileUsage.MEDIA)
101        result = manager.file_url("test.png", mock_request).split("?")[0]
102
103        self.assertEqual(result, "http://example.com/files/media/public/test.png")

Similar to TransactionTestCase, but use transaction.atomic() to achieve test isolation.

In most situations, TestCase should be preferred to TransactionTestCase as it allows faster execution. However, there are some situations where using TransactionTestCase might be necessary (e.g. testing some transactional behavior).

On database backends with no transaction support, TestCase behaves as TransactionTestCase.

def test_resolve_storage_file(self):
73    def test_resolve_storage_file(self):
74        """Test resolving uploaded storage file"""
75        manager = FileManager(FileUsage.MEDIA)
76        result = manager.file_url("test.png").split("?")[0]
77        self.assertEqual(result, "/files/media/public/test.png")

Test resolving uploaded storage file

def test_resolve_full_static_with_request(self):
79    def test_resolve_full_static_with_request(self):
80        """Test resolving static file with request builds absolute URI"""
81        mock_request = HttpRequest()
82        mock_request.META = {
83            "HTTP_HOST": "example.com",
84            "SERVER_NAME": "example.com",
85        }
86
87        manager = FileManager(FileUsage.MEDIA)
88        result = manager.file_url("/static/icon.svg", mock_request)
89
90        self.assertEqual(result, "http://example.com/static/icon.svg")

Test resolving static file with request builds absolute URI

def test_resolve_full_file_backend_with_request(self):
 92    def test_resolve_full_file_backend_with_request(self):
 93        """Test resolving FileBackend file with request"""
 94        mock_request = HttpRequest()
 95        mock_request.META = {
 96            "HTTP_HOST": "example.com",
 97            "SERVER_NAME": "example.com",
 98        }
 99
100        manager = FileManager(FileUsage.MEDIA)
101        result = manager.file_url("test.png", mock_request).split("?")[0]
102
103        self.assertEqual(result, "http://example.com/files/media/public/test.png")

Test resolving FileBackend file with request

@skipUnless(s3_test_server_available(), 'S3 test server not available')
class TestResolveFileUrlS3Backend(authentik.admin.files.tests.utils.FileTestS3BackendMixin, django.test.testcases.TestCase):
106@skipUnless(s3_test_server_available(), "S3 test server not available")
107class TestResolveFileUrlS3Backend(FileTestS3BackendMixin, TestCase):
108    @CONFIG.patch("storage.media.s3.custom_domain", "s3.test:8080/test")
109    @CONFIG.patch("storage.media.s3.secure_urls", False)
110    def test_resolve_full_s3_backend(self):
111        """Test resolving S3Backend returns presigned URL as-is"""
112        mock_request = HttpRequest()
113        mock_request.META = {
114            "HTTP_HOST": "example.com",
115            "SERVER_NAME": "example.com",
116        }
117
118        manager = FileManager(FileUsage.MEDIA)
119        result = manager.file_url("test.png", mock_request)
120
121        # S3 URLs should be returned as-is (already absolute)
122        self.assertTrue(result.startswith("http://s3.test:8080/test"))

Similar to TransactionTestCase, but use transaction.atomic() to achieve test isolation.

In most situations, TestCase should be preferred to TransactionTestCase as it allows faster execution. However, there are some situations where using TransactionTestCase might be necessary (e.g. testing some transactional behavior).

On database backends with no transaction support, TestCase behaves as TransactionTestCase.

@CONFIG.patch('storage.media.s3.custom_domain', 's3.test:8080/test')
@CONFIG.patch('storage.media.s3.secure_urls', False)
def test_resolve_full_s3_backend(self):
108    @CONFIG.patch("storage.media.s3.custom_domain", "s3.test:8080/test")
109    @CONFIG.patch("storage.media.s3.secure_urls", False)
110    def test_resolve_full_s3_backend(self):
111        """Test resolving S3Backend returns presigned URL as-is"""
112        mock_request = HttpRequest()
113        mock_request.META = {
114            "HTTP_HOST": "example.com",
115            "SERVER_NAME": "example.com",
116        }
117
118        manager = FileManager(FileUsage.MEDIA)
119        result = manager.file_url("test.png", mock_request)
120
121        # S3 URLs should be returned as-is (already absolute)
122        self.assertTrue(result.startswith("http://s3.test:8080/test"))

Test resolving S3Backend returns presigned URL as-is

class TestThemedUrls(authentik.admin.files.tests.utils.FileTestFileBackendMixin, django.test.testcases.TestCase):
125class TestThemedUrls(FileTestFileBackendMixin, TestCase):
126    """Test FileManager.themed_urls method"""
127
128    def test_themed_urls_none_path(self):
129        """Test themed_urls returns None for None path"""
130        manager = FileManager(FileUsage.MEDIA)
131        result = manager.themed_urls(None)
132        self.assertIsNone(result)
133
134    def test_themed_urls_empty_path(self):
135        """Test themed_urls returns None for empty path"""
136        manager = FileManager(FileUsage.MEDIA)
137        result = manager.themed_urls("")
138        self.assertIsNone(result)
139
140    def test_themed_urls_no_theme_variable(self):
141        """Test themed_urls returns None when no %(theme)s in path"""
142        manager = FileManager(FileUsage.MEDIA)
143        result = manager.themed_urls("logo.png")
144        self.assertIsNone(result)
145
146    def test_themed_urls_with_theme_variable(self):
147        """Test themed_urls returns dict of URLs for each theme"""
148        manager = FileManager(FileUsage.MEDIA)
149        result = manager.themed_urls("logo-%(theme)s.png")
150
151        self.assertIsInstance(result, dict)
152        self.assertIn("light", result)
153        self.assertIn("dark", result)
154        self.assertIn("logo-light.png", result["light"])
155        self.assertIn("logo-dark.png", result["dark"])
156
157    def test_themed_urls_with_request(self):
158        """Test themed_urls builds absolute URLs with request"""
159        mock_request = HttpRequest()
160        mock_request.META = {
161            "HTTP_HOST": "example.com",
162            "SERVER_NAME": "example.com",
163        }
164
165        manager = FileManager(FileUsage.MEDIA)
166        result = manager.themed_urls("logo-%(theme)s.svg", mock_request)
167
168        self.assertIsInstance(result, dict)
169        light_url = urlparse(result["light"])
170        dark_url = urlparse(result["dark"])
171        self.assertEqual(light_url.scheme, "http")
172        self.assertEqual(light_url.netloc, "example.com")
173        self.assertEqual(dark_url.scheme, "http")
174        self.assertEqual(dark_url.netloc, "example.com")
175
176    def test_themed_urls_passthrough_with_theme_variable(self):
177        """Test themed_urls returns dict for passthrough URLs with %(theme)s"""
178        manager = FileManager(FileUsage.MEDIA)
179        # External URLs with %(theme)s should return themed URLs
180        result = manager.themed_urls("https://example.com/logo-%(theme)s.png")
181        self.assertIsInstance(result, dict)
182        self.assertEqual(result["light"], "https://example.com/logo-light.png")
183        self.assertEqual(result["dark"], "https://example.com/logo-dark.png")
184
185    def test_themed_urls_passthrough_without_theme_variable(self):
186        """Test themed_urls returns None for passthrough URLs without %(theme)s"""
187        manager = FileManager(FileUsage.MEDIA)
188        # External URLs without %(theme)s should return None
189        result = manager.themed_urls("https://example.com/logo.png")
190        self.assertIsNone(result)

Test FileManager.themed_urls method

def test_themed_urls_none_path(self):
128    def test_themed_urls_none_path(self):
129        """Test themed_urls returns None for None path"""
130        manager = FileManager(FileUsage.MEDIA)
131        result = manager.themed_urls(None)
132        self.assertIsNone(result)

Test themed_urls returns None for None path

def test_themed_urls_empty_path(self):
134    def test_themed_urls_empty_path(self):
135        """Test themed_urls returns None for empty path"""
136        manager = FileManager(FileUsage.MEDIA)
137        result = manager.themed_urls("")
138        self.assertIsNone(result)

Test themed_urls returns None for empty path

def test_themed_urls_no_theme_variable(self):
140    def test_themed_urls_no_theme_variable(self):
141        """Test themed_urls returns None when no %(theme)s in path"""
142        manager = FileManager(FileUsage.MEDIA)
143        result = manager.themed_urls("logo.png")
144        self.assertIsNone(result)

Test themed_urls returns None when no %(theme)s in path

def test_themed_urls_with_theme_variable(self):
146    def test_themed_urls_with_theme_variable(self):
147        """Test themed_urls returns dict of URLs for each theme"""
148        manager = FileManager(FileUsage.MEDIA)
149        result = manager.themed_urls("logo-%(theme)s.png")
150
151        self.assertIsInstance(result, dict)
152        self.assertIn("light", result)
153        self.assertIn("dark", result)
154        self.assertIn("logo-light.png", result["light"])
155        self.assertIn("logo-dark.png", result["dark"])

Test themed_urls returns dict of URLs for each theme

def test_themed_urls_with_request(self):
157    def test_themed_urls_with_request(self):
158        """Test themed_urls builds absolute URLs with request"""
159        mock_request = HttpRequest()
160        mock_request.META = {
161            "HTTP_HOST": "example.com",
162            "SERVER_NAME": "example.com",
163        }
164
165        manager = FileManager(FileUsage.MEDIA)
166        result = manager.themed_urls("logo-%(theme)s.svg", mock_request)
167
168        self.assertIsInstance(result, dict)
169        light_url = urlparse(result["light"])
170        dark_url = urlparse(result["dark"])
171        self.assertEqual(light_url.scheme, "http")
172        self.assertEqual(light_url.netloc, "example.com")
173        self.assertEqual(dark_url.scheme, "http")
174        self.assertEqual(dark_url.netloc, "example.com")

Test themed_urls builds absolute URLs with request

def test_themed_urls_passthrough_with_theme_variable(self):
176    def test_themed_urls_passthrough_with_theme_variable(self):
177        """Test themed_urls returns dict for passthrough URLs with %(theme)s"""
178        manager = FileManager(FileUsage.MEDIA)
179        # External URLs with %(theme)s should return themed URLs
180        result = manager.themed_urls("https://example.com/logo-%(theme)s.png")
181        self.assertIsInstance(result, dict)
182        self.assertEqual(result["light"], "https://example.com/logo-light.png")
183        self.assertEqual(result["dark"], "https://example.com/logo-dark.png")

Test themed_urls returns dict for passthrough URLs with %(theme)s

def test_themed_urls_passthrough_without_theme_variable(self):
185    def test_themed_urls_passthrough_without_theme_variable(self):
186        """Test themed_urls returns None for passthrough URLs without %(theme)s"""
187        manager = FileManager(FileUsage.MEDIA)
188        # External URLs without %(theme)s should return None
189        result = manager.themed_urls("https://example.com/logo.png")
190        self.assertIsNone(result)

Test themed_urls returns None for passthrough URLs without %(theme)s