feat: Add AI report generation with DIFY integration

- Add Users table for display name resolution from AD authentication
- Integrate DIFY AI service for report content generation
- Create docx assembly service with image embedding from MinIO
- Add REST API endpoints for report generation and download
- Add WebSocket notifications for generation progress
- Add frontend UI with progress modal and download functionality
- Add integration tests for report generation flow

Report sections (Traditional Chinese):
- 事件摘要 (Summary)
- 時間軸 (Timeline)
- 參與人員 (Participants)
- 處理過程 (Resolution Process)
- 目前狀態 (Current Status)
- 最終處置結果 (Final Resolution)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
egg
2025-12-04 18:32:40 +08:00
parent 77091eefb5
commit 3927441103
32 changed files with 4374 additions and 8 deletions

164
tests/test_user_service.py Normal file
View File

@@ -0,0 +1,164 @@
"""Unit tests for user service
Tests for the users table and upsert operations used in report generation.
"""
import pytest
from datetime import datetime, timedelta
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.core.database import Base
from app.modules.auth.models import User
from app.modules.auth.services.user_service import upsert_user, get_user_by_id, get_display_name
# Create in-memory SQLite database for testing
@pytest.fixture
def db_session():
"""Create a fresh database session for each test"""
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(bind=engine)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
session = SessionLocal()
try:
yield session
finally:
session.close()
class TestUpsertUser:
"""Tests for upsert_user function"""
def test_create_new_user(self, db_session):
"""Test creating a new user record"""
user = upsert_user(
db=db_session,
user_id="test@example.com",
display_name="Test User 測試用戶",
office_location="Taipei",
job_title="Engineer",
)
assert user.user_id == "test@example.com"
assert user.display_name == "Test User 測試用戶"
assert user.office_location == "Taipei"
assert user.job_title == "Engineer"
assert user.last_login_at is not None
assert user.created_at is not None
def test_update_existing_user(self, db_session):
"""Test updating an existing user record"""
# Create initial user
user1 = upsert_user(
db=db_session,
user_id="test@example.com",
display_name="Original Name",
office_location="Taipei",
job_title="Junior Engineer",
)
original_created_at = user1.created_at
original_last_login = user1.last_login_at
# Wait a tiny bit to ensure timestamp difference
import time
time.sleep(0.01)
# Update same user
user2 = upsert_user(
db=db_session,
user_id="test@example.com",
display_name="Updated Name 更新名稱",
office_location="Kaohsiung",
job_title="Senior Engineer",
)
# Verify update
assert user2.user_id == "test@example.com"
assert user2.display_name == "Updated Name 更新名稱"
assert user2.office_location == "Kaohsiung"
assert user2.job_title == "Senior Engineer"
# created_at should be preserved
assert user2.created_at == original_created_at
# last_login_at should be updated
assert user2.last_login_at >= original_last_login
def test_upsert_with_null_optional_fields(self, db_session):
"""Test upsert with null office_location and job_title"""
user = upsert_user(
db=db_session,
user_id="test@example.com",
display_name="Test User",
office_location=None,
job_title=None,
)
assert user.office_location is None
assert user.job_title is None
def test_update_clears_optional_fields(self, db_session):
"""Test that updating with None clears optional fields"""
# Create with values
upsert_user(
db=db_session,
user_id="test@example.com",
display_name="Test User",
office_location="Taipei",
job_title="Engineer",
)
# Update with None
user = upsert_user(
db=db_session,
user_id="test@example.com",
display_name="Test User",
office_location=None,
job_title=None,
)
assert user.office_location is None
assert user.job_title is None
class TestGetUserById:
"""Tests for get_user_by_id function"""
def test_get_existing_user(self, db_session):
"""Test getting an existing user"""
upsert_user(
db=db_session,
user_id="test@example.com",
display_name="Test User",
)
user = get_user_by_id(db_session, "test@example.com")
assert user is not None
assert user.display_name == "Test User"
def test_get_nonexistent_user(self, db_session):
"""Test getting a user that doesn't exist"""
user = get_user_by_id(db_session, "nonexistent@example.com")
assert user is None
class TestGetDisplayName:
"""Tests for get_display_name function"""
def test_get_display_name_existing_user(self, db_session):
"""Test getting display name for existing user"""
upsert_user(
db=db_session,
user_id="test@example.com",
display_name="Test User 測試用戶",
)
name = get_display_name(db_session, "test@example.com")
assert name == "Test User 測試用戶"
def test_get_display_name_nonexistent_user(self, db_session):
"""Test fallback to email for nonexistent user"""
name = get_display_name(db_session, "unknown@example.com")
# Should return email as fallback
assert name == "unknown@example.com"