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
75 changes: 75 additions & 0 deletions .github/workflows/test-workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Run Tests

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
workflow_dispatch: # Allow manual trigger

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
fail-fast: false # Continue other versions if one fails

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip' # Cache pip dependencies

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov pytest-mock requests-mock

- name: Run unit tests
run: |
pytest -v -m "unit" --cov=. --cov-report=term-missing --cov-report=xml
env:
PYTHONPATH: ${{ github.workspace }}

- name: Run integration tests
run: |
pytest -v -m "integration" --cov=. --cov-append --cov-report=term-missing --cov-report=xml
env:
PYTHONPATH: ${{ github.workspace }}

- name: Run API tests
run: |
pytest -v -m "api" --cov=. --cov-append --cov-report=term-missing --cov-report=xml
env:
PYTHONPATH: ${{ github.workspace }}

- name: Run all unmarked tests
run: |
pytest -v -m "not slow" --cov=. --cov-append --cov-report=term-missing --cov-report=xml
env:
PYTHONPATH: ${{ github.workspace }}

- name: Generate coverage report
if: always()
run: |
python -m pip install coverage
coverage report --show-missing || true

- name: Upload coverage artifact
if: matrix.python-version == '3.10'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: |
coverage.xml
htmlcov/
retention-days: 30
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ ___pycache__
*.pyc
.DS_Store

# Test coverage
.coverage
htmlcov/
.pytest_cache/

# Ignore output files
logs/
output/
Expand Down
73 changes: 73 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "sfs-processor"
version = "0.1.0"
description = "Swedish legal document processor"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"requests>=2.25.0",
"pyyaml>=6.0",
"markdown>=3.4.0",
]

[project.optional-dependencies]
test = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-mock>=3.12.0",
"requests-mock>=1.11.0",
]

[tool.pytest.ini_options]
# Test discovery
testpaths = ["test"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]

# Output options
addopts = [
"-v", # Verbose output
"--tb=short", # Shorter traceback format
"--strict-markers", # Error on unknown markers
"--color=yes", # Colored output
"-ra", # Show summary of all test outcomes
"--cov=.", # Coverage for all modules
"--cov-report=term-missing", # Show missing lines in coverage
"--cov-report=html:htmlcov", # HTML coverage report
"--cov-branch", # Branch coverage
]

# Markers for test categorization
markers = [
"unit: Unit tests that don't require external resources",
"integration: Integration tests that test multiple components",
"api: Tests that interact with external APIs (mocked)",
"slow: Tests that take significant time to run",
]

# Coverage settings
[tool.coverage.run]
source = ["."]
omit = [
"test/*",
"*/test_*",
"*/__pycache__/*",
"*/site-packages/*",
".venv/*",
"venv/*",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
128 changes: 128 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""
Shared pytest fixtures and configuration for sfs-processor tests.
"""
import pytest
from pathlib import Path


@pytest.fixture
def project_root():
"""Return the project root directory."""
return Path(__file__).parent.parent


@pytest.fixture
def test_data_dir(project_root):
"""Return the test data directory."""
return project_root / "test" / "data"


@pytest.fixture
def sample_temporal_title():
"""Sample temporal title with date markers for testing."""
return """/Rubriken upphör att gälla U:2025-07-15/
Förordning (2023:30) om statsbidrag till regioner för åtgärder för att höja driftsäkerheten på hälso- och sjukvårdens fastigheter
/Rubriken träder i kraft I:2025-07-15/
Förordning om statsbidrag till regioner för åtgärder för att höja driftsäkerheten på fastigheter för hälso- och sjukvård"""


@pytest.fixture
def sample_sfs_document():
"""Sample SFS document data for testing."""
return {
'beteckning': '2023:30',
'rubrik': """/Rubriken upphör att gälla U:2025-07-15/
Förordning (2023:30) om statsbidrag till regioner för åtgärder för att höja driftsäkerheten på hälso- och sjukvårdens fastigheter
/Rubriken träder i kraft I:2025-07-15/
Förordning om statsbidrag till regioner för åtgärder för att höja driftsäkerheten på fastigheter för hälso- och sjukvård""",
'fulltext': {
'innehall': 'Test innehåll här...'
}
}


@pytest.fixture
def mock_riksdagen_responses(requests_mock):
"""
Mock common Riksdagen API responses.
Can be customized per test by accessing the requests_mock fixture.
"""
# Mock successful proposition (prop 2024/25:1 -> HB031)
requests_mock.get(
'https://data.riksdagen.se/dokument/HB031.json',
json={
'dokumentstatus': {
'dokument': {
'dokumentnamn': 'Prop. 2024/25:1',
'titel': 'Budgetpropositionen för 2025',
'rm': '2024/25',
'beteckning': '1',
'typ': 'prop',
'dokument_url_html': 'https://data.riksdagen.se/dokument/HB031.html'
}
}
}
)

# Mock successful proposition (prop 2023/24:144 -> HA03144)
requests_mock.get(
'https://data.riksdagen.se/dokument/HA03144.json',
json={
'dokumentstatus': {
'dokument': {
'dokumentnamn': 'Prop. 2023/24:144',
'titel': 'Test proposition',
'rm': '2023/24',
'beteckning': '144',
'typ': 'prop',
'dokument_url_html': 'https://data.riksdagen.se/dokument/HA03144.html'
}
}
}
)

# Mock successful bet (committee report) (bet 2023/24:JuU3 -> HA01JuU3)
requests_mock.get(
'https://data.riksdagen.se/dokument/HA01JuU3.json',
json={
'dokumentstatus': {
'dokument': {
'dokumentnamn': 'Bet. 2023/24:JuU3',
'titel': 'Justitieutskottets betänkande',
'rm': '2023/24',
'beteckning': 'JuU3',
'typ': 'bet',
'dokument_url_html': 'https://data.riksdagen.se/dokument/HA01JuU3.html'
}
}
}
)

# Mock riksdagsskrivelse (rskr 2023/24:9 -> HA049)
requests_mock.get(
'https://data.riksdagen.se/dokument/HA049.json',
json={
'dokumentstatus': {
'dokument': {
'dokumentnamn': 'Rskr. 2023/24:9',
'titel': 'Riksdagens skrivelse',
'rm': '2023/24',
'beteckning': '9',
'typ': 'rskr',
'dokument_url_html': 'https://data.riksdagen.se/dokument/HA049.html'
}
}
}
)

return requests_mock


@pytest.fixture
def mock_riksdagen_404(requests_mock):
"""Mock a 404 response from Riksdagen API."""
requests_mock.get(
'https://data.riksdagen.se/dokument/G60340.json',
status_code=404
)
return requests_mock
Loading