|
|
|
|
@ -1,8 +1,11 @@
|
|
|
|
|
# bjy: 导入操作系统接口模块
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
|
|
|
|
|
|
# bjy: 从Django中导入设置、文件上传、命令调用、分页器、静态文件、测试工具、URL反向解析和时区工具
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
from django.contrib.auth.models import User
|
|
|
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
|
|
|
from django.core.management import call_command
|
|
|
|
|
from django.core.paginator import Paginator
|
|
|
|
|
@ -16,6 +19,7 @@ from accounts.models import BlogUser
|
|
|
|
|
from blog.forms import BlogSearchForm
|
|
|
|
|
from blog.models import Article, Category, Tag, SideBar, Links
|
|
|
|
|
from blog.templatetags.blog_tags import load_pagination_info, load_articletags
|
|
|
|
|
from blog.views import LikeArticle
|
|
|
|
|
from djangoblog.utils import get_current_site, get_sha256
|
|
|
|
|
# bjy: 从项目中导入OAuth相关模型
|
|
|
|
|
from oauth.models import OAuthUser, OAuthConfig
|
|
|
|
|
@ -290,3 +294,284 @@ class ArticleTest(TestCase):
|
|
|
|
|
call_command("clear_cache")
|
|
|
|
|
call_command("sync_user_avatar")
|
|
|
|
|
call_command("build_search_words")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestLikeArticle(TestCase):
|
|
|
|
|
"""测试 LikeArticle 视图类中的 post 方法"""
|
|
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
|
"""
|
|
|
|
|
初始化测试所需的数据和工具
|
|
|
|
|
"""
|
|
|
|
|
self.factory = RequestFactory()
|
|
|
|
|
self.user = BlogUser.objects.create_user(username='testuser', password='password')
|
|
|
|
|
# 创建分类(Article模型需要category字段)
|
|
|
|
|
self.category = Category.objects.create(
|
|
|
|
|
name="Test Category",
|
|
|
|
|
slug="test-category"
|
|
|
|
|
)
|
|
|
|
|
self.article = Article.objects.create(
|
|
|
|
|
title="Test Article",
|
|
|
|
|
body="This is a test article.",
|
|
|
|
|
author=self.user,
|
|
|
|
|
category=self.category, # Article模型必需字段
|
|
|
|
|
views=0,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@patch('blog.models.Article.objects.get')
|
|
|
|
|
def test_post_like_article_successfully(self, mock_get_article):
|
|
|
|
|
"""
|
|
|
|
|
测试场景:用户第一次点赞文章成功
|
|
|
|
|
|
|
|
|
|
输入:
|
|
|
|
|
- 已登录用户
|
|
|
|
|
- 存在的文章 ID
|
|
|
|
|
- 用户尚未点赞该文章
|
|
|
|
|
|
|
|
|
|
输出:
|
|
|
|
|
- type = 1 表示新增点赞
|
|
|
|
|
- like_sum 更新为 1
|
|
|
|
|
- state = 200 成功状态码
|
|
|
|
|
"""
|
|
|
|
|
# 设置 mock 返回值
|
|
|
|
|
mock_article = MagicMock()
|
|
|
|
|
mock_article.users_like.filter.return_value.exists.return_value = False
|
|
|
|
|
mock_article.users_like.count.return_value = 1
|
|
|
|
|
mock_get_article.return_value = mock_article
|
|
|
|
|
|
|
|
|
|
# 构造 POST 请求
|
|
|
|
|
request = self.factory.post('/like/', {'article_id': str(self.article.id)})
|
|
|
|
|
request.user = self.user
|
|
|
|
|
|
|
|
|
|
# 执行被测函数
|
|
|
|
|
response = LikeArticle().post(request)
|
|
|
|
|
|
|
|
|
|
# 断言调用了 add 方法表示点赞
|
|
|
|
|
mock_article.users_like.add.assert_called_once_with(self.user)
|
|
|
|
|
mock_article.users_like.remove.assert_not_called()
|
|
|
|
|
|
|
|
|
|
# 解析响应内容
|
|
|
|
|
content = json.loads(response.content.decode())
|
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
self.assertEqual(content['type'], 1)
|
|
|
|
|
self.assertEqual(content['like_sum'], 1)
|
|
|
|
|
self.assertEqual(content['state'], 200)
|
|
|
|
|
|
|
|
|
|
@patch('blog.models.Article.objects.get')
|
|
|
|
|
def test_post_unlike_article_successfully(self, mock_get_article):
|
|
|
|
|
"""
|
|
|
|
|
测试场景:用户取消点赞文章成功
|
|
|
|
|
|
|
|
|
|
输入:
|
|
|
|
|
- 已登录用户
|
|
|
|
|
- 存在的文章 ID
|
|
|
|
|
- 用户已经点赞了该文章
|
|
|
|
|
|
|
|
|
|
输出:
|
|
|
|
|
- type = 0 表示取消点赞
|
|
|
|
|
- like_sum 更新为 0
|
|
|
|
|
- state = 200 成功状态码
|
|
|
|
|
"""
|
|
|
|
|
# 设置 mock 返回值
|
|
|
|
|
mock_article = MagicMock()
|
|
|
|
|
mock_article.users_like.filter.return_value.exists.return_value = True
|
|
|
|
|
mock_article.users_like.count.return_value = 0
|
|
|
|
|
mock_get_article.return_value = mock_article
|
|
|
|
|
|
|
|
|
|
# 构造 POST 请求
|
|
|
|
|
request = self.factory.post('/like/', {'article_id': str(self.article.id)})
|
|
|
|
|
request.user = self.user
|
|
|
|
|
|
|
|
|
|
# 执行被测函数
|
|
|
|
|
response = LikeArticle().post(request)
|
|
|
|
|
|
|
|
|
|
# 断言调用了 remove 方法表示取消点赞
|
|
|
|
|
mock_article.users_like.remove.assert_called_once_with(self.user)
|
|
|
|
|
mock_article.users_like.add.assert_not_called()
|
|
|
|
|
|
|
|
|
|
# 解析响应内容
|
|
|
|
|
content = json.loads(response.content.decode())
|
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
self.assertEqual(content['type'], 0)
|
|
|
|
|
self.assertEqual(content['like_sum'], 0)
|
|
|
|
|
self.assertEqual(content['state'], 200)
|
|
|
|
|
|
|
|
|
|
@patch('blog.models.Article.objects.get')
|
|
|
|
|
def test_post_article_does_not_exist(self, mock_get_article):
|
|
|
|
|
"""
|
|
|
|
|
测试场景:提供的文章 ID 不存在
|
|
|
|
|
|
|
|
|
|
输入:
|
|
|
|
|
- 任意用户
|
|
|
|
|
- 不存在的文章 ID
|
|
|
|
|
|
|
|
|
|
输出:
|
|
|
|
|
- state = 400 错误状态码
|
|
|
|
|
- data 包含“文章不存在”提示
|
|
|
|
|
"""
|
|
|
|
|
# 设置 mock 抛出 DoesNotExist 异常
|
|
|
|
|
mock_get_article.side_effect = Article.DoesNotExist
|
|
|
|
|
|
|
|
|
|
# 构造 POST 请求
|
|
|
|
|
request = self.factory.post('/like/', {'article_id': '999'})
|
|
|
|
|
request.user = self.user
|
|
|
|
|
|
|
|
|
|
# 执行被测函数
|
|
|
|
|
response = LikeArticle().post(request)
|
|
|
|
|
|
|
|
|
|
# 解析响应内容
|
|
|
|
|
content = json.loads(response.content.decode())
|
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
self.assertEqual(content['state'], 400)
|
|
|
|
|
self.assertIn("文章不存在", content['data'])
|
|
|
|
|
|
|
|
|
|
@patch('blog.models.Article.objects.get')
|
|
|
|
|
def test_post_internal_server_error(self, mock_get_article):
|
|
|
|
|
"""
|
|
|
|
|
测试场景:系统内部发生异常
|
|
|
|
|
|
|
|
|
|
输入:
|
|
|
|
|
- 任意用户
|
|
|
|
|
- 导致异常的操作(如数据库连接失败等)
|
|
|
|
|
|
|
|
|
|
输出:
|
|
|
|
|
- state = 500 错误状态码
|
|
|
|
|
- data 包含具体异常描述
|
|
|
|
|
"""
|
|
|
|
|
# 设置 mock 抛出通用异常
|
|
|
|
|
mock_get_article.side_effect = Exception("数据库连接超时")
|
|
|
|
|
|
|
|
|
|
# 构造 POST 请求
|
|
|
|
|
request = self.factory.post('/like/', {'article_id': str(self.article.id)})
|
|
|
|
|
request.user = self.user
|
|
|
|
|
|
|
|
|
|
# 执行被测函数
|
|
|
|
|
response = LikeArticle().post(request)
|
|
|
|
|
|
|
|
|
|
# 解析响应内容
|
|
|
|
|
content = json.loads(response.content.decode())
|
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
self.assertEqual(content['state'], 500)
|
|
|
|
|
self.assertIn("服务器错误", content['data'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LikeIntegrationTests(TestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
"""设置测试数据"""
|
|
|
|
|
self.client = Client()
|
|
|
|
|
self.user = BlogUser.objects.create_user(
|
|
|
|
|
username='testuser',
|
|
|
|
|
email='test@example.com',
|
|
|
|
|
password='testpass123'
|
|
|
|
|
)
|
|
|
|
|
self.other_user = BlogUser.objects.create_user(
|
|
|
|
|
username='otheruser',
|
|
|
|
|
email='other@example.com',
|
|
|
|
|
password='testpass123'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 创建分类(因为Article模型需要category字段)
|
|
|
|
|
self.category = Category.objects.create(
|
|
|
|
|
name='测试分类',
|
|
|
|
|
slug='test-category'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.article = Article.objects.create(
|
|
|
|
|
title='Test Article',
|
|
|
|
|
body='Test content',
|
|
|
|
|
author=self.user,
|
|
|
|
|
category=self.category, # 必须提供category
|
|
|
|
|
# 其他必填字段使用默认值
|
|
|
|
|
status='p', # 发布状态
|
|
|
|
|
comment_status='o', # 开放评论
|
|
|
|
|
type='a', # 文章类型
|
|
|
|
|
article_order=0,
|
|
|
|
|
show_toc=False
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_like_workflow(self):
|
|
|
|
|
"""测试完整的点赞流程"""
|
|
|
|
|
# 1. 用户登录
|
|
|
|
|
self.client.login(username='testuser', password='testpass123')
|
|
|
|
|
|
|
|
|
|
# 2. 发送点赞请求
|
|
|
|
|
response = self.client.post(
|
|
|
|
|
reverse('blog:like_article'),
|
|
|
|
|
{'article_id': self.article.id},
|
|
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 3. 验证响应
|
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
self.assertEqual(response.json()['type'], 1) # 点赞操作
|
|
|
|
|
|
|
|
|
|
# 4. 验证数据库状态
|
|
|
|
|
self.assertTrue(self.article.users_like.filter(id=self.user.id).exists())
|
|
|
|
|
self.assertEqual(self.article.users_like.count(), 1)
|
|
|
|
|
|
|
|
|
|
# 5. 测试取消点赞
|
|
|
|
|
response = self.client.post(
|
|
|
|
|
reverse('blog:like_article'),
|
|
|
|
|
{'article_id': self.article.id},
|
|
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 6. 验证取消点赞
|
|
|
|
|
self.assertEqual(response.json()['type'], 0) # 取消点赞操作
|
|
|
|
|
self.assertFalse(self.article.users_like.filter(id=self.user.id).exists())
|
|
|
|
|
self.assertEqual(self.article.users_like.count(), 0)
|
|
|
|
|
|
|
|
|
|
def test_multiple_users_liking(self):
|
|
|
|
|
"""测试多个用户点赞同一篇文章"""
|
|
|
|
|
# 第一个用户点赞
|
|
|
|
|
self.client.login(username='testuser', password='testpass123')
|
|
|
|
|
self.client.post(
|
|
|
|
|
reverse('blog:like_article'),
|
|
|
|
|
{'article_id': self.article.id},
|
|
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 第二个用户点赞
|
|
|
|
|
self.client.login(username='otheruser', password='testpass123')
|
|
|
|
|
response = self.client.post(
|
|
|
|
|
reverse('blog:like_article'),
|
|
|
|
|
{'article_id': self.article.id},
|
|
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 验证两个用户都点赞成功
|
|
|
|
|
self.assertEqual(self.article.users_like.count(), 2)
|
|
|
|
|
self.assertTrue(self.article.users_like.filter(id=self.user.id).exists())
|
|
|
|
|
self.assertTrue(self.article.users_like.filter(id=self.other_user.id).exists())
|
|
|
|
|
|
|
|
|
|
def test_like_nonexistent_article(self):
|
|
|
|
|
"""测试给不存在的文章点赞"""
|
|
|
|
|
self.client.login(username='testuser', password='testpass123')
|
|
|
|
|
|
|
|
|
|
response = self.client.post(
|
|
|
|
|
reverse('blog:like_article'),
|
|
|
|
|
{'article_id': 1145}, # 不存在的文章ID
|
|
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
|
self.assertIn('文章不存在', response.json()['data'])
|
|
|
|
|
|
|
|
|
|
def test_like_without_login(self):
|
|
|
|
|
"""测试未登录用户点赞"""
|
|
|
|
|
response = self.client.post(
|
|
|
|
|
reverse('blog:like_article'),
|
|
|
|
|
{'article_id': self.article.id},
|
|
|
|
|
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 应该重定向到登录页面或者返回错误
|
|
|
|
|
self.assertIn(response.status_code, [302, 403]) # 重定向或权限拒绝
|
|
|
|
|
|
|
|
|
|
def test_like_with_invalid_method(self):
|
|
|
|
|
"""测试使用错误的HTTP方法"""
|
|
|
|
|
self.client.login(username='testuser', password='testpass123')
|
|
|
|
|
|
|
|
|
|
response = self.client.get(reverse('blog:like_article')) # 使用GET而不是POST
|
|
|
|
|
|
|
|
|
|
self.assertEqual(response.status_code, 405) # Method Not Allowed
|
|
|
|
|
|