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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ nosetests.xml
coverage.xml
*,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
Expand Down Expand Up @@ -99,3 +100,6 @@ ENV/
!/conf/config.default.json

/data/

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

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
[tool.poetry]
name = "jd-autosign"
version = "0.1.0"
description = "JD Auto Sign-in Tool"
authors = ["Your Name <your.email@example.com>"]
readme = "README.md"
packages = [{include = "app"}]

[tool.poetry.dependencies]
python = "^3.8"
pyquery = "*"
requests = "*"

[tool.poetry.group.gui]
optional = true

[tool.poetry.group.gui.dependencies]
PyQt5 = "^5.15.0"

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

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

[tool.pytest.ini_options]
minversion = "7.0"
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"
addopts = [
"--strict-markers",
"--strict-config",
"--verbose",
"--cov=app",
"--cov-branch",
"--cov-report=term-missing:skip-covered",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
"--cov-fail-under=0", # TODO: Change to 80 when adding real tests
]
markers = [
"unit: marks tests as unit tests",
"integration: marks tests as integration tests",
"slow: marks tests as slow",
]

[tool.coverage.run]
source = ["app"]
omit = [
"*/tests/*",
"*/__pycache__/*",
"*/venv/*",
"*/virtualenv/*",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if __name__ == .__main__.:",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if False:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
precision = 2
show_missing = true
skip_covered = false

[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.
124 changes: 124 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""Shared pytest fixtures and configuration."""
import json
import os
import tempfile
from pathlib import Path
from unittest.mock import Mock

import pytest


@pytest.fixture
def temp_dir():
"""Create a temporary directory for test files."""
with tempfile.TemporaryDirectory() as tmpdir:
yield Path(tmpdir)


@pytest.fixture
def mock_config():
"""Create a mock configuration object."""
return {
"username": "test_user",
"password": "test_password",
"headless": True,
"timeout": 30,
"jobs": ["bean", "daka"],
"retry_times": 3
}


@pytest.fixture
def mock_config_file(temp_dir, mock_config):
"""Create a temporary config file."""
config_path = temp_dir / "config.json"
with open(config_path, "w", encoding="utf-8") as f:
json.dump(mock_config, f)
return config_path


@pytest.fixture
def mock_browser():
"""Create a mock browser object."""
browser = Mock()
browser.page.return_value = Mock()
browser.quit = Mock()
return browser


@pytest.fixture
def mock_requests_session():
"""Create a mock requests session."""
session = Mock()
response = Mock()
response.status_code = 200
response.json.return_value = {"success": True}
response.text = "Mock response"
session.get.return_value = response
session.post.return_value = response
return session


@pytest.fixture
def sample_html():
"""Provide sample HTML for testing."""
return """
<html>
<head><title>Test Page</title></head>
<body>
<div class="container">
<h1>Test Header</h1>
<button id="sign-btn">Sign In</button>
<div class="points">100</div>
</div>
</body>
</html>
"""


@pytest.fixture
def mock_pyquery(sample_html):
"""Create a mock PyQuery object."""
from pyquery import PyQuery
return PyQuery(sample_html)


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


@pytest.fixture
def capture_logs():
"""Capture log output during tests."""
import logging
from io import StringIO

log_capture = StringIO()
handler = logging.StreamHandler(log_capture)
handler.setLevel(logging.DEBUG)

logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

yield log_capture

logger.removeHandler(handler)


def pytest_configure(config):
"""Configure pytest with custom markers."""
config.addinivalue_line(
"markers", "unit: mark test as a unit test"
)
config.addinivalue_line(
"markers", "integration: mark test as an integration test"
)
config.addinivalue_line(
"markers", "slow: mark test as slow running"
)
Empty file added tests/integration/__init__.py
Empty file.
110 changes: 110 additions & 0 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""Validation tests to ensure the testing infrastructure is set up correctly."""
import json
import sys
from pathlib import Path

import pytest


class TestInfrastructureValidation:
"""Test class to validate the testing infrastructure setup."""

@pytest.mark.unit
def test_python_version(self):
"""Test that Python version is compatible."""
assert sys.version_info >= (3, 8), "Python 3.8 or higher is required"

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

# Check important directories exist
assert (project_root / "app").exists(), "app directory should 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 important files exist
assert (project_root / "pyproject.toml").exists(), "pyproject.toml should exist"
assert (project_root / "README.md").exists(), "README.md should exist"

@pytest.mark.unit
def test_fixtures_available(self, temp_dir, mock_config, mock_browser):
"""Test that pytest fixtures are properly configured."""
# Test temp_dir fixture
assert temp_dir.exists()
assert temp_dir.is_dir()

# Test mock_config fixture
assert isinstance(mock_config, dict)
assert "username" in mock_config
assert "password" in mock_config

# Test mock_browser fixture
assert hasattr(mock_browser, "page")
assert hasattr(mock_browser, "quit")

@pytest.mark.unit
def test_mock_config_file(self, mock_config_file):
"""Test that mock config file fixture works correctly."""
assert mock_config_file.exists()

with open(mock_config_file, "r", encoding="utf-8") as f:
config = json.load(f)

assert config["username"] == "test_user"
assert config["password"] == "test_password"

@pytest.mark.unit
def test_markers_configured(self, request):
"""Test that custom markers are properly configured."""
markers = request.config.getini("markers")
marker_names = [marker.split(":")[0] for marker in markers]

assert "unit" in marker_names
assert "integration" in marker_names
assert "slow" in marker_names

@pytest.mark.unit
def test_coverage_configured(self):
"""Test that coverage is properly configured."""
# Coverage is configured in pyproject.toml
project_root = Path(__file__).parent.parent
pyproject_path = project_root / "pyproject.toml"

with open(pyproject_path, "r") as f:
content = f.read()

assert "--cov=app" in content
assert "--cov-fail-under=" in content # Check that coverage threshold is configured

@pytest.mark.integration
def test_import_app_modules(self):
"""Test that app modules can be imported."""
# Test that app package exists and has required modules
project_root = Path(__file__).parent.parent
app_dir = project_root / "app"

assert app_dir.exists()
assert (app_dir / "main.py").exists()
assert (app_dir / "config.py").exists()
assert (app_dir / "browser.py").exists()
assert (app_dir / "job" / "common.py").exists()

@pytest.mark.slow
@pytest.mark.unit
def test_slow_marker(self):
"""Test that the slow marker works (this is a dummy slow test)."""
import time
time.sleep(0.1) # Simulate a slow test
assert True

def test_no_marker(self):
"""Test without any marker to ensure unmarked tests work."""
assert True


def test_standalone_function():
"""Test that standalone test functions work."""
assert 1 + 1 == 2
Empty file added tests/unit/__init__.py
Empty file.