Skip to content

Commit 776a219

Browse files
committed
Fix some tests and add more
1 parent e732cae commit 776a219

File tree

5 files changed

+273
-27
lines changed

5 files changed

+273
-27
lines changed

tests/test_api.py

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
# Corrected import for FakeAsyncRedis based on common usage and docs
1111
from fakeredis import FakeAsyncRedis
12-
from fastapi import Response
1312
from fastapi.testclient import TestClient
1413
from freezegun import freeze_time # For controlling time in tests
1514

@@ -540,17 +539,16 @@ def assert_suggestions_error_response(response, expected_status=422):
540539

541540
@patch.dict(os.environ, {"SUGGESTIONS_API_KEY": "valid-suggestions-key"})
542541
@patch("mxgo.auth.JWT_SECRET", "test_secret_key_for_development_only")
543-
@patch("mxgo.api.validate_email_whitelist", new_callable=AsyncMock)
544542
@patch("mxgo.api.generate_suggestions", new_callable=AsyncMock)
545-
def test_suggestions_api_success_single_request(mock_generate_suggestions, mock_validate_whitelist):
543+
def test_suggestions_api_success_single_request(mock_generate_suggestions):
546544
"""Test successful suggestions API call with single request."""
547-
mock_validate_whitelist.return_value = None # User is whitelisted
548545

549546
# Mock the suggestions response
550547

