minitest-cli 0.16.1__tar.gz → 0.16.2__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 (103) hide show
  1. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/PKG-INFO +1 -1
  2. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/pyproject.toml +1 -1
  3. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/core/credentials.py +1 -0
  4. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/core/oauth.py +16 -18
  5. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/core/token_exchange.py +9 -2
  6. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_auth.py +20 -7
  7. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/uv.lock +1 -1
  8. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/.env.example +0 -0
  9. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/.github/workflows/ci.yml +0 -0
  10. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/.github/workflows/install-scripts.yml +0 -0
  11. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/.github/workflows/release.yml +0 -0
  12. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/.gitignore +0 -0
  13. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/.opencode/skill/release/SKILL.md +0 -0
  14. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/AGENTS.md +0 -0
  15. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/README.md +0 -0
  16. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/RELEASE.md +0 -0
  17. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/install.ps1 +0 -0
  18. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/install.sh +0 -0
  19. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/pyrightconfig.json +0 -0
  20. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/renovate.json +0 -0
  21. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/__init__.py +0 -0
  22. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/api/__init__.py +0 -0
  23. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/api/apps_manager_client.py +0 -0
  24. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/api/client.py +0 -0
  25. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/assets/__init__.py +0 -0
  26. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/assets/callback.html +0 -0
  27. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/__init__.py +0 -0
  28. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/app_knowledge.py +0 -0
  29. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
  30. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/apps.py +0 -0
  31. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/apps_dependencies.py +0 -0
  32. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/apps_helpers.py +0 -0
  33. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/auth.py +0 -0
  34. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/auth_api_key.py +0 -0
  35. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/batch.py +0 -0
  36. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/batch_helpers.py +0 -0
  37. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/build.py +0 -0
  38. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/build_helpers.py +0 -0
  39. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/env.py +0 -0
  40. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/env_helpers.py +0 -0
  41. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/flow_types.py +0 -0
  42. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/init.py +0 -0
  43. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/init_playbook.py +0 -0
  44. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/run.py +0 -0
  45. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/run_display.py +0 -0
  46. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/run_helpers.py +0 -0
  47. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/run_targets.py +0 -0
  48. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/skill.py +0 -0
  49. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/test_file.py +0 -0
  50. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/test_file_helpers.py +0 -0
  51. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/test_file_list.py +0 -0
  52. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/test_profile.py +0 -0
  53. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/test_profile_default.py +0 -0
  54. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/test_profile_helpers.py +0 -0
  55. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/test_profile_list.py +0 -0
  56. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/upgrade.py +0 -0
  57. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/user_story.py +0 -0
  58. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/user_story_bindings.py +0 -0
  59. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/user_story_criteria.py +0 -0
  60. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/user_story_helpers.py +0 -0
  61. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/user_story_modify.py +0 -0
  62. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/commands/user_story_profiles.py +0 -0
  63. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/core/__init__.py +0 -0
  64. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/core/app_context.py +0 -0
  65. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/core/auth.py +0 -0
  66. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/core/config.py +0 -0
  67. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/core/tenants.py +0 -0
  68. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/main.py +0 -0
  69. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/models/__init__.py +0 -0
  70. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/models/app.py +0 -0
  71. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/models/app_env_vars.py +0 -0
  72. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/models/base.py +0 -0
  73. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/models/batch.py +0 -0
  74. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/models/build.py +0 -0
  75. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/models/story_run.py +0 -0
  76. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/models/targets.py +0 -0
  77. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/models/user_story.py +0 -0
  78. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/utils/__init__.py +0 -0
  79. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/utils/mermaid.py +0 -0
  80. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/utils/output.py +0 -0
  81. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/utils/skill_refresh.py +0 -0
  82. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/src/minitest_cli/utils/update_check.py +0 -0
  83. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/__init__.py +0 -0
  84. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_app_knowledge_commands.py +0 -0
  85. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_apps_commands.py +0 -0
  86. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_apps_dependencies.py +0 -0
  87. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_auth_api_key.py +0 -0
  88. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_auth_commands.py +0 -0
  89. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_batch_commands.py +0 -0
  90. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_build_commands.py +0 -0
  91. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_code_quality.py +0 -0
  92. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_env.py +0 -0
  93. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_flow_types_commands.py +0 -0
  94. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_init_command.py +0 -0
  95. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_mermaid.py +0 -0
  96. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_run_commands.py +0 -0
  97. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_skill_command.py +0 -0
  98. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_test_file_commands.py +0 -0
  99. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_test_profile_commands.py +0 -0
  100. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_upgrade_command.py +0 -0
  101. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_user_story_bindings_commands.py +0 -0
  102. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_user_story_commands.py +0 -0
  103. {minitest_cli-0.16.1 → minitest_cli-0.16.2}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: minitest-cli
