Public Access
DEVX-91: refactor: add find_task_by_identifier, config fallbacks for tools
Post-merge / detect-type (push) Successful in 8s
Post-merge / validate-commit-msg (push) Successful in 10s
Post-merge / vikunja (push) Successful in 11s
Post-merge / sync-wiki (push) Successful in 18s
Post-merge / configure-repo (push) Successful in 10s
Post-merge / release (push) Successful in 27s
Build Images / detect-type (push) Successful in 42s
Post-merge / badges (push) Successful in 30s
Post-merge / publish (push) Successful in 15s
Build Images / build-and-push (push) Successful in 2m51s
Build Images / cleanup (push) Successful in 1m44s
Post-merge / detect-type (push) Successful in 8s
Post-merge / validate-commit-msg (push) Successful in 10s
Post-merge / vikunja (push) Successful in 11s
Post-merge / sync-wiki (push) Successful in 18s
Post-merge / configure-repo (push) Successful in 10s
Post-merge / release (push) Successful in 27s
Build Images / detect-type (push) Successful in 42s
Post-merge / badges (push) Successful in 30s
Post-merge / publish (push) Successful in 15s
Build Images / build-and-push (push) Successful in 2m51s
Build Images / cleanup (push) Successful in 1m44s
This commit was merged in pull request #145.
This commit is contained in:
@@ -407,6 +407,25 @@ class VikunjaClient:
|
|||||||
r = self._request("GET", f"/projects/{project_id}/tasks", params=params)
|
r = self._request("GET", f"/projects/{project_id}/tasks", params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
|
def find_task_by_identifier(self, project_id: int, identifier: str, per_page: int = 50) -> dict[str, Any] | None:
|
||||||
|
"""Find a task by its identifier (e.g. ``DEVX-42``) in a project.
|
||||||
|
|
||||||
|
Paginates through all tasks in the project. Returns the task dict
|
||||||
|
or None if not found.
|
||||||
|
"""
|
||||||
|
page = 1
|
||||||
|
while True:
|
||||||
|
tasks = self.list_project_tasks(project_id, page=page, per_page=per_page)
|
||||||
|
if not tasks:
|
||||||
|
break
|
||||||
|
for t in tasks:
|
||||||
|
if t.get("identifier") == identifier:
|
||||||
|
return t
|
||||||
|
if len(tasks) < per_page:
|
||||||
|
break
|
||||||
|
page += 1
|
||||||
|
return None
|
||||||
|
|
||||||
def create_task(self, project_id: int, title: str, description: str = "") -> dict[str, Any]:
|
def create_task(self, project_id: int, title: str, description: str = "") -> dict[str, Any]:
|
||||||
"""Create a task in a project and return the created task dict.
|
"""Create a task in a project and return the created task dict.
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import os
|
|||||||
import click
|
import click
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from devx.config import GITEA_API_URL
|
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
||||||
|
|
||||||
DEFAULT_MAX_RUNNERS = 3
|
DEFAULT_MAX_RUNNERS = 3
|
||||||
|
|
||||||
@@ -151,9 +151,9 @@ def main(
|
|||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
token = os.environ.get("CI_GITEA_TOKEN", "")
|
||||||
|
|
||||||
if owner is None:
|
if owner is None:
|
||||||
owner = os.environ.get("DEVX_REPO_OWNER", "oblachno-oss")
|
owner = os.environ.get("DEVX_REPO_OWNER", "") or REPO_OWNER
|
||||||
if repo is None:
|
if repo is None:
|
||||||
repo = os.environ.get("DEVX_REPO_NAME", "devx")
|
repo = os.environ.get("DEVX_REPO_NAME", "") or REPO_NAME
|
||||||
|
|
||||||
count = get_runner_count(GITEA_API_URL, token, owner, repo)
|
count = get_runner_count(GITEA_API_URL, token, owner, repo)
|
||||||
indices = generate_indices(count)
|
indices = generate_indices(count)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import time
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
from devx.config import REPO_NAME, REPO_OWNER
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
from devx.molecule.molecule_ci_guard import (
|
from devx.molecule.molecule_ci_guard import (
|
||||||
poll_for_other_failures,
|
poll_for_other_failures,
|
||||||
@@ -53,10 +54,10 @@ def cli(pytest_args: tuple[str, ...]) -> None:
|
|||||||
run_id = int(os.environ.get("RUN_ID", "0"))
|
run_id = int(os.environ.get("RUN_ID", "0"))
|
||||||
job_name = os.environ.get("JOB_NAME", "integration-tests")
|
job_name = os.environ.get("JOB_NAME", "integration-tests")
|
||||||
current_index = int(os.environ.get("MATRIX_INDEX", "0"))
|
current_index = int(os.environ.get("MATRIX_INDEX", "0"))
|
||||||
repository = os.environ.get("GITEA_REPOSITORY", "oblachno-oss/devx")
|
repository = os.environ.get("GITEA_REPOSITORY", "")
|
||||||
owner, _sep, repo = repository.partition("/")
|
owner, _sep, repo = repository.partition("/")
|
||||||
if not owner or not repo:
|
if not owner or not repo:
|
||||||
owner, repo = "oblachno-oss", "devx"
|
owner, repo = REPO_OWNER, REPO_NAME
|
||||||
|
|
||||||
if not all([gitea_url, token, run_id]):
|
if not all([gitea_url, token, run_id]):
|
||||||
click.echo(_("GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation."))
|
click.echo(_("GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation."))
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from pathlib import Path
|
|||||||
import click
|
import click
|
||||||
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
||||||
|
|
||||||
from devx.config import GITEA_API_URL
|
from devx.config import GITEA_API_URL, REPO_OWNER
|
||||||
from devx.gitea_cli import TeaCLI, TeaCLIError, configure_tea_login
|
from devx.gitea_cli import TeaCLI, TeaCLIError, configure_tea_login
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ def _default_gitea_registry_url() -> str:
|
|||||||
base = base[: -len("/api/v1")]
|
base = base[: -len("/api/v1")]
|
||||||
elif base.endswith("/api"):
|
elif base.endswith("/api"):
|
||||||
base = base[: -len("/api")]
|
base = base[: -len("/api")]
|
||||||
owner = os.environ.get("DEVX_REPO_OWNER", "oblachno-oss")
|
owner = os.environ.get("DEVX_REPO_OWNER", "") or REPO_OWNER
|
||||||
return f"{base}/api/packages/{owner}/pypi"
|
return f"{base}/api/packages/{owner}/pypi"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import click
|
|||||||
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
||||||
|
|
||||||
from devx.api_clients import GiteaClient
|
from devx.api_clients import GiteaClient
|
||||||
from devx.config import GITEA_API_URL
|
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
||||||
from devx.exceptions import APIError
|
from devx.exceptions import APIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
@@ -230,8 +230,8 @@ def main(dry_run: bool, repo: str | None, verify: bool, strict: bool) -> None:
|
|||||||
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set."))
|
raise click.ClickException(_("ERROR: CI_GITEA_TOKEN is not set."))
|
||||||
|
|
||||||
if repo is None:
|
if repo is None:
|
||||||
owner = os.environ.get("DEVX_REPO_OWNER", "oblachno-oss")
|
owner = os.environ.get("DEVX_REPO_OWNER", "") or REPO_OWNER
|
||||||
repo_name = os.environ.get("DEVX_REPO_NAME", "devx")
|
repo_name = os.environ.get("DEVX_REPO_NAME", "") or REPO_NAME
|
||||||
else:
|
else:
|
||||||
owner, repo_name = repo.split("/")
|
owner, repo_name = repo.split("/")
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import os
|
|||||||
import click
|
import click
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from devx.config import GITEA_API_URL
|
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
||||||
|
|
||||||
DEFAULT_MAX_RUNNERS = 3
|
DEFAULT_MAX_RUNNERS = 3
|
||||||
|
|
||||||
@@ -145,9 +145,9 @@ def main(
|
|||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
token = os.environ.get("CI_GITEA_TOKEN", "")
|
||||||
|
|
||||||
if owner is None:
|
if owner is None:
|
||||||
owner = os.environ.get("DEVX_REPO_OWNER", "oblachno-oss")
|
owner = os.environ.get("DEVX_REPO_OWNER", "") or REPO_OWNER
|
||||||
if repo is None:
|
if repo is None:
|
||||||
repo = os.environ.get("DEVX_REPO_NAME", "devx")
|
repo = os.environ.get("DEVX_REPO_NAME", "") or REPO_NAME
|
||||||
|
|
||||||
count = get_runner_count(GITEA_API_URL, token, owner, repo)
|
count = get_runner_count(GITEA_API_URL, token, owner, repo)
|
||||||
indices = generate_indices(count)
|
indices = generate_indices(count)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ from pathlib import Path
|
|||||||
import click
|
import click
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from devx.config import REPO_NAME, REPO_OWNER
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
POLL_INTERVAL = 10
|
POLL_INTERVAL = 10
|
||||||
@@ -167,10 +168,10 @@ def cli(pairs: tuple[str, ...], roles_root: Path | None) -> None:
|
|||||||
run_id = int(os.environ.get("RUN_ID", "0"))
|
run_id = int(os.environ.get("RUN_ID", "0"))
|
||||||
job_name = os.environ.get("JOB_NAME", "molecule-tests")
|
job_name = os.environ.get("JOB_NAME", "molecule-tests")
|
||||||
current_index = int(os.environ.get("MATRIX_INDEX", "0"))
|
current_index = int(os.environ.get("MATRIX_INDEX", "0"))
|
||||||
repository = os.environ.get("GITEA_REPOSITORY", "oblachno-oss/devx")
|
repository = os.environ.get("GITEA_REPOSITORY", "")
|
||||||
owner, _sep, repo = repository.partition("/")
|
owner, _sep, repo = repository.partition("/")
|
||||||
if not owner or not repo:
|
if not owner or not repo:
|
||||||
owner, repo = "oblachno-oss", "devx"
|
owner, repo = REPO_OWNER, REPO_NAME
|
||||||
|
|
||||||
if not all([gitea_url, token, run_id]):
|
if not all([gitea_url, token, run_id]):
|
||||||
click.echo(_("GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation."))
|
click.echo(_("GITEA_URL/CI_GITEA_TOKEN/RUN_ID not set; running without cross-runner cancellation."))
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ Usage::
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import tomllib
|
import tomllib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -27,8 +26,7 @@ def cli() -> None:
|
|||||||
"""Validate devx configuration in pyproject.toml."""
|
"""Validate devx configuration in pyproject.toml."""
|
||||||
path = Path("pyproject.toml")
|
path = Path("pyproject.toml")
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
click.echo(_("pyproject.toml not found in current directory."))
|
raise click.ClickException(_("pyproject.toml not found in current directory."))
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
with open(path, "rb") as f: # noqa: PTH123
|
with open(path, "rb") as f: # noqa: PTH123
|
||||||
data = tomllib.load(f)
|
data = tomllib.load(f)
|
||||||
@@ -65,7 +63,7 @@ def cli() -> None:
|
|||||||
if errors:
|
if errors:
|
||||||
for err in errors:
|
for err in errors:
|
||||||
click.echo(f"ERROR: {err}", err=True)
|
click.echo(f"ERROR: {err}", err=True)
|
||||||
sys.exit(1)
|
raise click.ClickException(_("Configuration validation failed."))
|
||||||
|
|
||||||
click.echo(_("Configuration OK: [tool.devx] present, devx versions consistent."))
|
click.echo(_("Configuration OK: [tool.devx] present, devx versions consistent."))
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ Usage::
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import subprocess # nosec B404
|
import subprocess # nosec B404
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
from devx.config import _load_pyproject_devx
|
from devx.config import _load_pyproject_devx
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
@@ -203,49 +203,37 @@ def _find_missing_tests(
|
|||||||
return missing
|
return missing
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
@click.command()
|
||||||
parser = argparse.ArgumentParser(
|
@click.option("--staged-only", is_flag=True, help=_("Only check staged files (for pre-commit)"))
|
||||||
description=_("Check that changed files have corresponding tests"),
|
@click.option("--warn-only", is_flag=True, help=_("Print warnings but always exit 0"))
|
||||||
)
|
def cli(staged_only: bool, warn_only: bool) -> None:
|
||||||
parser.add_argument(
|
"""Check that changed files have corresponding tests."""
|
||||||
"--staged-only",
|
|
||||||
action="store_true",
|
|
||||||
help=_("Only check staged files (for pre-commit)"),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--warn-only",
|
|
||||||
action="store_true",
|
|
||||||
help=_("Print warnings but always exit 0"),
|
|
||||||
)
|
|
||||||
args = parser.parse_args(argv)
|
|
||||||
|
|
||||||
repo_root = Path.cwd()
|
repo_root = Path.cwd()
|
||||||
rules, skip_patterns, test_indicators, skip_extensions = _load_rules()
|
rules, skip_patterns, test_indicators, skip_extensions = _load_rules()
|
||||||
|
|
||||||
files = _changed_files(args.staged_only, repo_root)
|
files = _changed_files(staged_only, repo_root)
|
||||||
if not files:
|
if not files:
|
||||||
print(_("[check_test_coverage] No changed files to check."))
|
click.echo(_("[check_test_coverage] No changed files to check."))
|
||||||
return 0
|
return
|
||||||
|
|
||||||
missing = _find_missing_tests(files, repo_root, rules, skip_patterns, test_indicators, skip_extensions)
|
missing = _find_missing_tests(files, repo_root, rules, skip_patterns, test_indicators, skip_extensions)
|
||||||
if not missing:
|
if not missing:
|
||||||
print(f"[check_test_coverage] All {len(files)} changed file(s) have tests.")
|
click.echo(f"[check_test_coverage] All {len(files)} changed file(s) have tests.")
|
||||||
return 0
|
return
|
||||||
|
|
||||||
print("[check_test_coverage] FAILED: missing tests for changed files:\n", file=sys.stderr)
|
click.echo("[check_test_coverage] FAILED: missing tests for changed files:\n", err=True)
|
||||||
for f, reason in missing.items():
|
for f, reason in missing.items():
|
||||||
print(f" {f}", file=sys.stderr)
|
click.echo(f" {f}", err=True)
|
||||||
print(f" -> {reason}", file=sys.stderr)
|
click.echo(f" -> {reason}", err=True)
|
||||||
|
|
||||||
print(
|
click.echo(
|
||||||
"\n[check_test_coverage] Fix: add the missing test file(s) before committing.",
|
_("\n[check_test_coverage] Fix: add the missing test file(s) before committing."),
|
||||||
file=sys.stderr,
|
err=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if args.warn_only:
|
if not warn_only:
|
||||||
return 0
|
raise click.ClickException(_("Missing tests for changed files."))
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": # pragma: no cover
|
if __name__ == "__main__": # pragma: no cover
|
||||||
sys.exit(main())
|
cli() # pragma: no cover
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ from typing import Any
|
|||||||
import click
|
import click
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from devx.config import GITEA_API_URL
|
from devx.config import GITEA_API_URL, REPO_OWNER
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
|
|
||||||
@@ -151,8 +151,8 @@ def select_for_deletion(
|
|||||||
@click.command()
|
@click.command()
|
||||||
@click.option(
|
@click.option(
|
||||||
"--owner",
|
"--owner",
|
||||||
required=True,
|
default=None,
|
||||||
help="Package owner (user or org).",
|
help="Package owner (user or org, default: from [tool.devx] repo_owner).",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--name",
|
"--name",
|
||||||
@@ -180,7 +180,7 @@ def select_for_deletion(
|
|||||||
help="Gitea API URL (defaults to DEVX_GITEA_API_URL or built-in default).",
|
help="Gitea API URL (defaults to DEVX_GITEA_API_URL or built-in default).",
|
||||||
)
|
)
|
||||||
def main(
|
def main(
|
||||||
owner: str,
|
owner: str | None,
|
||||||
names: tuple[str, ...],
|
names: tuple[str, ...],
|
||||||
keep: int,
|
keep: int,
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
@@ -190,6 +190,10 @@ def main(
|
|||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
token = os.environ.get("CI_GITEA_TOKEN", "")
|
||||||
if not token:
|
if not token:
|
||||||
raise click.ClickException(_("CI_GITEA_TOKEN environment variable required"))
|
raise click.ClickException(_("CI_GITEA_TOKEN environment variable required"))
|
||||||
|
if not owner:
|
||||||
|
owner = REPO_OWNER
|
||||||
|
if not owner:
|
||||||
|
raise click.ClickException(_("Package owner not specified. Use --owner or set [tool.devx] repo_owner."))
|
||||||
base_url = api_url or GITEA_API_URL
|
base_url = api_url or GITEA_API_URL
|
||||||
|
|
||||||
total_deleted = 0
|
total_deleted = 0
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from typing import Any, cast
|
|||||||
import click
|
import click
|
||||||
|
|
||||||
from devx.api_clients import GiteaClient
|
from devx.api_clients import GiteaClient
|
||||||
from devx.config import GITEA_API_URL, REPO_OWNER
|
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
|
||||||
from devx.exceptions import APIError
|
from devx.exceptions import APIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ def main(repo: str | None, owner: str | None, branch: str, api_url: str | None)
|
|||||||
token = os.environ.get("CI_GITEA_TOKEN", "")
|
token = os.environ.get("CI_GITEA_TOKEN", "")
|
||||||
|
|
||||||
if repo is None:
|
if repo is None:
|
||||||
repo = os.environ.get("DEVX_REPO_NAME", "")
|
repo = os.environ.get("DEVX_REPO_NAME", "") or REPO_NAME
|
||||||
if not repo:
|
if not repo:
|
||||||
raise click.ClickException(_("ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME."))
|
raise click.ClickException(_("ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME."))
|
||||||
|
|
||||||
|
|||||||
+10
-18
@@ -76,24 +76,16 @@ def get_vikunja_task_title(task_id: str) -> str:
|
|||||||
if not token:
|
if not token:
|
||||||
raise click.ClickException(_("VIKUNJA_TOKEN is not set. Required to derive PR title."))
|
raise click.ClickException(_("VIKUNJA_TOKEN is not set. Required to derive PR title."))
|
||||||
client = VikunjaClient(VIKUNJA_API_URL, token)
|
client = VikunjaClient(VIKUNJA_API_URL, token)
|
||||||
page = 1
|
task = client.find_task_by_identifier(VIKUNJA_PROJECT_ID, task_id, per_page=DEFAULT_PER_PAGE)
|
||||||
while True:
|
if not task:
|
||||||
tasks = client.list_project_tasks(VIKUNJA_PROJECT_ID, page=page, per_page=DEFAULT_PER_PAGE)
|
raise click.ClickException(
|
||||||
if not tasks:
|
_(
|
||||||
break
|
"Could not find Vikunja task {task_id} in project {project_id}.",
|
||||||
matches = [t for t in tasks if t.get("identifier") == task_id]
|
task_id=task_id,
|
||||||
if matches:
|
project_id=VIKUNJA_PROJECT_ID,
|
||||||
return str(matches[0].get("title", ""))
|
),
|
||||||
if len(tasks) < DEFAULT_PER_PAGE:
|
)
|
||||||
break
|
return str(task.get("title", ""))
|
||||||
page += 1
|
|
||||||
raise click.ClickException(
|
|
||||||
_(
|
|
||||||
"Could not find Vikunja task {task_id} in project {project_id}.",
|
|
||||||
task_id=task_id,
|
|
||||||
project_id=VIKUNJA_PROJECT_ID,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def find_existing_pr(client: GiteaClient, branch: str) -> dict | None:
|
def find_existing_pr(client: GiteaClient, branch: str) -> dict | None:
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ def _generate(prefix: str) -> str:
|
|||||||
@click.option(
|
@click.option(
|
||||||
"--prefix",
|
"--prefix",
|
||||||
default=TASK_PREFIX,
|
default=TASK_PREFIX,
|
||||||
help="Task ID prefix for commit preprocessor (default: DEVX_TASK_PREFIX env var or 'DEVX').",
|
help="Task ID prefix for commit preprocessor (default: from [tool.devx] task_prefix in pyproject.toml).",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--output",
|
"--output",
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ def _download_binary() -> None:
|
|||||||
TARGET_PATH.chmod(0o755)
|
TARGET_PATH.chmod(0o755)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
@click.command()
|
||||||
|
def cli() -> None:
|
||||||
"""Install checkmake if not already present."""
|
"""Install checkmake if not already present."""
|
||||||
if shutil.which("checkmake") is not None:
|
if shutil.which("checkmake") is not None:
|
||||||
return
|
return
|
||||||
@@ -66,4 +67,4 @@ def main() -> None:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": # pragma: no cover
|
if __name__ == "__main__": # pragma: no cover
|
||||||
main() # pragma: no cover
|
cli() # pragma: no cover
|
||||||
|
|||||||
@@ -59,17 +59,7 @@ def task_exists(task_id: str) -> bool:
|
|||||||
if not token:
|
if not token:
|
||||||
return False
|
return False
|
||||||
client = VikunjaClient(VIKUNJA_API_URL, token)
|
client = VikunjaClient(VIKUNJA_API_URL, token)
|
||||||
page = 1
|
return client.find_task_by_identifier(VIKUNJA_PROJECT_ID, task_id, per_page=DEFAULT_PER_PAGE) is not None
|
||||||
while True:
|
|
||||||
tasks = client.list_project_tasks(VIKUNJA_PROJECT_ID, page=page, per_page=DEFAULT_PER_PAGE)
|
|
||||||
if not tasks:
|
|
||||||
break
|
|
||||||
if any(t.get("identifier") == task_id for t in tasks):
|
|
||||||
return True
|
|
||||||
if len(tasks) < DEFAULT_PER_PAGE:
|
|
||||||
break
|
|
||||||
page += 1
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def validate(branch: str) -> None:
|
def validate(branch: str) -> None:
|
||||||
|
|||||||
@@ -463,14 +463,6 @@
|
|||||||
"ru": "Bumping version: {current} -> v{new_version}",
|
"ru": "Bumping version: {current} -> v{new_version}",
|
||||||
"zh": "Bumping version: {current} -> v{new_version}"
|
"zh": "Bumping version: {current} -> v{new_version}"
|
||||||
},
|
},
|
||||||
"Check that changed files have corresponding tests": {
|
|
||||||
"bg": "Check that changed files have corresponding tests",
|
|
||||||
"de": "Check that changed files have corresponding tests",
|
|
||||||
"en": "Check that changed files have corresponding tests",
|
|
||||||
"pl": "Check that changed files have corresponding tests",
|
|
||||||
"ru": "Check that changed files have corresponding tests",
|
|
||||||
"zh": "Check that changed files have corresponding tests"
|
|
||||||
},
|
|
||||||
"Checking CLI command documentation...": {
|
"Checking CLI command documentation...": {
|
||||||
"bg": "Checking CLI command documentation...",
|
"bg": "Checking CLI command documentation...",
|
||||||
"de": "Checking CLI command documentation...",
|
"de": "Checking CLI command documentation...",
|
||||||
@@ -2190,5 +2182,37 @@
|
|||||||
"pl": "Waiting for CI checks to complete (timeout: {timeout}s)...",
|
"pl": "Waiting for CI checks to complete (timeout: {timeout}s)...",
|
||||||
"ru": "Waiting for CI checks to complete (timeout: {timeout}s)...",
|
"ru": "Waiting for CI checks to complete (timeout: {timeout}s)...",
|
||||||
"zh": "Waiting for CI checks to complete (timeout: {timeout}s)..."
|
"zh": "Waiting for CI checks to complete (timeout: {timeout}s)..."
|
||||||
|
},
|
||||||
|
"\n[check_test_coverage] Fix: add the missing test file(s) before committing.": {
|
||||||
|
"en": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.",
|
||||||
|
"bg": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.",
|
||||||
|
"de": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.",
|
||||||
|
"pl": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.",
|
||||||
|
"ru": "\n[check_test_coverage] Fix: add the missing test file(s) before committing.",
|
||||||
|
"zh": "\n[check_test_coverage] Fix: add the missing test file(s) before committing."
|
||||||
|
},
|
||||||
|
"Package owner not specified. Use --owner or set [tool.devx] repo_owner.": {
|
||||||
|
"en": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.",
|
||||||
|
"bg": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.",
|
||||||
|
"de": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.",
|
||||||
|
"pl": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.",
|
||||||
|
"ru": "Package owner not specified. Use --owner or set [tool.devx] repo_owner.",
|
||||||
|
"zh": "Package owner not specified. Use --owner or set [tool.devx] repo_owner."
|
||||||
|
},
|
||||||
|
"Configuration validation failed.": {
|
||||||
|
"en": "Configuration validation failed.",
|
||||||
|
"bg": "Configuration validation failed.",
|
||||||
|
"de": "Configuration validation failed.",
|
||||||
|
"pl": "Configuration validation failed.",
|
||||||
|
"ru": "Configuration validation failed.",
|
||||||
|
"zh": "Configuration validation failed."
|
||||||
|
},
|
||||||
|
"Missing tests for changed files.": {
|
||||||
|
"en": "Missing tests for changed files.",
|
||||||
|
"bg": "Missing tests for changed files.",
|
||||||
|
"de": "Missing tests for changed files.",
|
||||||
|
"pl": "Missing tests for changed files.",
|
||||||
|
"ru": "Missing tests for changed files.",
|
||||||
|
"zh": "Missing tests for changed files."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -781,6 +781,42 @@ class TestVikunjaClient:
|
|||||||
call_kwargs = client._session.request.call_args.kwargs
|
call_kwargs = client._session.request.call_args.kwargs
|
||||||
assert call_kwargs["json"]["description"] == ""
|
assert call_kwargs["json"]["description"] == ""
|
||||||
|
|
||||||
|
def test_find_task_by_identifier_found(self) -> None:
|
||||||
|
client = VikunjaClient("https://work.example.com", "tok")
|
||||||
|
client._session.request = MagicMock(
|
||||||
|
return_value=_mock_response([{"identifier": "DEVX-1"}, {"identifier": "DEVX-42", "title": "Found"}])
|
||||||
|
)
|
||||||
|
result = client.find_task_by_identifier(6, "DEVX-42", per_page=50)
|
||||||
|
assert result is not None
|
||||||
|
assert result["title"] == "Found"
|
||||||
|
|
||||||
|
def test_find_task_by_identifier_not_found(self) -> None:
|
||||||
|
client = VikunjaClient("https://work.example.com", "tok")
|
||||||
|
client._session.request = MagicMock(
|
||||||
|
return_value=_mock_response([{"identifier": "DEVX-1"}, {"identifier": "DEVX-2"}])
|
||||||
|
)
|
||||||
|
result = client.find_task_by_identifier(6, "DEVX-99", per_page=50)
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_find_task_by_identifier_empty_project(self) -> None:
|
||||||
|
client = VikunjaClient("https://work.example.com", "tok")
|
||||||
|
client._session.request = MagicMock(return_value=_mock_response([]))
|
||||||
|
result = client.find_task_by_identifier(6, "DEVX-1", per_page=50)
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_find_task_by_identifier_paginates(self) -> None:
|
||||||
|
client = VikunjaClient("https://work.example.com", "tok")
|
||||||
|
full_page = [{"identifier": f"DEVX-{i}"} for i in range(50)]
|
||||||
|
client._session.request = MagicMock(
|
||||||
|
side_effect=[
|
||||||
|
_mock_response(full_page),
|
||||||
|
_mock_response([{"identifier": "DEVX-50", "title": "Found on page 2"}]),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
result = client.find_task_by_identifier(6, "DEVX-50", per_page=50)
|
||||||
|
assert result is not None
|
||||||
|
assert result["title"] == "Found on page 2"
|
||||||
|
|
||||||
|
|
||||||
class TestIsRetryable:
|
class TestIsRetryable:
|
||||||
def test_connection_error_is_retryable(self) -> None:
|
def test_connection_error_is_retryable(self) -> None:
|
||||||
|
|||||||
@@ -705,4 +705,33 @@ class TestCLICleanImages:
|
|||||||
)
|
)
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "FAILED" in result.output
|
assert "FAILED" in result.output
|
||||||
assert "failed" in result.output.lower()
|
|
||||||
|
@patch("devx.tools.clean_images.REPO_OWNER", "oblachno-oss")
|
||||||
|
def test_owner_from_config(self) -> None:
|
||||||
|
from devx.tools.clean_images import main as clean_main
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
mock_resp = MagicMock()
|
||||||
|
mock_resp.json.return_value = []
|
||||||
|
mock_resp.raise_for_status = MagicMock()
|
||||||
|
with patch.dict("os.environ", {"CI_GITEA_TOKEN": "fake"}):
|
||||||
|
with patch("devx.tools.clean_images.requests.get", return_value=mock_resp):
|
||||||
|
result = runner.invoke(
|
||||||
|
clean_main,
|
||||||
|
["--name", "ci-base", "--dry-run"],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "oblachno-oss/ci-base" in result.output
|
||||||
|
|
||||||
|
@patch("devx.tools.clean_images.REPO_OWNER", "")
|
||||||
|
def test_no_owner_raises(self) -> None:
|
||||||
|
from devx.tools.clean_images import main as clean_main
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
with patch.dict("os.environ", {"CI_GITEA_TOKEN": "fake"}):
|
||||||
|
result = runner.invoke(
|
||||||
|
clean_main,
|
||||||
|
["--name", "ci-base"],
|
||||||
|
)
|
||||||
|
assert result.exit_code != 0
|
||||||
|
assert "owner" in result.output.lower()
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
from devx.tools.check_test_coverage import (
|
from devx.tools.check_test_coverage import (
|
||||||
BUILTIN_RULES,
|
BUILTIN_RULES,
|
||||||
DEFAULT_SKIP_EXTENSIONS,
|
DEFAULT_SKIP_EXTENSIONS,
|
||||||
@@ -13,7 +15,7 @@ from devx.tools.check_test_coverage import (
|
|||||||
_load_rules,
|
_load_rules,
|
||||||
_resolve_test_path,
|
_resolve_test_path,
|
||||||
_should_skip_file,
|
_should_skip_file,
|
||||||
main,
|
cli,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -201,7 +203,10 @@ class TestMain:
|
|||||||
),
|
),
|
||||||
patch("devx.tools.check_test_coverage.Path.cwd", return_value=tmp_path),
|
patch("devx.tools.check_test_coverage.Path.cwd", return_value=tmp_path),
|
||||||
):
|
):
|
||||||
assert main([]) == 0
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(cli, [])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "No changed files" in result.output
|
||||||
|
|
||||||
def test_all_have_tests(self, tmp_path: Path) -> None:
|
def test_all_have_tests(self, tmp_path: Path) -> None:
|
||||||
(tmp_path / "scripts" / "tests").mkdir(parents=True)
|
(tmp_path / "scripts" / "tests").mkdir(parents=True)
|
||||||
@@ -214,7 +219,10 @@ class TestMain:
|
|||||||
),
|
),
|
||||||
patch("devx.tools.check_test_coverage.Path.cwd", return_value=tmp_path),
|
patch("devx.tools.check_test_coverage.Path.cwd", return_value=tmp_path),
|
||||||
):
|
):
|
||||||
assert main([]) == 0
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(cli, [])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "have tests" in result.output
|
||||||
|
|
||||||
def test_missing_test_returns_1(self, tmp_path: Path) -> None:
|
def test_missing_test_returns_1(self, tmp_path: Path) -> None:
|
||||||
with (
|
with (
|
||||||
@@ -225,7 +233,9 @@ class TestMain:
|
|||||||
),
|
),
|
||||||
patch("devx.tools.check_test_coverage.Path.cwd", return_value=tmp_path),
|
patch("devx.tools.check_test_coverage.Path.cwd", return_value=tmp_path),
|
||||||
):
|
):
|
||||||
assert main([]) == 1
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(cli, [])
|
||||||
|
assert result.exit_code != 0
|
||||||
|
|
||||||
def test_warn_only_returns_0(self, tmp_path: Path) -> None:
|
def test_warn_only_returns_0(self, tmp_path: Path) -> None:
|
||||||
with (
|
with (
|
||||||
@@ -236,4 +246,6 @@ class TestMain:
|
|||||||
),
|
),
|
||||||
patch("devx.tools.check_test_coverage.Path.cwd", return_value=tmp_path),
|
patch("devx.tools.check_test_coverage.Path.cwd", return_value=tmp_path),
|
||||||
):
|
):
|
||||||
assert main(["--warn-only"]) == 0
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(cli, ["--warn-only"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ class TestMain:
|
|||||||
assert "CI_GITEA_TOKEN" in result.output
|
assert "CI_GITEA_TOKEN" in result.output
|
||||||
|
|
||||||
@patch.dict("os.environ", {"CI_GITEA_TOKEN": "tok"}, clear=True)
|
@patch.dict("os.environ", {"CI_GITEA_TOKEN": "tok"}, clear=True)
|
||||||
|
@patch("devx.tools.configure_repo.REPO_NAME", "")
|
||||||
def test_main_no_repo(self) -> None:
|
def test_main_no_repo(self) -> None:
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(main, [])
|
result = runner.invoke(main, [])
|
||||||
@@ -197,3 +198,19 @@ class TestMain:
|
|||||||
call_args = mock_client_cls.call_args
|
call_args = mock_client_cls.call_args
|
||||||
assert call_args[0][2] == "oblachno" # owner
|
assert call_args[0][2] == "oblachno" # owner
|
||||||
assert call_args[0][3] == "infra" # repo
|
assert call_args[0][3] == "infra" # repo
|
||||||
|
|
||||||
|
@patch.dict("os.environ", {"CI_GITEA_TOKEN": "tok"}, clear=True)
|
||||||
|
@patch("devx.tools.configure_repo.REPO_NAME", "devx")
|
||||||
|
@patch("devx.tools.configure_repo.REPO_OWNER", "oblachno-oss")
|
||||||
|
@patch("devx.tools.configure_repo.GiteaClient")
|
||||||
|
def test_main_repo_from_pyproject(self, mock_client_cls: MagicMock) -> None:
|
||||||
|
"""When no env var is set, repo name should come from pyproject.toml."""
|
||||||
|
mock_client = MagicMock()
|
||||||
|
mock_client_cls.return_value = mock_client
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(main, [])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
call_args = mock_client_cls.call_args
|
||||||
|
assert call_args[0][2] == "oblachno-oss" # owner
|
||||||
|
assert call_args[0][3] == "devx" # repo
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class TestGetVikunjaTaskTitle:
|
|||||||
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
||||||
def test_found(self, mock_client_cls: MagicMock) -> None:
|
def test_found(self, mock_client_cls: MagicMock) -> None:
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_client.list_project_tasks.return_value = [{"identifier": "DEVX-42", "title": "Add feature"}]
|
mock_client.find_task_by_identifier.return_value = {"identifier": "DEVX-42", "title": "Add feature"}
|
||||||
mock_client_cls.return_value = mock_client
|
mock_client_cls.return_value = mock_client
|
||||||
assert get_vikunja_task_title("DEVX-42") == "Add feature"
|
assert get_vikunja_task_title("DEVX-42") == "Add feature"
|
||||||
|
|
||||||
@@ -69,20 +69,7 @@ class TestGetVikunjaTaskTitle:
|
|||||||
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
||||||
def test_not_found(self, mock_client_cls: MagicMock) -> None:
|
def test_not_found(self, mock_client_cls: MagicMock) -> None:
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_client.list_project_tasks.return_value = []
|
mock_client.find_task_by_identifier.return_value = None
|
||||||
mock_client_cls.return_value = mock_client
|
|
||||||
with pytest.raises(click.ClickException, match="Could not find"):
|
|
||||||
get_vikunja_task_title("DEVX-42")
|
|
||||||
|
|
||||||
@patch("devx.tools.create_pr.VikunjaClient")
|
|
||||||
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
|
||||||
def test_pagination_not_found(self, mock_client_cls: MagicMock) -> None:
|
|
||||||
from devx.config import DEFAULT_PER_PAGE
|
|
||||||
|
|
||||||
mock_client = MagicMock()
|
|
||||||
page1 = [{"identifier": f"OTHER-{i}"} for i in range(DEFAULT_PER_PAGE)]
|
|
||||||
page2 = [{"identifier": "OTHER-99"}]
|
|
||||||
mock_client.list_project_tasks.side_effect = [page1, page2]
|
|
||||||
mock_client_cls.return_value = mock_client
|
mock_client_cls.return_value = mock_client
|
||||||
with pytest.raises(click.ClickException, match="Could not find"):
|
with pytest.raises(click.ClickException, match="Could not find"):
|
||||||
get_vikunja_task_title("DEVX-42")
|
get_vikunja_task_title("DEVX-42")
|
||||||
|
|||||||
@@ -63,13 +63,19 @@ class TestDownloadBinary:
|
|||||||
|
|
||||||
class TestMain:
|
class TestMain:
|
||||||
def test_already_installed(self) -> None:
|
def test_already_installed(self) -> None:
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
with patch("shutil.which", return_value="/usr/bin/checkmake"):
|
with patch("shutil.which", return_value="/usr/bin/checkmake"):
|
||||||
install_checkmake.main()
|
runner = CliRunner()
|
||||||
|
runner.invoke(install_checkmake.cli, [])
|
||||||
|
|
||||||
def test_install_with_go(self) -> None:
|
def test_install_with_go(self) -> None:
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
with patch("shutil.which", side_effect=[None, "/usr/bin/go"]):
|
with patch("shutil.which", side_effect=[None, "/usr/bin/go"]):
|
||||||
with patch("subprocess.run") as mock_run:
|
with patch("subprocess.run") as mock_run:
|
||||||
install_checkmake.main()
|
runner = CliRunner()
|
||||||
|
runner.invoke(install_checkmake.cli, [])
|
||||||
mock_run.assert_called_once_with(
|
mock_run.assert_called_once_with(
|
||||||
[
|
[
|
||||||
"/usr/bin/go",
|
"/usr/bin/go",
|
||||||
@@ -80,6 +86,8 @@ class TestMain:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_download_when_no_go(self, tmp_path: Path) -> None:
|
def test_download_when_no_go(self, tmp_path: Path) -> None:
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
target = tmp_path / "checkmake"
|
target = tmp_path / "checkmake"
|
||||||
|
|
||||||
def _write_file(url: str, path: str) -> tuple[str, None]:
|
def _write_file(url: str, path: str) -> tuple[str, None]:
|
||||||
@@ -90,5 +98,6 @@ class TestMain:
|
|||||||
with patch("shutil.which", side_effect=[None, None]):
|
with patch("shutil.which", side_effect=[None, None]):
|
||||||
with patch.object(platform, "machine", return_value="x86_64"):
|
with patch.object(platform, "machine", return_value="x86_64"):
|
||||||
with patch("urllib.request.urlretrieve", side_effect=_write_file) as mock_retrieve:
|
with patch("urllib.request.urlretrieve", side_effect=_write_file) as mock_retrieve:
|
||||||
install_checkmake.main()
|
runner = CliRunner()
|
||||||
|
runner.invoke(install_checkmake.cli, [])
|
||||||
mock_retrieve.assert_called_once()
|
mock_retrieve.assert_called_once()
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class TestTaskExists:
|
|||||||
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
||||||
def test_found(self, mock_client_cls: MagicMock) -> None:
|
def test_found(self, mock_client_cls: MagicMock) -> None:
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_client.list_project_tasks.return_value = [{"identifier": "DEVX-42"}]
|
mock_client.find_task_by_identifier.return_value = {"identifier": "DEVX-42"}
|
||||||
mock_client_cls.return_value = mock_client
|
mock_client_cls.return_value = mock_client
|
||||||
assert task_exists("DEVX-42") is True
|
assert task_exists("DEVX-42") is True
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class TestTaskExists:
|
|||||||
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
||||||
def test_not_found(self, mock_client_cls: MagicMock) -> None:
|
def test_not_found(self, mock_client_cls: MagicMock) -> None:
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_client.list_project_tasks.return_value = [{"identifier": "DEVX-99"}]
|
mock_client.find_task_by_identifier.return_value = None
|
||||||
mock_client_cls.return_value = mock_client
|
mock_client_cls.return_value = mock_client
|
||||||
assert task_exists("DEVX-42") is False
|
assert task_exists("DEVX-42") is False
|
||||||
|
|
||||||
@@ -59,37 +59,6 @@ class TestTaskExists:
|
|||||||
def test_no_token(self) -> None:
|
def test_no_token(self) -> None:
|
||||||
assert task_exists("DEVX-42") is False
|
assert task_exists("DEVX-42") is False
|
||||||
|
|
||||||
@patch("devx.tools.pre_push_check.VikunjaClient")
|
|
||||||
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
|
||||||
def test_pagination(self, mock_client_cls: MagicMock) -> None:
|
|
||||||
mock_client = MagicMock()
|
|
||||||
# First page: full page (50 items, none matching), second page: match
|
|
||||||
page1 = [{"identifier": f"OTHER-{i}"} for i in range(50)]
|
|
||||||
page2 = [{"identifier": "DEVX-42"}]
|
|
||||||
mock_client.list_project_tasks.side_effect = [page1, page2]
|
|
||||||
mock_client_cls.return_value = mock_client
|
|
||||||
assert task_exists("DEVX-42") is True
|
|
||||||
|
|
||||||
@patch("devx.tools.pre_push_check.VikunjaClient")
|
|
||||||
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
|
||||||
def test_empty_project(self, mock_client_cls: MagicMock) -> None:
|
|
||||||
mock_client = MagicMock()
|
|
||||||
mock_client.list_project_tasks.return_value = []
|
|
||||||
mock_client_cls.return_value = mock_client
|
|
||||||
assert task_exists("DEVX-42") is False
|
|
||||||
|
|
||||||
@patch("devx.tools.pre_push_check.VikunjaClient")
|
|
||||||
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
|
||||||
def test_pagination_not_found(self, mock_client_cls: MagicMock) -> None:
|
|
||||||
from devx.config import DEFAULT_PER_PAGE
|
|
||||||
|
|
||||||
mock_client = MagicMock()
|
|
||||||
page1 = [{"identifier": f"OTHER-{i}"} for i in range(DEFAULT_PER_PAGE)]
|
|
||||||
page2 = [{"identifier": "OTHER-99"}]
|
|
||||||
mock_client.list_project_tasks.side_effect = [page1, page2]
|
|
||||||
mock_client_cls.return_value = mock_client
|
|
||||||
assert task_exists("DEVX-42") is False
|
|
||||||
|
|
||||||
|
|
||||||
class TestValidate:
|
class TestValidate:
|
||||||
def test_master_branch_skips(self) -> None:
|
def test_master_branch_skips(self) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user