minitest-cli 0.8.2__tar.gz → 0.8.3__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.
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/AGENTS.md +1 -1
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/PKG-INFO +1 -1
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/pyproject.toml +1 -1
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/build.py +2 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/build_helpers.py +37 -1
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/build.py +1 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_build_commands.py +73 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/uv.lock +1 -1
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.env.example +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.github/workflows/ci.yml +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.github/workflows/install-scripts.yml +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.github/workflows/release.yml +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.gitignore +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.opencode/skill/release/SKILL.md +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/README.md +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/RELEASE.md +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/install.ps1 +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/install.sh +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/pyrightconfig.json +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/__init__.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/api/__init__.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/api/apps_manager_client.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/api/client.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/assets/__init__.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/assets/callback.html +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/__init__.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/app_knowledge.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/apps.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/apps_helpers.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/auth.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/batch.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/batch_helpers.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/flow_types.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/maintenance_check.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/run.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/run_display.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/run_helpers.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/skill.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/upgrade.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/user_story.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/user_story_helpers.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/user_story_modify.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/__init__.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/app_context.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/auth.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/config.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/credentials.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/oauth.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/tenants.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/token_exchange.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/main.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/__init__.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/app.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/base.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/maintenance_check.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/story_run.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/user_story.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/utils/__init__.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/utils/output.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/utils/skill_refresh.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/utils/update_check.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/__init__.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_app_knowledge_commands.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_apps_commands.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_auth.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_auth_commands.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_batch_commands.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_code_quality.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_flow_types_commands.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_run_commands.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_skill_command.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_upgrade_command.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_user_story_commands.py +0 -0
- {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_version.py +0 -0
|
@@ -49,7 +49,7 @@ uv add <package> # Add new dependency (always
|
|
|
49
49
|
### Output Convention
|
|
50
50
|
- `--json` flag: JSON to stdout, diagnostics to stderr
|
|
51
51
|
- Without `--json`: human-friendly rich tables to stdout, diagnostics to stderr
|
|
52
|
-
- Exit codes: 0=Success, 1=General error, 2=Auth error, 3=Network/API error, 4=Not found
|
|
52
|
+
- Exit codes: 0=Success, 1=General error, 2=Auth error, 3=Network/API error, 4=Not found, 5=Build invalid
|
|
53
53
|
|
|
54
54
|
### No Interactive Prompts
|
|
55
55
|
- All input via flags, env vars, or stdin
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: minitest-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.3
|
|
4
4
|
Summary: Minitest CLI – command-line interface for the Minitest testing platform
|
|
5
5
|
Project-URL: Homepage, https://minitap.ai/
|
|
6
6
|
Project-URL: Source, https://github.com/minitap-ai/minitest-cli
|
|
@@ -14,6 +14,7 @@ from minitest_cli.commands.build_helpers import (
|
|
|
14
14
|
format_build_row,
|
|
15
15
|
format_pagination_info,
|
|
16
16
|
handle_response_error,
|
|
17
|
+
print_validation_warnings,
|
|
17
18
|
resolve_app,
|
|
18
19
|
run_api_call,
|
|
19
20
|
upload_status_message,
|
|
@@ -79,6 +80,7 @@ def upload(
|
|
|
79
80
|
if json_mode:
|
|
80
81
|
output(result_dict, json_mode=True)
|
|
81
82
|
else:
|
|
83
|
+
print_validation_warnings(result.validation_warnings)
|
|
82
84
|
print_success(f"Build uploaded: {file.name} ({resolved_platform})")
|
|
83
85
|
output(result_dict, json_mode=False)
|
|
84
86
|
|
|
@@ -12,10 +12,13 @@ import typer
|
|
|
12
12
|
from minitest_cli.core.app_context import resolve_app_id
|
|
13
13
|
from minitest_cli.core.config import Settings
|
|
14
14
|
from minitest_cli.models import BuildListResponse, BuildResponse
|
|
15
|
-
from minitest_cli.utils.output import print_error
|
|
15
|
+
from minitest_cli.utils.output import err_console, print_error
|
|
16
16
|
|
|
17
17
|
EXIT_NETWORK_ERROR = 3
|
|
18
18
|
EXIT_NOT_FOUND = 4
|
|
19
|
+
EXIT_BUILD_INVALID = 5
|
|
20
|
+
|
|
21
|
+
BUILD_INVALID_ERROR_CODE = "build_invalid"
|
|
19
22
|
|
|
20
23
|
# ---------------------------------------------------------------------------
|
|
21
24
|
# Context accessors
|
|
@@ -93,11 +96,44 @@ def handle_response_error(resp: httpx.Response, *, resource: str = "Build") -> N
|
|
|
93
96
|
if resp.status_code == 404:
|
|
94
97
|
print_error(f"{resource} not found: {extract_detail(resp)}")
|
|
95
98
|
raise typer.Exit(code=EXIT_NOT_FOUND)
|
|
99
|
+
if resp.status_code == 422 and _try_handle_build_invalid(resp):
|
|
100
|
+
raise typer.Exit(code=EXIT_BUILD_INVALID)
|
|
96
101
|
if resp.status_code >= 400:
|
|
97
102
|
print_error(f"API error ({resp.status_code}): {extract_detail(resp)}")
|
|
98
103
|
raise typer.Exit(code=EXIT_NETWORK_ERROR)
|
|
99
104
|
|
|
100
105
|
|
|
106
|
+
def _try_handle_build_invalid(resp: httpx.Response) -> bool:
|
|
107
|
+
"""If response is a build-invalid error, render issues and return True."""
|
|
108
|
+
try:
|
|
109
|
+
body = resp.json()
|
|
110
|
+
except Exception: # noqa: BLE001
|
|
111
|
+
return False
|
|
112
|
+
if not isinstance(body, dict) or body.get("error_code") != BUILD_INVALID_ERROR_CODE:
|
|
113
|
+
return False
|
|
114
|
+
issues = body.get("issues") or []
|
|
115
|
+
print_error("Build rejected: failed validation for virtual-device execution.")
|
|
116
|
+
for issue in issues:
|
|
117
|
+
if not isinstance(issue, dict):
|
|
118
|
+
continue
|
|
119
|
+
code = issue.get("code", "unknown")
|
|
120
|
+
message = issue.get("message", "")
|
|
121
|
+
err_console.print(f" [red]✖[/red] {code}: {message}")
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def print_validation_warnings(warnings: list[dict] | None) -> None:
|
|
126
|
+
"""Print non-fatal validation warnings to stderr."""
|
|
127
|
+
if not warnings:
|
|
128
|
+
return
|
|
129
|
+
for warning in warnings:
|
|
130
|
+
if not isinstance(warning, dict):
|
|
131
|
+
continue
|
|
132
|
+
code = warning.get("code", "unknown")
|
|
133
|
+
message = warning.get("message", "")
|
|
134
|
+
err_console.print(f" [yellow]⚠[/yellow] {code}: {message}")
|
|
135
|
+
|
|
136
|
+
|
|
101
137
|
def run_api_call[T](coro: Coroutine[object, object, T]) -> T:
|
|
102
138
|
"""Run an async API coroutine, catching network errors → exit 3."""
|
|
103
139
|
try:
|
|
@@ -125,6 +125,30 @@ class TestHandleResponseError:
|
|
|
125
125
|
handle_response_error(resp)
|
|
126
126
|
assert exc_info.value.exit_code == 3
|
|
127
127
|
|
|
128
|
+
def test_422_build_invalid_exits_5(self) -> None:
|
|
129
|
+
resp = _mock_response(
|
|
130
|
+
422,
|
|
131
|
+
{
|
|
132
|
+
"error_code": "build_invalid",
|
|
133
|
+
"issues": [
|
|
134
|
+
{
|
|
135
|
+
"code": "android.no_compatible_abi",
|
|
136
|
+
"message": "APK does not include x86_64 ABI.",
|
|
137
|
+
"detail": {},
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
with pytest.raises(Exit) as exc_info:
|
|
143
|
+
handle_response_error(resp)
|
|
144
|
+
assert exc_info.value.exit_code == 5
|
|
145
|
+
|
|
146
|
+
def test_422_other_falls_through(self) -> None:
|
|
147
|
+
resp = _mock_response(422, {"detail": "validation error"})
|
|
148
|
+
with pytest.raises(Exit) as exc_info:
|
|
149
|
+
handle_response_error(resp)
|
|
150
|
+
assert exc_info.value.exit_code == 3
|
|
151
|
+
|
|
128
152
|
|
|
129
153
|
class TestFormatPaginationInfo:
|
|
130
154
|
def test_with_total_shows_page_and_range(self) -> None:
|
|
@@ -279,6 +303,55 @@ class TestUploadCommand:
|
|
|
279
303
|
|
|
280
304
|
assert result.exit_code == 3
|
|
281
305
|
|
|
306
|
+
def test_upload_build_invalid_422_exits_5_and_prints_issues(self, tmp_path: Path) -> None:
|
|
307
|
+
build_file = tmp_path / "bad.apk"
|
|
308
|
+
build_file.write_bytes(b"data")
|
|
309
|
+
settings = _make_settings(tmp_path)
|
|
310
|
+
body = {
|
|
311
|
+
"error_code": "build_invalid",
|
|
312
|
+
"issues": [
|
|
313
|
+
{
|
|
314
|
+
"code": "android.no_compatible_abi",
|
|
315
|
+
"message": "APK does not include x86_64 ABI.",
|
|
316
|
+
"detail": {"abis": ["arm64-v8a"]},
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"code": "android.test_only_apk",
|
|
320
|
+
"message": "APK is marked test-only.",
|
|
321
|
+
"detail": {},
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
}
|
|
325
|
+
client = _mock_upload_client(_mock_response(422, body))
|
|
326
|
+
|
|
327
|
+
with patch("minitest_cli.commands.build.ApiClient", return_value=client):
|
|
328
|
+
result = _run_with_context(["upload", str(build_file)], settings)
|
|
329
|
+
|
|
330
|
+
assert result.exit_code == 5
|
|
331
|
+
combined = result.output + (result.stderr if result.stderr_bytes else "")
|
|
332
|
+
assert "android.no_compatible_abi" in combined or "no_compatible_abi" in combined
|
|
333
|
+
|
|
334
|
+
def test_upload_with_validation_warnings_succeeds_and_prints_them(self, tmp_path: Path) -> None:
|
|
335
|
+
build_file = tmp_path / "warn.apk"
|
|
336
|
+
build_file.write_bytes(b"data")
|
|
337
|
+
settings = _make_settings(tmp_path)
|
|
338
|
+
body = {
|
|
339
|
+
**_UPLOAD_RESPONSE,
|
|
340
|
+
"validationWarnings": [
|
|
341
|
+
{
|
|
342
|
+
"code": "android.unsigned_apk",
|
|
343
|
+
"message": "APK is not signed.",
|
|
344
|
+
"detail": {},
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
}
|
|
348
|
+
client = _mock_upload_client(_mock_response(200, body))
|
|
349
|
+
|
|
350
|
+
with patch("minitest_cli.commands.build.ApiClient", return_value=client):
|
|
351
|
+
result = _run_with_context(["upload", str(build_file)], settings, json_mode=False)
|
|
352
|
+
|
|
353
|
+
assert result.exit_code == 0
|
|
354
|
+
|
|
282
355
|
def test_upload_requires_auth(self, tmp_path: Path) -> None:
|
|
283
356
|
build_file = tmp_path / "test.apk"
|
|
284
357
|
build_file.write_bytes(b"data")
|
|
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
|
{minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/app_knowledge_helpers.py
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
|