3
- Version: 0.16.1
3
+ Version: 0.16.2
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.16.1"
3
+ version = "0.16.2"
4
4
  description = "Minitest CLI – command-line interface for the Minitest testing platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -23,6 +23,7 @@ class Credentials(BaseModel):
23
23
  expires_at: float
24
24
  user_id: str
25
25
  email: str
26
+ client_id: str | None = None
26
27
 
27
28
  @property
28
29
  def is_expired(self) -> bool:
@@ -31,19 +31,27 @@ _ASSETS = importlib.resources.files("minitest_cli.assets")
31
31
  def refresh_token(settings: Settings, creds: Credentials) -> Credentials | None:
32
32
  """Refresh an expired access token, saving new credentials to disk.
33
33
 
34
- Returns None on a transient failure. Raises SessionRevokedError when the
35
- auth server rejects the token (4xx) so callers can clear it and re-login.
34
+ OAuth sessions must refresh against the same client that created them, so a
35
+ persisted client_id is required. Returns None on a transient failure. Raises
36
+ SessionRevokedError when the token can never succeed (missing client_id, or a
37
+ 4xx rejection) so callers can clear it and re-login.
36
38
  """
37
39
  if not settings.supabase_url or not settings.supabase_publishable_key:
38
40
  return None
41
+ if not creds.client_id:
42
+ raise SessionRevokedError
39
43
 
40
44
  supabase_url = settings.supabase_url.rstrip("/")
41
45
  try:
42
46
  response = httpx.post(
43
- f"{supabase_url}/auth/v1/token?grant_type=refresh_token",
44
- json={"refresh_token": creds.refresh_token},
47
+ f"{supabase_url}/auth/v1/oauth/token",
48
+ data={
49
+ "grant_type": "refresh_token",
50
+ "refresh_token": creds.refresh_token,
51
+ "client_id": creds.client_id,
52
+ },
45
53
  headers={
46
- "Content-Type": "application/json",
54
+ "Content-Type": "application/x-www-form-urlencoded",
47
55
  "apikey": settings.supabase_publishable_key,
48
56
  },
49
57
  timeout=15.0,
@@ -64,21 +72,11 @@ def refresh_token(settings: Settings, creds: Credentials) -> Credentials | None:
64
72
  if not isinstance(data, dict):
65
73
  return None
66
74
 
67
- return parse_and_save_token_response(settings, data)
75
+ return parse_and_save_token_response(settings, data, creds.client_id)
68
76
 
69
77
 
70
78
  def oauth_pkce_login(settings: Settings) -> Credentials:
71
- """Run the full OAuth PKCE login flow via Supabase's OAuth2 server.
72
-
73
- Steps:
74
- 1. Start local callback server
75
- 2. Dynamically register an OAuth2 client with Supabase
76
- 3. Generate PKCE code verifier + challenge
77
- 4. Open browser to Supabase authorize endpoint (shows hosted sign-in page)
78
- 5. Wait for callback with authorization code
79
- 6. Exchange code + verifier for tokens at Supabase token endpoint
80
- 7. Save and return credentials
81
- """
79
+ """Run the full OAuth PKCE login flow via Supabase's OAuth2 server."""
82
80
  supabase_url = settings.supabase_url.rstrip("/")
83
81
 
84
82
  # PKCE challenge: base64url(sha256(verifier)) without padding
@@ -191,7 +189,7 @@ def oauth_pkce_login(settings: Settings) -> Credentials:
191
189
  if not isinstance(response_data, dict):
192
190
  auth_error("Token exchange returned unexpected response format.")
193
191
 
194
- creds = parse_and_save_token_response(settings, response_data)
192
+ creds = parse_and_save_token_response(settings, response_data, client_id)
195
193
  if creds is None:
196
194
  auth_error("Failed to parse token response.")
197
195
 
@@ -43,8 +43,14 @@ def get_apikey_header(settings: Settings) -> str:
43
43
  )
44
44
 
45
45
 
