""" 企业微信用户绑定 API 测试 """ from django.contrib.auth.models import User from django.test import TestCase, override_settings from rest_framework.test import APIClient from basic_info.models import Employee, Merchant, MerchantTypeEnum @override_settings(AGENT_ACCESS_KEY='test-key-123') class WecomBindingAPITestCase(TestCase): """测试企业微信用户绑定接口""" def setUp(self): self.client = APIClient() self.url = '/api/v2/wecom/binduser/' self.auth_header = 'test-key-123' # 创建商户 self.merchant = Merchant.objects.create( name='测试商户', type=MerchantTypeEnum.STORE, ) # 创建用户 self.user = User.objects.create_user( username='testuser', password='testpass123', ) # 创建员工并关联用户 self.employee = Employee.objects.create( merchant=self.merchant, name='测试员工', sys_user=self.user, ) def _post(self, data, auth=None): """辅助方法:发送 POST 请求""" headers = {'HTTP_AUTHORIZATION': auth or self.auth_header} return self.client.post(self.url, data, format='json', **headers) # ==================== 认证测试 ==================== def test_missing_authorization_header(self): """缺少 Authorization 头应返回 401""" resp = self.client.post(self.url, {}, format='json') self.assertEqual(resp.status_code, 401) def test_invalid_authorization_key(self): """错误的 AGENT_ACCESS_KEY 应返回 401""" resp = self._post( {'wecom_user_id': 'wx123', 'username': 'testuser', 'password': 'testpass123'}, auth='wrong-key', ) self.assertEqual(resp.status_code, 401) # ==================== 参数验证测试 ==================== def test_missing_required_fields(self): """缺少必填字段应返回 400""" resp = self._post({}) self.assertEqual(resp.status_code, 400) data = resp.json() self.assertIn('wecom_user_id', data) self.assertIn('username', data) self.assertIn('password', data) def test_missing_wecom_user_id(self): """缺少 wecom_user_id 应返回 400""" resp = self._post({'username': 'testuser', 'password': 'testpass123'}) self.assertEqual(resp.status_code, 400) self.assertIn('wecom_user_id', resp.json()) # ==================== 用户凭据验证测试 ==================== def test_wrong_password(self): """密码错误应返回 401""" resp = self._post({ 'wecom_user_id': 'wx123', 'username': 'testuser', 'password': 'wrongpass', }) self.assertEqual(resp.status_code, 401) self.assertEqual(resp.json()['error'], '用户名或密码错误') def test_nonexistent_username(self): """不存在的用户名应返回 401""" resp = self._post({ 'wecom_user_id': 'wx123', 'username': 'nouser', 'password': 'testpass123', }) self.assertEqual(resp.status_code, 401) def test_inactive_user(self): """被禁用的用户应返回 403""" self.user.is_active = False self.user.save() resp = self._post({ 'wecom_user_id': 'wx123', 'username': 'testuser', 'password': 'testpass123', }) # Django authenticate() returns None for inactive users by default # so it will be 401 (用户名或密码错误) self.assertIn(resp.status_code, [401, 403]) def test_user_without_employee(self): """用户未关联员工应返回 404""" user2 = User.objects.create_user(username='noemployee', password='pass123') resp = self._post({ 'wecom_user_id': 'wx123', 'username': 'noemployee', 'password': 'pass123', }) self.assertEqual(resp.status_code, 404) self.assertEqual(resp.json()['error'], '该用户未关联任何员工记录') # ==================== 绑定成功测试 ==================== def test_binding_success(self): """正常绑定应返回 200""" resp = self._post({ 'wecom_user_id': 'wx_user_001', 'username': 'testuser', 'password': 'testpass123', }) self.assertEqual(resp.status_code, 200) data = resp.json() self.assertEqual(data['message'], '绑定成功') self.assertEqual(data['employee_id'], self.employee.id) self.assertEqual(data['employee_name'], '测试员工') self.assertEqual(data['wecom_user_id'], 'wx_user_001') # 验证数据库 self.employee.refresh_from_db() self.assertEqual(self.employee.wecom_user_id, 'wx_user_001') def test_binding_idempotent(self): """重复绑定相同 wecom_user_id 应幂等返回 200""" self.employee.wecom_user_id = 'wx_user_001' self.employee.save() resp = self._post({ 'wecom_user_id': 'wx_user_001', 'username': 'testuser', 'password': 'testpass123', }) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.json()['message'], '绑定成功') # ==================== 冲突测试 ==================== def test_wecom_user_id_already_bound_to_other_employee(self): """wecom_user_id 已绑定到其他员工应返回 409""" other_employee = Employee.objects.create( merchant=self.merchant, name='其他员工', wecom_user_id='wx_user_001', ) resp = self._post({ 'wecom_user_id': 'wx_user_001', 'username': 'testuser', 'password': 'testpass123', }) self.assertEqual(resp.status_code, 409) self.assertIn('该企业微信用户已绑定到其他员工', resp.json()['error']) def test_employee_already_has_different_wecom_user_id_without_force(self): """员工已有不同 wecom_user_id 且未传 force_update 应返回 409""" self.employee.wecom_user_id = 'wx_old_user' self.employee.save() resp = self._post({ 'wecom_user_id': 'wx_new_user', 'username': 'testuser', 'password': 'testpass123', }) self.assertEqual(resp.status_code, 409) data = resp.json() self.assertEqual(data['error'], '该员工已绑定其他企业微信用户') self.assertEqual(data['current_wecom_user_id'], 'wx_old_user') self.assertIn('hint', data) def test_employee_already_has_different_wecom_user_id_with_force(self): """员工已有不同 wecom_user_id 但传了 force_update=true 应覆盖成功""" self.employee.wecom_user_id = 'wx_old_user' self.employee.save() resp = self._post({ 'wecom_user_id': 'wx_new_user', 'username': 'testuser', 'password': 'testpass123', 'force_update': True, }) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.json()['wecom_user_id'], 'wx_new_user') self.employee.refresh_from_db() self.assertEqual(self.employee.wecom_user_id, 'wx_new_user') def test_force_update_false_explicitly(self): """显式传 force_update=false 应拒绝覆盖""" self.employee.wecom_user_id = 'wx_old_user' self.employee.save() resp = self._post({ 'wecom_user_id': 'wx_new_user', 'username': 'testuser', 'password': 'testpass123', 'force_update': False, }) self.assertEqual(resp.status_code, 409)