From 30c0c373616373e10d5d3c4d22b0d4ee61392d41 Mon Sep 17 00:00:00 2001 From: Shay Palachy-Affek Date: Tue, 3 Mar 2026 16:11:06 +0200 Subject: [PATCH 1/8] Add separate CI workflows with smoke-gated tests --- .github/workflows/codecoverage.yml | 43 +++++++++++++++++++++++++ .github/workflows/integration-tests.yml | 38 ++++++++++++++++++++++ .github/workflows/lint.yml | 39 ++++++++++++++++++++++ .github/workflows/pre-commit-ci.yml | 33 +++++++++++++++++++ .github/workflows/smoke-tests.yml | 37 +++++++++++++++++++++ .github/workflows/unit-tests.yml | 38 ++++++++++++++++++++++ .pre-commit-ci.yaml | 3 ++ .pre-commit-config.yaml | 14 ++++++++ pyproject.toml | 2 ++ 9 files changed, 247 insertions(+) create mode 100644 .github/workflows/codecoverage.yml create mode 100644 .github/workflows/integration-tests.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/pre-commit-ci.yml create mode 100644 .github/workflows/smoke-tests.yml create mode 100644 .github/workflows/unit-tests.yml create mode 100644 .pre-commit-ci.yaml create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml new file mode 100644 index 0000000..4e28303 --- /dev/null +++ b/.github/workflows/codecoverage.yml @@ -0,0 +1,43 @@ +name: codecoverage + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + coverage: + runs-on: ubuntu-latest + timeout-minutes: 25 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run tests with coverage + run: | + pytest -q tests/unit tests/integration \ + --cov=denbust \ + --cov-report=term-missing \ + --cov-report=xml:coverage.xml + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-xml + path: coverage.xml diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 0000000..73bc263 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,38 @@ +name: integration-tests + +on: + workflow_run: + workflows: ["smoke-tests"] + types: [completed] + workflow_dispatch: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }} + cancel-in-progress: true + +jobs: + integration: + if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + timeout-minutes: 25 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ github.event.workflow_run.head_repository.full_name || github.repository }} + ref: ${{ github.event.workflow_run.head_sha || github.sha }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run integration tests + run: pytest -q tests/integration diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..10259d9 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,39 @@ +name: lint + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run Ruff format check + run: ruff format --check . + + - name: Run Ruff lint + run: ruff check . + + - name: Run mypy + run: mypy src/ diff --git a/.github/workflows/pre-commit-ci.yml b/.github/workflows/pre-commit-ci.yml new file mode 100644 index 0000000..0942174 --- /dev/null +++ b/.github/workflows/pre-commit-ci.yml @@ -0,0 +1,33 @@ +name: pre-commit.ci + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + pre-commit: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run pre-commit + run: pre-commit run --all-files --show-diff-on-failure diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml new file mode 100644 index 0000000..7cad5fd --- /dev/null +++ b/.github/workflows/smoke-tests.yml @@ -0,0 +1,37 @@ +name: smoke-tests + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + smoke: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run smoke test suite + run: | + pytest -q \ + tests/unit/test_config.py \ + tests/unit/test_models.py \ + tests/unit/test_seen.py diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..1b1b9c8 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,38 @@ +name: unit-tests + +on: + workflow_run: + workflows: ["smoke-tests"] + types: [completed] + workflow_dispatch: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }} + cancel-in-progress: true + +jobs: + unit: + if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ github.event.workflow_run.head_repository.full_name || github.repository }} + ref: ${{ github.event.workflow_run.head_sha || github.sha }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run unit tests + run: pytest -q tests/unit diff --git a/.pre-commit-ci.yaml b/.pre-commit-ci.yaml new file mode 100644 index 0000000..cd6016b --- /dev/null +++ b/.pre-commit-ci.yaml @@ -0,0 +1,3 @@ +ci: + autofix_prs: false + autoupdate_schedule: monthly diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5d6996a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,14 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-merge-conflict + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-yaml + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.7 + hooks: + - id: ruff + - id: ruff-format diff --git a/pyproject.toml b/pyproject.toml index 43a0d73..72680e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,8 +27,10 @@ dependencies = [ dev = [ "pytest>=8.0.0", "pytest-asyncio>=0.24.0", + "pytest-cov>=5.0.0", "respx>=0.21.0", "pytest-mock>=3.12.0", + "pre-commit>=3.7.0", "ruff>=0.5.0", "mypy>=1.10.0", "types-beautifulsoup4>=4.12.0", From df9e08138c9f3737d5de6fa6edcb27e88b6d1201 Mon Sep 17 00:00:00 2001 From: Shay Palachy-Affek Date: Tue, 3 Mar 2026 16:29:42 +0200 Subject: [PATCH 2/8] ci: add smoke suite and gate unit/integration workflows --- .github/workflows/integration-tests.yml | 3 +- .github/workflows/smoke-tests.yml | 5 +--- .github/workflows/unit-tests.yml | 3 +- tests/smoke/test_smoke_suite.py | 39 +++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 tests/smoke/test_smoke_suite.py diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 73bc263..7035872 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -4,7 +4,6 @@ on: workflow_run: workflows: ["smoke-tests"] types: [completed] - workflow_dispatch: concurrency: group: ci-${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }} @@ -12,7 +11,7 @@ concurrency: jobs: integration: - if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest timeout-minutes: 25 diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 7cad5fd..abff8c1 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -31,7 +31,4 @@ jobs: - name: Run smoke test suite run: | - pytest -q \ - tests/unit/test_config.py \ - tests/unit/test_models.py \ - tests/unit/test_seen.py + pytest -q --maxfail=1 tests/smoke diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 1b1b9c8..788631c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -4,7 +4,6 @@ on: workflow_run: workflows: ["smoke-tests"] types: [completed] - workflow_dispatch: concurrency: group: ci-${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }} @@ -12,7 +11,7 @@ concurrency: jobs: unit: - if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest timeout-minutes: 20 diff --git a/tests/smoke/test_smoke_suite.py b/tests/smoke/test_smoke_suite.py new file mode 100644 index 0000000..51837f3 --- /dev/null +++ b/tests/smoke/test_smoke_suite.py @@ -0,0 +1,39 @@ +"""Fast smoke tests used as a CI gate before heavier suites.""" + +from pathlib import Path + +from typer.testing import CliRunner + +from denbust.cli import app +from denbust.config import Config +from denbust.store.seen import SeenStore + + +def test_cli_version_command_smoke() -> None: + """CLI entrypoint should load and return version output.""" + runner = CliRunner() + result = runner.invoke(app, ["version"]) + + assert result.exit_code == 0 + assert "denbust version" in result.output + + +def test_config_defaults_smoke() -> None: + """Core config model should instantiate with defaults.""" + config = Config() + + assert config.days > 0 + assert config.max_articles > 0 + assert len(config.keywords) > 0 + + +def test_seen_store_round_trip_smoke(tmp_path: Path) -> None: + """Seen store should persist and reload URLs.""" + path = tmp_path / "seen.json" + + first = SeenStore(path) + first.mark_seen(["https://example.com/article"]) + first.save() + + second = SeenStore(path) + assert second.is_seen("https://example.com/article") From 74efd0b4ca70822385dc5921a1868a187013fd9c Mon Sep 17 00:00:00 2001 From: Shay Palachy-Affek Date: Tue, 3 Mar 2026 17:00:12 +0200 Subject: [PATCH 3/8] ci: fix lint import order and ignore fixture html whitespace --- .pre-commit-config.yaml | 1 + src/denbust/pipeline.py | 2 +- tests/integration/test_pipeline.py | 2 +- tests/unit/test_dedup.py | 2 +- tests/unit/test_models.py | 4 +--- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d6996a..34e2a4a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,6 +5,7 @@ repos: - id: check-merge-conflict - id: end-of-file-fixer - id: trailing-whitespace + exclude: ^tests/fixtures/html/ - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit diff --git a/src/denbust/pipeline.py b/src/denbust/pipeline.py index 7c32201..4cb2f97 100644 --- a/src/denbust/pipeline.py +++ b/src/denbust/pipeline.py @@ -7,8 +7,8 @@ from denbust.classifier.relevance import Classifier, create_classifier from denbust.config import Config, SourceType, load_config -from denbust.dedup.similarity import Deduplicator, create_deduplicator from denbust.data_models import ClassifiedArticle, RawArticle, UnifiedItem +from denbust.dedup.similarity import Deduplicator, create_deduplicator from denbust.output.formatter import print_items from denbust.sources.base import Source from denbust.sources.maariv import create_maariv_source diff --git a/tests/integration/test_pipeline.py b/tests/integration/test_pipeline.py index d35bc70..d051da6 100644 --- a/tests/integration/test_pipeline.py +++ b/tests/integration/test_pipeline.py @@ -147,8 +147,8 @@ class TestDeduplicateArticles: def test_deduplicate_similar_articles(self) -> None: """Test deduplicating similar articles.""" - from denbust.dedup.similarity import Deduplicator from denbust.data_models import ClassificationResult, ClassifiedArticle + from denbust.dedup.similarity import Deduplicator articles = [ ClassifiedArticle( diff --git a/tests/unit/test_dedup.py b/tests/unit/test_dedup.py index a71bad5..c61ec78 100644 --- a/tests/unit/test_dedup.py +++ b/tests/unit/test_dedup.py @@ -4,7 +4,6 @@ from pydantic import HttpUrl -from denbust.dedup.similarity import ArticleGroup, Deduplicator, create_deduplicator from denbust.data_models import ( Category, ClassificationResult, @@ -12,6 +11,7 @@ RawArticle, SubCategory, ) +from denbust.dedup.similarity import ArticleGroup, Deduplicator, create_deduplicator def make_article(title: str, source: str, url: str = "https://example.com/1") -> ClassifiedArticle: diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py index 716bc0e..fafaf00 100644 --- a/tests/unit/test_models.py +++ b/tests/unit/test_models.py @@ -2,10 +2,8 @@ from datetime import UTC, datetime -from pydantic import HttpUrl - import pytest -from pydantic import ValidationError +from pydantic import HttpUrl, ValidationError from denbust.data_models import ( Category, From eefbd5da5541cd72a424008fd82519ac4e6410fe Mon Sep 17 00:00:00 2001 From: Shay Palachy-Affek Date: Tue, 3 Mar 2026 17:01:33 +0200 Subject: [PATCH 4/8] ci: skip eof fixer for fixture html files --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 34e2a4a..9bbc111 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,6 +4,7 @@ repos: hooks: - id: check-merge-conflict - id: end-of-file-fixer + exclude: ^tests/fixtures/html/ - id: trailing-whitespace exclude: ^tests/fixtures/html/ - id: check-yaml From 9285503ba3b19fa48fc85e07bb68e31d8f33ae7a Mon Sep 17 00:00:00 2001 From: Shay Palachy-Affek Date: Tue, 3 Mar 2026 17:04:31 +0200 Subject: [PATCH 5/8] chore: remove trailing whitespace for pre-commit --- .gitignore | 4 ++-- docs/product_def.md | 42 +++++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index acaeb6e..459c882 100644 --- a/.gitignore +++ b/.gitignore @@ -182,9 +182,9 @@ cython_debug/ .abstra/ # Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, +# and can be added to the global gitignore or merged into this file. However, if you prefer, # you could uncomment the following to ignore the entire vscode folder # .vscode/ diff --git a/docs/product_def.md b/docs/product_def.md index 3e13f68..6c72677 100644 --- a/docs/product_def.md +++ b/docs/product_def.md @@ -9,24 +9,24 @@ date: ינואר 2026 במאבק בתעשיית המין בישראל, החוק בעדנו. אסור לסרסר בנשים, גברים וקטינות. אסור להביא א.נשים לעסוק בזנות. אסור להחזיק מקום המשמש לזנות. ובזכות מאבק ארוך-שנים של המטה, אסור גם לצרוך זנות. החוק מייצר תודעה – אך האפקטיביות שלו נבחנת ביחס לאכיפה. -אכיפה חזקה מקדמת הרתעה, מחזקת את הערכים הנורמטיביים באיסור סרסור וצריכת זנות ומצמצת את הביקוש לקיומה של תעשיית המין האפלה. לעומת זאת, אכיפה חלשה או בררנית מורידה מן האפקטיביות של החוק, מעלימה עין להתפשטות הפשיעה ומשאירה נשים ונערות חשופות לניצול קשה. גם השיח הציבורי סביב האכיפה, הסיקור התקשורתי והנרטיב בו, יכולים להשפיע מאוד על התודעה החברתית סביב תעשיית המין. +אכיפה חזקה מקדמת הרתעה, מחזקת את הערכים הנורמטיביים באיסור סרסור וצריכת זנות ומצמצת את הביקוש לקיומה של תעשיית המין האפלה. לעומת זאת, אכיפה חלשה או בררנית מורידה מן האפקטיביות של החוק, מעלימה עין להתפשטות הפשיעה ומשאירה נשים ונערות חשופות לניצול קשה. גם השיח הציבורי סביב האכיפה, הסיקור התקשורתי והנרטיב בו, יכולים להשפיע מאוד על התודעה החברתית סביב תעשיית המין. -פרויקט "מדד האכיפה" יבחן את מאמצי האכיפה של המשטרה בתחום זה ויציג אותם באופן שקוף ומופשט לציבור. כגוף בעל נוכחות תקשורתית ויכולות בתחום התקשורת והמדיה, שיקוף מידע זה יכול להשפיע באופן ממשי על התודעה והשיח הציבורי בתחום. במקביל, המטה יעקוב אחרי הליכים פתוחים ויגיש חוות דעת משפטיות במקרים המתאימים מתוך הידע והניסיון שלנו, במטרה לקדם כתבי אישום. +פרויקט "מדד האכיפה" יבחן את מאמצי האכיפה של המשטרה בתחום זה ויציג אותם באופן שקוף ומופשט לציבור. כגוף בעל נוכחות תקשורתית ויכולות בתחום התקשורת והמדיה, שיקוף מידע זה יכול להשפיע באופן ממשי על התודעה והשיח הציבורי בתחום. במקביל, המטה יעקוב אחרי הליכים פתוחים ויגיש חוות דעת משפטיות במקרים המתאימים מתוך הידע והניסיון שלנו, במטרה לקדם כתבי אישום. ## מטרות הפרויקט ומנגנוני יישום לפרויקט ארבעה מטרות מרכזיות: 1. פיקוח אזרחי על מצב האכיפה בתחום הסרסור, צריכת זנות וסגירת זירות זנות במטרה למפות את מצב האכיפה ולזהות פערים ולקדם מדיניות מטיבה בהתאם. -2. העלאת מודעות ציבורית למצב האמיתי של המאבק בתעשיית המין תוך שיקוף נתוני אכיפה ותיווך משמעותם לציבור בתקשורת וברשתות של המטה. +2. העלאת מודעות ציבורית למצב האמיתי של המאבק בתעשיית המין תוך שיקוף נתוני אכיפה ותיווך משמעותם לציבור בתקשורת וברשתות של המטה. 3. יצירת לחץ ציבורי במטרה לעודד אכיפה אפקטיבית יותר בתחום. 4. חיזוק שיתוף הפעולה בין המטה לבין גורמי אכיפת החוק, תוך שיתוף ביכולות המשפטיות של המטה וההיכרות העמוקה עם השטח ועם המתרחש ברמה היומיומית בזנות בישראל. על מנת להגשים מטרות אלו, הפרויקט יכלול מספר מנגנונים מקבילים: -- איסוף מידע -- שיקוף מידע -- תרומת ידע +- איסוף מידע +- שיקוף מידע +- תרומת ידע ### איסוף מידע @@ -78,7 +78,7 @@ date: ינואר 2026 אופן איסוף המידע ייעשה בשלבים. בתחילת הפרויקט, המידע ייאסף מחיפושים ידניים במאגרי מידע משפטיים על בסיס מנוי ומניהול התראות גוגל כדי לגלות על פרסומים תקשורתיים בתחום ועל אירועים שיש לעקוב אחריהם. לצד פעולות אלו, ייבנה פורמט להגשת בקשות חופש מידע אודות המידע שלא זמין לציבור הרחב וייקבע תדירות הגשת הבקשות על מנת לחשוף את הנתונים העדכניים והרלוונטיים ביותר. בעת הצורך, המטה ייעזר באסטרטגיות הלובי שלו כדי להבטיח כי המידע שגורמי אכיפת חוק מחויבים למסור יימסר. -במהלך התנעת הפרויקט, יחד עם יועצות ויועצים מתחום הסייבר ומתחום אכיפת החוק, יאופיין כלי אשר מסוגל לרכז את המידע מאת מקורות מידע משפטיים ותקשורתיים. הכלי יסרוק מאגרים פתוחים על מנת לרכז את ההליכים ופריטי המידע הרלוונטיים. כיום אין מאגר אחד אשר מרכז את המידע אודות מצב האכיפה, ועל מנת להבין את המצב יש צורך לקרוא כתבות והודעות דוברות ולחבר את המידע באופן ידני, ולהשלים את החוסרים בתהליך של בקשת חופש מידע. תהליך זה מורכב ודורש זמן רב, אך אוטומציה של החיפוש יכולה לייעל אותו באופן משמעותי. מתוך סקירה ראשונית, מסתמן כי לא ניתן לבנות כלי אשר יפעל בתוך מאגרי מידע סגורים (למשל, נבו או תקדין) אך כי, במגבלות מסוימות, יהיה ניתן להפעיל כלי כזה באתר של הרשות השופטת, שכן הפרסומים שם פומביים ופתוחים לציבור הרחב, ולצידו גם בפלטפורמות תקשורתיות. לאחר אפיון ובחינת הסוגיות המשפטיות והטכנולוגיות, הכלי ייבנה במסגרת הפרויקט. +במהלך התנעת הפרויקט, יחד עם יועצות ויועצים מתחום הסייבר ומתחום אכיפת החוק, יאופיין כלי אשר מסוגל לרכז את המידע מאת מקורות מידע משפטיים ותקשורתיים. הכלי יסרוק מאגרים פתוחים על מנת לרכז את ההליכים ופריטי המידע הרלוונטיים. כיום אין מאגר אחד אשר מרכז את המידע אודות מצב האכיפה, ועל מנת להבין את המצב יש צורך לקרוא כתבות והודעות דוברות ולחבר את המידע באופן ידני, ולהשלים את החוסרים בתהליך של בקשת חופש מידע. תהליך זה מורכב ודורש זמן רב, אך אוטומציה של החיפוש יכולה לייעל אותו באופן משמעותי. מתוך סקירה ראשונית, מסתמן כי לא ניתן לבנות כלי אשר יפעל בתוך מאגרי מידע סגורים (למשל, נבו או תקדין) אך כי, במגבלות מסוימות, יהיה ניתן להפעיל כלי כזה באתר של הרשות השופטת, שכן הפרסומים שם פומביים ופתוחים לציבור הרחב, ולצידו גם בפלטפורמות תקשורתיות. לאחר אפיון ובחינת הסוגיות המשפטיות והטכנולוגיות, הכלי ייבנה במסגרת הפרויקט. בשביל לשקף את המידע, במסגרת התנעת הפרויקט הצוות ייעצב עמוד אינטרנט בו יוצג כל המידע. המידע יוצג באופן אסטטי, מונגש לציבור ומנותח לפי תבחינים מהותיים וכמותיים. התצוגה תכלול, בין היתר: @@ -88,7 +88,7 @@ date: ינואר 2026 - כמות ההליכים הפליליים בכל תחום ועדכונים בהתאם לפומביות המידע - ניתוח מהותי של נתונים – היקף צווי הסגירה שניתנו ללא חלוקת קנסות; היקף צווי הסגירה שניתנו במסגרתם נעצרו א.נשים ממעגל הזנות; ועוד -כך, המידע הסטטיסטי יהיה זמין לציבור המתעניינים ולכלי תקשורת הסוקרים את התופעה, וכמו כן המטה יוכל לתאר את מצב המאבק, פערים בין גופי אכיפה או מחוזות שונים, מגמת ההתעמרות בא.נשים במעגל הזנות, וכו'. +כך, המידע הסטטיסטי יהיה זמין לציבור המתעניינים ולכלי תקשורת הסוקרים את התופעה, וכמו כן המטה יוכל לתאר את מצב המאבק, פערים בין גופי אכיפה או מחוזות שונים, מגמת ההתעמרות בא.נשים במעגל הזנות, וכו'. ## ניהול הפרויקט @@ -108,20 +108,20 @@ date: ינואר 2026 ### תכלול הפרויקט (שוטף) -- תכלול הנתונים והמידע - - ? אחוז משרה מנהלת אכיפה + מנהלת דוברות + מנהלת קשרי ממשל - - עלות ניהול הכלי בשוטף +- תכלול הנתונים והמידע + - ? אחוז משרה מנהלת אכיפה + מנהלת דוברות + מנהלת קשרי ממשל + - עלות ניהול הכלי בשוטף -- ניהול השיח השוטף עם הקואליציה - - ? אחוז משרה מנהלת אכיפה + מנהלת קואליציה +- ניהול השיח השוטף עם הקואליציה + - ? אחוז משרה מנהלת אכיפה + מנהלת קואליציה -- ניהול האתר, הרשתות החברתיות ותחום התקשורת - - ? אחוז משרה מנהלת דוברות + מנהלת אכיפה +- ניהול האתר, הרשתות החברתיות ותחום התקשורת + - ? אחוז משרה מנהלת דוברות + מנהלת אכיפה -- מעקב אחרי ההליכים והשתתפות במקרים המתאימים - - הגשת מידע/חוות דעת במקרים המתאימים; הגשת בקשה להצטרף כידיד בית משפט בעת הצורך וכאשר רלוונטי - - בנוסף, חיזוק הקשר עם המשטרה והפרקליטות באמצעות שיח שוטף - - ? אחוז משרה מנהלת אכיפה +- מעקב אחרי ההליכים והשתתפות במקרים המתאימים + - הגשת מידע/חוות דעת במקרים המתאימים; הגשת בקשה להצטרף כידיד בית משפט בעת הצורך וכאשר רלוונטי + - בנוסף, חיזוק הקשר עם המשטרה והפרקליטות באמצעות שיח שוטף + - ? אחוז משרה מנהלת אכיפה ## מדידת השפעת הפרויקט @@ -132,5 +132,5 @@ date: ינואר 2026 3. מעקב אחרי נתוני האכיפה ומגמת עלייה של פעולות האכיפה לאחר הפעלת המדד לתקופה ממושכת. 4. כמות ההליכים בהם המטה השתתף וההשפעה של המטה באותם הליכים. -- מועד מדידה: שנה לאחר עליית המדד לאוויר -- משאב נדרש: עלות מדידה +- מועד מדידה: שנה לאחר עליית המדד לאוויר +- משאב נדרש: עלות מדידה From a74ad8bf2fc4af6b6ae09dec558be3e1aa681f1c Mon Sep 17 00:00:00 2001 From: Shay Palachy-Affek Date: Tue, 3 Mar 2026 17:08:43 +0200 Subject: [PATCH 6/8] test: make integration fixture date window stable --- tests/integration/test_pipeline.py | 7 +++++-- tests/integration/test_scrapers.py | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/integration/test_pipeline.py b/tests/integration/test_pipeline.py index d051da6..179e458 100644 --- a/tests/integration/test_pipeline.py +++ b/tests/integration/test_pipeline.py @@ -21,6 +21,7 @@ # Load fixture files FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" +TEST_LOOKBACK_DAYS = 5000 def load_fixture(path: str) -> str: @@ -104,7 +105,7 @@ async def test_fetch_rss_source(self) -> None: sources = [RSSSource("ynet", "https://ynet.co.il/feed.xml")] keywords = ["בית בושת", "זנות", "סרסור"] - articles = await fetch_all_sources(sources, days=14, keywords=keywords) + articles = await fetch_all_sources(sources, days=TEST_LOOKBACK_DAYS, keywords=keywords) # Should find articles matching keywords assert len(articles) >= 1 @@ -226,7 +227,9 @@ async def test_full_pipeline_mocked(self, tmp_path: Path) -> None: sources = create_sources(config) assert len(sources) == 1 - articles = await fetch_all_sources(sources, days=14, keywords=config.keywords) + articles = await fetch_all_sources( + sources, days=TEST_LOOKBACK_DAYS, keywords=config.keywords + ) # Should find some matching articles assert len(articles) >= 1 diff --git a/tests/integration/test_scrapers.py b/tests/integration/test_scrapers.py index 76a72cc..fc810c5 100644 --- a/tests/integration/test_scrapers.py +++ b/tests/integration/test_scrapers.py @@ -11,6 +11,7 @@ # Load fixture files FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" +TEST_LOOKBACK_DAYS = 5000 def load_fixture(path: str) -> str: @@ -38,7 +39,7 @@ async def test_parse_search_results(self) -> None: ) scraper = MakoScraper() - articles = await scraper.fetch(days=14, keywords=["סרסור"]) + articles = await scraper.fetch(days=TEST_LOOKBACK_DAYS, keywords=["סרסור"]) # Should find articles from the fixture assert len(articles) >= 1 @@ -65,7 +66,9 @@ async def test_deduplicates_results(self) -> None: scraper = MakoScraper() # Search with multiple keywords - articles = await scraper.fetch(days=14, keywords=["סרסור", "זנות", "בית בושת"]) + articles = await scraper.fetch( + days=TEST_LOOKBACK_DAYS, keywords=["סרסור", "זנות", "בית בושת"] + ) # Should deduplicate by URL urls = [str(a.url) for a in articles] @@ -88,7 +91,7 @@ async def test_handles_empty_results(self) -> None: ) scraper = MaarivScraper() - articles = await scraper.fetch(days=14, keywords=["test"]) + articles = await scraper.fetch(days=TEST_LOOKBACK_DAYS, keywords=["test"]) # Should return empty list, not crash assert articles == [] @@ -101,7 +104,7 @@ async def test_handles_http_error(self) -> None: respx.get("https://www.maariv.co.il/search").mock(return_value=Response(404)) scraper = MaarivScraper() - articles = await scraper.fetch(days=14, keywords=["test"]) + articles = await scraper.fetch(days=TEST_LOOKBACK_DAYS, keywords=["test"]) # Should return empty list, not crash assert articles == [] @@ -121,7 +124,9 @@ async def test_parse_rss_feed(self) -> None: respx.get("https://ynet.co.il/feed.xml").mock(return_value=Response(200, text=rss_content)) source = RSSSource("ynet", "https://ynet.co.il/feed.xml") - articles = await source.fetch(days=14, keywords=["בית בושת", "זנות", "צו סגירה"]) + articles = await source.fetch( + days=TEST_LOOKBACK_DAYS, keywords=["בית בושת", "זנות", "צו סגירה"] + ) # Should find matching articles assert len(articles) >= 1 @@ -144,7 +149,7 @@ async def test_filters_by_keywords(self) -> None: source = RSSSource("ynet", "https://ynet.co.il/feed.xml") # Search for keyword that doesn't match - articles = await source.fetch(days=14, keywords=["מילה_שלא_קיימת"]) + articles = await source.fetch(days=TEST_LOOKBACK_DAYS, keywords=["מילה_שלא_קיימת"]) # Should not find any articles assert len(articles) == 0 @@ -158,7 +163,7 @@ async def test_handles_feed_error(self) -> None: respx.get("https://ynet.co.il/feed.xml").mock(return_value=Response(500)) source = RSSSource("ynet", "https://ynet.co.il/feed.xml") - articles = await source.fetch(days=14, keywords=["test"]) + articles = await source.fetch(days=TEST_LOOKBACK_DAYS, keywords=["test"]) # Should return empty list, not crash assert articles == [] From 457d1288f5a2ed7298a968be00426ec38d6dc34e Mon Sep 17 00:00:00 2001 From: Shay Palachy-Affek Date: Wed, 4 Mar 2026 12:25:01 +0200 Subject: [PATCH 7/8] ci: harden workflow_run test jobs for concurrency and fork safety --- .github/workflows/integration-tests.yml | 8 ++++++-- .github/workflows/unit-tests.yml | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 7035872..4c06f41 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -5,13 +5,16 @@ on: workflows: ["smoke-tests"] types: [completed] +permissions: + contents: read + concurrency: - group: ci-${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }} + group: ci-${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name || github.repository }}-${{ github.event.workflow_run.head_branch || github.ref }} cancel-in-progress: true jobs: integration: - if: ${{ github.event.workflow_run.conclusion == 'success' }} + if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository }} runs-on: ubuntu-latest timeout-minutes: 25 @@ -21,6 +24,7 @@ jobs: with: repository: ${{ github.event.workflow_run.head_repository.full_name || github.repository }} ref: ${{ github.event.workflow_run.head_sha || github.sha }} + persist-credentials: false - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 788631c..e2c119c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -5,13 +5,16 @@ on: workflows: ["smoke-tests"] types: [completed] +permissions: + contents: read + concurrency: - group: ci-${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }} + group: ci-${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name || github.repository }}-${{ github.event.workflow_run.head_branch || github.ref }} cancel-in-progress: true jobs: unit: - if: ${{ github.event.workflow_run.conclusion == 'success' }} + if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository }} runs-on: ubuntu-latest timeout-minutes: 20 @@ -21,6 +24,7 @@ jobs: with: repository: ${{ github.event.workflow_run.head_repository.full_name || github.repository }} ref: ${{ github.event.workflow_run.head_sha || github.sha }} + persist-credentials: false - name: Set up Python uses: actions/setup-python@v5 From eacb5ce0cd3358bd8eca7a2741686d98ea147348 Mon Sep 17 00:00:00 2001 From: Shay Palachy Date: Wed, 4 Mar 2026 13:04:16 +0200 Subject: [PATCH 8/8] Consolidate CI into single ci-test workflow --- .github/workflows/ci-test.yml | 197 ++++++++++++++++++++++++ .github/workflows/codecoverage.yml | 43 ------ .github/workflows/integration-tests.yml | 41 ----- .github/workflows/lint.yml | 39 ----- .github/workflows/pre-commit-ci.yml | 33 ---- .github/workflows/smoke-tests.yml | 34 ---- .github/workflows/unit-tests.yml | 41 ----- 7 files changed, 197 insertions(+), 231 deletions(-) create mode 100644 .github/workflows/ci-test.yml delete mode 100644 .github/workflows/codecoverage.yml delete mode 100644 .github/workflows/integration-tests.yml delete mode 100644 .github/workflows/lint.yml delete mode 100644 .github/workflows/pre-commit-ci.yml delete mode 100644 .github/workflows/smoke-tests.yml delete mode 100644 .github/workflows/unit-tests.yml diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml new file mode 100644 index 0000000..3147882 --- /dev/null +++ b/.github/workflows/ci-test.yml @@ -0,0 +1,197 @@ +name: ci-test + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +permissions: + contents: read + +env: + DEFAULT_PYTHON_VERSION: "3.11" + +jobs: + smoke-tests: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run smoke tests + run: pytest -q --maxfail=1 tests/smoke + + pre-commit: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run pre-commit + run: pre-commit run --all-files --show-diff-on-failure + + lint: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run Ruff format check + run: ruff format --check . + + - name: Run Ruff lint + run: ruff check . + + - name: Run mypy + run: mypy src/ + + unit-tests: + needs: [smoke-tests] + runs-on: ubuntu-latest + timeout-minutes: 20 + + env: + COVERAGE_FILE: coverage-unit.dat + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run unit tests + run: pytest -q tests/unit --cov=denbust --cov-report=term-missing --cov-report= + + - name: Upload unit coverage data + uses: actions/upload-artifact@v4 + with: + name: coverage-unit-data + path: coverage-unit.dat + + integration-tests: + needs: [smoke-tests] + runs-on: ubuntu-latest + timeout-minutes: 25 + + env: + COVERAGE_FILE: coverage-integration.dat + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Run integration tests + run: pytest -q tests/integration --cov=denbust --cov-report=term-missing --cov-report= + + - name: Upload integration coverage data + uses: actions/upload-artifact@v4 + with: + name: coverage-integration-data + path: coverage-integration.dat + + coverage: + needs: [pre-commit, lint, unit-tests, integration-tests] + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + + - name: Download unit coverage data + uses: actions/download-artifact@v4 + with: + name: coverage-unit-data + path: coverage-data/unit + + - name: Download integration coverage data + uses: actions/download-artifact@v4 + with: + name: coverage-integration-data + path: coverage-data/integration + + - name: Combine and report coverage + run: | + python -m coverage combine coverage-data/unit/coverage-unit.dat coverage-data/integration/coverage-integration.dat + python -m coverage report --show-missing + python -m coverage xml -o coverage.xml + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-xml + path: coverage.xml diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml deleted file mode 100644 index 4e28303..0000000 --- a/.github/workflows/codecoverage.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: codecoverage - -on: - pull_request: - push: - branches: [main] - -concurrency: - group: ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - coverage: - runs-on: ubuntu-latest - timeout-minutes: 25 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: "pip" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e ".[dev]" - - - name: Run tests with coverage - run: | - pytest -q tests/unit tests/integration \ - --cov=denbust \ - --cov-report=term-missing \ - --cov-report=xml:coverage.xml - - - name: Upload coverage artifact - uses: actions/upload-artifact@v4 - with: - name: coverage-xml - path: coverage.xml diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml deleted file mode 100644 index 4c06f41..0000000 --- a/.github/workflows/integration-tests.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: integration-tests - -on: - workflow_run: - workflows: ["smoke-tests"] - types: [completed] - -permissions: - contents: read - -concurrency: - group: ci-${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name || github.repository }}-${{ github.event.workflow_run.head_branch || github.ref }} - cancel-in-progress: true - -jobs: - integration: - if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository }} - runs-on: ubuntu-latest - timeout-minutes: 25 - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - repository: ${{ github.event.workflow_run.head_repository.full_name || github.repository }} - ref: ${{ github.event.workflow_run.head_sha || github.sha }} - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: "pip" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e ".[dev]" - - - name: Run integration tests - run: pytest -q tests/integration diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 10259d9..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: lint - -on: - pull_request: - push: - branches: [main] - -concurrency: - group: ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - lint: - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: "pip" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e ".[dev]" - - - name: Run Ruff format check - run: ruff format --check . - - - name: Run Ruff lint - run: ruff check . - - - name: Run mypy - run: mypy src/ diff --git a/.github/workflows/pre-commit-ci.yml b/.github/workflows/pre-commit-ci.yml deleted file mode 100644 index 0942174..0000000 --- a/.github/workflows/pre-commit-ci.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: pre-commit.ci - -on: - pull_request: - push: - branches: [main] - -concurrency: - group: ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - pre-commit: - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: "pip" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e ".[dev]" - - - name: Run pre-commit - run: pre-commit run --all-files --show-diff-on-failure diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml deleted file mode 100644 index abff8c1..0000000 --- a/.github/workflows/smoke-tests.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: smoke-tests - -on: - pull_request: - push: - branches: [main] - -concurrency: - group: ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - smoke: - runs-on: ubuntu-latest - timeout-minutes: 10 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: "pip" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e ".[dev]" - - - name: Run smoke test suite - run: | - pytest -q --maxfail=1 tests/smoke diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml deleted file mode 100644 index e2c119c..0000000 --- a/.github/workflows/unit-tests.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: unit-tests - -on: - workflow_run: - workflows: ["smoke-tests"] - types: [completed] - -permissions: - contents: read - -concurrency: - group: ci-${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name || github.repository }}-${{ github.event.workflow_run.head_branch || github.ref }} - cancel-in-progress: true - -jobs: - unit: - if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository }} - runs-on: ubuntu-latest - timeout-minutes: 20 - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - repository: ${{ github.event.workflow_run.head_repository.full_name || github.repository }} - ref: ${{ github.event.workflow_run.head_sha || github.sha }} - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: "pip" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e ".[dev]" - - - name: Run unit tests - run: pytest -q tests/unit