Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,79 @@
*.pyc
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*.pyc

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Virtual environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# OS files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Claude Code settings
.claude/*
306 changes: 306 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

82 changes: 82 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
[tool.poetry]
name = "davemail"
version = "0.1.0"
description = "Email management and synchronization tool"
authors = ["Your Name <your.email@example.com>"]
readme = "README.md"
packages = [{include = "davemail.py"}]

[tool.poetry.dependencies]
python = "^3.8"
configobj = "^5.0"
notmuch = "*"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
pytest-cov = "^4.1.0"
pytest-mock = "^3.11.0"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--strict-config",
"--cov=.",
"--cov-report=term-missing",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
"--cov-fail-under=80",
"-v"
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow running tests"
]
filterwarnings = [
"error",
"ignore::UserWarning",
"ignore::DeprecationWarning"
]

[tool.coverage.run]
source = ["."]
omit = [
"tests/*",
"*/tests/*",
"*/venv/*",
"*/.venv/*",
"*/site-packages/*",
".tox/*",
"setup.py",
"conftest.py"
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:"
]
show_missing = true
skip_covered = false
precision = 2

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"
Empty file added tests/__init__.py
Empty file.
107 changes: 107 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import os
import tempfile
import shutil
from pathlib import Path
from unittest.mock import MagicMock

import pytest


@pytest.fixture
def temp_dir():
"""Create a temporary directory for testing."""
temp_dir = tempfile.mkdtemp()
yield Path(temp_dir)
shutil.rmtree(temp_dir)


@pytest.fixture
def temp_file(temp_dir):
"""Create a temporary file for testing."""
temp_file = temp_dir / "test_file.txt"
temp_file.write_text("test content")
return temp_file


@pytest.fixture
def mock_config():
"""Mock configuration object for testing."""
return {
"test_maildir": {
"tag_folder_mapping": {
"inbox": "INBOX",
"sent": "Sent",
"new": "new"
}
}
}


@pytest.fixture
def mock_configobj(mock_config):
"""Mock ConfigObj instance."""
mock = MagicMock()
mock.__iter__ = lambda self: iter(mock_config.keys())
mock.__getitem__ = lambda self, key: mock_config[key]
return mock


@pytest.fixture
def mock_notmuch_database():
"""Mock notmuch database for testing."""
mock_db = MagicMock()
mock_query = MagicMock()
mock_message = MagicMock()

mock_message.get_filename.return_value = "/path/to/message"
mock_message.get_tags.return_value = ["inbox", "unread"]
mock_message.add_tag.return_value = None
mock_message.remove_tag.return_value = None

mock_query.search_messages.return_value = [mock_message]
mock_db.create_query.return_value = mock_query
mock_db.__enter__ = lambda self: mock_db
mock_db.__exit__ = lambda self, *args: None

return mock_db


@pytest.fixture
def mock_subprocess():
"""Mock subprocess calls for testing."""
mock = MagicMock()
mock.call.return_value = 0
mock.check_output.return_value = b"test output"
return mock


@pytest.fixture(autouse=True)
def clean_environment():
"""Clean environment variables before each test."""
original_env = os.environ.copy()
yield
os.environ.clear()
os.environ.update(original_env)


@pytest.fixture
def sample_email_content():
"""Sample email content for testing."""
return """From: test@example.com
To: recipient@example.com
Subject: Test Email
Date: Mon, 01 Jan 2024 12:00:00 +0000

This is a test email content.
"""


@pytest.fixture
def mock_email_message():
"""Mock email.message.Message object."""
mock = MagicMock()
mock.get.return_value = "test@example.com"
mock.get_all.return_value = ["test@example.com"]
mock.get_content_type.return_value = "text/plain"
mock.get_payload.return_value = "test email body"
return mock
Empty file added tests/integration/__init__.py
Empty file.
122 changes: 122 additions & 0 deletions tests/test_infrastructure_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import pytest
import sys
from pathlib import Path


def test_python_version():
"""Test that Python version is compatible."""
assert sys.version_info >= (3, 8), "Python 3.8+ is required"


def test_project_structure():
"""Test that the project structure is set up correctly."""
project_root = Path(__file__).parent.parent

# Check main files exist
assert (project_root / "davemail.py").exists(), "davemail.py should exist"
assert (project_root / "pyproject.toml").exists(), "pyproject.toml should exist"

# Check test directories exist
assert (project_root / "tests").exists(), "tests directory should exist"
assert (project_root / "tests" / "unit").exists(), "tests/unit directory should exist"
assert (project_root / "tests" / "integration").exists(), "tests/integration directory should exist"

# Check __init__.py files exist
assert (project_root / "tests" / "__init__.py").exists(), "tests/__init__.py should exist"
assert (project_root / "tests" / "unit" / "__init__.py").exists(), "tests/unit/__init__.py should exist"
assert (project_root / "tests" / "integration" / "__init__.py").exists(), "tests/integration/__init__.py should exist"

# Check conftest.py exists
assert (project_root / "tests" / "conftest.py").exists(), "tests/conftest.py should exist"


def test_pytest_configuration():
"""Test that pytest configuration is accessible."""
import pytest

# Test that pytest can find the configuration by collecting without running
config = pytest.main(["--collect-only", "--quiet", "--no-cov"])
assert config in [0, 5], "Pytest should be able to collect tests successfully"


def test_fixtures_available():
"""Test that common fixtures are available from conftest.py."""
# These fixtures should be available due to conftest.py
fixtures = [
'temp_dir',
'temp_file',
'mock_config',
'mock_configobj',
'mock_notmuch_database',
'mock_subprocess',
'sample_email_content',
'mock_email_message'
]

# This test will pass if conftest.py is properly configured
# The fixtures will be available to other tests
assert True, "Fixtures should be available from conftest.py"


def test_coverage_config():
"""Test that coverage configuration is working."""
try:
import coverage
# Test that coverage module is available
assert True, "Coverage module should be importable"
except ImportError:
pytest.skip("Coverage not installed, skipping coverage config test")


@pytest.mark.unit
def test_unit_marker():
"""Test that unit marker is configured."""
assert True, "Unit marker should be available"


@pytest.mark.integration
def test_integration_marker():
"""Test that integration marker is configured."""
assert True, "Integration marker should be available"


@pytest.mark.slow
def test_slow_marker():
"""Test that slow marker is configured."""
assert True, "Slow marker should be available"


def test_temp_dir_fixture(temp_dir):
"""Test that temp_dir fixture works."""
assert temp_dir.exists(), "Temp directory should exist"
assert temp_dir.is_dir(), "Temp directory should be a directory"

# Test we can create files in it
test_file = temp_dir / "test.txt"
test_file.write_text("test")
assert test_file.exists(), "Should be able to create files in temp directory"


def test_temp_file_fixture(temp_file):
"""Test that temp_file fixture works."""
assert temp_file.exists(), "Temp file should exist"
assert temp_file.is_file(), "Temp file should be a file"
assert temp_file.read_text() == "test content", "Temp file should have expected content"


def test_mock_config_fixture(mock_config):
"""Test that mock_config fixture works."""
assert isinstance(mock_config, dict), "Mock config should be a dictionary"
assert "test_maildir" in mock_config, "Mock config should have test_maildir"
assert "tag_folder_mapping" in mock_config["test_maildir"], "Mock config should have tag_folder_mapping"


def test_mock_configobj_fixture(mock_configobj):
"""Test that mock_configobj fixture works."""
# Test iteration
keys = list(mock_configobj)
assert "test_maildir" in keys, "Mock ConfigObj should be iterable"

# Test item access
maildir_config = mock_configobj["test_maildir"]
assert "tag_folder_mapping" in maildir_config, "Mock ConfigObj should support item access"
Empty file added tests/unit/__init__.py
Empty file.