import json import time import threading from queue import Queue from unittest.mock import patch, MagicMock from django.test import TestCase from django.contrib.auth import get_user_model from django.urls import reverse from rest_framework import status from rest_framework.test import APIClient from rest_framework_simplejwt.tokens import RefreshToken from basic_info import models as basic_models from . import services User = get_user_model() class SSEAPITestCase(TestCase): """SSE API测试用例""" def setUp(self): """设置测试数据""" # 创建测试商户 self.merchant1 = basic_models.Merchant.objects.create( name='测试商户1', type=basic_models.MerchantTypeEnum.STORE, ) self.merchant2 = basic_models.Merchant.objects.create( name='测试商户2', type=basic_models.MerchantTypeEnum.FACTORY, ) # 创建测试用户 self.user1 = User.objects.create_user(username='user1', password='testpass') self.user2 = User.objects.create_user(username='user2', password='testpass') self.user_no_employee = User.objects.create_user(username='no_employee', password='testpass') # 创建员工并关联商户 self.employee1 = basic_models.Employee.objects.create( merchant=self.merchant1, sys_user=self.user1, name='员工1', mobile='13800138001', ) self.employee2 = basic_models.Employee.objects.create( merchant=self.merchant2, sys_user=self.user2, name='员工2', mobile='13800138002', ) # 生成JWT token refresh1 = RefreshToken.for_user(self.user1) self.token1 = str(refresh1.access_token) refresh2 = RefreshToken.for_user(self.user2) self.token2 = str(refresh2.access_token) refresh_no_employee = RefreshToken.for_user(self.user_no_employee) self.token_no_employee = str(refresh_no_employee.access_token) # 设置客户端 self.client = APIClient() # 清理所有连接 services._connections.clear() def test_sse_connection_without_token(self): """测试没有JWT token的SSE连接""" response = self.client.get('/sse/') self.assertEqual(response.status_code, 403) self.assertEqual(response.content, b'Authentication failed or user has no associated merchant') # 检查连接未被添加到服务中 self.assertEqual(len(services._connections), 0) def test_sse_connection_with_invalid_token(self): """测试无效JWT token的SSE连接""" response = self.client.get( '/sse/', HTTP_AUTHORIZATION='Bearer invalid_token' ) self.assertEqual(response.status_code, 403) self.assertEqual(response.content, b'Authentication failed or user has no associated merchant') # 检查连接未被添加到服务中 self.assertEqual(len(services._connections), 0) def test_sse_connection_user_no_employee(self): """测试用户无关联员工的情况""" response = self.client.get( '/sse/', HTTP_AUTHORIZATION=f'Bearer {self.token_no_employee}' ) self.assertEqual(response.status_code, 403) self.assertEqual(response.content, b'Authentication failed or user has no associated merchant') # 检查连接未被添加到服务中 self.assertEqual(len(services._connections), 0) def test_push_test_event_to_merchant(self): """测试向特定商户推送测试事件""" # 手动建立连接 queue1 = Queue() services.push_connection(self.merchant1.id, queue1) # 推送事件 response = self.client.post( '/sse/push/', HTTP_AUTHORIZATION=f'Bearer {self.token1}' ) self.assertEqual(response.status_code, 200) data = response.json() self.assertEqual(data['status'], 'ok') self.assertEqual(data['message'], 'Test event broadcasted to your merchant') self.assertEqual(data['merchant_id'], self.merchant1.id) self.assertEqual(data['clients'], 1) # 清理连接 services.remove_connection(self.merchant1.id, queue1) def test_push_test_event_unauthenticated(self): """测试未认证用户推送事件""" response = self.client.post('/sse/push/') self.assertEqual(response.status_code, 401) def test_push_test_event_user_no_employee(self): """测试无关联员工用户推送事件""" response = self.client.post( '/sse/push/', HTTP_AUTHORIZATION=f'Bearer {self.token_no_employee}' ) self.assertEqual(response.status_code, 403) data = response.json() self.assertEqual(data['error'], 'User has no associated merchant') def test_get_sse_status(self): """测试获取SSE状态""" # 手动建立两个商户的连接 queue1 = Queue() queue2 = Queue() services.push_connection(self.merchant1.id, queue1) services.push_connection(self.merchant2.id, queue2) # 获取状态 response = self.client.get( '/sse/status/', HTTP_AUTHORIZATION=f'Bearer {self.token1}' ) self.assertEqual(response.status_code, 200) data = response.json() self.assertEqual(data['status'], 'running') self.assertEqual(data['total_clients'], 2) # 所有连接数 self.assertEqual(data['merchant_clients'], 1) # 当前商户的连接数 self.assertEqual(data['merchant_id'], self.merchant1.id) # 清理连接 services.remove_connection(self.merchant1.id, queue1) services.remove_connection(self.merchant2.id, queue2) def test_get_sse_status_unauthenticated(self): """测试未认证用户获取状态""" response = self.client.get('/sse/status/') self.assertEqual(response.status_code, 401) def test_shutdown_merchant_connections(self): """测试关闭商户连接""" # 手动建立两个商户的连接 queue1 = Queue() queue2 = Queue() services.push_connection(self.merchant1.id, queue1) services.push_connection(self.merchant2.id, queue2) # 关闭商户1的连接 response = self.client.post( '/sse/shutdown/', HTTP_AUTHORIZATION=f'Bearer {self.token1}' ) self.assertEqual(response.status_code, 200) data = response.json() self.assertEqual(data['status'], 'ok') self.assertEqual(data['message'], 'Your merchant\'s SSE connections have been closed') self.assertEqual(data['merchant_id'], self.merchant1.id) self.assertEqual(data['clients'], 1) # 检查连接状态 merchant1_connections = services.get_merchant_connections(self.merchant1.id) merchant2_connections = services.get_merchant_connections(self.merchant2.id) self.assertEqual(len(merchant1_connections), 0) self.assertEqual(len(merchant2_connections), 1) # 清理连接 services.remove_connection(self.merchant2.id, queue2) def test_shutdown_merchant_connections_unauthenticated(self): """测试未认证用户关闭连接""" response = self.client.post('/sse/shutdown/') self.assertEqual(response.status_code, 401) def test_merchant_isolation(self): """测试商户隔离""" # 为两个商户分别建立连接 queue1 = Queue() queue2 = Queue() queue3 = Queue() queue4 = Queue() services.push_connection(self.merchant1.id, queue1) services.push_connection(self.merchant1.id, queue2) services.push_connection(self.merchant2.id, queue3) services.push_connection(self.merchant2.id, queue4) # 向商户1推送事件 response = self.client.post( '/sse/push/', HTTP_AUTHORIZATION=f'Bearer {self.token1}' ) self.assertEqual(response.status_code, 200) data = response.json() self.assertEqual(data['merchant_id'], self.merchant1.id) self.assertEqual(data['clients'], 2) # 商户1有2个客户端 # 向商户2推送事件 response = self.client.post( '/sse/push/', HTTP_AUTHORIZATION=f'Bearer {self.token2}' ) self.assertEqual(response.status_code, 200) data = response.json() self.assertEqual(data['merchant_id'], self.merchant2.id) self.assertEqual(data['clients'], 2) # 商户2有2个客户端 # 确认商户隔离 merchant1_connections = services.get_merchant_connections(self.merchant1.id) merchant2_connections = services.get_merchant_connections(self.merchant2.id) self.assertEqual(len(merchant1_connections), 2) self.assertEqual(len(merchant2_connections), 2) # 清理连接 services.remove_connection(self.merchant1.id, queue1) services.remove_connection(self.merchant1.id, queue2) services.remove_connection(self.merchant2.id, queue3) services.remove_connection(self.merchant2.id, queue4) def test_sse_connection_with_options_request(self): """测试OPTIONS预检请求""" response = self.client.options('/sse/') # OPTIONS请求应该成功 self.assertEqual(response.status_code, 200) # 带Origin头的OPTIONS请求应该返回CORS头 response_with_origin = self.client.options( '/sse/', HTTP_ORIGIN='https://example.com' ) self.assertEqual(response_with_origin.status_code, 200) self.assertIn('Access-Control-Allow-Origin', response_with_origin) self.assertIn('Access-Control-Allow-Methods', response_with_origin) self.assertIn('Access-Control-Allow-Headers', response_with_origin) class SSEServicesTestCase(TestCase): """SSE服务层测试用例""" def setUp(self): """设置测试数据""" self.merchant1_id = 1 self.merchant2_id = 2 # 清理所有连接 services._connections.clear() def test_push_connection(self): """测试添加连接""" queue1 = Queue() queue2 = Queue() # 添加连接 services.push_connection(self.merchant1_id, queue1) services.push_connection(self.merchant1_id, queue2) services.push_connection(self.merchant2_id, Queue()) # 检查连接 merchant1_connections = services.get_merchant_connections(self.merchant1_id) merchant2_connections = services.get_merchant_connections(self.merchant2_id) self.assertEqual(len(merchant1_connections), 2) self.assertEqual(len(merchant2_connections), 1) self.assertIn(queue1, merchant1_connections) self.assertIn(queue2, merchant1_connections) def test_remove_connection(self): """测试移除连接""" queue1 = Queue() queue2 = Queue() # 添加连接 services.push_connection(self.merchant1_id, queue1) services.push_connection(self.merchant1_id, queue2) # 移除一个连接 services.remove_connection(self.merchant1_id, queue1) # 检查连接 merchant1_connections = services.get_merchant_connections(self.merchant1_id) self.assertEqual(len(merchant1_connections), 1) self.assertNotIn(queue1, merchant1_connections) self.assertIn(queue2, merchant1_connections) def test_remove_all_merchant_connections(self): """测试移除商户所有连接""" queue1 = Queue() queue2 = Queue() # 添加连接 services.push_connection(self.merchant1_id, queue1) services.push_connection(self.merchant1_id, queue2) # 移除所有连接 services.remove_connection(self.merchant1_id, queue1) services.remove_connection(self.merchant1_id, queue2) # 检查连接 merchant1_connections = services.get_merchant_connections(self.merchant1_id) self.assertEqual(len(merchant1_connections), 0) # 商户记录应该被移除 self.assertNotIn(self.merchant1_id, services._connections) def test_get_all_connections_count(self): """测试获取所有连接数""" # 添加连接 services.push_connection(self.merchant1_id, Queue()) services.push_connection(self.merchant1_id, Queue()) services.push_connection(self.merchant2_id, Queue()) # 检查总连接数 total = services.get_all_connections_count() self.assertEqual(total, 3) def test_push_event_to_merchant(self): """测试向特定商户推送事件""" queue1 = Queue() queue2 = Queue() queue3 = Queue() # 添加连接 services.push_connection(self.merchant1_id, queue1) services.push_connection(self.merchant1_id, queue2) services.push_connection(self.merchant2_id, queue3) # 向商户1推送事件 event_data = {'type': 'test', 'message': 'test message'} services.push_sse_event_to_merchant(self.merchant1_id, event_data) # 检查消息 self.assertEqual(queue1.qsize(), 1) self.assertEqual(queue2.qsize(), 1) self.assertEqual(queue3.qsize(), 0) # 商户2不应该收到消息 # 检查消息内容 self.assertEqual(queue1.get_nowait(), event_data) self.assertEqual(queue2.get_nowait(), event_data) def test_push_event_to_nonexistent_merchant(self): """测试向不存在的商户推送事件""" # 向不存在的商户推送事件 event_data = {'type': 'test', 'message': 'test message'} services.push_sse_event_to_merchant(999, event_data) # 不应该抛出异常,也不会有连接受到影响 self.assertEqual(len(services._connections), 0) def test_push_simple_message_to_merchant(self): """测试向特定商户推送简单消息""" queue1 = Queue() # 添加连接 services.push_connection(self.merchant1_id, queue1) # 推送简单消息 services.push_simple_message_with_object_id_to_merchant( self.merchant1_id, 'order_paid', '订单已支付', 12345 ) # 检查消息 self.assertEqual(queue1.qsize(), 1) # 检查消息内容 message = queue1.get_nowait() self.assertEqual(message['mode'], 'simple_message') self.assertEqual(message['type'], 'order_paid') self.assertEqual(message['message'], '订单已支付') self.assertEqual(message['object_id'], 12345) self.assertEqual(message['merchant_id'], self.merchant1_id)