import json from unittest.mock import patch # 用于模拟外部依赖(如第三方API调用) from django.conf import settings from django.contrib import auth # 用于用户认证相关操作 from django.test import Client, RequestFactory, TestCase # Django测试工具 from django.urls import reverse # 用于反向解析URL from djangoblog.utils import get_sha256 # 导入自定义加密工具函数 from oauth.models import OAuthConfig # 导入OAuth配置模型 from oauth.oauthmanager import BaseOauthManager # 导入OAuth基础管理器 # Create your tests here. class OAuthConfigTest(TestCase): """测试OAuth配置模型及基础登录流程""" def setUp(self): """测试前初始化:创建客户端和请求工厂""" self.client = Client() # 用于模拟HTTP请求的客户端 self.factory = RequestFactory() # 用于创建请求对象的工厂 def test_oauth_login_test(self): """测试OAuth配置创建后,登录链接和授权回调的正确性""" # 创建一个微博的OAuth配置 c = OAuthConfig() c.type = 'weibo' c.appkey = 'appkey' c.appsecret = 'appsecret' c.save() # 测试访问微博OAuth登录链接是否重定向到正确的授权页面 response = self.client.get('/oauth/oauthlogin?type=weibo') self.assertEqual(response.status_code, 302) # 验证重定向状态码 self.assertTrue("api.weibo.com" in response.url) # 验证重定向到微博API # 测试授权回调接口是否正常重定向 response = self.client.get('/oauth/authorize?type=weibo&code=code') self.assertEqual(response.status_code, 302) # 验证重定向状态码 self.assertEqual(response.url, '/') # 验证默认重定向到首页 class OauthLoginTest(TestCase): """测试各第三方平台的OAuth登录流程""" def setUp(self) -> None: """测试前初始化:创建客户端、请求工厂,并初始化所有平台的OAuth配置""" self.client = Client() self.factory = RequestFactory() self.apps = self.init_apps() # 初始化所有启用的OAuth应用 def init_apps(self): """为所有BaseOauthManager的子类(各平台管理器)创建对应的OAuth配置""" # 获取所有第三方平台的OAuth管理器实例 applications = [p() for p in BaseOauthManager.__subclasses__()] # 为每个平台创建默认配置并保存到数据库 for application in applications: c = OAuthConfig() c.type = application.ICON_NAME.lower() c.appkey = 'appkey' c.appsecret = 'appsecret' c.save() return applications def get_app_by_type(self, type): """根据平台类型获取对应的OAuth管理器实例""" for app in self.apps: if app.ICON_NAME.lower() == type: return app @patch("oauth.oauthmanager.WBOauthManager.do_post") @patch("oauth.oauthmanager.WBOauthManager.do_get") def test_weibo_login(self, mock_do_get, mock_do_post): """测试微博登录流程:获取授权链接、Token及用户信息""" # 获取微博OAuth管理器实例 weibo_app = self.get_app_by_type('weibo') assert weibo_app # 确保实例存在 # 验证授权链接生成(无需mock,直接调用方法) url = weibo_app.get_authorization_url() # 模拟获取Token的响应 mock_do_post.return_value = json.dumps({ "access_token": "access_token", "uid": "uid" }) # 模拟获取用户信息的响应 mock_do_get.return_value = json.dumps({ "avatar_large": "avatar_large", "screen_name": "screen_name", "id": "id", "email": "email", }) # 测试通过code获取用户信息 userinfo = weibo_app.get_access_token_by_code('code') # 验证用户信息是否正确 self.assertEqual(userinfo.token, 'access_token') self.assertEqual(userinfo.openid, 'id') @patch("oauth.oauthmanager.GoogleOauthManager.do_post") @patch("oauth.oauthmanager.GoogleOauthManager.do_get") def test_google_login(self, mock_do_get, mock_do_post): """测试谷歌登录流程:获取Token及用户信息""" google_app = self.get_app_by_type('google') assert google_app # 验证授权链接生成 url = google_app.get_authorization_url() # 模拟获取Token的响应 mock_do_post.return_value = json.dumps({ "access_token": "access_token", "id_token": "id_token", }) # 模拟获取用户信息的响应 mock_do_get.return_value = json.dumps({ "picture": "picture", "name": "name", "sub": "sub", "email": "email", }) # 测试获取Token和用户信息 token = google_app.get_access_token_by_code('code') userinfo = google_app.get_oauth_userinfo() # 验证用户信息 self.assertEqual(userinfo.token, 'access_token') self.assertEqual(userinfo.openid, 'sub') @patch("oauth.oauthmanager.GitHubOauthManager.do_post") @patch("oauth.oauthmanager.GitHubOauthManager.do_get") def test_github_login(self, mock_do_get, mock_do_post): """测试GitHub登录流程:验证授权链接、Token及用户信息""" github_app = self.get_app_by_type('github') assert github_app # 验证授权链接包含GitHub域名和client_id参数 url = github_app.get_authorization_url() self.assertTrue("github.com" in url) self.assertTrue("client_id" in url) # 模拟GitHub返回的Token(query string格式) mock_do_post.return_value = "access_token=gho_16C7e42F292c6912E7710c838347Ae178B4a&scope=repo%2Cgist&token_type=bearer" # 模拟用户信息响应 mock_do_get.return_value = json.dumps({ "avatar_url": "avatar_url", "name": "name", "id": "id", "email": "email", }) # 测试获取Token和用户信息 token = github_app.get_access_token_by_code('code') userinfo = github_app.get_oauth_userinfo() # 验证Token和openid self.assertEqual(userinfo.token, 'gho_16C7e42F292c6912E7710c838347Ae178B4a') self.assertEqual(userinfo.openid, 'id') @patch("oauth.oauthmanager.FaceBookOauthManager.do_post") @patch("oauth.oauthmanager.FaceBookOauthManager.do_get") def test_facebook_login(self, mock_do_get, mock_do_post): """测试Facebook登录流程:验证授权链接、Token及用户信息""" facebook_app = self.get_app_by_type('facebook') assert facebook_app # 验证授权链接包含Facebook域名 url = facebook_app.get_authorization_url() self.assertTrue("facebook.com" in url) # 模拟获取Token的响应 mock_do_post.return_value = json.dumps({ "access_token": "access_token", }) # 模拟用户信息响应(包含嵌套的头像结构) mock_do_get.return_value = json.dumps({ "name": "name", "id": "id", "email": "email", "picture": { "data": { "url": "url" } } }) # 测试获取Token和用户信息 token = facebook_app.get_access_token_by_code('code') userinfo = facebook_app.get_oauth_userinfo() # 验证Token self.assertEqual(userinfo.token, 'access_token') @patch("oauth.oauthmanager.QQOauthManager.do_get", side_effect=[ # 模拟三次GET请求的响应:1.获取Token 2.获取OpenID 3.获取用户信息 'access_token=access_token&expires_in=3600', 'callback({"client_id":"appid","openid":"openid"} );', json.dumps({ "nickname": "nickname", "email": "email", "figureurl": "figureurl", "openid": "openid", }) ]) def test_qq_login(self, mock_do_get): """测试QQ登录流程(QQ需单独获取OpenID)""" qq_app = self.get_app_by_type('qq') assert qq_app # 验证授权链接包含QQ域名 url = qq_app.get_authorization_url() self.assertTrue("qq.com" in url) # 测试获取Token和用户信息 token = qq_app.get_access_token_by_code('code') userinfo = qq_app.get_oauth_userinfo() # 验证Token self.assertEqual(userinfo.token, 'access_token') @patch("oauth.oauthmanager.WBOauthManager.do_post") @patch("oauth.oauthmanager.WBOauthManager.do_get") def test_weibo_authoriz_login_with_email(self, mock_do_get, mock_do_post): """测试微博登录(用户提供邮箱):验证自动注册登录流程""" # 模拟获取Token的响应 mock_do_post.return_value = json.dumps({ "access_token": "access_token", "uid": "uid" }) # 模拟包含邮箱的用户信息 mock_user_info = { "avatar_large": "avatar_large", "screen_name": "screen_name1", "id": "id", "email": "email", } mock_do_get.return_value = json.dumps(mock_user_info) # 测试访问登录链接是否重定向到微博授权页 response = self.client.get('/oauth/oauthlogin?type=weibo') self.assertEqual(response.status_code, 302) self.assertTrue("api.weibo.com" in response.url) # 测试授权回调后是否重定向到首页 response = self.client.get('/oauth/authorize?type=weibo&code=code') self.assertEqual(response.status_code, 302) self.assertEqual(response.url, '/') # 验证用户已登录,信息正确 user = auth.get_user(self.client) assert user.is_authenticated self.assertTrue(user.is_authenticated) self.assertEqual(user.username, mock_user_info['screen_name']) self.assertEqual(user.email, mock_user_info['email']) # 测试退出登录后再次登录是否正常 self.client.logout() response = self.client.get('/oauth/authorize?type=weibo&code=code') self.assertEqual(response.status_code, 302) self.assertEqual(response.url, '/') # 再次验证用户登录状态和信息 user = auth.get_user(self.client) assert user.is_authenticated self.assertTrue(user.is_authenticated) self.assertEqual(user.username, mock_user_info['screen_name']) self.assertEqual(user.email, mock_user_info['email']) @patch("oauth.oauthmanager.WBOauthManager.do_post") @patch("oauth.oauthmanager.WBOauthManager.do_get") def test_weibo_authoriz_login_without_email(self, mock_do_get, mock_do_post): """测试微博登录(用户未提供邮箱):验证补充邮箱、绑定及确认流程""" # 模拟获取Token的响应 mock_do_post.return_value = json.dumps({ "access_token": "access_token", "uid": "uid" }) # 模拟不包含邮箱的用户信息 mock_user_info = { "avatar_large": "avatar_large", "screen_name": "screen_name1", "id": "id", } mock_do_get.return_value = json.dumps(mock_user_info) # 测试访问登录链接 response = self.client.get('/oauth/oauthlogin?type=weibo') self.assertEqual(response.status_code, 302) self.assertTrue("api.weibo.com" in response.url) # 测试授权后是否跳转到补充邮箱页面 response = self.client.get('/oauth/authorize?type=weibo&code=code') self.assertEqual(response.status_code, 302) # 提取补充邮箱页面的OAuth用户ID oauth_user_id = int(response.url.split('/')[-1].split('.')[0]) self.assertEqual(response.url, f'/oauth/requireemail/{oauth_user_id}.html') # 测试提交邮箱后是否跳转到绑定成功页 response = self.client.post(response.url, {'email': 'test@gmail.com', 'oauthid': oauth_user_id}) self.assertEqual(response.status_code, 302) # 生成验证签名(模拟邮箱确认流程) sign = get_sha256(settings.SECRET_KEY + str(oauth_user_id) + settings.SECRET_KEY) url = reverse('oauth:bindsuccess', kwargs={'oauthid': oauth_user_id}) self.assertEqual(response.url, f'{url}?type=email') # 测试邮箱确认后是否跳转到成功页,且用户登录状态正确 path = reverse('oauth:email_confirm', kwargs={'id': oauth_user_id, 'sign': sign}) response = self.client.get(path) self.assertEqual(response.status_code, 302) self.assertEqual(response.url, f'/oauth/bindsuccess/{oauth_user_id}.html?type=success') # 验证用户信息(用户名、邮箱)及OAuth用户关联正确 user = auth.get_user(self.client) from oauth.models import OAuthUser oauth_user = OAuthUser.objects.get(author=user) self.assertTrue(user.is_authenticated) self.assertEqual(user.username, mock_user_info['screen_name']) self.assertEqual(user.email, 'test@gmail.com') self.assertEqual(oauth_user.pk, oauth_user_id)