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.
Files changed (75) hide show
  1. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/AGENTS.md +1 -1
  2. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/PKG-INFO +1 -1
  3. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/pyproject.toml +1 -1
  4. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/build.py +2 -0
  5. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/build_helpers.py +37 -1
  6. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/build.py +1 -0
  7. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_build_commands.py +73 -0
  8. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/uv.lock +1 -1
  9. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.env.example +0 -0
  10. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.github/workflows/ci.yml +0 -0
  11. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.github/workflows/install-scripts.yml +0 -0
  12. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.github/workflows/release.yml +0 -0
  13. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.gitignore +0 -0
  14. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/.opencode/skill/release/SKILL.md +0 -0
  15. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/README.md +0 -0
  16. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/RELEASE.md +0 -0
  17. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/install.ps1 +0 -0
  18. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/install.sh +0 -0
  19. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/pyrightconfig.json +0 -0
  20. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/__init__.py +0 -0
  21. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/api/__init__.py +0 -0
  22. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/api/apps_manager_client.py +0 -0
  23. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/api/client.py +0 -0
  24. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/assets/__init__.py +0 -0
  25. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/assets/callback.html +0 -0
  26. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/__init__.py +0 -0
  27. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/app_knowledge.py +0 -0
  28. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
  29. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/apps.py +0 -0
  30. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/apps_helpers.py +0 -0
  31. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/auth.py +0 -0
  32. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/batch.py +0 -0
  33. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/batch_helpers.py +0 -0
  34. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/flow_types.py +0 -0
  35. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/maintenance_check.py +0 -0
  36. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/run.py +0 -0
  37. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/run_display.py +0 -0
  38. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/run_helpers.py +0 -0
  39. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/skill.py +0 -0
  40. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/upgrade.py +0 -0
  41. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/user_story.py +0 -0
  42. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/user_story_helpers.py +0 -0
  43. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/commands/user_story_modify.py +0 -0
  44. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/__init__.py +0 -0
  45. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/app_context.py +0 -0
  46. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/auth.py +0 -0
  47. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/config.py +0 -0
  48. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/credentials.py +0 -0
  49. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/oauth.py +0 -0
  50. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/tenants.py +0 -0
  51. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/core/token_exchange.py +0 -0
  52. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/main.py +0 -0
  53. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/__init__.py +0 -0
  54. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/app.py +0 -0
  55. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/base.py +0 -0
  56. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/maintenance_check.py +0 -0
  57. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/story_run.py +0 -0
  58. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/models/user_story.py +0 -0
  59. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/utils/__init__.py +0 -0
  60. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/utils/output.py +0 -0
  61. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/utils/skill_refresh.py +0 -0
  62. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/src/minitest_cli/utils/update_check.py +0 -0
  63. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/__init__.py +0 -0
  64. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_app_knowledge_commands.py +0 -0
  65. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_apps_commands.py +0 -0
  66. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_auth.py +0 -0
  67. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_auth_commands.py +0 -0
  68. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_batch_commands.py +0 -0
  69. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_code_quality.py +0 -0
  70. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_flow_types_commands.py +0 -0
  71. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_run_commands.py +0 -0
  72. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_skill_command.py +0 -0
  73. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_upgrade_command.py +0 -0
  74. {minitest_cli-0.8.2 → minitest_cli-0.8.3}/tests/test_user_story_commands.py +0 -0
  75. {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.2
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "minitest-cli"
3
- version = "0.8.2"
3
+ version = "0.8.3"
4
4
  description = "Minitest CLI – command-line interface for the Minitest testing platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -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:
@@ -13,6 +13,7 @@ class BuildResponse(CamelModel):
13
13
  original_name: str
14
14
  size_bytes: int | None = None
15
15
  created_at: datetime
16
+ validation_warnings: list[dict] | None = None
16
17
 
17
18
 
18
19
  class BuildListResponse(CamelModel):
@@ -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")
@@ -141,7 +141,7 @@ wheels = [
141
141
 
142
142
  [[package]]
143
143
  name = "minitest-cli"
144
- version = "0.8.2"
144
+ version = "0.8.3"
145
145
  source = { editable = "." }
146
146
  dependencies = [
147
147
  { name = "httpx" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes