devops-bot-sdk 1.4.139__tar.gz → 1.4.141__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/devops_bot_sdk.egg-info/SOURCES.txt +2 -1
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/git_ops.py +33 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/api.py +42 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/models.py +39 -0
- devops_bot_sdk-1.4.141/tests/test_git_action.py +103 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/README.md +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/pyproject.toml +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/client.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/setup.cfg +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/tests/test_agents_connectors_builder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
|
|
2
2
|
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.141
|
|
4
4
|
|
|
5
5
|
Public surface:
|
|
6
6
|
BackendClient.from_config() — create client from ~/.agentos/config.toml
|
|
@@ -30,7 +30,7 @@ Rules:
|
|
|
30
30
|
- All data egress through submit_webhook only
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
__version__ = "1.4.
|
|
33
|
+
__version__ = "1.4.141" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
|
|
34
34
|
__author__ = "AgentOS" # sdk.client.SDK_VERSION and the UI server version all read this.
|
|
35
35
|
|
|
36
36
|
from sdk.client import BackendClient
|
|
@@ -410,3 +410,36 @@ async def finish_pr(
|
|
|
410
410
|
|
|
411
411
|
return {"pushed": pushed, "pr_url": pr_url, "pr_error": pr_error,
|
|
412
412
|
"committed": committed, "pr_unavailable": pr_unavailable}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
async def push_branch(
|
|
416
|
+
project_path: str, branch: str, github_token: str | None,
|
|
417
|
+
title: str = "chore: sync work",
|
|
418
|
+
) -> dict:
|
|
419
|
+
"""Stage all, commit, and push ``branch`` — WITHOUT opening a PR. Best-effort.
|
|
420
|
+
|
|
421
|
+
The push-only sibling of :func:`finish_pr`: same commit + ``--force-with-lease``
|
|
422
|
+
push transport (HTTPS+token when a token is available, else the box's SSH keys),
|
|
423
|
+
but it deliberately never opens, merges, or touches a pull request. Used by the
|
|
424
|
+
cross-VPS ``git_push`` action (a Lead pushing a member's work to the remote
|
|
425
|
+
without raising a PR).
|
|
426
|
+
|
|
427
|
+
Returns ``{"pushed": bool, "committed": bool, "branch": str,
|
|
428
|
+
"push_error": str | None}`` — never raises.
|
|
429
|
+
"""
|
|
430
|
+
path = str(Path(project_path).expanduser())
|
|
431
|
+
env = _git_env(github_token)
|
|
432
|
+
|
|
433
|
+
await _run(["git", "add", "-A"], path, env)
|
|
434
|
+
commit_rc, _, _ = await _run(["git", "commit", "-m", title], path, env)
|
|
435
|
+
committed = commit_rc == 0
|
|
436
|
+
|
|
437
|
+
rc, _, push_err = await _run(
|
|
438
|
+
["git", "push", "-u", "origin", branch, "--force-with-lease"],
|
|
439
|
+
path, env, timeout=300.0,
|
|
440
|
+
)
|
|
441
|
+
pushed = rc == 0
|
|
442
|
+
if not pushed:
|
|
443
|
+
logger.warning("git_ops.push_only_failed branch=%s error=%s", branch, push_err[:300])
|
|
444
|
+
return {"pushed": pushed, "committed": committed, "branch": branch,
|
|
445
|
+
"push_error": None if pushed else f"push failed: {push_err[:200]}"}
|
|
@@ -19,6 +19,7 @@ from sdk.ui.models import (
|
|
|
19
19
|
AgentRunRequest,
|
|
20
20
|
AllowlistEntry,
|
|
21
21
|
ConfigureRequest,
|
|
22
|
+
GitActionRequest,
|
|
22
23
|
PromptRegenerateRequest,
|
|
23
24
|
PromptRunRequest,
|
|
24
25
|
PromptVerifyRequest,
|
|
@@ -142,6 +143,47 @@ async def remove_from_allowlist(ip: str = Query(...)) -> dict:
|
|
|
142
143
|
raise HTTPException(status_code=400, detail=str(exc))
|
|
143
144
|
|
|
144
145
|
|
|
146
|
+
# ── GitHub trigger (PR-only / push-only; never merges) ───────────────────────
|
|
147
|
+
# Standalone git action for the cross-VPS `git_pr` / `git_push` intents: a Lead
|
|
148
|
+
# opens a PR or pushes a member's work on THIS VPS. Delegates to `git_ops`
|
|
149
|
+
# (finish_pr / push_branch — both best-effort, neither ever merges). The GitHub
|
|
150
|
+
# token is fetched server-side from the backend, never taken from the request.
|
|
151
|
+
|
|
152
|
+
@router.post("/git", dependencies=[_auth])
|
|
153
|
+
async def git_action(body: GitActionRequest) -> dict:
|
|
154
|
+
"""Open a PR (``operation="pr"``) or push a branch (``operation="push"``).
|
|
155
|
+
|
|
156
|
+
Body: ``GitActionRequest{operation, project_path, branch, base?, title?, body?,
|
|
157
|
+
repo_url?}``. Returns the ``git_ops`` result verbatim (``pushed``, ``pr_url``,
|
|
158
|
+
``pr_unavailable`` …). An SSH-only box with no token/``gh`` reports
|
|
159
|
+
``pr_unavailable`` (a soft warning), not an error. 502 if the path is not a
|
|
160
|
+
usable git repo.
|
|
161
|
+
"""
|
|
162
|
+
from sdk.client import BackendClient
|
|
163
|
+
from sdk.git_ops import ensure_repo, finish_pr, push_branch
|
|
164
|
+
|
|
165
|
+
github_token = await BackendClient.from_config().github_token()
|
|
166
|
+
|
|
167
|
+
ok = await ensure_repo(body.project_path, body.repo_url or None, github_token)
|
|
168
|
+
if not ok:
|
|
169
|
+
raise HTTPException(
|
|
170
|
+
status_code=502,
|
|
171
|
+
detail=f"{body.project_path} is not a usable git repo (and could not be cloned).",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
title = body.title or f"chore: {body.operation} {body.branch}"
|
|
175
|
+
if body.operation == "push":
|
|
176
|
+
result = await push_branch(body.project_path, body.branch, github_token, title=title)
|
|
177
|
+
else:
|
|
178
|
+
# base blank → pass the branch so finish_pr's base==head guard resolves
|
|
179
|
+
# the real default branch (never opens a PR against itself).
|
|
180
|
+
base = body.base or body.branch
|
|
181
|
+
result = await finish_pr(
|
|
182
|
+
body.project_path, body.branch, base, title, body.body, github_token,
|
|
183
|
+
)
|
|
184
|
+
return {"engine": "git", "operation": body.operation, **result}
|
|
185
|
+
|
|
186
|
+
|
|
145
187
|
@router.post("/configure", dependencies=[_auth])
|
|
146
188
|
async def configure(body: ConfigureRequest) -> dict:
|
|
147
189
|
from sdk import config as sdk_config
|
|
@@ -93,6 +93,45 @@ class WorkspaceRequest(BaseModel):
|
|
|
93
93
|
return value or None
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
class GitActionRequest(BaseModel):
|
|
97
|
+
"""Body for ``POST /api/ui/git`` — a standalone GitHub trigger.
|
|
98
|
+
|
|
99
|
+
Mirrors the cross-VPS ``git_pr`` / ``git_push`` actions (devops-bot-ai
|
|
100
|
+
``modules/cross_vps/action_router.py``). PR-only or push-only; the SDK NEVER
|
|
101
|
+
merges (see ``git_ops.finish_pr`` contract). The GitHub token is fetched
|
|
102
|
+
server-side (``BackendClient.github_token``) — it is not accepted in the body.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
operation: str = Field(..., examples=["pr", "push"])
|
|
106
|
+
project_path: str = Field(..., description="Absolute path to the repo working dir on this VPS.")
|
|
107
|
+
branch: str = Field(..., description="The feature branch to commit + push (never the base).")
|
|
108
|
+
base: str = Field("", description="PR base branch; the default branch is resolved when blank.")
|
|
109
|
+
title: str = Field("", description="Commit message + PR title.")
|
|
110
|
+
body: str = Field("", description="PR description (ignored for push).")
|
|
111
|
+
repo_url: str = Field("", description="Optional clone URL if the path is empty/missing.")
|
|
112
|
+
|
|
113
|
+
@field_validator("operation")
|
|
114
|
+
@classmethod
|
|
115
|
+
def _op(cls, value: str) -> str:
|
|
116
|
+
value = (value or "").strip().lower()
|
|
117
|
+
if value not in {"pr", "push"}:
|
|
118
|
+
raise ValueError("operation must be 'pr' or 'push'.")
|
|
119
|
+
return value
|
|
120
|
+
|
|
121
|
+
@field_validator("project_path", "branch")
|
|
122
|
+
@classmethod
|
|
123
|
+
def _required(cls, value: str) -> str:
|
|
124
|
+
value = (value or "").strip()
|
|
125
|
+
if not value:
|
|
126
|
+
raise ValueError("project_path and branch are required and must be non-empty.")
|
|
127
|
+
return value
|
|
128
|
+
|
|
129
|
+
@field_validator("base", "title", "body", "repo_url")
|
|
130
|
+
@classmethod
|
|
131
|
+
def _strip_opt(cls, value: str) -> str:
|
|
132
|
+
return (value or "").strip()
|
|
133
|
+
|
|
134
|
+
|
|
96
135
|
class TicketRequest(BaseModel):
|
|
97
136
|
"""Body for ``POST /api/ui/ticket`` — one or more Jira ticket keys."""
|
|
98
137
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Tests for the standalone GitHub trigger — model + route (PRD-3 / DBAI-103).
|
|
2
|
+
|
|
3
|
+
``POST /api/ui/git`` (``sdk/ui/api.py``) backs the cross-VPS ``git_pr`` / ``git_push``
|
|
4
|
+
actions: the devops-bot-ai action router proxies here so a Lead can open a PR or
|
|
5
|
+
push a member's work on this VPS. The SDK NEVER merges (``git_ops.finish_pr`` /
|
|
6
|
+
``push_branch`` contract). The GitHub token is fetched server-side, never taken
|
|
7
|
+
from the request body.
|
|
8
|
+
|
|
9
|
+
Validation tests need only ``pydantic``; the route test needs the ``[ui]`` extra
|
|
10
|
+
(``fastapi``) and is skipped when it is absent.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from unittest.mock import AsyncMock, patch
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
from pydantic import ValidationError
|
|
18
|
+
|
|
19
|
+
from sdk.ui.models import GitActionRequest
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ── GitActionRequest validation ───────────────────────────────────────
|
|
23
|
+
class TestGitActionRequest:
|
|
24
|
+
def test_pr_minimal_ok(self):
|
|
25
|
+
m = GitActionRequest(operation="pr", project_path="/srv/repo", branch="feature/x")
|
|
26
|
+
assert (m.operation, m.project_path, m.branch) == ("pr", "/srv/repo", "feature/x")
|
|
27
|
+
assert m.base == "" and m.title == "" and m.body == "" and m.repo_url == ""
|
|
28
|
+
|
|
29
|
+
def test_operation_normalised(self):
|
|
30
|
+
assert GitActionRequest(operation="PUSH", project_path="/r", branch="b").operation == "push"
|
|
31
|
+
|
|
32
|
+
def test_strips_path_and_branch(self):
|
|
33
|
+
m = GitActionRequest(operation="pr", project_path=" /srv/r ", branch=" feat ")
|
|
34
|
+
assert m.project_path == "/srv/r" and m.branch == "feat"
|
|
35
|
+
|
|
36
|
+
@pytest.mark.parametrize("op", ["merge", "rebase", "", "delete"])
|
|
37
|
+
def test_bad_operation_rejected(self, op):
|
|
38
|
+
with pytest.raises(ValidationError):
|
|
39
|
+
GitActionRequest(operation=op, project_path="/r", branch="b")
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize("field", ["project_path", "branch"])
|
|
42
|
+
def test_blank_required_rejected(self, field):
|
|
43
|
+
kwargs = {"operation": "pr", "project_path": "/r", "branch": "b"}
|
|
44
|
+
kwargs[field] = " "
|
|
45
|
+
with pytest.raises(ValidationError):
|
|
46
|
+
GitActionRequest(**kwargs)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ── POST /api/ui/git route ────────────────────────────────────────────
|
|
50
|
+
fastapi = pytest.importorskip("fastapi")
|
|
51
|
+
from fastapi.testclient import TestClient # noqa: E402
|
|
52
|
+
|
|
53
|
+
from sdk.ui.api import require_token, router # noqa: E402
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _app():
|
|
57
|
+
app = fastapi.FastAPI()
|
|
58
|
+
app.include_router(router)
|
|
59
|
+
app.dependency_overrides[require_token] = lambda: None
|
|
60
|
+
return TestClient(app)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TestGitRoute:
|
|
64
|
+
def test_pr_calls_finish_pr_never_merges(self):
|
|
65
|
+
finish = AsyncMock(return_value={"pushed": True, "pr_url": "http://pr/1", "pr_unavailable": False})
|
|
66
|
+
with patch("sdk.client.BackendClient.from_config") as m_cfg, \
|
|
67
|
+
patch("sdk.git_ops.ensure_repo", AsyncMock(return_value=True)), \
|
|
68
|
+
patch("sdk.git_ops.finish_pr", finish), \
|
|
69
|
+
patch("sdk.git_ops.push_branch", AsyncMock()) as m_push:
|
|
70
|
+
m_cfg.return_value.github_token = AsyncMock(return_value="gho_x")
|
|
71
|
+
resp = _app().post("/api/ui/git", json={
|
|
72
|
+
"operation": "pr", "project_path": "/srv/repo", "branch": "feature/x",
|
|
73
|
+
"title": "My PR", "body": "desc",
|
|
74
|
+
})
|
|
75
|
+
assert resp.status_code == 200
|
|
76
|
+
data = resp.json()
|
|
77
|
+
assert data["engine"] == "git" and data["operation"] == "pr" and data["pr_url"] == "http://pr/1"
|
|
78
|
+
finish.assert_awaited_once()
|
|
79
|
+
m_push.assert_not_called()
|
|
80
|
+
|
|
81
|
+
def test_push_calls_push_branch_no_pr(self):
|
|
82
|
+
push = AsyncMock(return_value={"pushed": True, "committed": True, "branch": "feature/x", "push_error": None})
|
|
83
|
+
with patch("sdk.client.BackendClient.from_config") as m_cfg, \
|
|
84
|
+
patch("sdk.git_ops.ensure_repo", AsyncMock(return_value=True)), \
|
|
85
|
+
patch("sdk.git_ops.push_branch", push), \
|
|
86
|
+
patch("sdk.git_ops.finish_pr", AsyncMock()) as m_finish:
|
|
87
|
+
m_cfg.return_value.github_token = AsyncMock(return_value=None)
|
|
88
|
+
resp = _app().post("/api/ui/git", json={
|
|
89
|
+
"operation": "push", "project_path": "/srv/repo", "branch": "feature/x",
|
|
90
|
+
})
|
|
91
|
+
assert resp.status_code == 200
|
|
92
|
+
assert resp.json()["operation"] == "push"
|
|
93
|
+
push.assert_awaited_once()
|
|
94
|
+
m_finish.assert_not_called()
|
|
95
|
+
|
|
96
|
+
def test_unusable_repo_502(self):
|
|
97
|
+
with patch("sdk.client.BackendClient.from_config") as m_cfg, \
|
|
98
|
+
patch("sdk.git_ops.ensure_repo", AsyncMock(return_value=False)):
|
|
99
|
+
m_cfg.return_value.github_token = AsyncMock(return_value=None)
|
|
100
|
+
resp = _app().post("/api/ui/git", json={
|
|
101
|
+
"operation": "pr", "project_path": "/nope", "branch": "b",
|
|
102
|
+
})
|
|
103
|
+
assert resp.status_code == 502
|
|
File without changes
|
{devops_bot_sdk-1.4.139 → devops_bot_sdk-1.4.141}/devops_bot_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|