Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.11", "3.12"]
python-version: ["3.11", "3.12", "3.13"]
env:
QT_QPA_PLATFORM: offscreen
PYTHONIOENCODING: utf-8
Expand All @@ -34,6 +34,9 @@ jobs:
- run: python -m pip install --upgrade pip
- run: pip install -e ".[dev]"
- run: pytest
- name: Linter check
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
run: ruff check absorber tests
# Qt is a typed C++ API behind stubs: a call can pass every test and still
# match no documented overload. That happened once, so it is gated now.
- name: Type check
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ die Versionierung [Semantic Versioning](https://semver.org/lang/de/).

## [Unreleased]

### Geändert

- CI-Matrix um Python 3.13 erweitert (Ubuntu, Windows, macOS).
- PEP-621-Metadaten in `pyproject.toml` um Python 3.13 Classifier, Projekt-URLs (Repository, Changelog) und Linter-Konfiguration (`[tool.ruff]`) ergänzt.
- Vertragstest (`tests/test_contract.py`) für PEP 621 Metadaten, Versionsparität, Lizenzdateien und Konfigurationsvertrag hinzugefügt; Testzahl von 115 auf 119 gestiegen.
- Linter-Prüfung (`ruff check absorber tests`) in CI-Workflow integriert.

## [0.1.1] - 2026-10-13

### Behoben
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ pip install -e ".[dev]"
QT_QPA_PLATFORM=offscreen pytest
```

115 tests, no network access, no real clipboard required. The core is deliberately free of
119 tests, no network access, no real clipboard required. The core is deliberately free of
GUI imports:

| Module | Responsibility |
Expand Down
2 changes: 1 addition & 1 deletion README_de.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ pip install -e ".[dev]"
QT_QPA_PLATFORM=offscreen pytest
```

115 Tests, ohne Netzzugriff und ohne echte Zwischenablage. Der Kern ist bewusst frei von
119 Tests, ohne Netzzugriff und ohne echte Zwischenablage. Der Kern ist bewusst frei von
GUI-Importen:

| Modul | Zuständigkeit |
Expand Down
13 changes: 12 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,35 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Desktop Environment :: File Managers",
"Topic :: Utilities",
]
dependencies = ["PySide6>=6.6"]

[project.optional-dependencies]
dev = ["pytest>=8.0", "pyright>=1.1.400"]
dev = ["pytest>=8.0", "pyright>=1.1.400", "ruff>=0.5.0"]

[project.gui-scripts]
absorber = "absorber.__main__:main"

[project.urls]
Homepage = "https://github.com/file-bricks/absorber"
Repository = "https://github.com/file-bricks/absorber"
Issues = "https://github.com/file-bricks/absorber/issues"
Changelog = "https://github.com/file-bricks/absorber/blob/main/CHANGELOG.md"

[tool.setuptools.packages.find]
include = ["absorber*"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-q"

[tool.ruff]
line-length = 120
target-version = "py311"

[tool.ruff.lint]
select = ["E", "F", "W", "C4", "UP"]
ignore = ["E501"]
79 changes: 79 additions & 0 deletions tests/test_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Contract tests for PEP 621 metadata, version consistency, and repository integrity."""

from __future__ import annotations

from pathlib import Path
import re

import absorber
from absorber import config

REPO_ROOT = Path(__file__).resolve().parent.parent
PYPROJECT_PATH = REPO_ROOT / "pyproject.toml"
README_PATH = REPO_ROOT / "README.md"
README_DE_PATH = REPO_ROOT / "README_de.md"
LICENSE_PATH = REPO_ROOT / "LICENSE"
THIRD_PARTY_LICENSES_PATH = REPO_ROOT / "THIRD_PARTY_LICENSES.txt"


def _read_pyproject() -> str:
return PYPROJECT_PATH.read_text(encoding="utf-8")


def test_pep621_metadata_fields():
content = _read_pyproject()
assert 'name = "absorber"' in content
assert 'requires-python = ">=3.11"' in content
assert 'license = { text = "MIT" }' in content
assert "[project.urls]" in content
assert 'Homepage = "https://github.com/file-bricks/absorber"' in content
assert 'Repository = "https://github.com/file-bricks/absorber"' in content
assert 'Issues = "https://github.com/file-bricks/absorber/issues"' in content
assert 'Changelog = "https://github.com/file-bricks/absorber/blob/main/CHANGELOG.md"' in content
assert '"Programming Language :: Python :: 3.11"' in content
assert '"Programming Language :: Python :: 3.12"' in content
assert '"Programming Language :: Python :: 3.13"' in content
assert '"License :: OSI Approved :: MIT License"' in content


def test_version_parity():
content = _read_pyproject()
match = re.search(r'version\s*=\s*"([^"]+)"', content)
assert match is not None, "pyproject.toml must define project version"
pyproject_version = match.group(1)
assert absorber.__version__ == pyproject_version


def test_essential_files_and_licenses():
assert LICENSE_PATH.is_file(), "LICENSE must exist"
license_text = LICENSE_PATH.read_text(encoding="utf-8")
assert "MIT License" in license_text

assert THIRD_PARTY_LICENSES_PATH.is_file(), "THIRD_PARTY_LICENSES.txt must exist"
third_party_text = THIRD_PARTY_LICENSES_PATH.read_text(encoding="utf-8")
assert "PySide6" in third_party_text
assert "LGPL" in third_party_text

readme_en = README_PATH.read_text(encoding="utf-8")
assert "THIRD_PARTY_LICENSES.txt" in readme_en
assert "LICENSE" in readme_en

readme_de = README_DE_PATH.read_text(encoding="utf-8")
assert "THIRD_PARTY_LICENSES.txt" in readme_de
assert "LICENSE" in readme_de


def test_config_defaults_contract():
expected_keys = {
"mode",
"target_dir",
"text_formats",
"copy_file_lists",
"path_to_clipboard",
"model_naming",
"model",
"ollama_url",
"virtual_retention_days",
"recent_max",
}
assert expected_keys.issubset(set(config.DEFAULTS.keys()))
Loading