DEVX-76: feat: add --auto-login to publish, extract configure_tea_login to gitea_cli
This commit is contained in:
@@ -138,9 +138,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
env:
|
run: make setup-image EXTRAS=release
|
||||||
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
|
||||||
run: make setup-release
|
|
||||||
- name: Build and publish release
|
- name: Build and publish release
|
||||||
env:
|
env:
|
||||||
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||||
@@ -148,7 +146,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
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
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -22,14 +22,12 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import subprocess # nosec B404
|
|
||||||
|
|
||||||
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
|
||||||
from devx.gitea_cli import TeaCLI, TeaCLIError
|
from devx.gitea_cli import TeaCLI, TeaCLIError, configure_tea_login
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
@@ -37,49 +35,6 @@ load_dotenv()
|
|||||||
logger = logging.getLogger("devx")
|
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:
|
def _create_issue_via_tea(repo: str, title: str, body: str) -> int:
|
||||||
"""Create issue via tea CLI. Returns issue index.
|
"""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."))
|
raise click.ClickException(_("ERROR: REPO_TOKEN is not set."))
|
||||||
|
|
||||||
if auto_login:
|
if auto_login:
|
||||||
_configure_tea_login()
|
configure_tea_login()
|
||||||
|
|
||||||
title = f"[CI] {workflow} workflow failed (run #{run_id})"
|
title = f"[CI] {workflow} workflow failed (run #{run_id})"
|
||||||
body = (
|
body = (
|
||||||
|
|||||||
+12
-1
@@ -29,7 +29,7 @@ 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
|
||||||
from devx.gitea_cli import TeaCLI, TeaCLIError
|
from devx.gitea_cli import TeaCLI, TeaCLIError, configure_tea_login
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
load_dotenv()
|
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. "
|
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.",
|
"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(
|
def main(
|
||||||
tag: str | None,
|
tag: str | None,
|
||||||
repo: str | None,
|
repo: str | None,
|
||||||
registry_url: str | None,
|
registry_url: str | None,
|
||||||
skip_build: bool,
|
skip_build: bool,
|
||||||
from_tag: bool,
|
from_tag: bool,
|
||||||
|
auto_login: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
if repo is None:
|
if repo is None:
|
||||||
repo = os.environ.get("GITHUB_REPOSITORY", "")
|
repo = os.environ.get("GITHUB_REPOSITORY", "")
|
||||||
@@ -287,6 +295,9 @@ def main(
|
|||||||
|
|
||||||
tea = TeaCLI(repo=repo)
|
tea = TeaCLI(repo=repo)
|
||||||
|
|
||||||
|
if auto_login:
|
||||||
|
configure_tea_login()
|
||||||
|
|
||||||
# Check if release already exists (idempotent — avoids failure when
|
# Check if release already exists (idempotent — avoids failure when
|
||||||
# called multiple times, e.g. by both post-merge and publish workflows)
|
# called multiple times, e.g. by both post-merge and publish workflows)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -40,15 +40,67 @@ Usage::
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess # nosec B404
|
import subprocess # nosec B404
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from devx.config import GITEA_API_URL
|
||||||
|
from devx.i18n import _
|
||||||
|
|
||||||
|
|
||||||
class TeaCLIError(Exception):
|
class TeaCLIError(Exception):
|
||||||
"""Raised when a tea CLI command fails."""
|
"""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:
|
class TeaCLI:
|
||||||
"""Wrapper around the ``tea`` Gitea CLI tool.
|
"""Wrapper around the ``tea`` Gitea CLI tool.
|
||||||
|
|
||||||
|
|||||||
+15
-6
@@ -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-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-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-test-unit devx-pytest-cov
|
||||||
|
.PHONY: devx-setup-image
|
||||||
|
|
||||||
# ── Vikunja task and PR management ────────────────────────────────────────────
|
# ── Vikunja task and PR management ────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -254,17 +255,25 @@ devx-clean:
|
|||||||
#
|
#
|
||||||
# When running inside a pre-built Docker runner image (ci-base, ci-quality,
|
# 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
|
# ci-full), all deps are already installed in /opt/venv. This target links
|
||||||
# the venv and installs the project itself (no-deps, fast).
|
# the venv and installs the project itself (with optional extras).
|
||||||
# Falls back to devx-setup-ci if /opt/venv is not present (local dev).
|
#
|
||||||
|
# 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:
|
devx-setup-image:
|
||||||
@if [ -d /opt/venv ]; then \
|
@if [ -d /opt/venv ]; then \
|
||||||
ln -sf /opt/venv $(DEVX_VENV); \
|
ln -sf /opt/venv $(DEVX_VENV); \
|
||||||
. $(DEVX_BIN)/activate && pip install -e . --no-deps 2>/dev/null; \
|
. $(DEVX_BIN)/activate && pip install -e .$(if $(EXTRAS),[$(EXTRAS)],) 2>/dev/null; \
|
||||||
echo "[devx-setup-image] Linked /opt/venv and installed project (no-deps)."; \
|
echo "[devx-setup-image] Linked /opt/venv and installed project$(if $(EXTRAS), with [$(EXTRAS)],)."; \
|
||||||
else \
|
else \
|
||||||
echo "[devx-setup-image] /opt/venv not found — falling back to devx-setup-ci"; \
|
echo "[devx-setup-image] /opt/venv not found — falling back to setup-ci"; \
|
||||||
$(MAKE) devx-setup-ci; \
|
$(MAKE) setup-ci; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Docker image build / push / cleanup ───────────────────────────────────────
|
# ── Docker image build / push / cleanup ───────────────────────────────────────
|
||||||
|
|||||||
@@ -1998,5 +1998,37 @@
|
|||||||
"pl": "",
|
"pl": "",
|
||||||
"ru": "",
|
"ru": "",
|
||||||
"zh": ""
|
"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
|
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:
|
class TestExtractIssueNumber:
|
||||||
@@ -359,3 +359,37 @@ class TestWhoami:
|
|||||||
mock_result = MagicMock(returncode=0, stdout="testuser", stderr="")
|
mock_result = MagicMock(returncode=0, stdout="testuser", stderr="")
|
||||||
with patch("subprocess.run", return_value=mock_result):
|
with patch("subprocess.run", return_value=mock_result):
|
||||||
assert cli.whoami() == "testuser"
|
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 click.testing import CliRunner
|
||||||
|
|
||||||
from devx.ci.notify_failure import _configure_tea_login, main
|
from devx.ci.notify_failure import main
|
||||||
from devx.gitea_cli import TeaCLIError
|
from devx.gitea_cli import TeaCLIError, configure_tea_login
|
||||||
|
|
||||||
|
|
||||||
class TestNotifyFailure:
|
class TestNotifyFailure:
|
||||||
@@ -116,7 +116,7 @@ class TestNotifyFailure:
|
|||||||
assert "REPO_TOKEN" in result.output
|
assert "REPO_TOKEN" in result.output
|
||||||
|
|
||||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
@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")
|
@patch("devx.ci.notify_failure.TeaCLI")
|
||||||
def test_auto_login_no_tea_skips(self, mock_tea_cls: MagicMock, mock_which: MagicMock) -> None:
|
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."""
|
"""--auto-login with tea not installed skips login and still creates issue."""
|
||||||
@@ -134,7 +134,7 @@ class TestNotifyFailure:
|
|||||||
assert "issue #60" in result.output
|
assert "issue #60" in result.output
|
||||||
|
|
||||||
@patch.dict("os.environ", {"REPO_TOKEN": ""}, clear=True)
|
@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")
|
@patch("devx.ci.notify_failure.TeaCLI")
|
||||||
def test_auto_login_no_token_skips_login(self, mock_tea_cls: MagicMock, mock_which: MagicMock) -> None:
|
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."""
|
"""--auto-login with no REPO_TOKEN skips login but raises before creating issue."""
|
||||||
@@ -152,20 +152,20 @@ class TestNotifyFailure:
|
|||||||
|
|
||||||
class TestConfigureTeaLogin:
|
class TestConfigureTeaLogin:
|
||||||
@patch.dict("os.environ", {"REPO_TOKEN": ""}, clear=True)
|
@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:
|
def test_no_token_skips(self, mock_which: MagicMock) -> None:
|
||||||
"""_configure_tea_login with no token prints skip message and returns."""
|
"""configure_tea_login with no token prints skip message and returns."""
|
||||||
_configure_tea_login()
|
configure_tea_login()
|
||||||
|
|
||||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
@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:
|
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 with no tea binary prints skip message and returns."""
|
||||||
_configure_tea_login()
|
configure_tea_login()
|
||||||
|
|
||||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||||
@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.subprocess.run")
|
@patch("devx.gitea_cli.subprocess.run")
|
||||||
@patch("devx.ci.notify_failure.TeaCLI")
|
@patch("devx.ci.notify_failure.TeaCLI")
|
||||||
def test_auto_login_configures_tea(
|
def test_auto_login_configures_tea(
|
||||||
self, mock_tea_cls: MagicMock, mock_subprocess: MagicMock, mock_which: MagicMock
|
self, mock_tea_cls: MagicMock, mock_subprocess: MagicMock, mock_which: MagicMock
|
||||||
@@ -192,8 +192,8 @@ class TestConfigureTeaLogin:
|
|||||||
assert mock_subprocess.call_count >= 2
|
assert mock_subprocess.call_count >= 2
|
||||||
|
|
||||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||||
@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.subprocess.run")
|
@patch("devx.gitea_cli.subprocess.run")
|
||||||
@patch("devx.ci.notify_failure.TeaCLI")
|
@patch("devx.ci.notify_failure.TeaCLI")
|
||||||
def test_auto_login_skips_if_already_configured(
|
def test_auto_login_skips_if_already_configured(
|
||||||
self, mock_tea_cls: MagicMock, mock_subprocess: MagicMock, mock_which: MagicMock
|
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"])
|
result = runner.invoke(main, ["", "owner/repo", "--skip-build"])
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "Tag is required" in result.output
|
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