46
- def parse_and_save_token_response(settings: Settings, data: dict[str, Any]) -> Credentials | None:
47
- """Parse a Supabase token response and persist credentials."""
46
+ def parse_and_save_token_response(
47
+ settings: Settings, data: dict[str, Any], client_id: str | None = None
48
+ ) -> Credentials | None:
49
+ """Parse a Supabase token response and persist credentials.
50
+
51
+ client_id is the OAuth client that owns the session; it must be persisted
52
+ so refresh_token can re-authenticate against the same client.
53
+ """
48
54
  try:
49
55
  user = data.get("user", {})
50
56
  if not isinstance(user, dict):
@@ -65,6 +71,7 @@ def parse_and_save_token_response(settings: Settings, data: dict[str, Any]) -> C
65
71
  expires_at=time.time() + int(expires_in),
66
72
  user_id=user_id,
67
73
  email=email,
74
+ client_id=client_id,
68
75
  )
69
76
  save_credentials(settings, creds)
70
77
  return creds
@@ -45,6 +45,7 @@ def _make_credentials(**overrides):
45
45
  "expires_at": time.time() + 3600, # 1 hour from now
46
46
  "user_id": "user-789",
47
47
  "email": "test@example.com",
48
+ "client_id": "client-abc",
48
49
  }
49
50
  defaults.update(overrides)
50
51
  return Credentials(**defaults)
@@ -283,10 +284,19 @@ class TestRefreshToken:
283
284
  assert result is not None
284
285
  assert result.access_token == "new-access"
285
286
  assert result.refresh_token == "new-refresh"
286
- # Credentials should be saved to disk
287
+ assert result.client_id == "client-abc"
287
288
  loaded = load_credentials(settings)
288
289
  assert loaded is not None
289
290
  assert loaded.access_token == "new-access"
291
+ assert loaded.client_id == "client-abc"
292
+
293
+ def test_raises_session_revoked_without_client_id(self, tmp_path):
294
+ """A session stored before client_id was persisted can never refresh."""
295
+ settings = _make_settings(tmp_path)
296
+ legacy_creds = _make_credentials(client_id=None)
297
+
298
+ with pytest.raises(SessionRevokedError):
299
+ refresh_token(settings, legacy_creds)
290
300
 
291
301
  def test_returns_none_on_transient_network_error(self, tmp_path):
292
302
  """A network-level failure is transient — creds are worth keeping and retrying."""
@@ -320,9 +330,10 @@ class TestRefreshToken:
320
330
  settings = _make_settings(tmp_path, supabase_publishable_key="")
321
331
  assert refresh_token(settings, _make_credentials()) is None
322
332
 
323
- def test_sends_correct_request(self, tmp_path):
333
+ def test_refreshes_against_oauth_endpoint_with_client_id(self, tmp_path):
334
+ """OAuth sessions must refresh at /oauth/token with the owning client_id."""
324
335
  settings = _make_settings(tmp_path)
325
- old_creds = _make_credentials(refresh_token="my-refresh")
336
+ old_creds = _make_credentials(refresh_token="my-refresh", client_id="client-xyz")
326
337
 
327
338
  mock_response = MagicMock()
328
339
  mock_response.status_code = 200
@@ -332,16 +343,18 @@ class TestRefreshToken:
332
343
  "expires_in": 3600,
333
344
  "user": {"id": "u", "email": "e@e.com"},
334
345
  }
335
- mock_response.raise_for_status = MagicMock()
336
346
 
337
347
  with patch("minitest_cli.core.oauth.httpx.post", return_value=mock_response) as mock_post:
338
348
  refresh_token(settings, old_creds)
339
349
 
340
350
  mock_post.assert_called_once()
341
351
  call_args = mock_post.call_args
342
- assert "grant_type=refresh_token" in call_args.args[0]
343
- assert call_args.kwargs["json"]["refresh_token"] == "my-refresh"
344
- assert call_args.kwargs["headers"]["apikey"] == "test-publishable-key"
352
+ assert call_args.args[0].endswith("/auth/v1/oauth/token")
353
+ assert call_args.kwargs["data"] == {
354
+ "grant_type": "refresh_token",
355
+ "refresh_token": "my-refresh",
356
+ "client_id": "client-xyz",
357
+ }
345
358
 
346
359
 
347
360
  class TestDecodeJwtClaims:
@@ -141,7 +141,7 @@ wheels = [
141
141
 
142
142
  [[package]]
143
143
  name = "minitest-cli"
144
- version = "0.16.1"
144
+ version = "0.16.2"
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