|
| 1 | +""" |
| 2 | +Test cases for blog context processors |
| 3 | +""" |
| 4 | +from unittest.mock import patch, Mock |
| 5 | + |
| 6 | +from django.test import TestCase, RequestFactory |
| 7 | +from django.utils import timezone |
| 8 | + |
| 9 | +from accounts.models import BlogUser |
| 10 | +from blog.context_processors import seo_processor |
| 11 | +from blog.models import Category, Article |
| 12 | +from djangoblog.utils import cache |
| 13 | + |
| 14 | + |
| 15 | +class SeoProcessorTest(TestCase): |
| 16 | + """测试SEO上下文处理器""" |
| 17 | + |
| 18 | + def setUp(self): |
| 19 | + """设置测试环境""" |
| 20 | + self.factory = RequestFactory() |
| 21 | + |
| 22 | + # 创建测试用户 |
| 23 | + self.user = BlogUser.objects.create_user( |
| 24 | + username='testuser', |
| 25 | + |
| 26 | + password='testpassword' |
| 27 | + ) |
| 28 | + |
| 29 | + # 创建测试分类 |
| 30 | + self.category = Category.objects.create( |
| 31 | + name='Test Category', |
| 32 | + slug='test-category' |
| 33 | + ) |
| 34 | + |
| 35 | + # 创建测试页面 |
| 36 | + self.page = Article.objects.create( |
| 37 | + title='Test Page', |
| 38 | + body='Test page content', |
| 39 | + author=self.user, |
| 40 | + type='p', # 页面类型 |
| 41 | + status='p', # 已发布 |
| 42 | + category=self.category |
| 43 | + ) |
| 44 | + |
| 45 | + # 清空缓存 |
| 46 | + cache.clear() |
| 47 | + |
| 48 | + def tearDown(self): |
| 49 | + """清理测试环境""" |
| 50 | + cache.clear() |
| 51 | + |
| 52 | + def test_processor_returns_required_variables(self): |
| 53 | + """测试上下文处理器返回必需的变量""" |
| 54 | + request = self.factory.get('/') |
| 55 | + result = seo_processor(request) |
| 56 | + |
| 57 | + # 验证必需的变量 |
| 58 | + required_keys = [ |
| 59 | + 'SITE_NAME', |
| 60 | + 'SHOW_GOOGLE_ADSENSE', |
| 61 | + 'GOOGLE_ADSENSE_CODES', |
| 62 | + 'SITE_SEO_DESCRIPTION', |
| 63 | + 'SITE_DESCRIPTION', |
| 64 | + 'SITE_KEYWORDS', |
| 65 | + 'SITE_BASE_URL', |
| 66 | + 'ARTICLE_SUB_LENGTH', |
| 67 | + 'nav_category_list', |
| 68 | + 'nav_pages', |
| 69 | + 'OPEN_SITE_COMMENT', |
| 70 | + 'BEIAN_CODE', |
| 71 | + 'ANALYTICS_CODE', |
| 72 | + 'BEIAN_CODE_GONGAN', |
| 73 | + 'SHOW_GONGAN_CODE', |
| 74 | + 'CURRENT_YEAR', |
| 75 | + 'GLOBAL_HEADER', |
| 76 | + 'GLOBAL_FOOTER', |
| 77 | + 'COMMENT_NEED_REVIEW', |
| 78 | + 'COLOR_SCHEME', |
| 79 | + ] |
| 80 | + |
| 81 | + for key in required_keys: |
| 82 | + self.assertIn(key, result) |
| 83 | + |
| 84 | + def test_processor_caching(self): |
| 85 | + """测试上下文处理器的缓存机制""" |
| 86 | + request = self.factory.get('/') |
| 87 | + |
| 88 | + # 第一次调用 - 应该设置缓存 |
| 89 | + result1 = seo_processor(request) |
| 90 | + |
| 91 | + # 验证缓存已设置 |
| 92 | + cached_value = cache.get('seo_processor') |
| 93 | + self.assertIsNotNone(cached_value) |
| 94 | + |
| 95 | + # 第二次调用 - 应该从缓存获取 |
| 96 | + result2 = seo_processor(request) |
| 97 | + |
| 98 | + # 验证两次调用返回相同的基础数据 |
| 99 | + # 注意:SITE_BASE_URL和CURRENT_YEAR是动态的,可能不同 |
| 100 | + self.assertEqual(result1['SITE_NAME'], result2['SITE_NAME']) |
| 101 | + self.assertEqual(result1['SITE_DESCRIPTION'], result2['SITE_DESCRIPTION']) |
| 102 | + |
| 103 | + def test_processor_with_anonymous_user(self): |
| 104 | + """测试匿名用户访问时的上下文处理器""" |
| 105 | + request = self.factory.get('/') |
| 106 | + result = seo_processor(request) |
| 107 | + |
| 108 | + # 验证返回结果 |
| 109 | + self.assertIsNotNone(result) |
| 110 | + self.assertIn('SITE_BASE_URL', result) |
| 111 | + |
| 112 | + def test_processor_with_https_request(self): |
| 113 | + """测试HTTPS请求的SITE_BASE_URL""" |
| 114 | + request = self.factory.get('/', secure=True) |
| 115 | + result = seo_processor(request) |
| 116 | + |
| 117 | + # 验证SITE_BASE_URL包含https |
| 118 | + self.assertTrue(result['SITE_BASE_URL'].startswith('https://')) |
| 119 | + |
| 120 | + def test_processor_with_http_request(self): |
| 121 | + """测试HTTP请求的SITE_BASE_URL""" |
| 122 | + request = self.factory.get('/') |
| 123 | + result = seo_processor(request) |
| 124 | + |
| 125 | + # 验证SITE_BASE_URL包含http |
| 126 | + self.assertTrue(result['SITE_BASE_URL'].startswith('http://')) |
| 127 | + |
| 128 | + def test_processor_current_year(self): |
| 129 | + """测试CURRENT_YEAR是当前年份""" |
| 130 | + request = self.factory.get('/') |
| 131 | + result = seo_processor(request) |
| 132 | + |
| 133 | + # 验证CURRENT_YEAR是当前年份 |
| 134 | + current_year = timezone.now().year |
| 135 | + self.assertEqual(result['CURRENT_YEAR'], current_year) |
| 136 | + |
| 137 | + def test_processor_nav_category_list(self): |
| 138 | + """测试导航分类列表""" |
| 139 | + request = self.factory.get('/') |
| 140 | + result = seo_processor(request) |
| 141 | + |
| 142 | + # 验证nav_category_list包含创建的分类 |
| 143 | + nav_categories = list(result['nav_category_list']) |
| 144 | + self.assertGreater(len(nav_categories), 0) |
| 145 | + |
| 146 | + # 验证分类在列表中 |
| 147 | + category_names = [cat.name for cat in nav_categories] |
| 148 | + self.assertIn('Test Category', category_names) |
| 149 | + |
| 150 | + def test_processor_nav_pages(self): |
| 151 | + """测试导航页面列表""" |
| 152 | + request = self.factory.get('/') |
| 153 | + result = seo_processor(request) |
| 154 | + |
| 155 | + # 验证nav_pages包含已发布的页面 |
| 156 | + nav_pages = list(result['nav_pages']) |
| 157 | + self.assertGreater(len(nav_pages), 0) |
| 158 | + |
| 159 | + # 验证创建的页面在列表中 |
| 160 | + page_titles = [page.title for page in nav_pages] |
| 161 | + self.assertIn('Test Page', page_titles) |
| 162 | + |
| 163 | + def test_processor_only_shows_published_pages(self): |
| 164 | + """测试上下文处理器只显示已发布的页面""" |
| 165 | + # 创建草稿页面 |
| 166 | + draft_page = Article.objects.create( |
| 167 | + title='Draft Page', |
| 168 | + body='Draft content', |
| 169 | + author=self.user, |
| 170 | + type='p', # 页面类型 |
| 171 | + status='d', # 草稿状态 |
| 172 | + category=self.category |
| 173 | + ) |
| 174 | + |
| 175 | + # 清除缓存以确保重新查询 |
| 176 | + cache.clear() |
| 177 | + |
| 178 | + request = self.factory.get('/') |
| 179 | + result = seo_processor(request) |
| 180 | + |
| 181 | + # 验证草稿页面不在导航页面列表中 |
| 182 | + nav_pages = list(result['nav_pages']) |
| 183 | + page_titles = [page.title for page in nav_pages] |
| 184 | + self.assertNotIn('Draft Page', page_titles) |
| 185 | + self.assertIn('Test Page', page_titles) |
| 186 | + |
| 187 | + def test_processor_cache_expiration(self): |
| 188 | + """测试缓存过期""" |
| 189 | + request = self.factory.get('/') |
| 190 | + |
| 191 | + # 第一次调用 |
| 192 | + result1 = seo_processor(request) |
| 193 | + |
| 194 | + # 手动删除缓存模拟过期 |
| 195 | + cache.delete('seo_processor') |
| 196 | + |
| 197 | + # 第二次调用应该重新生成缓存 |
| 198 | + result2 = seo_processor(request) |
| 199 | + |
| 200 | + # 验证结果仍然正确 |
| 201 | + self.assertIsNotNone(result2) |
| 202 | + self.assertIn('SITE_NAME', result2) |
| 203 | + |
| 204 | + @patch('blog.context_processors.get_blog_setting') |
| 205 | + def test_processor_with_custom_blog_settings(self, mock_get_blog_setting): |
| 206 | + """测试使用自定义博客设置""" |
| 207 | + # 模拟博客设置 |
| 208 | + mock_setting = Mock() |
| 209 | + mock_setting.site_name = 'Test Blog' |
| 210 | + mock_setting.site_description = 'A test blog' |
| 211 | + mock_setting.site_seo_description = 'SEO description' |
| 212 | + mock_setting.site_keywords = 'test, blog' |
| 213 | + mock_setting.article_sub_length = 100 |
| 214 | + mock_setting.show_google_adsense = False |
| 215 | + mock_setting.google_adsense_codes = '' |
| 216 | + mock_setting.open_site_comment = True |
| 217 | + mock_setting.beian_code = '' |
| 218 | + mock_setting.analytics_code = '' |
| 219 | + mock_setting.gongan_beiancode = '' |
| 220 | + mock_setting.show_gongan_code = False |
| 221 | + mock_setting.global_header = '' |
| 222 | + mock_setting.global_footer = '' |
| 223 | + mock_setting.comment_need_review = False |
| 224 | + mock_setting.color_scheme = 'light' |
| 225 | + |
| 226 | + mock_get_blog_setting.return_value = mock_setting |
| 227 | + |
| 228 | + # 清除缓存 |
| 229 | + cache.clear() |
| 230 | + |
| 231 | + request = self.factory.get('/') |
| 232 | + result = seo_processor(request) |
| 233 | + |
| 234 | + # 验证返回的值与模拟的设置匹配 |
| 235 | + self.assertEqual(result['SITE_NAME'], 'Test Blog') |
| 236 | + self.assertEqual(result['SITE_DESCRIPTION'], 'A test blog') |
| 237 | + self.assertEqual(result['SITE_SEO_DESCRIPTION'], 'SEO description') |
| 238 | + self.assertEqual(result['SITE_KEYWORDS'], 'test, blog') |
| 239 | + self.assertEqual(result['ARTICLE_SUB_LENGTH'], 100) |
| 240 | + self.assertEqual(result['SHOW_GOOGLE_ADSENSE'], False) |
| 241 | + |
| 242 | + def test_processor_site_base_url_with_different_hosts(self): |
| 243 | + """测试不同主机名的SITE_BASE_URL""" |
| 244 | + hosts = ['example.com', 'blog.example.com', 'localhost:8000'] |
| 245 | + |
| 246 | + for host in hosts: |
| 247 | + request = self.factory.get('/', HTTP_HOST=host) |
| 248 | + result = seo_processor(request) |
| 249 | + |
| 250 | + # 验证SITE_BASE_URL包含正确的主机名 |
| 251 | + self.assertIn(host, result['SITE_BASE_URL']) |
| 252 | + |
| 253 | + def test_processor_dynamic_values_not_cached(self): |
| 254 | + """测试动态值不被缓存(SITE_BASE_URL和CURRENT_YEAR)""" |
| 255 | + # 第一次请求 - HTTP |
| 256 | + request1 = self.factory.get('/') |
| 257 | + result1 = seo_processor(request1) |
| 258 | + site_url1 = result1['SITE_BASE_URL'] |
| 259 | + |
| 260 | + # 第二次请求 - HTTPS(使用缓存的数据但动态值应该更新) |
| 261 | + request2 = self.factory.get('/', secure=True) |
| 262 | + result2 = seo_processor(request2) |
| 263 | + site_url2 = result2['SITE_BASE_URL'] |
| 264 | + |
| 265 | + # 验证SITE_BASE_URL不同(一个是http,一个是https) |
| 266 | + self.assertNotEqual(site_url1, site_url2) |
| 267 | + self.assertTrue(site_url1.startswith('http://')) |
| 268 | + self.assertTrue(site_url2.startswith('https://')) |
| 269 | + |
| 270 | + def test_processor_handles_empty_settings(self): |
| 271 | + """测试处理器处理空设置""" |
| 272 | + # 清除缓存 |
| 273 | + cache.clear() |
| 274 | + |
| 275 | + request = self.factory.get('/') |
| 276 | + result = seo_processor(request) |
| 277 | + |
| 278 | + # 即使某些设置为空,处理器也应该正常工作 |
| 279 | + self.assertIsNotNone(result) |
| 280 | + self.assertIn('SITE_NAME', result) |
| 281 | + |
| 282 | + @patch('blog.context_processors.logger') |
| 283 | + def test_processor_logs_cache_miss(self, mock_logger): |
| 284 | + """测试缓存未命中时记录日志""" |
| 285 | + # 清除缓存 |
| 286 | + cache.clear() |
| 287 | + |
| 288 | + request = self.factory.get('/') |
| 289 | + result = seo_processor(request) |
| 290 | + |
| 291 | + # 验证logger.info被调用 |
| 292 | + self.assertTrue(mock_logger.info.called) |
| 293 | + # 验证日志消息 |
| 294 | + call_args = str(mock_logger.info.call_args) |
| 295 | + self.assertIn('set processor cache', call_args) |
| 296 | + |
| 297 | + def test_processor_multiple_categories(self): |
| 298 | + """测试多个分类的情况""" |
| 299 | + # 创建额外的分类 |
| 300 | + Category.objects.create(name='Category 2', slug='category-2') |
| 301 | + Category.objects.create(name='Category 3', slug='category-3') |
| 302 | + |
| 303 | + # 清除缓存 |
| 304 | + cache.clear() |
| 305 | + |
| 306 | + request = self.factory.get('/') |
| 307 | + result = seo_processor(request) |
| 308 | + |
| 309 | + # 验证所有分类都在列表中 |
| 310 | + nav_categories = list(result['nav_category_list']) |
| 311 | + self.assertEqual(len(nav_categories), 3) |
| 312 | + |
| 313 | + def test_processor_multiple_pages(self): |
| 314 | + """测试多个页面的情况""" |
| 315 | + # 创建额外的页面 |
| 316 | + Article.objects.create( |
| 317 | + title='Page 2', |
| 318 | + body='Content 2', |
| 319 | + author=self.user, |
| 320 | + type='p', |
| 321 | + status='p', |
| 322 | + category=self.category |
| 323 | + ) |
| 324 | + Article.objects.create( |
| 325 | + title='Page 3', |
| 326 | + body='Content 3', |
| 327 | + author=self.user, |
| 328 | + type='p', |
| 329 | + status='p', |
| 330 | + category=self.category |
| 331 | + ) |
| 332 | + |
| 333 | + # 清除缓存 |
| 334 | + cache.clear() |
| 335 | + |
| 336 | + request = self.factory.get('/') |
| 337 | + result = seo_processor(request) |
| 338 | + |
| 339 | + # 验证所有页面都在列表中 |
| 340 | + nav_pages = list(result['nav_pages']) |
| 341 | + self.assertEqual(len(nav_pages), 3) |
| 342 | + |
| 343 | + def test_processor_excludes_articles_from_nav_pages(self): |
| 344 | + """测试nav_pages不包含文章(只包含页面)""" |
| 345 | + # 创建一个文章 |
| 346 | + Article.objects.create( |
| 347 | + title='Test Article', |
| 348 | + body='Article content', |
| 349 | + author=self.user, |
| 350 | + type='a', # 文章类型 |
| 351 | + status='p', |
| 352 | + category=self.category |
| 353 | + ) |
| 354 | + |
| 355 | + # 清除缓存 |
| 356 | + cache.clear() |
| 357 | + |
| 358 | + request = self.factory.get('/') |
| 359 | + result = seo_processor(request) |
| 360 | + |
| 361 | + # 验证文章不在nav_pages中 |
| 362 | + nav_pages = list(result['nav_pages']) |
| 363 | + page_titles = [page.title for page in nav_pages] |
| 364 | + self.assertNotIn('Test Article', page_titles) |
| 365 | + self.assertIn('Test Page', page_titles) # 但页面应该在 |
| 366 | + |
| 367 | + def test_processor_color_scheme_setting(self): |
| 368 | + """测试COLOR_SCHEME设置""" |
| 369 | + request = self.factory.get('/') |
| 370 | + result = seo_processor(request) |
| 371 | + |
| 372 | + # 验证COLOR_SCHEME存在且有值 |
| 373 | + self.assertIn('COLOR_SCHEME', result) |
| 374 | + self.assertIsNotNone(result['COLOR_SCHEME']) |
0 commit comments