powerbase-cli 0.1.0__tar.gz → 0.1.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.
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/PKG-INFO +9 -5
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/README.md +8 -4
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/pyproject.toml +1 -1
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/auth.py +40 -8
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/session.py +11 -2
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/transport.py +16 -1
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli.egg-info/PKG-INFO +9 -5
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_cli_commands.py +57 -2
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_cli_help.py +7 -6
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_session.py +12 -1
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_transport.py +16 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/setup.cfg +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/__init__.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/__main__.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/api.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/certs/powerbase-test-ca.pem +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/cli.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/__init__.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/agent.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/branch.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/config_cmd.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/context.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/database.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/instance.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/org.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/parser.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/publish.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/sandbox.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/shared.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/commands/sql.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli/config.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli.egg-info/SOURCES.txt +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli.egg-info/dependency_links.txt +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli.egg-info/entry_points.txt +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/src/powerbase_cli.egg-info/top_level.txt +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_api.py +0 -0
- {powerbase_cli-0.1.0 → powerbase_cli-0.1.2}/tests/test_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: powerbase-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: CLI for operating Powerbase console workflows
|
|
5
5
|
Author: Powerbase
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -101,10 +101,12 @@ This will:
|
|
|
101
101
|
|
|
102
102
|
1. request a CLI login session from Powerbase
|
|
103
103
|
2. print a browser URL
|
|
104
|
-
3. poll until browser approval or until `--timeout` elapses (default **
|
|
104
|
+
3. poll until browser approval or until `--timeout` elapses (default **600** seconds, ten minutes)
|
|
105
105
|
4. save the resulting session to `~/.config/powerbase/auth.json`
|
|
106
106
|
|
|
107
|
-
Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than
|
|
107
|
+
Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than ten minutes to complete login.
|
|
108
|
+
For agent-guided workflows, prefer `powerbase auth login --no-wait --json` so the CLI returns the `login_url` and exits immediately instead of blocking on polling.
|
|
109
|
+
If the browser link expires or the user is not currently signed in to the console, rerun `powerbase auth login --no-wait --json` to generate a fresh login URL before retrying.
|
|
108
110
|
If an agent or wrapper script is coordinating the workflow, it is still helpful
|
|
109
111
|
to tell the user: after browser approval, return to the current session so the
|
|
110
112
|
remaining steps can continue.
|
|
@@ -278,13 +280,15 @@ When Openclaw or another LLM agent uses `powerbase`:
|
|
|
278
280
|
- run discovery commands before write operations
|
|
279
281
|
- set context for long multi-step tasks
|
|
280
282
|
- use `auth status` before workflows that assume login
|
|
281
|
-
-
|
|
283
|
+
- prefer `auth login --no-wait --json` for browser login so the agent can hand `login_url` back to the user
|
|
284
|
+
- if a protected command says authentication is missing or expired, rerun `auth login --no-wait --json` to generate a fresh login URL
|
|
285
|
+
- do not have the agent open the browser login URL itself; the user must complete that approval step
|
|
282
286
|
- remember that `--json` overrides a saved `config output text` setting for that command
|
|
283
287
|
|
|
284
288
|
Recommended agent sequence:
|
|
285
289
|
|
|
286
290
|
1. `powerbase auth status --json`
|
|
287
|
-
2. If unauthenticated, run `powerbase auth login
|
|
291
|
+
2. If unauthenticated, or if a protected command reports an auth error, run `powerbase auth login --no-wait --json`, return the `login_url` to the user, and wait for them to approve in the browser
|
|
288
292
|
3. `powerbase context show --json`
|
|
289
293
|
4. If no instance is selected, run `powerbase instance list --json`
|
|
290
294
|
5. If no suitable instance exists, prefer `powerbase instance create --name ... --org-id ... --json`
|
|
@@ -93,10 +93,12 @@ This will:
|
|
|
93
93
|
|
|
94
94
|
1. request a CLI login session from Powerbase
|
|
95
95
|
2. print a browser URL
|
|
96
|
-
3. poll until browser approval or until `--timeout` elapses (default **
|
|
96
|
+
3. poll until browser approval or until `--timeout` elapses (default **600** seconds, ten minutes)
|
|
97
97
|
4. save the resulting session to `~/.config/powerbase/auth.json`
|
|
98
98
|
|
|
99
|
-
Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than
|
|
99
|
+
Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than ten minutes to complete login.
|
|
100
|
+
For agent-guided workflows, prefer `powerbase auth login --no-wait --json` so the CLI returns the `login_url` and exits immediately instead of blocking on polling.
|
|
101
|
+
If the browser link expires or the user is not currently signed in to the console, rerun `powerbase auth login --no-wait --json` to generate a fresh login URL before retrying.
|
|
100
102
|
If an agent or wrapper script is coordinating the workflow, it is still helpful
|
|
101
103
|
to tell the user: after browser approval, return to the current session so the
|
|
102
104
|
remaining steps can continue.
|
|
@@ -270,13 +272,15 @@ When Openclaw or another LLM agent uses `powerbase`:
|
|
|
270
272
|
- run discovery commands before write operations
|
|
271
273
|
- set context for long multi-step tasks
|
|
272
274
|
- use `auth status` before workflows that assume login
|
|
273
|
-
-
|
|
275
|
+
- prefer `auth login --no-wait --json` for browser login so the agent can hand `login_url` back to the user
|
|
276
|
+
- if a protected command says authentication is missing or expired, rerun `auth login --no-wait --json` to generate a fresh login URL
|
|
277
|
+
- do not have the agent open the browser login URL itself; the user must complete that approval step
|
|
274
278
|
- remember that `--json` overrides a saved `config output text` setting for that command
|
|
275
279
|
|
|
276
280
|
Recommended agent sequence:
|
|
277
281
|
|
|
278
282
|
1. `powerbase auth status --json`
|
|
279
|
-
2. If unauthenticated, run `powerbase auth login
|
|
283
|
+
2. If unauthenticated, or if a protected command reports an auth error, run `powerbase auth login --no-wait --json`, return the `login_url` to the user, and wait for them to approve in the browser
|
|
280
284
|
3. `powerbase context show --json`
|
|
281
285
|
4. If no instance is selected, run `powerbase instance list --json`
|
|
282
286
|
5. If no suitable instance exists, prefer `powerbase instance create --name ... --org-id ... --json`
|
|
@@ -10,6 +10,25 @@ def handle_auth_login(args: argparse.Namespace) -> int:
|
|
|
10
10
|
store, config, _, _, api = build_api(args)
|
|
11
11
|
data = api.start_cli_login()
|
|
12
12
|
login_id = data["login_id"]
|
|
13
|
+
if getattr(args, "no_wait_login", False):
|
|
14
|
+
render_output(
|
|
15
|
+
args,
|
|
16
|
+
{
|
|
17
|
+
"status": "pending",
|
|
18
|
+
"login_id": login_id,
|
|
19
|
+
"login_url": data["login_url"],
|
|
20
|
+
"poll_interval": data.get("poll_interval", 2),
|
|
21
|
+
"next_action": "ask_user_to_open_url",
|
|
22
|
+
"message": (
|
|
23
|
+
"Ask the user to open login_url in a browser and approve the request. "
|
|
24
|
+
"If the user is not already signed in to the console or this link expires, "
|
|
25
|
+
"rerun `powerbase auth login --no-wait --json` to generate a fresh login URL. "
|
|
26
|
+
"After approval, rerun `powerbase auth status --json` or invoke "
|
|
27
|
+
"`powerbase auth login` without `--no-wait` to finish in this session."
|
|
28
|
+
),
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
return 0
|
|
13
32
|
timeout_sec = int(args.login_timeout)
|
|
14
33
|
if timeout_sec < 0:
|
|
15
34
|
raise RuntimeError("--timeout must be >= 0 (0 means wait indefinitely).")
|
|
@@ -30,8 +49,9 @@ def handle_auth_login(args: argparse.Namespace) -> int:
|
|
|
30
49
|
if remaining <= 0:
|
|
31
50
|
raise RuntimeError(
|
|
32
51
|
f"Login timed out after {timeout_sec} seconds without browser approval. "
|
|
33
|
-
"Complete login sooner,
|
|
34
|
-
"
|
|
52
|
+
"Complete login sooner, rerun `powerbase auth login --no-wait --json` to "
|
|
53
|
+
"generate a fresh login URL, or pass a larger `--timeout` "
|
|
54
|
+
"(use `--timeout 0` to wait indefinitely)."
|
|
35
55
|
)
|
|
36
56
|
else:
|
|
37
57
|
remaining = float("inf")
|
|
@@ -116,24 +136,37 @@ def register_auth_commands(subparsers: argparse._SubParsersAction[argparse.Argum
|
|
|
116
136
|
help="Manage login state.",
|
|
117
137
|
description="Log in, inspect, refresh, or clear the saved Powerbase session.",
|
|
118
138
|
)
|
|
119
|
-
auth_sub = auth.add_subparsers(
|
|
139
|
+
auth_sub = auth.add_subparsers(
|
|
140
|
+
dest="auth_command",
|
|
141
|
+
metavar="{login,status,refresh,logout}",
|
|
142
|
+
)
|
|
120
143
|
p = auth_sub.add_parser(
|
|
121
144
|
"login",
|
|
122
145
|
help="Start browser login.",
|
|
123
146
|
description=(
|
|
124
147
|
"Start browser-based login. This prints a login URL, polls until approval in the browser "
|
|
125
|
-
"or until --timeout seconds elapse (default
|
|
126
|
-
"~/.config/powerbase/auth.json. Use --timeout 0 to wait without a time limit."
|
|
148
|
+
"or until --timeout seconds elapse (default 600), then saves the session to "
|
|
149
|
+
"~/.config/powerbase/auth.json. Use --timeout 0 to wait without a time limit. "
|
|
150
|
+
"Use --no-wait to print the login URL and exit immediately without polling."
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
p.add_argument(
|
|
154
|
+
"--no-wait",
|
|
155
|
+
dest="no_wait_login",
|
|
156
|
+
action="store_true",
|
|
157
|
+
help=(
|
|
158
|
+
"Return the login URL and exit immediately without polling for approval. "
|
|
159
|
+
"Useful for agent-guided flows where the user must open the URL manually."
|
|
127
160
|
),
|
|
128
161
|
)
|
|
129
162
|
p.add_argument(
|
|
130
163
|
"--timeout",
|
|
131
164
|
dest="login_timeout",
|
|
132
165
|
type=int,
|
|
133
|
-
default=
|
|
166
|
+
default=600,
|
|
134
167
|
metavar="SECONDS",
|
|
135
168
|
help=(
|
|
136
|
-
"Stop polling after this many seconds if login is not approved (default:
|
|
169
|
+
"Stop polling after this many seconds if login is not approved (default: 600). "
|
|
137
170
|
"Pass 0 to wait indefinitely."
|
|
138
171
|
),
|
|
139
172
|
)
|
|
@@ -157,7 +190,6 @@ def register_auth_commands(subparsers: argparse._SubParsersAction[argparse.Argum
|
|
|
157
190
|
p.set_defaults(handler=handle_auth_logout)
|
|
158
191
|
p = auth_sub.add_parser(
|
|
159
192
|
"token-set",
|
|
160
|
-
help="Save tokens into auth.json.",
|
|
161
193
|
description=(
|
|
162
194
|
"Save an access token and optional refresh token into ~/.config/powerbase/auth.json. "
|
|
163
195
|
"If you skip the refresh token, the CLI cannot auto-refresh when the access token expires."
|
|
@@ -39,6 +39,12 @@ class SessionManager:
|
|
|
39
39
|
self.tls_insecure = tls_insecure
|
|
40
40
|
self.ca_cert_file = ca_cert_file
|
|
41
41
|
|
|
42
|
+
def _login_guidance(self) -> str:
|
|
43
|
+
return (
|
|
44
|
+
"Run `powerbase auth login --no-wait --json` to generate a fresh login URL, "
|
|
45
|
+
"ask the user to open it in their own browser, and retry after approval."
|
|
46
|
+
)
|
|
47
|
+
|
|
42
48
|
def _urlopen(self, req: request.Request):
|
|
43
49
|
if self.tls_insecure:
|
|
44
50
|
context = ssl.create_default_context()
|
|
@@ -81,9 +87,12 @@ class SessionManager:
|
|
|
81
87
|
def refresh(self, auth: AuthState | None = None) -> AuthState:
|
|
82
88
|
auth = auth or self.get_auth_state()
|
|
83
89
|
if not auth:
|
|
84
|
-
raise SessionError("No authentication session available.")
|
|
90
|
+
raise SessionError(f"No authentication session available. {self._login_guidance()}")
|
|
85
91
|
if not auth.session.refresh_token:
|
|
86
|
-
raise SessionError(
|
|
92
|
+
raise SessionError(
|
|
93
|
+
"Current session has no refresh token or can no longer be refreshed. "
|
|
94
|
+
f"{self._login_guidance()}"
|
|
95
|
+
)
|
|
87
96
|
if not self.base_url or not self.anon_key:
|
|
88
97
|
raise SessionError("base_url and anon_key are required to refresh the session.")
|
|
89
98
|
|
|
@@ -27,6 +27,12 @@ class PowerbaseTransport:
|
|
|
27
27
|
self.config = config
|
|
28
28
|
self.session_manager = session_manager
|
|
29
29
|
|
|
30
|
+
def _login_guidance(self) -> str:
|
|
31
|
+
return (
|
|
32
|
+
"Run `powerbase auth login --no-wait --json` to generate a fresh login URL, "
|
|
33
|
+
"ask the user to open it in their own browser, and retry after approval."
|
|
34
|
+
)
|
|
35
|
+
|
|
30
36
|
def _urlopen(self, req: request.Request):
|
|
31
37
|
if self.config.tls_insecure:
|
|
32
38
|
context = ssl.create_default_context()
|
|
@@ -76,7 +82,11 @@ class PowerbaseTransport:
|
|
|
76
82
|
data = json.loads(body_text)
|
|
77
83
|
except json.JSONDecodeError:
|
|
78
84
|
data = {"error": body_text}
|
|
79
|
-
|
|
85
|
+
message = data.get("error") or body_text or exc.reason
|
|
86
|
+
if exc.code == 401:
|
|
87
|
+
base_message = str(message).strip() or "Authentication failed."
|
|
88
|
+
message = f"{base_message} {self._login_guidance()}"
|
|
89
|
+
return ApiError(str(message), exc.code)
|
|
80
90
|
|
|
81
91
|
def invoke(
|
|
82
92
|
self,
|
|
@@ -95,6 +105,11 @@ class PowerbaseTransport:
|
|
|
95
105
|
raise ApiError("Powerbase anon key is not configured.")
|
|
96
106
|
|
|
97
107
|
auth = self.session_manager.ensure_valid() if requires_auth else None
|
|
108
|
+
if requires_auth and not auth:
|
|
109
|
+
raise ApiError(
|
|
110
|
+
f"No authentication session available. {self._login_guidance()}",
|
|
111
|
+
401,
|
|
112
|
+
)
|
|
98
113
|
url = f"{self.config.base_url.rstrip('/')}/functions/v1/{function_path}"
|
|
99
114
|
payload = None if body is None else json.dumps(body).encode("utf-8")
|
|
100
115
|
request_headers = self._build_headers(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: powerbase-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: CLI for operating Powerbase console workflows
|
|
5
5
|
Author: Powerbase
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -101,10 +101,12 @@ This will:
|
|
|
101
101
|
|
|
102
102
|
1. request a CLI login session from Powerbase
|
|
103
103
|
2. print a browser URL
|
|
104
|
-
3. poll until browser approval or until `--timeout` elapses (default **
|
|
104
|
+
3. poll until browser approval or until `--timeout` elapses (default **600** seconds, ten minutes)
|
|
105
105
|
4. save the resulting session to `~/.config/powerbase/auth.json`
|
|
106
106
|
|
|
107
|
-
Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than
|
|
107
|
+
Use `--timeout 0` to wait without a time limit. Use a larger value if users need more than ten minutes to complete login.
|
|
108
|
+
For agent-guided workflows, prefer `powerbase auth login --no-wait --json` so the CLI returns the `login_url` and exits immediately instead of blocking on polling.
|
|
109
|
+
If the browser link expires or the user is not currently signed in to the console, rerun `powerbase auth login --no-wait --json` to generate a fresh login URL before retrying.
|
|
108
110
|
If an agent or wrapper script is coordinating the workflow, it is still helpful
|
|
109
111
|
to tell the user: after browser approval, return to the current session so the
|
|
110
112
|
remaining steps can continue.
|
|
@@ -278,13 +280,15 @@ When Openclaw or another LLM agent uses `powerbase`:
|
|
|
278
280
|
- run discovery commands before write operations
|
|
279
281
|
- set context for long multi-step tasks
|
|
280
282
|
- use `auth status` before workflows that assume login
|
|
281
|
-
-
|
|
283
|
+
- prefer `auth login --no-wait --json` for browser login so the agent can hand `login_url` back to the user
|
|
284
|
+
- if a protected command says authentication is missing or expired, rerun `auth login --no-wait --json` to generate a fresh login URL
|
|
285
|
+
- do not have the agent open the browser login URL itself; the user must complete that approval step
|
|
282
286
|
- remember that `--json` overrides a saved `config output text` setting for that command
|
|
283
287
|
|
|
284
288
|
Recommended agent sequence:
|
|
285
289
|
|
|
286
290
|
1. `powerbase auth status --json`
|
|
287
|
-
2. If unauthenticated, run `powerbase auth login
|
|
291
|
+
2. If unauthenticated, or if a protected command reports an auth error, run `powerbase auth login --no-wait --json`, return the `login_url` to the user, and wait for them to approve in the browser
|
|
288
292
|
3. `powerbase context show --json`
|
|
289
293
|
4. If no instance is selected, run `powerbase instance list --json`
|
|
290
294
|
5. If no suitable instance exists, prefer `powerbase instance create --name ... --org-id ... --json`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import sys
|
|
4
5
|
import tempfile
|
|
5
6
|
import unittest
|
|
@@ -36,6 +37,18 @@ class FakeApiPending:
|
|
|
36
37
|
return {"status": "pending", "poll_interval": 2}
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
class FakeApiNoWait:
|
|
41
|
+
def start_cli_login(self):
|
|
42
|
+
return {
|
|
43
|
+
"login_id": "login-nowait",
|
|
44
|
+
"login_url": "https://console.example.com/cli-auth/login-nowait",
|
|
45
|
+
"poll_interval": 2,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def poll_cli_login(self, login_id: str):
|
|
49
|
+
raise AssertionError("poll_cli_login should not be called when --no-wait is set")
|
|
50
|
+
|
|
51
|
+
|
|
39
52
|
class FakeApi:
|
|
40
53
|
def start_cli_login(self):
|
|
41
54
|
return {
|
|
@@ -101,7 +114,15 @@ class CliCommandTests(unittest.TestCase):
|
|
|
101
114
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
102
115
|
store = ConfigStore(Path(temp_dir))
|
|
103
116
|
config = AppConfig(base_url="https://console.example.com", anon_key="anon")
|
|
104
|
-
args = Namespace(
|
|
117
|
+
args = Namespace(
|
|
118
|
+
config_dir=temp_dir,
|
|
119
|
+
base_url=None,
|
|
120
|
+
anon_key=None,
|
|
121
|
+
json=True,
|
|
122
|
+
login_timeout=300,
|
|
123
|
+
no_wait_login=False,
|
|
124
|
+
insecure=False,
|
|
125
|
+
)
|
|
105
126
|
|
|
106
127
|
with mock.patch("powerbase_cli.commands.auth.build_api", return_value=(store, config, None, None, FakeApi())):
|
|
107
128
|
with mock.patch("powerbase_cli.commands.auth.time.sleep", return_value=None):
|
|
@@ -114,6 +135,32 @@ class CliCommandTests(unittest.TestCase):
|
|
|
114
135
|
self.assertEqual(saved.session.access_token, "new-access")
|
|
115
136
|
self.assertEqual(saved.user["id"], "user-1")
|
|
116
137
|
|
|
138
|
+
def test_auth_login_no_wait_returns_login_url_without_polling(self) -> None:
|
|
139
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
140
|
+
store = ConfigStore(Path(temp_dir))
|
|
141
|
+
config = AppConfig(base_url="https://console.example.com", anon_key="anon")
|
|
142
|
+
args = Namespace(
|
|
143
|
+
config_dir=temp_dir,
|
|
144
|
+
base_url=None,
|
|
145
|
+
anon_key=None,
|
|
146
|
+
json=True,
|
|
147
|
+
login_timeout=300,
|
|
148
|
+
no_wait_login=True,
|
|
149
|
+
insecure=False,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
with mock.patch("powerbase_cli.commands.auth.build_api", return_value=(store, config, None, None, FakeApiNoWait())):
|
|
153
|
+
with mock.patch("sys.stdout", new=StringIO()) as stdout:
|
|
154
|
+
exit_code = auth_handle_auth_login(args)
|
|
155
|
+
|
|
156
|
+
self.assertEqual(exit_code, 0)
|
|
157
|
+
payload = json.loads(stdout.getvalue())
|
|
158
|
+
self.assertEqual(payload["status"], "pending")
|
|
159
|
+
self.assertEqual(payload["login_id"], "login-nowait")
|
|
160
|
+
self.assertEqual(payload["next_action"], "ask_user_to_open_url")
|
|
161
|
+
self.assertIn("login_url", payload)
|
|
162
|
+
self.assertIsNone(store.load_auth())
|
|
163
|
+
|
|
117
164
|
def test_auth_login_times_out(self) -> None:
|
|
118
165
|
clock = {"t": 0.0}
|
|
119
166
|
|
|
@@ -126,7 +173,15 @@ class CliCommandTests(unittest.TestCase):
|
|
|
126
173
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
127
174
|
store = ConfigStore(Path(temp_dir))
|
|
128
175
|
config = AppConfig(base_url="https://console.example.com", anon_key="anon")
|
|
129
|
-
args = Namespace(
|
|
176
|
+
args = Namespace(
|
|
177
|
+
config_dir=temp_dir,
|
|
178
|
+
base_url=None,
|
|
179
|
+
anon_key=None,
|
|
180
|
+
json=False,
|
|
181
|
+
login_timeout=5,
|
|
182
|
+
no_wait_login=False,
|
|
183
|
+
insecure=False,
|
|
184
|
+
)
|
|
130
185
|
|
|
131
186
|
with mock.patch("powerbase_cli.commands.auth.build_api", return_value=(store, config, None, None, FakeApiPending())):
|
|
132
187
|
with mock.patch("powerbase_cli.commands.auth.time.monotonic", fake_monotonic):
|
|
@@ -28,15 +28,16 @@ class CliHelpTests(unittest.TestCase):
|
|
|
28
28
|
login_parser = auth_parser._subparsers._group_actions[0].choices["login"]
|
|
29
29
|
help_text = login_parser.format_help()
|
|
30
30
|
self.assertIn("--timeout", help_text)
|
|
31
|
-
self.assertIn("
|
|
31
|
+
self.assertIn("--no-wait", help_text)
|
|
32
|
+
self.assertIn("600", help_text)
|
|
32
33
|
|
|
33
|
-
def
|
|
34
|
+
def test_auth_help_does_not_list_token_set_subcommand(self) -> None:
|
|
34
35
|
parser = build_parser()
|
|
35
36
|
auth_parser = parser._subparsers._group_actions[0].choices["auth"]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
self.assertIn("
|
|
37
|
+
help_text = auth_parser.format_help()
|
|
38
|
+
self.assertNotIn("token-set", help_text)
|
|
39
|
+
subcommands = auth_parser._subparsers._group_actions[0].choices
|
|
40
|
+
self.assertIn("token-set", subcommands)
|
|
40
41
|
|
|
41
42
|
def test_context_use_instance_help_mentions_source(self) -> None:
|
|
42
43
|
parser = build_parser()
|
|
@@ -11,7 +11,7 @@ from unittest import mock
|
|
|
11
11
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
12
12
|
|
|
13
13
|
from powerbase_cli.config import BUNDLED_CA_CERT_SENTINEL, AuthSession, AuthState, ConfigStore
|
|
14
|
-
from powerbase_cli.session import SessionManager
|
|
14
|
+
from powerbase_cli.session import SessionError, SessionManager
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class FakeResponse:
|
|
@@ -29,6 +29,17 @@ class FakeResponse:
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class SessionManagerTests(unittest.TestCase):
|
|
32
|
+
def test_refresh_without_auth_instructs_user_to_log_in_again(self) -> None:
|
|
33
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
34
|
+
store = ConfigStore(Path(temp_dir))
|
|
35
|
+
manager = SessionManager(store, "https://console.example.com", "anon")
|
|
36
|
+
|
|
37
|
+
with self.assertRaises(SessionError) as ctx:
|
|
38
|
+
manager.refresh()
|
|
39
|
+
|
|
40
|
+
self.assertIn("No authentication session available", str(ctx.exception))
|
|
41
|
+
self.assertIn("powerbase auth login --no-wait --json", str(ctx.exception))
|
|
42
|
+
|
|
32
43
|
def test_refresh_updates_saved_auth_file(self) -> None:
|
|
33
44
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
34
45
|
store = ConfigStore(Path(temp_dir))
|
|
@@ -35,6 +35,21 @@ def build_http_error(status: int, payload: dict[str, object]) -> HTTPError:
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class PowerbaseTransportTests(unittest.TestCase):
|
|
38
|
+
def test_invoke_requires_login_when_session_missing(self) -> None:
|
|
39
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
40
|
+
store = ConfigStore(Path(temp_dir))
|
|
41
|
+
manager = SessionManager(store, "https://console.example.com", "anon")
|
|
42
|
+
transport = PowerbaseTransport(AppConfig(base_url="https://console.example.com", anon_key="anon"), manager)
|
|
43
|
+
|
|
44
|
+
with mock.patch("powerbase_cli.transport.request.urlopen") as urlopen_mock:
|
|
45
|
+
with self.assertRaises(ApiError) as ctx:
|
|
46
|
+
transport.invoke("instances", method="GET")
|
|
47
|
+
|
|
48
|
+
self.assertEqual(ctx.exception.status, 401)
|
|
49
|
+
self.assertIn("No authentication session available", str(ctx.exception))
|
|
50
|
+
self.assertIn("powerbase auth login --no-wait --json", str(ctx.exception))
|
|
51
|
+
self.assertEqual(urlopen_mock.call_count, 0)
|
|
52
|
+
|
|
38
53
|
def test_invoke_uses_unverified_tls_context_when_insecure(self) -> None:
|
|
39
54
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
40
55
|
store = ConfigStore(Path(temp_dir))
|
|
@@ -167,6 +182,7 @@ class PowerbaseTransportTests(unittest.TestCase):
|
|
|
167
182
|
transport.invoke("instances", method="GET")
|
|
168
183
|
|
|
169
184
|
self.assertEqual(ctx.exception.status, 401)
|
|
185
|
+
self.assertIn("powerbase auth login --no-wait --json", str(ctx.exception))
|
|
170
186
|
self.assertEqual(refresh_mock.call_count, 0)
|
|
171
187
|
self.assertEqual(urlopen_mock.call_count, 1)
|
|
172
188
|
|
|
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
|