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
161 changes: 161 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
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/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
Pipfile.lock

# poetry
# Do not ignore poetry.lock - it should be committed
# poetry.lock

# pdm
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# IDEs
.vscode/
.idea/
*.swp
*.swo
*~

# Claude settings
.claude/*

# OS files
.DS_Store
Thumbs.db

# Redis dump files
dump.rdb

# Temporary files
*.tmp
*.temp
*.bak
1,259 changes: 1,259 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

85 changes: 85 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
[tool.poetry]
name = "event-monitor"
version = "0.1.0"
description = "Event monitoring system using Scrapy and Redis"
authors = ["Your Name <you@example.com>"]
readme = "README.md"
packages = [{include = "EventMonitor"}]

[tool.poetry.dependencies]
python = "^3.8"
scrapy = "^2.11.0"
redis = "^5.0.1"
lxml = "^4.9.3"

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

[tool.poetry.scripts]
test = "pytest:main"
tests = "pytest:main"

[tool.pytest.ini_options]
minversion = "7.0"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--strict-config",
"--cov=EventMonitor",
"--cov=collect_person_rel",
"--cov-branch",
"--cov-report=term-missing:skip-covered",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
"--cov-fail-under=0",
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow tests",
]

[tool.coverage.run]
source = ["EventMonitor", "collect_person_rel"]
branch = true
omit = [
"*/tests/*",
"*/test_*",
"*/__pycache__/*",
"*/venv/*",
"*/virtualenv/*",
"*/.venv/*",
"*/setup.py",
]

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

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

[tool.coverage.xml]
output = "coverage.xml"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Empty file added tests/__init__.py
Empty file.
144 changes: 144 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import os
import sys
import tempfile
import shutil
from pathlib import Path
from unittest.mock import Mock, MagicMock

import pytest
import redis
from scrapy.http import TextResponse, Request

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


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


@pytest.fixture
def mock_redis_connection():
"""Mock Redis connection for testing."""
mock_pool = Mock()
mock_conn = MagicMock()

mock_conn.sadd.return_value = 1
mock_conn.spop.return_value = "test_data###value1###value2###value3"
mock_conn.get.return_value = "test_value"
mock_conn.set.return_value = True

return mock_conn


@pytest.fixture
def mock_redis_pool(monkeypatch, mock_redis_connection):
"""Mock Redis connection pool."""
def mock_connection_pool(*args, **kwargs):
return Mock()

def mock_redis(*args, **kwargs):
return mock_redis_connection

monkeypatch.setattr(redis, "ConnectionPool", mock_connection_pool)
monkeypatch.setattr(redis, "Redis", mock_redis)

return mock_redis_connection


@pytest.fixture
def sample_rel_data(temp_dir):
"""Create sample relation data file for testing."""
rel_file = temp_dir / "rel_data.txt"
content = """person1###relation1###person2###type1
person3###relation2###person4###type2
person5###relation3###person6###type3
"""
rel_file.write_text(content)
return rel_file


@pytest.fixture
def sample_person_data(temp_dir):
"""Create sample person data file for testing."""
person_file = temp_dir / "person.txt"
content = """张三
李四
王五
"""
person_file.write_text(content)
return person_file


@pytest.fixture
def scrapy_response():
"""Create a mock Scrapy response for testing."""
def _create_response(url="http://test.com", body="<html><body>Test content</body></html>",
status=200, headers=None):
request = Request(url)
return TextResponse(
url=url,
request=request,
body=body.encode('utf-8'),
status=status,
headers=headers or {}
)
return _create_response


@pytest.fixture
def mock_scrapy_settings():
"""Mock Scrapy settings for testing."""
return {
'BOT_NAME': 'test_bot',
'SPIDER_MODULES': ['EventMonitor.spiders'],
'NEWSPIDER_MODULE': 'EventMonitor.spiders',
'ROBOTSTXT_OBEY': True,
'CONCURRENT_REQUESTS': 16,
'DOWNLOAD_DELAY': 0,
}


@pytest.fixture
def mock_news_parser():
"""Mock news parser for testing."""
parser = Mock()
parser.extract_content.return_value = {
'title': 'Test News Title',
'content': 'Test news content',
'date': '2024-01-01',
'source': 'Test Source'
}
return parser


@pytest.fixture
def sample_html_content():
"""Sample HTML content for testing."""
return """
<html>
<head><title>Test Page</title></head>
<body>
<h1>Test Article</h1>
<div class="content">
<p>This is test content about person1 and person2.</p>
<p>They have some relation in this news.</p>
</div>
<div class="date">2024-01-01</div>
</body>
</html>
"""


@pytest.fixture(autouse=True)
def reset_sys_modules():
"""Reset sys.modules to avoid import issues between tests."""
modules_snapshot = sys.modules.copy()
yield
for module in list(sys.modules.keys()):
if module.startswith('EventMonitor') and module in sys.modules:
del sys.modules[module]
sys.modules.update(modules_snapshot)
Empty file added tests/integration/__init__.py
Empty file.
Loading