Add Himalaya search integration tests and fix 0 results display
- Add test mailbox with 5 sample emails for integration testing - Add himalaya_test_config.toml for local Maildir backend testing - Create 12 integration tests covering search by from/to/subject/body - Fix search results display to clear list and show message when 0 results - Add clear_content() method to ContentContainer widget
This commit is contained in:
@@ -1003,9 +1003,21 @@ class EmailViewerApp(App):
|
||||
config = get_config()
|
||||
|
||||
# Add search results header
|
||||
if results:
|
||||
header_label = f"Search: '{query}' ({len(results)} result{'s' if len(results) != 1 else ''})"
|
||||
else:
|
||||
header_label = f"Search: '{query}' - No results found"
|
||||
envelopes_list.append(ListItem(GroupHeader(label=header_label)))
|
||||
|
||||
if not results:
|
||||
# Clear the message viewer when no results
|
||||
content_container = self.query_one(ContentContainer)
|
||||
content_container.clear_content()
|
||||
self.message_store.envelopes = []
|
||||
self.total_messages = 0
|
||||
self.current_message_id = 0
|
||||
return
|
||||
|
||||
# Create a temporary message store for search results
|
||||
search_store = MessageStore()
|
||||
search_store.load(results, self.sort_order_ascending)
|
||||
|
||||
@@ -176,6 +176,14 @@ class ContentContainer(ScrollableContainer):
|
||||
format_type = "text" if self.current_mode == "markdown" else "html"
|
||||
self.content_worker = self.fetch_message_content(message_id, format_type)
|
||||
|
||||
def clear_content(self) -> None:
|
||||
"""Clear the message content display."""
|
||||
self.content.update("")
|
||||
self.html_content.update("")
|
||||
self.current_content = None
|
||||
self.current_message_id = None
|
||||
self.border_title = "No message selected"
|
||||
|
||||
def _update_content(self, content: str | None) -> None:
|
||||
"""Update the content widgets with the fetched content."""
|
||||
if content is None:
|
||||
|
||||
24
tests/fixtures/himalaya_test_config.toml
vendored
Normal file
24
tests/fixtures/himalaya_test_config.toml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Himalaya Test Configuration
|
||||
#
|
||||
# This configuration file sets up a local Maildir test account for integration testing.
|
||||
# Copy this file to ~/.config/himalaya/config.toml or merge with existing config.
|
||||
#
|
||||
# Usage:
|
||||
# himalaya -c tests/fixtures/himalaya_test_config.toml envelope list -a test-account
|
||||
# himalaya -c tests/fixtures/himalaya_test_config.toml envelope list -a test-account from edson
|
||||
#
|
||||
# Or set the config path and use the test account:
|
||||
# export HIMALAYA_CONFIG=tests/fixtures/himalaya_test_config.toml
|
||||
# himalaya envelope list -a test-account
|
||||
|
||||
[accounts.test-account]
|
||||
default = true
|
||||
email = "test@example.com"
|
||||
display-name = "Test User"
|
||||
|
||||
# Maildir backend configuration
|
||||
backend.type = "maildir"
|
||||
backend.root-dir = "tests/fixtures/test_mailbox"
|
||||
|
||||
# Message configuration
|
||||
message.send.backend.type = "none"
|
||||
22
tests/fixtures/test_mailbox/INBOX/cur/1702500005.000005.test:2,S
vendored
Normal file
22
tests/fixtures/test_mailbox/INBOX/cur/1702500005.000005.test:2,S
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
From: Edson Martinez <edson.martinez@example.com>
|
||||
To: Test User <test@example.com>
|
||||
Subject: DevOps weekly report
|
||||
Date: Fri, 14 Dec 2025 16:00:00 -0600
|
||||
Message-ID: <msg005@example.com>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Hi Team,
|
||||
|
||||
Here's the weekly DevOps report:
|
||||
|
||||
1. Server uptime: 99.9%
|
||||
2. Deployments this week: 12
|
||||
3. Incidents resolved: 3
|
||||
4. Pending tasks: 5
|
||||
|
||||
The CI/CD pipeline improvements are on track for next week.
|
||||
|
||||
Best,
|
||||
Edson Martinez
|
||||
DevOps Lead
|
||||
17
tests/fixtures/test_mailbox/INBOX/cur/1702600004.000004.test:2,S
vendored
Normal file
17
tests/fixtures/test_mailbox/INBOX/cur/1702600004.000004.test:2,S
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
From: Carol Davis <carol.davis@example.com>
|
||||
To: Test User <test@example.com>
|
||||
Subject: Re: Budget spreadsheet
|
||||
Date: Thu, 15 Dec 2025 11:20:00 -0600
|
||||
Message-ID: <msg004@example.com>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Hi,
|
||||
|
||||
Thanks for sending over the budget spreadsheet. I've reviewed it and everything looks good.
|
||||
|
||||
One small note: the Q3 numbers need to be updated with the final figures from accounting.
|
||||
|
||||
Let me know once that's done.
|
||||
|
||||
Carol
|
||||
19
tests/fixtures/test_mailbox/INBOX/cur/1702700003.000003.test:2,
vendored
Normal file
19
tests/fixtures/test_mailbox/INBOX/cur/1702700003.000003.test:2,
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
From: Bob Williams <bob.williams@example.com>
|
||||
To: Test User <test@example.com>
|
||||
Subject: Urgent: Server maintenance tonight
|
||||
Date: Wed, 16 Dec 2025 18:45:00 -0600
|
||||
Message-ID: <msg003@example.com>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
URGENT
|
||||
|
||||
The production server will be undergoing maintenance tonight from 11pm to 2am.
|
||||
Please save all your work before 10:30pm.
|
||||
|
||||
Edson from the DevOps team will be handling the maintenance.
|
||||
|
||||
Contact the IT helpdesk if you have any concerns.
|
||||
|
||||
Bob Williams
|
||||
IT Department
|
||||
17
tests/fixtures/test_mailbox/INBOX/cur/1702800002.000002.test:2,S
vendored
Normal file
17
tests/fixtures/test_mailbox/INBOX/cur/1702800002.000002.test:2,S
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
From: Alice Johnson <alice.johnson@example.com>
|
||||
To: Test User <test@example.com>
|
||||
Subject: Project proposal review
|
||||
Date: Tue, 17 Dec 2025 14:30:00 -0600
|
||||
Message-ID: <msg002@example.com>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Hello,
|
||||
|
||||
I've attached the project proposal for your review.
|
||||
Please take a look and let me know if you have any questions.
|
||||
|
||||
The deadline for feedback is Friday.
|
||||
|
||||
Thanks,
|
||||
Alice Johnson
|
||||
17
tests/fixtures/test_mailbox/INBOX/cur/1702900001.000001.test:2,S
vendored
Normal file
17
tests/fixtures/test_mailbox/INBOX/cur/1702900001.000001.test:2,S
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
From: John Smith <john.smith@example.com>
|
||||
To: Test User <test@example.com>
|
||||
Subject: Meeting tomorrow at 10am
|
||||
Date: Mon, 18 Dec 2025 09:00:00 -0600
|
||||
Message-ID: <msg001@example.com>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Hi Test User,
|
||||
|
||||
Just a reminder about our meeting tomorrow at 10am in the conference room.
|
||||
We'll be discussing the Q4 budget review.
|
||||
|
||||
Please bring your laptop.
|
||||
|
||||
Best regards,
|
||||
John Smith
|
||||
274
tests/test_himalaya_integration.py
Normal file
274
tests/test_himalaya_integration.py
Normal file
@@ -0,0 +1,274 @@
|
||||
"""Integration tests for Himalaya client with test mailbox.
|
||||
|
||||
These tests use a local Maildir test mailbox to verify himalaya operations
|
||||
without touching real email accounts.
|
||||
|
||||
Run with:
|
||||
pytest tests/test_himalaya_integration.py -v
|
||||
|
||||
Requires:
|
||||
- himalaya CLI installed
|
||||
- tests/fixtures/test_mailbox with sample emails
|
||||
- tests/fixtures/himalaya_test_config.toml
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
# Path to the test config
|
||||
TEST_CONFIG = Path(__file__).parent / "fixtures" / "himalaya_test_config.toml"
|
||||
TEST_MAILBOX = Path(__file__).parent / "fixtures" / "test_mailbox"
|
||||
|
||||
|
||||
def himalaya_available() -> bool:
|
||||
"""Check if himalaya CLI is installed."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["himalaya", "--version"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return result.returncode == 0
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
|
||||
|
||||
# Skip all tests if himalaya is not installed
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not himalaya_available(),
|
||||
reason="himalaya CLI not installed",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def himalaya_cmd():
|
||||
"""Return the base himalaya command with test config."""
|
||||
# Note: -a must come after the subcommand in himalaya
|
||||
return f"himalaya -c {TEST_CONFIG}"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def account_arg():
|
||||
"""Return the account argument for himalaya."""
|
||||
return "-a test-account"
|
||||
|
||||
|
||||
class TestHimalayaListEnvelopes:
|
||||
"""Tests for listing envelopes."""
|
||||
|
||||
def test_list_all_envelopes(self, himalaya_cmd, account_arg):
|
||||
"""Test listing all envelopes from test mailbox."""
|
||||
result = subprocess.run(
|
||||
f"{himalaya_cmd} envelope list {account_arg} -o json",
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, f"Error: {result.stderr}"
|
||||
|
||||
envelopes = json.loads(result.stdout)
|
||||
assert len(envelopes) == 5, f"Expected 5 emails, got {len(envelopes)}"
|
||||
|
||||
def test_envelope_has_required_fields(self, himalaya_cmd, account_arg):
|
||||
"""Test that envelopes have all required fields."""
|
||||
result = subprocess.run(
|
||||
f"{himalaya_cmd} envelope list {account_arg} -o json",
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0
|
||||
|
||||
envelopes = json.loads(result.stdout)
|
||||
required_fields = ["id", "subject", "from", "to", "date"]
|
||||
|
||||
for envelope in envelopes:
|
||||
for field in required_fields:
|
||||
assert field in envelope, f"Missing field: {field}"
|
||||
|
||||
|
||||
class TestHimalayaSearch:
|
||||
"""Tests for search functionality."""
|
||||
|
||||
def test_search_by_from_name(self, himalaya_cmd, account_arg):
|
||||
"""Test searching by sender name."""
|
||||
result = subprocess.run(
|
||||
f'{himalaya_cmd} envelope list {account_arg} -o json "from edson"',
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, f"Error: {result.stderr}"
|
||||
|
||||
envelopes = json.loads(result.stdout)
|
||||
assert len(envelopes) == 1
|
||||
assert "Edson" in envelopes[0]["from"]["name"]
|
||||
|
||||
def test_search_by_body_content(self, himalaya_cmd, account_arg):
|
||||
"""Test searching by body content."""
|
||||
result = subprocess.run(
|
||||
f'{himalaya_cmd} envelope list {account_arg} -o json "body edson"',
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, f"Error: {result.stderr}"
|
||||
|
||||
envelopes = json.loads(result.stdout)
|
||||
# Should find 2: one from Edson, one mentioning Edson in body
|
||||
assert len(envelopes) == 2
|
||||
|
||||
def test_search_by_subject(self, himalaya_cmd, account_arg):
|
||||
"""Test searching by subject."""
|
||||
result = subprocess.run(
|
||||
f'{himalaya_cmd} envelope list {account_arg} -o json "subject meeting"',
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, f"Error: {result.stderr}"
|
||||
|
||||
envelopes = json.loads(result.stdout)
|
||||
assert len(envelopes) == 1
|
||||
assert "Meeting" in envelopes[0]["subject"]
|
||||
|
||||
def test_search_compound_or_query(self, himalaya_cmd, account_arg):
|
||||
"""Test compound OR search query."""
|
||||
result = subprocess.run(
|
||||
f'{himalaya_cmd} envelope list {account_arg} -o json "from edson or body edson"',
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, f"Error: {result.stderr}"
|
||||
|
||||
envelopes = json.loads(result.stdout)
|
||||
# Should find both emails mentioning Edson
|
||||
assert len(envelopes) >= 2
|
||||
|
||||
def test_search_no_results(self, himalaya_cmd, account_arg):
|
||||
"""Test search that returns no results."""
|
||||
result = subprocess.run(
|
||||
f'{himalaya_cmd} envelope list {account_arg} -o json "from nonexistent_person_xyz"',
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, f"Error: {result.stderr}"
|
||||
|
||||
envelopes = json.loads(result.stdout)
|
||||
assert len(envelopes) == 0
|
||||
|
||||
def test_search_full_compound_query(self, himalaya_cmd, account_arg):
|
||||
"""Test the full compound query format used by our search function."""
|
||||
query = "edson"
|
||||
search_query = f"from {query} or to {query} or subject {query} or body {query}"
|
||||
|
||||
result = subprocess.run(
|
||||
f'{himalaya_cmd} envelope list {account_arg} -o json "{search_query}"',
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, f"Error: {result.stderr}"
|
||||
|
||||
envelopes = json.loads(result.stdout)
|
||||
# Should find emails where Edson is sender or mentioned in body
|
||||
assert len(envelopes) >= 2
|
||||
|
||||
|
||||
class TestHimalayaReadMessage:
|
||||
"""Tests for reading message content."""
|
||||
|
||||
def test_read_message_by_id(self, himalaya_cmd, account_arg):
|
||||
"""Test reading a message by ID."""
|
||||
# First get the list to find an ID
|
||||
list_result = subprocess.run(
|
||||
f"{himalaya_cmd} envelope list {account_arg} -o json",
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert list_result.returncode == 0
|
||||
|
||||
envelopes = json.loads(list_result.stdout)
|
||||
message_id = envelopes[0]["id"]
|
||||
|
||||
# Read the message
|
||||
read_result = subprocess.run(
|
||||
f"{himalaya_cmd} message read {account_arg} {message_id}",
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert read_result.returncode == 0, f"Error: {read_result.stderr}"
|
||||
assert len(read_result.stdout) > 0
|
||||
|
||||
|
||||
class TestHimalayaAsyncClient:
|
||||
"""Tests for the async himalaya client module."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_envelopes_async(self):
|
||||
"""Test the async search_envelopes function."""
|
||||
# Import here to avoid issues if himalaya module has import errors
|
||||
import sys
|
||||
|
||||
# Add project root to path for proper imports
|
||||
project_root = str(Path(__file__).parent.parent)
|
||||
if project_root not in sys.path:
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
from src.services.himalaya import client as himalaya_client
|
||||
|
||||
# Note: This test would need the CLI to use our test config
|
||||
# For now, just verify the function exists and has correct signature
|
||||
assert hasattr(himalaya_client, "search_envelopes")
|
||||
assert asyncio.iscoroutinefunction(himalaya_client.search_envelopes)
|
||||
|
||||
|
||||
# Additional test for edge cases
|
||||
class TestSearchEdgeCases:
|
||||
"""Edge case tests for search."""
|
||||
|
||||
def test_search_with_special_characters(self, himalaya_cmd, account_arg):
|
||||
"""Test searching with special characters in query."""
|
||||
# This should not crash, even if no results
|
||||
result = subprocess.run(
|
||||
f'{himalaya_cmd} envelope list {account_arg} -o json "subject Q4"',
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
# May fail or succeed depending on himalaya version
|
||||
# Just verify it doesn't crash catastrophically
|
||||
assert result.returncode in [0, 1]
|
||||
|
||||
def test_search_case_insensitive(self, himalaya_cmd, account_arg):
|
||||
"""Test that search is case insensitive."""
|
||||
result_upper = subprocess.run(
|
||||
f'{himalaya_cmd} envelope list {account_arg} -o json "from EDSON"',
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
result_lower = subprocess.run(
|
||||
f'{himalaya_cmd} envelope list {account_arg} -o json "from edson"',
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
# Both should succeed
|
||||
assert result_upper.returncode == 0
|
||||
assert result_lower.returncode == 0
|
||||
|
||||
# Results should be the same (case insensitive)
|
||||
upper_envelopes = json.loads(result_upper.stdout)
|
||||
lower_envelopes = json.loads(result_lower.stdout)
|
||||
assert len(upper_envelopes) == len(lower_envelopes)
|
||||
Reference in New Issue
Block a user