feat: Migrate to MySQL and add unified environment configuration
## Database Migration (SQLite → MySQL) - Add Alembic migration framework - Add 'tr_' prefix to all tables to avoid conflicts in shared database - Remove SQLite support, use MySQL exclusively - Add pymysql driver dependency - Change ad_token column to Text type for long JWT tokens ## Unified Environment Configuration - Centralize all hardcoded settings to environment variables - Backend: Extend Settings class in app/core/config.py - Frontend: Use Vite environment variables (import.meta.env) - Docker: Move credentials to environment variables - Update .env.example files with comprehensive documentation ## Test Organization - Move root-level test files to tests/ directory: - test_chat_room.py → tests/test_chat_room.py - test_websocket.py → tests/test_websocket.py - test_realtime_implementation.py → tests/test_realtime_implementation.py - Fix path references in test_realtime_implementation.py Breaking Changes: - CORS now requires explicit origins (no more wildcard) - All database tables renamed with 'tr_' prefix - SQLite no longer supported 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
169
tests/test_chat_room.py
Normal file
169
tests/test_chat_room.py
Normal file
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test script for chat room management API"""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.database import Base
|
||||
from app.modules.auth.models import UserSession
|
||||
from app.modules.chat_room.models import IncidentRoom, RoomMember, RoomTemplate, RoomStatus, MemberRole
|
||||
from app.modules.chat_room.services.room_service import room_service
|
||||
from app.modules.chat_room.services.membership_service import membership_service
|
||||
from app.modules.chat_room.services.template_service import template_service
|
||||
from app.modules.chat_room.schemas import CreateRoomRequest, IncidentType, SeverityLevel
|
||||
|
||||
# Setup test database
|
||||
engine = create_engine("sqlite:///./test_chat_room.db")
|
||||
Base.metadata.create_all(bind=engine)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
def test_room_management():
|
||||
"""Test the core room management functionality"""
|
||||
db = SessionLocal()
|
||||
|
||||
print("=" * 60)
|
||||
print("Testing Chat Room Management System")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# Test users
|
||||
admin_email = "ymirliu@panjit.com.tw"
|
||||
user1_email = "john.doe@panjit.com.tw"
|
||||
user2_email = "jane.smith@panjit.com.tw"
|
||||
|
||||
# 1. Test admin check
|
||||
print("\n1. Testing admin identification:")
|
||||
is_admin = membership_service.is_system_admin(admin_email)
|
||||
print(f" - Is {admin_email} admin? {is_admin}")
|
||||
is_admin = membership_service.is_system_admin(user1_email)
|
||||
print(f" - Is {user1_email} admin? {is_admin}")
|
||||
|
||||
# 2. Create a room
|
||||
print("\n2. Creating a new incident room:")
|
||||
room_data = CreateRoomRequest(
|
||||
title="Equipment Failure in Line A",
|
||||
incident_type=IncidentType.EQUIPMENT_FAILURE,
|
||||
severity=SeverityLevel.HIGH,
|
||||
location="Production Floor A",
|
||||
description="Main conveyor belt stopped working"
|
||||
)
|
||||
room = room_service.create_room(db, user1_email, room_data)
|
||||
print(f" - Room created: {room.title}")
|
||||
print(f" - Room ID: {room.room_id}")
|
||||
print(f" - Created by: {room.created_by}")
|
||||
print(f" - Status: {room.status}")
|
||||
|
||||
# 3. Check initial membership
|
||||
print("\n3. Checking initial membership:")
|
||||
role = membership_service.get_user_role_in_room(db, room.room_id, user1_email)
|
||||
print(f" - {user1_email} role: {role}")
|
||||
|
||||
# 4. Add members
|
||||
print("\n4. Adding members to the room:")
|
||||
member2 = membership_service.add_member(
|
||||
db, room.room_id, user2_email, MemberRole.EDITOR, user1_email
|
||||
)
|
||||
print(f" - Added {user2_email} as {member2.role}")
|
||||
|
||||
member3 = membership_service.add_member(
|
||||
db, room.room_id, admin_email, MemberRole.VIEWER, user1_email
|
||||
)
|
||||
print(f" - Added {admin_email} as {member3.role}")
|
||||
|
||||
# 5. List room members
|
||||
print("\n5. Listing all room members:")
|
||||
members = membership_service.get_room_members(db, room.room_id)
|
||||
for member in members:
|
||||
print(f" - {member.user_id}: {member.role}")
|
||||
|
||||
# 6. Test ownership transfer
|
||||
print("\n6. Testing ownership transfer:")
|
||||
print(f" - Current owner: {user1_email}")
|
||||
print(f" - Transferring to: {user2_email}")
|
||||
success = membership_service.transfer_ownership(
|
||||
db, room.room_id, user1_email, user2_email
|
||||
)
|
||||
if success:
|
||||
print(" - Transfer successful!")
|
||||
# Check new roles
|
||||
role1 = membership_service.get_user_role_in_room(db, room.room_id, user1_email)
|
||||
role2 = membership_service.get_user_role_in_room(db, room.room_id, user2_email)
|
||||
print(f" - {user1_email} new role: {role1}")
|
||||
print(f" - {user2_email} new role: {role2}")
|
||||
# Check audit fields (refetch room)
|
||||
db.refresh(room)
|
||||
print(f" - Transfer recorded at: {room.ownership_transferred_at}")
|
||||
print(f" - Transfer by: {room.ownership_transferred_by}")
|
||||
|
||||
# 7. Test admin override
|
||||
print("\n7. Testing admin override capabilities:")
|
||||
# Admin can update room even if only a viewer
|
||||
from app.modules.chat_room.schemas import UpdateRoomRequest
|
||||
update_data = UpdateRoomRequest(
|
||||
severity=SeverityLevel.CRITICAL,
|
||||
status=RoomStatus.RESOLVED,
|
||||
resolution_notes="Admin resolved the issue"
|
||||
)
|
||||
# Simulate admin update (in real API, this would be checked in dependencies)
|
||||
is_admin = membership_service.is_system_admin(admin_email)
|
||||
if is_admin:
|
||||
print(f" - Admin {admin_email} updating room (has override)")
|
||||
room = room_service.update_room(db, room.room_id, update_data)
|
||||
print(f" - New severity: {room.severity}")
|
||||
print(f" - New status: {room.status}")
|
||||
print(f" - Resolution notes: {room.resolution_notes}")
|
||||
|
||||
# 8. Test room templates
|
||||
print("\n8. Testing room templates:")
|
||||
templates = template_service.get_templates(db)
|
||||
print(f" - Found {len(templates)} templates")
|
||||
for template in templates:
|
||||
print(f" * {template.name}: {template.incident_type}")
|
||||
|
||||
if templates:
|
||||
# Create room from template
|
||||
print("\n9. Creating room from template:")
|
||||
template = templates[0]
|
||||
room2 = template_service.create_room_from_template(
|
||||
db,
|
||||
template.template_id,
|
||||
user1_email,
|
||||
title="New Equipment Issue",
|
||||
location="Line B",
|
||||
description="Using template for quick setup"
|
||||
)
|
||||
print(f" - Room created from template: {room2.title}")
|
||||
print(f" - Incident type: {room2.incident_type}")
|
||||
print(f" - Severity: {room2.severity}")
|
||||
# Check default members
|
||||
members = membership_service.get_room_members(db, room2.room_id)
|
||||
print(f" - Auto-added {len(members)} members from template")
|
||||
|
||||
# 10. Test user's room listing
|
||||
print("\n10. Testing user's room listing:")
|
||||
from app.modules.chat_room.schemas import RoomFilterParams
|
||||
filters = RoomFilterParams(limit=10, offset=0)
|
||||
|
||||
# User1's rooms
|
||||
rooms, total = room_service.list_user_rooms(db, user1_email, filters, False)
|
||||
print(f" - {user1_email} has access to {total} room(s)")
|
||||
|
||||
# Admin's rooms (with admin flag)
|
||||
rooms, total = room_service.list_user_rooms(db, admin_email, filters, True)
|
||||
print(f" - Admin {admin_email} can see {total} room(s)")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("All tests completed successfully!")
|
||||
print("=" * 60)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError during testing: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_room_management()
|
||||
409
tests/test_realtime_implementation.py
Normal file
409
tests/test_realtime_implementation.py
Normal file
@@ -0,0 +1,409 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Complete test suite for realtime messaging implementation
|
||||
Tests all core functionality against tasks.md requirements
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add project root to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from sqlalchemy import inspect
|
||||
from app.core.database import engine, SessionLocal
|
||||
from app.modules.realtime.models import Message, MessageReaction, MessageEditHistory, MessageType
|
||||
from app.modules.realtime.services.message_service import MessageService
|
||||
from app.modules.chat_room.models import IncidentRoom, RoomMember, MemberRole, IncidentType, SeverityLevel, RoomStatus
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
def print_section(title):
|
||||
"""Print formatted section header"""
|
||||
print("\n" + "=" * 60)
|
||||
print(f" {title}")
|
||||
print("=" * 60)
|
||||
|
||||
def test_database_schema():
|
||||
"""Test 1: Database Schema (tasks.md section 1)"""
|
||||
print_section("測試 1: 資料庫架構")
|
||||
|
||||
inspector = inspect(engine)
|
||||
tables = inspector.get_table_names()
|
||||
|
||||
# 1.1 Check messages table
|
||||
print("\n1.1 檢查 messages 表...")
|
||||
assert "messages" in tables, "❌ messages table not found"
|
||||
|
||||
columns = {col['name']: col for col in inspector.get_columns('messages')}
|
||||
required_columns = ['message_id', 'room_id', 'sender_id', 'content',
|
||||
'message_type', 'message_metadata', 'created_at',
|
||||
'edited_at', 'deleted_at', 'sequence_number']
|
||||
|
||||
for col in required_columns:
|
||||
assert col in columns, f"❌ Column {col} not found"
|
||||
print(f"✓ messages 表包含所有必要欄位: {', '.join(required_columns)}")
|
||||
|
||||
# 1.2 Check message_reactions table
|
||||
print("\n1.2 檢查 message_reactions 表...")
|
||||
assert "message_reactions" in tables, "❌ message_reactions table not found"
|
||||
|
||||
columns = {col['name']: col for col in inspector.get_columns('message_reactions')}
|
||||
required_columns = ['reaction_id', 'message_id', 'user_id', 'emoji', 'created_at']
|
||||
|
||||
for col in required_columns:
|
||||
assert col in columns, f"❌ Column {col} not found"
|
||||
print(f"✓ message_reactions 表包含所有必要欄位: {', '.join(required_columns)}")
|
||||
|
||||
# 1.3 Check message_edit_history table
|
||||
print("\n1.3 檢查 message_edit_history 表...")
|
||||
assert "message_edit_history" in tables, "❌ message_edit_history table not found"
|
||||
|
||||
columns = {col['name']: col for col in inspector.get_columns('message_edit_history')}
|
||||
required_columns = ['edit_id', 'message_id', 'original_content', 'edited_by', 'edited_at']
|
||||
|
||||
for col in required_columns:
|
||||
assert col in columns, f"❌ Column {col} not found"
|
||||
print(f"✓ message_edit_history 表包含所有必要欄位: {', '.join(required_columns)}")
|
||||
|
||||
# 1.4 Check indexes
|
||||
print("\n1.4 檢查索引...")
|
||||
indexes = inspector.get_indexes('messages')
|
||||
index_names = [idx['name'] for idx in indexes]
|
||||
|
||||
required_indexes = ['ix_messages_room_created', 'ix_messages_room_sequence', 'ix_messages_sender']
|
||||
for idx in required_indexes:
|
||||
assert idx in index_names, f"❌ Index {idx} not found"
|
||||
print(f"✓ 所有必要索引已建立: {', '.join(required_indexes)}")
|
||||
|
||||
print("\n✅ 資料庫架構測試通過!")
|
||||
return True
|
||||
|
||||
def test_message_models():
|
||||
"""Test 3.1: Message Models and Schemas"""
|
||||
print_section("測試 3.1: 訊息模型與 Schema")
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# Create test room first
|
||||
test_room = IncidentRoom(
|
||||
room_id=str(uuid.uuid4()),
|
||||
title="Test Room for Messages",
|
||||
incident_type=IncidentType.EQUIPMENT_FAILURE,
|
||||
severity=SeverityLevel.MEDIUM,
|
||||
status=RoomStatus.ACTIVE,
|
||||
created_by="test@example.com"
|
||||
)
|
||||
db.add(test_room)
|
||||
db.commit()
|
||||
|
||||
# 3.1.1 Test Message model (via service)
|
||||
print("\n3.1.1 測試 Message 模型...")
|
||||
message = MessageService.create_message(
|
||||
db=db,
|
||||
room_id=test_room.room_id,
|
||||
sender_id="test@example.com",
|
||||
content="Test message",
|
||||
message_type=MessageType.TEXT,
|
||||
metadata={"test": "data"}
|
||||
)
|
||||
print(f"✓ Message 模型建立成功: {message.message_id}")
|
||||
|
||||
# Test all message types (via service)
|
||||
print("\n3.1.3 測試訊息類型枚舉...")
|
||||
message_types = [MessageType.TEXT, MessageType.IMAGE_REF, MessageType.FILE_REF,
|
||||
MessageType.SYSTEM, MessageType.INCIDENT_DATA]
|
||||
for msg_type in message_types:
|
||||
MessageService.create_message(
|
||||
db=db,
|
||||
room_id=test_room.room_id,
|
||||
sender_id="test@example.com",
|
||||
content=f"Test {msg_type.value}",
|
||||
message_type=msg_type
|
||||
)
|
||||
print(f"✓ 所有訊息類型支援: {[t.value for t in message_types]}")
|
||||
|
||||
# 3.1.1 Test MessageReaction model
|
||||
print("\n3.1.1 測試 MessageReaction 模型...")
|
||||
reaction = MessageReaction(
|
||||
message_id=message.message_id,
|
||||
user_id="test@example.com",
|
||||
emoji="👍"
|
||||
)
|
||||
db.add(reaction)
|
||||
db.commit()
|
||||
print(f"✓ MessageReaction 模型建立成功: {reaction.emoji}")
|
||||
|
||||
# 3.1.1 Test MessageEditHistory model
|
||||
print("\n3.1.1 測試 MessageEditHistory 模型...")
|
||||
edit_history = MessageEditHistory(
|
||||
message_id=message.message_id,
|
||||
original_content="Original content",
|
||||
edited_by="test@example.com"
|
||||
)
|
||||
db.add(edit_history)
|
||||
db.commit()
|
||||
print(f"✓ MessageEditHistory 模型建立成功")
|
||||
|
||||
print("\n✅ 訊息模型測試通過!")
|
||||
return True
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
db.query(MessageEditHistory).delete()
|
||||
db.query(MessageReaction).delete()
|
||||
db.query(Message).delete()
|
||||
db.query(IncidentRoom).filter(IncidentRoom.room_id == test_room.room_id).delete()
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
def test_message_service():
|
||||
"""Test 4: Message Service Layer"""
|
||||
print_section("測試 4: 訊息服務層")
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# Create test room and member
|
||||
test_room = IncidentRoom(
|
||||
room_id=str(uuid.uuid4()),
|
||||
title="Test Room for Service",
|
||||
incident_type=IncidentType.QUALITY_ISSUE,
|
||||
severity=SeverityLevel.HIGH,
|
||||
status=RoomStatus.ACTIVE,
|
||||
created_by="test@example.com"
|
||||
)
|
||||
db.add(test_room)
|
||||
db.commit()
|
||||
|
||||
# 4.1.1 Test create_message
|
||||
print("\n4.1.1 測試 create_message...")
|
||||
message = MessageService.create_message(
|
||||
db=db,
|
||||
room_id=test_room.room_id,
|
||||
sender_id="test@example.com",
|
||||
content="Test message from service",
|
||||
message_type=MessageType.TEXT,
|
||||
metadata={"mentions": ["user1@example.com"]}
|
||||
)
|
||||
assert message.message_id is not None
|
||||
assert message.sequence_number > 0
|
||||
print(f"✓ 訊息建立成功: ID={message.message_id}, Seq={message.sequence_number}")
|
||||
|
||||
# 4.1.1 Test get_messages with pagination
|
||||
print("\n4.1.1 測試 get_messages (分頁)...")
|
||||
# Create more messages
|
||||
for i in range(5):
|
||||
MessageService.create_message(
|
||||
db=db,
|
||||
room_id=test_room.room_id,
|
||||
sender_id="test@example.com",
|
||||
content=f"Message {i}"
|
||||
)
|
||||
|
||||
result = MessageService.get_messages(db=db, room_id=test_room.room_id, limit=3)
|
||||
assert result.total == 6 # 1 original + 5 new
|
||||
assert len(result.messages) == 3
|
||||
assert result.has_more == True
|
||||
print(f"✓ 分頁查詢成功: total={result.total}, returned={len(result.messages)}, has_more={result.has_more}")
|
||||
|
||||
# 4.1.1 Test edit_message
|
||||
print("\n4.1.1 測試 edit_message...")
|
||||
edited = MessageService.edit_message(
|
||||
db=db,
|
||||
message_id=message.message_id,
|
||||
user_id="test@example.com",
|
||||
new_content="Edited content"
|
||||
)
|
||||
assert edited is not None
|
||||
assert edited.content == "Edited content"
|
||||
assert edited.edited_at is not None
|
||||
print(f"✓ 訊息編輯成功: edited_at={edited.edited_at}")
|
||||
|
||||
# Check edit history was created
|
||||
edit_history = db.query(MessageEditHistory).filter(
|
||||
MessageEditHistory.message_id == message.message_id
|
||||
).first()
|
||||
assert edit_history is not None
|
||||
assert edit_history.original_content == "Test message from service"
|
||||
print(f"✓ 編輯歷史已記錄: original_content={edit_history.original_content}")
|
||||
|
||||
# 4.1.1 Test delete_message (soft delete)
|
||||
print("\n4.1.1 測試 delete_message (軟刪除)...")
|
||||
deleted = MessageService.delete_message(
|
||||
db=db,
|
||||
message_id=message.message_id,
|
||||
user_id="test@example.com"
|
||||
)
|
||||
assert deleted is not None
|
||||
assert deleted.deleted_at is not None
|
||||
print(f"✓ 訊息軟刪除成功: deleted_at={deleted.deleted_at}")
|
||||
|
||||
# Verify soft delete (should not appear in get_messages by default)
|
||||
result = MessageService.get_messages(db=db, room_id=test_room.room_id, include_deleted=False)
|
||||
deleted_messages = [m for m in result.messages if m.message_id == message.message_id]
|
||||
assert len(deleted_messages) == 0
|
||||
print(f"✓ 軟刪除訊息不出現在查詢中")
|
||||
|
||||
# 4.2.1 Test add_reaction
|
||||
print("\n4.2.1 測試 add_reaction...")
|
||||
msg2 = MessageService.create_message(
|
||||
db=db,
|
||||
room_id=test_room.room_id,
|
||||
sender_id="test@example.com",
|
||||
content="Message for reactions"
|
||||
)
|
||||
|
||||
reaction = MessageService.add_reaction(
|
||||
db=db,
|
||||
message_id=msg2.message_id,
|
||||
user_id="user1@example.com",
|
||||
emoji="👍"
|
||||
)
|
||||
assert reaction is not None
|
||||
print(f"✓ 反應新增成功: {reaction.emoji}")
|
||||
|
||||
# 4.2.1 Test get_message_reactions
|
||||
print("\n4.2.1 測試 get_message_reactions...")
|
||||
MessageService.add_reaction(db=db, message_id=msg2.message_id, user_id="user2@example.com", emoji="👍")
|
||||
MessageService.add_reaction(db=db, message_id=msg2.message_id, user_id="user3@example.com", emoji="❤️")
|
||||
|
||||
reactions = MessageService.get_message_reactions(db=db, message_id=msg2.message_id)
|
||||
assert len(reactions) == 2 # 👍 and ❤️
|
||||
thumbs_up = [r for r in reactions if r.emoji == "👍"][0]
|
||||
assert thumbs_up.count == 2
|
||||
print(f"✓ 反應統計成功: 👍={thumbs_up.count}, ❤️={len([r for r in reactions if r.emoji == '❤️'])}")
|
||||
|
||||
# 4.1.1 Test search_messages
|
||||
print("\n4.1.1 測試 search_messages...")
|
||||
MessageService.create_message(db=db, room_id=test_room.room_id, sender_id="test@example.com",
|
||||
content="Equipment failure detected")
|
||||
result = MessageService.search_messages(db=db, room_id=test_room.room_id, query="Equipment")
|
||||
assert result.total >= 1
|
||||
print(f"✓ 訊息搜尋成功: found {result.total} messages")
|
||||
|
||||
print("\n✅ 訊息服務層測試通過!")
|
||||
return True
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
db.query(MessageEditHistory).delete()
|
||||
db.query(MessageReaction).delete()
|
||||
db.query(Message).delete()
|
||||
db.query(IncidentRoom).filter(IncidentRoom.room_id == test_room.room_id).delete()
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
def test_websocket_manager():
|
||||
"""Test 2: WebSocket Infrastructure"""
|
||||
print_section("測試 2: WebSocket 基礎架構")
|
||||
|
||||
from app.modules.realtime.websocket_manager import WebSocketManager
|
||||
|
||||
manager = WebSocketManager()
|
||||
|
||||
# 2.1.2 Test manager methods exist
|
||||
print("\n2.1.2 檢查 WebSocketManager 方法...")
|
||||
required_methods = ['connect', 'disconnect', 'broadcast_to_room',
|
||||
'send_personal', 'get_room_connections', 'get_online_users',
|
||||
'is_user_online', 'set_typing', 'get_typing_users']
|
||||
|
||||
for method in required_methods:
|
||||
assert hasattr(manager, method), f"❌ Method {method} not found"
|
||||
print(f"✓ 所有必要方法已實作: {', '.join(required_methods)}")
|
||||
|
||||
# Test data structures
|
||||
print("\n2.1.2 檢查內部資料結構...")
|
||||
assert hasattr(manager, '_room_connections')
|
||||
assert hasattr(manager, '_user_connections')
|
||||
assert hasattr(manager, '_typing_users')
|
||||
print(f"✓ 連線池資料結構已初始化")
|
||||
|
||||
print("\n✅ WebSocket 基礎架構測試通過!")
|
||||
return True
|
||||
|
||||
def test_module_structure():
|
||||
"""Test 2.1.1: Module Structure"""
|
||||
print_section("測試 2.1.1: 模組結構")
|
||||
|
||||
import os
|
||||
base_path = "app/modules/realtime"
|
||||
|
||||
required_files = [
|
||||
'__init__.py',
|
||||
'websocket_manager.py',
|
||||
'models.py',
|
||||
'schemas.py',
|
||||
'router.py',
|
||||
'services/message_service.py'
|
||||
]
|
||||
|
||||
for file in required_files:
|
||||
file_path = os.path.join(base_path, file)
|
||||
assert os.path.exists(file_path), f"❌ File {file_path} not found"
|
||||
|
||||
print(f"✓ 所有必要檔案已建立: {', '.join(required_files)}")
|
||||
|
||||
# Test imports
|
||||
print("\n測試模組匯入...")
|
||||
from app.modules.realtime import Message, MessageReaction, MessageEditHistory, MessageType, router
|
||||
from app.modules.realtime.websocket_manager import manager
|
||||
from app.modules.realtime.services.message_service import MessageService
|
||||
from app.modules.realtime import schemas
|
||||
|
||||
print("✓ 所有模組成功匯入")
|
||||
|
||||
print("\n✅ 模組結構測試通過!")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Run all tests"""
|
||||
print("\n" + "=" * 60)
|
||||
print(" 即時訊息功能完整測試")
|
||||
print(" Testing Realtime Messaging Implementation")
|
||||
print("=" * 60)
|
||||
|
||||
tests = [
|
||||
("模組結構", test_module_structure),
|
||||
("資料庫架構", test_database_schema),
|
||||
("WebSocket 基礎架構", test_websocket_manager),
|
||||
("訊息模型", test_message_models),
|
||||
("訊息服務層", test_message_service),
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
for name, test_func in tests:
|
||||
try:
|
||||
test_func()
|
||||
passed += 1
|
||||
except AssertionError as e:
|
||||
print(f"\n❌ {name} 測試失敗: {e}")
|
||||
failed += 1
|
||||
except Exception as e:
|
||||
print(f"\n❌ {name} 測試錯誤: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
failed += 1
|
||||
|
||||
# Final summary
|
||||
print("\n" + "=" * 60)
|
||||
print(" 測試總結 (Test Summary)")
|
||||
print("=" * 60)
|
||||
print(f"通過: {passed}/{len(tests)}")
|
||||
print(f"失敗: {failed}/{len(tests)}")
|
||||
|
||||
if failed == 0:
|
||||
print("\n🎉 所有測試通過!")
|
||||
print("\n已完成核心功能:")
|
||||
print(" ✓ 資料庫架構 (Section 1)")
|
||||
print(" ✓ WebSocket 基礎架構 (Section 2)")
|
||||
print(" ✓ 訊息處理 (Section 3)")
|
||||
print(" ✓ 訊息服務層 (Section 4)")
|
||||
print(" ✓ REST API 端點 (Section 5)")
|
||||
return 0
|
||||
else:
|
||||
print(f"\n⚠️ 有 {failed} 個測試失敗")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
98
tests/test_websocket.py
Executable file
98
tests/test_websocket.py
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test WebSocket realtime messaging functionality"""
|
||||
import asyncio
|
||||
import websockets
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
async def test_websocket_connection():
|
||||
"""Test basic WebSocket connection and message sending"""
|
||||
# Note: This is a simplified test. In production, you'd need proper authentication
|
||||
uri = "ws://localhost:8000/api/ws/test-room-123?token=test-user@example.com"
|
||||
|
||||
print("Connecting to WebSocket...")
|
||||
try:
|
||||
async with websockets.connect(uri) as websocket:
|
||||
print("✓ Connected to WebSocket!")
|
||||
|
||||
# Send a text message
|
||||
message = {
|
||||
"type": "message",
|
||||
"content": "Hello from WebSocket test!",
|
||||
"message_type": "text"
|
||||
}
|
||||
|
||||
print(f"\nSending message: {message}")
|
||||
await websocket.send(json.dumps(message))
|
||||
|
||||
# Wait for acknowledgment
|
||||
response = await asyncio.wait_for(websocket.recv(), timeout=5)
|
||||
response_data = json.loads(response)
|
||||
print(f"✓ Received response: {response_data}")
|
||||
|
||||
if response_data.get("type") == "ack":
|
||||
print(f" Message ID: {response_data.get('message_id')}")
|
||||
print(f" Sequence: {response_data.get('sequence_number')}")
|
||||
|
||||
except websockets.exceptions.InvalidStatusCode as e:
|
||||
if e.status_code == 4001:
|
||||
print("✗ Connection rejected: Not a member of room")
|
||||
print(" This is expected for test room without proper membership")
|
||||
else:
|
||||
print(f"✗ Connection failed with status {e.status_code}")
|
||||
except Exception as e:
|
||||
print(f"✗ Error: {e}")
|
||||
|
||||
|
||||
async def test_rest_api():
|
||||
"""Test REST API endpoints"""
|
||||
import aiohttp
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("Testing REST API Endpoints")
|
||||
print("=" * 50)
|
||||
|
||||
# Note: This requires authentication in production
|
||||
# For now, just test if endpoints are registered
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# Test health check
|
||||
async with session.get("http://localhost:8000/health") as resp:
|
||||
if resp.status == 200:
|
||||
print("✓ Health check endpoint working")
|
||||
else:
|
||||
print(f"✗ Health check failed: {resp.status}")
|
||||
|
||||
# Test API docs
|
||||
async with session.get("http://localhost:8000/docs") as resp:
|
||||
if resp.status == 200:
|
||||
print("✓ API documentation accessible")
|
||||
else:
|
||||
print(f"✗ API docs failed: {resp.status}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 50)
|
||||
print("WebSocket Realtime Messaging Test")
|
||||
print("=" * 50)
|
||||
|
||||
# Run REST API tests
|
||||
asyncio.run(test_rest_api())
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("Testing WebSocket Connection")
|
||||
print("=" * 50)
|
||||
|
||||
# Run WebSocket test
|
||||
asyncio.run(test_websocket_connection())
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("Test Summary:")
|
||||
print("- Database tables created ✓")
|
||||
print("- Models and schemas defined ✓")
|
||||
print("- WebSocket manager implemented ✓")
|
||||
print("- Message service layer created ✓")
|
||||
print("- REST API endpoints registered ✓")
|
||||
print("- Server starts successfully ✓")
|
||||
print("=" * 50)
|
||||
Reference in New Issue
Block a user