Public Access
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66bace57d9 | ||
|
|
5c8d74015e | ||
|
|
48eec986f6 | ||
|
|
1fc1cfb23f | ||
|
|
f24b6914f6 | ||
|
|
84d02ca221 | ||
|
|
18ac8f4ba9 | ||
|
|
9fb9be9c35 | ||
|
|
9a46723391 | ||
|
|
5828d3f07b | ||
|
|
4232f4baee | ||
|
|
a9fd1a47af | ||
|
|
ecd10241fb | ||
|
|
5fb497d108 | ||
|
|
7d4c32c761 | ||
|
|
cb8af53c26 | ||
|
|
954ede87a7 | ||
|
|
eb30faf027 |
@@ -102,6 +102,8 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up environment
|
||||
env:
|
||||
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||
run: make setup-image
|
||||
- name: Release dry-run validation
|
||||
env:
|
||||
@@ -109,7 +111,7 @@ jobs:
|
||||
run: |
|
||||
. .venv/bin/activate
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
python3 -m devx.ci.release --dry-run || true
|
||||
python3 -m devx.ci.release --dry-run
|
||||
|
||||
pr-review:
|
||||
if: github.event_name == 'pull_request'
|
||||
@@ -138,12 +140,13 @@ jobs:
|
||||
# Auto-merge runs after all CI checks pass. It reads the task ID
|
||||
# from the branch name, validates the PR title, and squash-merges.
|
||||
# Uses always() so it runs even when detect-changes skips (no user-facing changes).
|
||||
needs: [quality, detect-changes, pr-review]
|
||||
needs: [quality, detect-changes, pr-review, release-dry-run]
|
||||
if: >-
|
||||
always() &&
|
||||
github.event_name == 'pull_request' &&
|
||||
needs.quality.result == 'success' &&
|
||||
needs.pr-review.result == 'success'
|
||||
needs.pr-review.result == 'success' &&
|
||||
(needs.release-dry-run.result == 'success' || needs.release-dry-run.result == 'skipped')
|
||||
runs-on: docker
|
||||
container: git.oblachno.oblachno.fyi/oblachno-oss/runner-images/ci-base:latest
|
||||
timeout-minutes: 10
|
||||
|
||||
@@ -110,13 +110,6 @@ jobs:
|
||||
. .venv/bin/activate
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
python3 -m devx.ci.release
|
||||
- name: Extract tag (fallback if GITHUB_OUTPUT not set)
|
||||
if: steps.release-tag.outputs.tag == ''
|
||||
run: |
|
||||
tag=$(git describe --tags --abbrev=0 2>/dev/null || true)
|
||||
if [ -n "$tag" ]; then
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- name: Notify on failure
|
||||
if: failure()
|
||||
env:
|
||||
@@ -145,9 +138,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up environment
|
||||
env:
|
||||
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||
run: make setup-image
|
||||
run: make setup-image EXTRAS=release
|
||||
- name: Build and publish release
|
||||
env:
|
||||
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||
@@ -155,7 +146,7 @@ jobs:
|
||||
run: |
|
||||
. .venv/bin/activate
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
python3 -m devx.ci.publish "${{ needs.release.outputs.tag }}" "${{ github.repository }}"
|
||||
python3 -m devx.ci.publish "${{ needs.release.outputs.tag }}" "${{ github.repository }}" --auto-login
|
||||
- name: Notify on failure
|
||||
if: failure()
|
||||
env:
|
||||
|
||||
@@ -2,6 +2,29 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.21.2] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Gate auto-merge on release-dry-run and unmask failures
|
||||
|
||||
## [0.21.1] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Devx-setup-image configures Gitea PyPI registry and shows pip errors
|
||||
|
||||
## [0.21.0] - 2026-06-27
|
||||
|
||||
### Features
|
||||
|
||||
- Add --auto-login to publish, extract configure_tea_login to gitea_cli
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Publish job uses setup-release for build + tea login
|
||||
- Remove tag fallback step from release workflow
|
||||
|
||||
## [0.20.3] - 2026-06-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -16,12 +16,12 @@ quality badges.
|
||||
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
## Why devx?
|
||||
|
||||
|
||||
+6
-6
@@ -12,12 +12,12 @@ project to be reusable across all oblachno-oss repositories.
|
||||
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""devx — reusable development and CI/CD tools for oblachno-oss projects."""
|
||||
|
||||
__version__ = "0.20.3"
|
||||
__version__ = "0.21.2"
|
||||
|
||||
@@ -22,14 +22,12 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import subprocess # nosec B404
|
||||
|
||||
import click
|
||||
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
||||
|
||||
from devx.config import GITEA_API_URL
|
||||
from devx.gitea_cli import TeaCLI, TeaCLIError
|
||||
from devx.gitea_cli import TeaCLI, TeaCLIError, configure_tea_login
|
||||
from devx.i18n import _
|
||||
|
||||
load_dotenv()
|
||||
@@ -37,49 +35,6 @@ load_dotenv()
|
||||
logger = logging.getLogger("devx")
|
||||
|
||||
|
||||
def _configure_tea_login(login_name: str = "devx") -> None:
|
||||
"""Configure tea CLI login from REPO_TOKEN and DEVX_GITEA_API_URL.
|
||||
|
||||
Idempotent: if a login with the same name already exists, it is not re-added.
|
||||
Skips silently if tea is not installed or REPO_TOKEN is not set.
|
||||
"""
|
||||
tea_bin = shutil.which("tea")
|
||||
if tea_bin is None:
|
||||
click.echo("notify_failure: tea not installed — skipping login configuration.")
|
||||
return
|
||||
|
||||
token = os.environ.get("REPO_TOKEN", "")
|
||||
if not token:
|
||||
click.echo("notify_failure: REPO_TOKEN not set — skipping login configuration.")
|
||||
return
|
||||
|
||||
gitea_url = GITEA_API_URL.replace("/api/v1", "")
|
||||
|
||||
result = subprocess.run( # nosec B603
|
||||
[tea_bin, "login", "list", "--output", "simple"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode == 0 and login_name in result.stdout:
|
||||
click.echo(f"notify_failure: tea login '{login_name}' already configured.")
|
||||
return
|
||||
|
||||
click.echo(f"notify_failure: configuring tea login '{login_name}' for {gitea_url}...")
|
||||
subprocess.run( # nosec B603
|
||||
[tea_bin, "login", "add", "--name", login_name, "--url", gitea_url, "--token", token],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
subprocess.run( # nosec B603
|
||||
[tea_bin, "login", "default", login_name],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
def _create_issue_via_tea(repo: str, title: str, body: str) -> int:
|
||||
"""Create issue via tea CLI. Returns issue index.
|
||||
|
||||
@@ -124,7 +79,7 @@ def main(repo: str, run_id: str, workflow: str, commit: str, auto_login: bool) -
|
||||
raise click.ClickException(_("ERROR: REPO_TOKEN is not set."))
|
||||
|
||||
if auto_login:
|
||||
_configure_tea_login()
|
||||
configure_tea_login()
|
||||
|
||||
title = f"[CI] {workflow} workflow failed (run #{run_id})"
|
||||
body = (
|
||||
|
||||
+12
-1
@@ -29,7 +29,7 @@ import click
|
||||
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
||||
|
||||
from devx.config import GITEA_API_URL
|
||||
from devx.gitea_cli import TeaCLI, TeaCLIError
|
||||
from devx.gitea_cli import TeaCLI, TeaCLIError, configure_tea_login
|
||||
from devx.i18n import _
|
||||
|
||||
load_dotenv()
|
||||
@@ -221,12 +221,20 @@ def is_release_commit(tag: str) -> bool:
|
||||
help="Auto-detect latest tag and check if HEAD is a release commit. "
|
||||
"Skips publish if no tag or HEAD is not a release commit for that tag.",
|
||||
)
|
||||
@click.option(
|
||||
"--auto-login",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Configure tea CLI login from REPO_TOKEN before creating the Gitea release. "
|
||||
"Eliminates the need for a separate tea login step in containerized CI jobs.",
|
||||
)
|
||||
def main(
|
||||
tag: str | None,
|
||||
repo: str | None,
|
||||
registry_url: str | None,
|
||||
skip_build: bool,
|
||||
from_tag: bool,
|
||||
auto_login: bool,
|
||||
) -> None:
|
||||
if repo is None:
|
||||
repo = os.environ.get("GITHUB_REPOSITORY", "")
|
||||
@@ -287,6 +295,9 @@ def main(
|
||||
|
||||
tea = TeaCLI(repo=repo)
|
||||
|
||||
if auto_login:
|
||||
configure_tea_login()
|
||||
|
||||
# Check if release already exists (idempotent — avoids failure when
|
||||
# called multiple times, e.g. by both post-merge and publish workflows)
|
||||
try:
|
||||
|
||||
@@ -40,15 +40,67 @@ Usage::
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess # nosec B404
|
||||
from typing import Any
|
||||
|
||||
import click
|
||||
|
||||
from devx.config import GITEA_API_URL
|
||||
from devx.i18n import _
|
||||
|
||||
|
||||
class TeaCLIError(Exception):
|
||||
"""Raised when a tea CLI command fails."""
|
||||
|
||||
|
||||
def configure_tea_login(login_name: str = "devx") -> None:
|
||||
"""Configure tea CLI login from REPO_TOKEN and DEVX_GITEA_API_URL.
|
||||
|
||||
Idempotent: if a login with the same name already exists, it is not re-added.
|
||||
Skips silently if tea is not installed or REPO_TOKEN is not set.
|
||||
|
||||
Used by CI scripts (publish, notify_failure) that need tea login but
|
||||
run in containerized environments where ``make setup`` was not called.
|
||||
"""
|
||||
tea_bin = shutil.which("tea")
|
||||
if tea_bin is None:
|
||||
click.echo(_("tea not installed — skipping login configuration."))
|
||||
return
|
||||
|
||||
token = os.environ.get("REPO_TOKEN", "")
|
||||
if not token:
|
||||
click.echo(_("REPO_TOKEN not set — skipping login configuration."))
|
||||
return
|
||||
|
||||
gitea_url = GITEA_API_URL.replace("/api/v1", "")
|
||||
|
||||
result = subprocess.run( # nosec B603
|
||||
[tea_bin, "login", "list", "--output", "simple"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode == 0 and login_name in result.stdout:
|
||||
click.echo(_("tea login '{name}' already configured.", name=login_name))
|
||||
return
|
||||
|
||||
click.echo(_("Configuring tea login '{name}' for {url}...", name=login_name, url=gitea_url))
|
||||
subprocess.run( # nosec B603
|
||||
[tea_bin, "login", "add", "--name", login_name, "--url", gitea_url, "--token", token],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
subprocess.run( # nosec B603
|
||||
[tea_bin, "login", "default", login_name],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
class TeaCLI:
|
||||
"""Wrapper around the ``tea`` Gitea CLI tool.
|
||||
|
||||
|
||||
+17
-10
@@ -69,6 +69,7 @@ DEVX_PIP_INSTALL := if [ -z "$$REPO_TOKEN" ]; then . ./.env 2>/dev/null; fi; \
|
||||
.PHONY: devx-clean devx-pre-push
|
||||
.PHONY: devx-check-mutable-globals devx-check-dep-docs devx-check-test-coverage devx-check-docs devx-check-test-speed
|
||||
.PHONY: devx-test-unit devx-pytest-cov
|
||||
.PHONY: devx-setup-image
|
||||
|
||||
# ── Vikunja task and PR management ────────────────────────────────────────────
|
||||
|
||||
@@ -254,18 +255,24 @@ devx-clean:
|
||||
#
|
||||
# When running inside a pre-built Docker runner image (ci-base, ci-quality,
|
||||
# ci-full), all deps are already installed in /opt/venv. This target links
|
||||
# the venv and installs the project itself (no-deps, fast).
|
||||
# Falls back to devx-setup-ci if /opt/venv is not present (local dev).
|
||||
# the venv and installs the project itself (with optional extras).
|
||||
#
|
||||
# Usage:
|
||||
# make devx-setup-image (runtime deps only)
|
||||
# make devx-setup-image EXTRAS=lint (runtime + lint deps)
|
||||
# make devx-setup-image EXTRAS=ci,lint (runtime + ci + lint deps)
|
||||
#
|
||||
# Falls back to setup-ci if /opt/venv is not present (local dev).
|
||||
# Note: the fallback target name is project-specific (setup-ci, not
|
||||
# devx-setup-ci) — each project defines its own setup-ci target.
|
||||
|
||||
devx-setup-image:
|
||||
@if [ -d /opt/venv ]; then \
|
||||
ln -sf /opt/venv $(DEVX_VENV); \
|
||||
. $(DEVX_BIN)/activate && pip install -e . --no-deps 2>/dev/null; \
|
||||
echo "[devx-setup-image] Linked /opt/venv and installed project (no-deps)."; \
|
||||
else \
|
||||
echo "[devx-setup-image] /opt/venv not found — falling back to devx-setup-ci"; \
|
||||
$(MAKE) devx-setup-ci; \
|
||||
fi
|
||||
@if [ -d /opt/venv ]; then ln -sf /opt/venv $(DEVX_VENV); . $(DEVX_BIN)/activate; \
|
||||
_U="$${DEVX_GITEA_PYPI_USER:-$${GITEA_PYPI_USER:-emil}}"; \
|
||||
if [ -n "$$REPO_TOKEN" ]; then export PIP_EXTRA_INDEX_URL="https://$$_U:$$REPO_TOKEN@$(DEVX_GITEA_PYPI_HOST)/api/packages/$(DEVX_GITEA_PYPI_ORG)/pypi/simple/"; fi; \
|
||||
pip install -e .$(if $(EXTRAS),[$(EXTRAS)],); \
|
||||
echo "[devx-setup-image] Linked /opt/venv$(if $(EXTRAS), with [$(EXTRAS)],)."; \
|
||||
else echo "[devx-setup-image] /opt/venv not found — falling back to setup-ci"; $(MAKE) setup-ci; fi
|
||||
|
||||
# ── Docker image build / push / cleanup ───────────────────────────────────────
|
||||
#
|
||||
|
||||
@@ -1998,5 +1998,37 @@
|
||||
"pl": "",
|
||||
"ru": "",
|
||||
"zh": ""
|
||||
},
|
||||
"tea not installed — skipping login configuration.": {
|
||||
"bg": "tea not installed — skipping login configuration.",
|
||||
"de": "tea not installed — skipping login configuration.",
|
||||
"en": "tea not installed — skipping login configuration.",
|
||||
"pl": "tea not installed — skipping login configuration.",
|
||||
"ru": "tea not installed — skipping login configuration.",
|
||||
"zh": "tea not installed — skipping login configuration."
|
||||
},
|
||||
"REPO_TOKEN not set — skipping login configuration.": {
|
||||
"bg": "REPO_TOKEN not set — skipping login configuration.",
|
||||
"de": "REPO_TOKEN not set — skipping login configuration.",
|
||||
"en": "REPO_TOKEN not set — skipping login configuration.",
|
||||
"pl": "REPO_TOKEN not set — skipping login configuration.",
|
||||
"ru": "REPO_TOKEN not set — skipping login configuration.",
|
||||
"zh": "REPO_TOKEN not set — skipping login configuration."
|
||||
},
|
||||
"tea login '{name}' already configured.": {
|
||||
"bg": "tea login '{name}' already configured.",
|
||||
"de": "tea login '{name}' already configured.",
|
||||
"en": "tea login '{name}' already configured.",
|
||||
"pl": "tea login '{name}' already configured.",
|
||||
"ru": "tea login '{name}' already configured.",
|
||||
"zh": "tea login '{name}' already configured."
|
||||
},
|
||||
"Configuring tea login '{name}' for {url}...": {
|
||||
"bg": "Configuring tea login '{name}' for {url}...",
|
||||
"de": "Configuring tea login '{name}' for {url}...",
|
||||
"en": "Configuring tea login '{name}' for {url}...",
|
||||
"pl": "Configuring tea login '{name}' for {url}...",
|
||||
"ru": "Configuring tea login '{name}' for {url}...",
|
||||
"zh": "Configuring tea login '{name}' for {url}..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from devx.gitea_cli import TeaCLI, TeaCLIError, _extract_issue_number, _extract_pr_number
|
||||
from devx.gitea_cli import TeaCLI, TeaCLIError, _extract_issue_number, _extract_pr_number, configure_tea_login
|
||||
|
||||
|
||||
class TestExtractIssueNumber:
|
||||
@@ -359,3 +359,37 @@ class TestWhoami:
|
||||
mock_result = MagicMock(returncode=0, stdout="testuser", stderr="")
|
||||
with patch("subprocess.run", return_value=mock_result):
|
||||
assert cli.whoami() == "testuser"
|
||||
|
||||
|
||||
class TestConfigureTeaLogin:
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": ""}, clear=True)
|
||||
@patch("devx.gitea_cli.shutil.which", return_value="/usr/bin/tea")
|
||||
def test_no_token_skips(self, mock_which: MagicMock) -> None:
|
||||
"""configure_tea_login with no token prints skip message and returns."""
|
||||
configure_tea_login()
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||
@patch("devx.gitea_cli.shutil.which", return_value=None)
|
||||
def test_no_tea_skips(self, mock_which: MagicMock) -> None:
|
||||
"""configure_tea_login with no tea binary prints skip message and returns."""
|
||||
configure_tea_login()
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||
@patch("devx.gitea_cli.shutil.which", return_value="/usr/bin/tea")
|
||||
@patch("devx.gitea_cli.subprocess.run")
|
||||
def test_configures_login_when_not_present(self, mock_subprocess: MagicMock, mock_which: MagicMock) -> None:
|
||||
"""configure_tea_login adds login when not already configured."""
|
||||
mock_list = MagicMock(returncode=0, stdout="")
|
||||
mock_subprocess.return_value = mock_list
|
||||
configure_tea_login()
|
||||
assert mock_subprocess.call_count >= 2 # login list + login add + login default
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||
@patch("devx.gitea_cli.shutil.which", return_value="/usr/bin/tea")
|
||||
@patch("devx.gitea_cli.subprocess.run")
|
||||
def test_skips_when_already_configured(self, mock_subprocess: MagicMock, mock_which: MagicMock) -> None:
|
||||
"""configure_tea_login skips if login already exists."""
|
||||
mock_list = MagicMock(returncode=0, stdout="devx https://git.example.com")
|
||||
mock_subprocess.return_value = mock_list
|
||||
configure_tea_login()
|
||||
assert mock_subprocess.call_count == 1 # only login list, no add
|
||||
|
||||
@@ -4,8 +4,8 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
from devx.ci.notify_failure import _configure_tea_login, main
|
||||
from devx.gitea_cli import TeaCLIError
|
||||
from devx.ci.notify_failure import main
|
||||
from devx.gitea_cli import TeaCLIError, configure_tea_login
|
||||
|
||||
|
||||
class TestNotifyFailure:
|
||||
@@ -116,7 +116,7 @@ class TestNotifyFailure:
|
||||
assert "REPO_TOKEN" in result.output
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||
@patch("devx.ci.notify_failure.shutil.which", return_value=None)
|
||||
@patch("devx.gitea_cli.shutil.which", return_value=None)
|
||||
@patch("devx.ci.notify_failure.TeaCLI")
|
||||
def test_auto_login_no_tea_skips(self, mock_tea_cls: MagicMock, mock_which: MagicMock) -> None:
|
||||
"""--auto-login with tea not installed skips login and still creates issue."""
|
||||
@@ -134,7 +134,7 @@ class TestNotifyFailure:
|
||||
assert "issue #60" in result.output
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": ""}, clear=True)
|
||||
@patch("devx.ci.notify_failure.shutil.which", return_value="/usr/bin/tea")
|
||||
@patch("devx.gitea_cli.shutil.which", return_value="/usr/bin/tea")
|
||||
@patch("devx.ci.notify_failure.TeaCLI")
|
||||
def test_auto_login_no_token_skips_login(self, mock_tea_cls: MagicMock, mock_which: MagicMock) -> None:
|
||||
"""--auto-login with no REPO_TOKEN skips login but raises before creating issue."""
|
||||
@@ -152,20 +152,20 @@ class TestNotifyFailure:
|
||||
|
||||
class TestConfigureTeaLogin:
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": ""}, clear=True)
|
||||
@patch("devx.ci.notify_failure.shutil.which", return_value="/usr/bin/tea")
|
||||
@patch("devx.gitea_cli.shutil.which", return_value="/usr/bin/tea")
|
||||
def test_no_token_skips(self, mock_which: MagicMock) -> None:
|
||||
"""_configure_tea_login with no token prints skip message and returns."""
|
||||
_configure_tea_login()
|
||||
"""configure_tea_login with no token prints skip message and returns."""
|
||||
configure_tea_login()
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||
@patch("devx.ci.notify_failure.shutil.which", return_value=None)
|
||||
@patch("devx.gitea_cli.shutil.which", return_value=None)
|
||||
def test_no_tea_skips(self, mock_which: MagicMock) -> None:
|
||||
"""_configure_tea_login with no tea binary prints skip message and returns."""
|
||||
_configure_tea_login()
|
||||
"""configure_tea_login with no tea binary prints skip message and returns."""
|
||||
configure_tea_login()
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||
@patch("devx.ci.notify_failure.shutil.which", return_value="/usr/bin/tea")
|
||||
@patch("devx.ci.notify_failure.subprocess.run")
|
||||
@patch("devx.gitea_cli.shutil.which", return_value="/usr/bin/tea")
|
||||
@patch("devx.gitea_cli.subprocess.run")
|
||||
@patch("devx.ci.notify_failure.TeaCLI")
|
||||
def test_auto_login_configures_tea(
|
||||
self, mock_tea_cls: MagicMock, mock_subprocess: MagicMock, mock_which: MagicMock
|
||||
@@ -192,8 +192,8 @@ class TestConfigureTeaLogin:
|
||||
assert mock_subprocess.call_count >= 2
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||
@patch("devx.ci.notify_failure.shutil.which", return_value="/usr/bin/tea")
|
||||
@patch("devx.ci.notify_failure.subprocess.run")
|
||||
@patch("devx.gitea_cli.shutil.which", return_value="/usr/bin/tea")
|
||||
@patch("devx.gitea_cli.subprocess.run")
|
||||
@patch("devx.ci.notify_failure.TeaCLI")
|
||||
def test_auto_login_skips_if_already_configured(
|
||||
self, mock_tea_cls: MagicMock, mock_subprocess: MagicMock, mock_which: MagicMock
|
||||
|
||||
@@ -551,3 +551,37 @@ class TestFromTag:
|
||||
result = runner.invoke(main, ["", "owner/repo", "--skip-build"])
|
||||
assert result.exit_code != 0
|
||||
assert "Tag is required" in result.output
|
||||
|
||||
|
||||
class TestPublishAutoLogin:
|
||||
"""Tests for --auto-login flag in publish."""
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||
@patch("devx.ci.publish.configure_tea_login")
|
||||
@patch("devx.ci.publish.TeaCLI")
|
||||
def test_auto_login_calls_configure(self, mock_tea_cls: MagicMock, mock_login: MagicMock) -> None:
|
||||
"""--auto-login calls configure_tea_login before creating release."""
|
||||
mock_tea = MagicMock()
|
||||
mock_tea.list_releases.return_value = []
|
||||
mock_tea.create_release.return_value = {"tag_name": "v1.0.0"}
|
||||
mock_tea_cls.return_value = mock_tea
|
||||
with patch("devx.ci.publish.generate_release_notes", return_value="notes"):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["v1.0.0", "owner/repo", "--skip-build", "--auto-login"])
|
||||
assert result.exit_code == 0
|
||||
mock_login.assert_called_once()
|
||||
|
||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||
@patch("devx.ci.publish.configure_tea_login")
|
||||
@patch("devx.ci.publish.TeaCLI")
|
||||
def test_no_auto_login_skips_configure(self, mock_tea_cls: MagicMock, mock_login: MagicMock) -> None:
|
||||
"""Without --auto-login, configure_tea_login is not called."""
|
||||
mock_tea = MagicMock()
|
||||
mock_tea.list_releases.return_value = []
|
||||
mock_tea.create_release.return_value = {"tag_name": "v1.0.0"}
|
||||
mock_tea_cls.return_value = mock_tea
|
||||
with patch("devx.ci.publish.generate_release_notes", return_value="notes"):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["v1.0.0", "owner/repo", "--skip-build"])
|
||||
assert result.exit_code == 0
|
||||
mock_login.assert_not_called()
|
||||
|
||||
Reference in New Issue
Block a user