551548
mock_response = EmailSuggestionResponse(
552549
email_identified="test-email-123",
553550
user_email_id="test@example.com",
551+
overview="Test email requesting content summarization",
554552
suggestions=[
555553
SuggestionDetail(
556554
suggestion_title="Summarize content",
@@ -574,7 +572,6 @@ def test_suggestions_api_success_single_request(mock_generate_suggestions, mock_
574572
response = make_suggestions_post_request(request_data)
575573

576574
assert_suggestions_successful_response(response, expected_num_requests=1)
577-
mock_validate_whitelist.assert_called_once()
578575
mock_generate_suggestions.assert_called_once()
579576

580577
# Verify the response content
@@ -587,17 +584,16 @@ def test_suggestions_api_success_single_request(mock_generate_suggestions, mock_
587584

588585
@patch.dict(os.environ, {"SUGGESTIONS_API_KEY": "valid-suggestions-key"})
589586
@patch("mxgo.auth.JWT_SECRET", "test_secret_key_for_development_only")
590-
@patch("mxgo.api.validate_email_whitelist", new_callable=AsyncMock)
591587
@patch("mxgo.api.generate_suggestions", new_callable=AsyncMock)
592-
def test_suggestions_api_success_multiple_requests(mock_generate_suggestions, mock_validate_whitelist):
588+
def test_suggestions_api_success_multiple_requests(mock_generate_suggestions):
593589
"""Test successful suggestions API call with multiple requests."""
594-
mock_validate_whitelist.return_value = None
595590

596591
# Mock responses for each request
597592
mock_responses = [
598593
EmailSuggestionResponse(
599594
email_identified=f"test-email-{i}",
600595
user_email_id="test@example.com",
596+
overview=f"Test email {i} overview",
601597
suggestions=[
602598
SuggestionDetail(
603599
suggestion_title="Ask anything",
@@ -629,7 +625,6 @@ def test_suggestions_api_success_multiple_requests(mock_generate_suggestions, mo
629625
response = make_suggestions_post_request(request_data)
630626

631627
assert_suggestions_successful_response(response, expected_num_requests=3)
632-
assert mock_validate_whitelist.call_count == 3
633628
assert mock_generate_suggestions.call_count == 3
634629

635630

@@ -655,22 +650,34 @@ def test_suggestions_api_invalid_jwt_token():
655650

656651
@patch.dict(os.environ, {"SUGGESTIONS_API_KEY": "valid-suggestions-key"})
657652
@patch("mxgo.auth.JWT_SECRET", "test_secret_key_for_development_only")
658-
@patch("mxgo.api.validate_email_whitelist", new_callable=AsyncMock)
659-
def test_suggestions_api_user_not_whitelisted(mock_validate_whitelist):
660-
"""Test suggestions API when user is not whitelisted."""
661-
# Mock whitelist validation to return an error response
662-
mock_validate_whitelist.return_value = Response(
663-
status_code=403, content=json.dumps({"detail": "User not whitelisted"})
653+
@patch("mxgo.api.generate_suggestions", new_callable=AsyncMock)
654+
def test_suggestions_api_user_not_whitelisted(mock_generate_suggestions):
655+
"""Test suggestions API processes normally since whitelist check was removed."""
656+
# Mock successful suggestions generation
657+
mock_response = EmailSuggestionResponse(
658+
email_identified="test-email-123",
659+
user_email_id="test@example.com",
660+
overview="Test email processed without whitelist check",
661+
suggestions=[
662+
SuggestionDetail(
663+
suggestion_title="Ask anything",
664+
suggestion_id="suggest-1",
665+
suggestion_to_email="ask@mxgo.ai",
666+
suggestion_cc_emails=[],
667+
suggestion_email_instructions="",
668+
),
669+
],
664670
)
671+
mock_generate_suggestions.return_value = mock_response
665672

666673
request_data = prepare_suggestions_request_data()
667674
response = make_suggestions_post_request(request_data)
668675

669-
# Should return 403 Forbidden instead of 200 with error suggestions
670-
assert response.status_code == 403, "Should return 403 Forbidden for non-whitelisted user"
676+
# Should return 200 OK since whitelist validation was removed
677+
assert response.status_code == 200, "Should return 200 OK since whitelist check removed"
671678
response_json = response.json()
672-
assert "detail" in response_json
673-
assert "Email verification required" in response_json["detail"]
679+
assert len(response_json) == 1
680+
assert response_json[0]["email_identified"] == "test-email-123"
674681

675682

676683
@patch.dict(os.environ, {"SUGGESTIONS_API_KEY": "valid-suggestions-key"})
@@ -728,6 +735,7 @@ def test_suggestions_api_with_attachments(mock_generate_suggestions, mock_valida
728735
mock_response = EmailSuggestionResponse(
729736
email_identified="test-email-123",
730737
user_email_id="test@example.com",
738+
overview="Email with PDF and Excel attachments requiring document analysis",
731739
suggestions=[
732740
SuggestionDetail(
733741
suggestion_title="Summarize documents",
@@ -778,6 +786,7 @@ def test_suggestions_api_with_cc_emails(mock_generate_suggestions, mock_validate
778786
mock_response = EmailSuggestionResponse(
779787
email_identified="test-email-123",
780788
user_email_id="test@example.com",
789+
overview="Meeting request with CC recipients",
781790
suggestions=[
782791
SuggestionDetail(
783792
suggestion_title="Schedule meeting",
@@ -824,6 +833,7 @@ def test_suggestions_api_rate_limiting_integration(client_with_patched_redis):
824833
mock_response = EmailSuggestionResponse(
825834
email_identified="test-email-123",
826835
user_email_id="test@example.com",
836+
overview="Test email for rate limiting integration",
827837
suggestions=[
828838
SuggestionDetail(
829839
suggestion_title="Ask anything",
@@ -851,6 +861,7 @@ def test_suggestions_api_subject_field_alias(mock_generate_suggestions, mock_val
851861
mock_response = EmailSuggestionResponse(
852862
email_identified="test-email-123",
853863
user_email_id="test@example.com",
864+
overview="Test email with Subject field alias",
854865
suggestions=[
855866
SuggestionDetail(
856867
suggestion_title="Ask anything",
@@ -886,6 +897,7 @@ def test_suggestions_api_default_suggestions_always_included(mock_generate_sugge
886897
mock_response = EmailSuggestionResponse(
887898
email_identified="test-email-123",
888899
user_email_id="test@example.com",
900+
overview="Email with statistical claims requiring fact verification",
889901
suggestions=[
890902
SuggestionDetail(
891903
suggestion_title="Fact check claims",

tests/test_api_integration.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -260,11 +260,25 @@ def mock_suggestions_model(self):
260260
@pytest.fixture
261261
def mock_generate_suggestions(self):
262262
"""Mock generate_suggestions function."""
263+
from mxgo.schemas import EmailSuggestionResponse, SuggestionDetail
264+
263265
with patch("mxgo.api.generate_suggestions") as mock_gen:
264-
mock_gen.return_value = AsyncMock()
265-
mock_gen.return_value.email_identified = "test_email_123"
266-
mock_gen.return_value.user_email_id = "test@example.com"
267-
mock_gen.return_value.suggestions = []
266+
mock_response = EmailSuggestionResponse(
267+
email_identified="test_email_123",
268+
user_email_id="test@example.com",
269+
overview="Test email processed successfully",
270+
suggestions=[
271+
SuggestionDetail(
272+
suggestion_title="Ask anything",
273+
suggestion_id="suggest-1",
274+
suggestion_to_email="ask@mxgo.ai",
275+
suggestion_cc_emails=[],
276+
suggestion_email_instructions="",
277+
)
278+
],
279+
risk_analysis=None,
280+
)
281+
mock_gen.return_value = mock_response
268282
yield mock_gen
269283

270284
@pytest.fixture
@@ -341,18 +355,20 @@ def test_suggestions_invalid_jwt_token(self, client, mock_env_vars, sample_sugge
341355
assert response.status_code == 401
342356

343357
def test_suggestions_user_not_whitelisted(
344-
self, client, mock_env_vars, valid_jwt_token, sample_suggestion_request, mock_suggestions_model
358+
self, client, mock_env_vars, valid_jwt_token, sample_suggestion_request, mock_suggestions_model, mock_generate_suggestions
345359
):
346-
"""Test /suggestions endpoint handles non-whitelisted users."""
360+
"""Test /suggestions endpoint processes normally since whitelist check was removed."""
347361
with patch("mxgo.validators.is_email_whitelisted", return_value=(False, False)):
348362
response = client.post(
349363
"/suggestions",
350364
json=sample_suggestion_request,
351365
headers={"Authorization": f"Bearer {valid_jwt_token}"},
352366
)
353367

354-
assert response.status_code == 403
355-
assert "Email verification required" in response.json()["detail"]
368+
assert response.status_code == 200
369+
response_json = response.json()
370+
assert len(response_json) == 1
371+
assert response_json[0]["email_identified"] == "test_email_123"
356372

357373

358374
class TestBackwardCompatibility:

tests/test_process_email.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
from mxgo.tasks import process_email_task
2525
from mxgo.tools.pdf_export_tool import MAX_FILENAME_LENGTH, PDFExportTool
2626

27+
# Import email_agent to ensure TokenUsage monkey patch is applied
28+
import mxgo.agents.email_agent # noqa: F401
29+
2730
AttachmentFileContent = tuple[str, bytes, str] # (filename, content_bytes, content_type)
2831

2932

tests/test_suggestions.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,3 +429,165 @@ async def test_suggestions_integration_error_handling(prepare_suggestion_request
429429
assert len(response.suggestions) >= 1
430430
has_ask = any(s.suggestion_to_email == "ask@mxgo.ai" for s in response.suggestions)
431431
assert has_ask, "Should return default suggestion even for empty content"
432+
433+
434+
@pytest.mark.asyncio
435+
async def test_risk_analysis_functionality(prepare_suggestion_request_data):
436+
"""Test risk analysis functionality in suggestions."""
437+
# Test suspicious email content
438+
request_data = prepare_suggestion_request_data(
439+
email_identified="risky-email-001",
440+
user_email_id="victim@company.com",
441+
sender_email="attacker@suspicious.com",
442+
subject="URGENT: Verify Your Account Now!",
443+
email_content="""
444+
Dear Customer,
445+
446+
Your account has been compromised. Click here immediately to verify:
447+
https://fake-bank.com/verify-account
448+
449+
You have 24 hours or your account will be closed permanently.
450+
451+
Best regards,
452+
Security Team
453+
""",
454+
attachments=[
455+
{"filename": "invoice.pdf.exe", "file_type": "application/x-executable", "file_size": 2048000}
456+
]
457+
)
458+
459+
request_obj = EmailSuggestionRequest(
460+
email_identified=request_data["email_identified"],
461+
user_email_id=request_data["user_email_id"],
462+
sender_email=request_data["sender_email"],
463+
cc_emails=request_data["cc_emails"],
464+
Subject=request_data["Subject"],
465+
email_content=request_data["email_content"],
466+
attachments=[EmailSuggestionAttachmentSummary(**att) for att in request_data["attachments"]],
467+
)
468+
469+
model = get_suggestions_model()
470+
response = await generate_suggestions(request_obj, model)
471+
472+
# Validate basic response structure
473+
assert response.email_identified == request_data["email_identified"]
474+
assert response.user_email_id == request_data["user_email_id"]
475+
assert response.overview is not None
476+
assert len(response.overview) > 0
477+
assert len(response.suggestions) >= 1
478+
479+
# Validate risk analysis is present
480+
assert response.risk_analysis is not None
481+
assert hasattr(response.risk_analysis, "risk_prob_pct")
482+
assert hasattr(response.risk_analysis, "spam_prob_pct")
483+
assert hasattr(response.risk_analysis, "ai_likelihood_pct")
484+
assert 0 <= response.risk_analysis.risk_prob_pct <= 100
485+
assert 0 <= response.risk_analysis.spam_prob_pct <= 100
486+
assert 0 <= response.risk_analysis.ai_likelihood_pct <= 100
487+
488+
# For suspicious email, expect higher risk scores
489+
# (Note: actual scores depend on LLM, so we just check structure)
490+
assert isinstance(response.risk_analysis.risk_reason, str)
491+
assert isinstance(response.risk_analysis.spam_reason, str)
492+
assert isinstance(response.risk_analysis.ai_explanation, str)
493+
494+
495+
@pytest.mark.asyncio
496+
async def test_risk_analysis_benign_email(prepare_suggestion_request_data):
497+
"""Test risk analysis with benign email content."""
498+
request_data = prepare_suggestion_request_data(
499+
email_identified="safe-email-001",
500+
user_email_id="employee@company.com",
501+
sender_email="colleague@company.com",
502+
subject="Weekly team meeting notes",
503+
email_content="""
504+
Hi team,
505+
506+
Here are the notes from our weekly meeting:
507+
508+
1. Project Alpha is on track for Q4 delivery
509+
2. New team member Sarah will start Monday
510+
3. Budget review scheduled for next Friday
511+
512+
Let me know if you have any questions.
513+
514+
Best,
515+
John
516+
""",
517+
)
518+
519+
request_obj = EmailSuggestionRequest(
520+
email_identified=request_data["email_identified"],
521+
user_email_id=request_data["user_email_id"],
522+
sender_email=request_data["sender_email"],
523+
cc_emails=request_data["cc_emails"],
524+
Subject=request_data["Subject"],
525+
email_content=request_data["email_content"],
526+
attachments=[],
527+
)
528+
529+
model = get_suggestions_model()
530+
response = await generate_suggestions(request_obj, model)
531+
532+
# Validate risk analysis structure
533+
assert response.risk_analysis is not None
534+
assert 0 <= response.risk_analysis.risk_prob_pct <= 100
535+
assert 0 <= response.risk_analysis.spam_prob_pct <= 100
536+
assert 0 <= response.risk_analysis.ai_likelihood_pct <= 100
537+
538+
# All reason fields should be strings (may be empty)
539+
assert isinstance(response.risk_analysis.risk_reason, str)
540+
assert isinstance(response.risk_analysis.spam_reason, str)
541+
assert isinstance(response.risk_analysis.ai_explanation, str)
542+
543+
544+
@pytest.mark.asyncio
545+
async def test_overview_generation(prepare_suggestion_request_data):
546+
"""Test that overview field is properly generated."""
547+
request_data = prepare_suggestion_request_data(
548+
email_identified="overview-test-001",
549+
user_email_id="user@company.com",
550+
sender_email="client@external.com",
551+
subject="Quarterly Business Review Meeting Request",
552+
email_content="""
553+
Dear Team,
554+
555+
I would like to schedule our quarterly business review for next week.
556+
Please let me know your availability for Tuesday or Wednesday afternoon.
557+
558+
We'll need to cover:
559+
- Q3 performance analysis
560+
- Budget planning for Q4
561+
- New client onboarding process
562+
563+
Thanks,
564+
Client
565+
""",
566+
)
567+
568+
request_obj = EmailSuggestionRequest(
569+
email_identified=request_data["email_identified"],
570+
user_email_id=request_data["user_email_id"],
571+
sender_email=request_data["sender_email"],
572+
cc_emails=request_data["cc_emails"],
573+
Subject=request_data["Subject"],
574+
email_content=request_data["email_content"],
575+
attachments=[],
576+
)
577+
578+
model = get_suggestions_model()
579+
response = await generate_suggestions(request_obj, model)
580+
581+
# Overview should be non-empty and meaningful
582+
assert response.overview is not None
583+
assert len(response.overview.strip()) > 0
584+
assert len(response.overview) <= 500 # Should be reasonably concise
585+
586+
# Overview should ideally relate to the subject/content
587+
# (This is a basic check - actual content quality depends on LLM)
588+
overview_lower = response.overview.lower()
589+
request_data["Subject"].lower().split()
590+
591+
# At least some connection to the email content should exist
592+
# (This is a weak test since LLM behavior can vary)
593+
assert len(overview_lower) > 10 # Not just a few words

0 commit comments

Comments
 (0)