entropy-data 0.3.2__tar.gz → 0.3.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.
- {entropy_data-0.3.2 → entropy_data-0.3.3}/CHANGELOG.md +9 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/PKG-INFO +1 -1
- {entropy_data-0.3.2 → entropy_data-0.3.3}/pyproject.toml +1 -1
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/cli.py +2 -0
- entropy_data-0.3.3/src/entropy_data/commands/connection.py +190 -0
- entropy_data-0.3.3/src/entropy_data/commands/organization.py +51 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/config.py +10 -3
- entropy_data-0.3.3/tests/commands/test_connection.py +176 -0
- entropy_data-0.3.3/tests/commands/test_organization.py +85 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/test_config.py +23 -0
- entropy_data-0.3.2/src/entropy_data/commands/connection.py +0 -90
- entropy_data-0.3.2/tests/commands/test_connection.py +0 -71
- {entropy_data-0.3.2 → entropy_data-0.3.3}/.editorconfig +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/.github/dependabot.yml +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/.github/pull_request_template.md +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/.github/workflows/ci.yaml +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/.github/workflows/release.yaml +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/.gitignore +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/.pre-commit-config.yaml +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/CLAUDE.md +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/Dockerfile +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/LICENSE +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/README.md +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/release +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/__init__.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/__main__.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/client.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/__init__.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/access.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/api_keys.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/assets.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/certifications.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/costs.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/datacontracts.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/dataproducts.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/definitions.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/events.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/example_data.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/gitconnections.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/import_export.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/lineage.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/search.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/settings.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/sourcesystems.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/tags.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/teams.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/test_results.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/commands/usage.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/output.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/src/entropy_data/util.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/__init__.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/commands/__init__.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/commands/test_api_keys.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/commands/test_assets.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/commands/test_costs.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/commands/test_gitconnections.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/commands/test_lineage.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/commands/test_settings.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/commands/test_tags.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/commands/test_teams.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/commands/test_usage.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/conftest.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.3}/tests/test_client.py +0 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.3.3]
|
|
6
|
+
|
|
7
|
+
- Add `entropy-data organization get` to fetch organization settings (vanity URL, host, full name, plan, SSO) for the API key in use. Backed by the new `GET /api/organization/settings` endpoint.
|
|
8
|
+
- Add `entropy-data connection get [name]` to inspect a stored connection. API key is masked by default; pass `--show-api-key` to print it in clear text. Use `-o json` for scripting.
|
|
9
|
+
- `entropy-data connection add` auto-fetches the organization vanity URL via `/api/organization/settings` and stores it on the connection. Best-effort: older servers or network errors fall back to no vanity URL.
|
|
10
|
+
- `connection list` surfaces the stored vanity URL.
|
|
11
|
+
|
|
3
12
|
## [0.3.2]
|
|
4
13
|
|
|
5
14
|
- Add `git-connection` subcommands to `dataproducts` and `datacontracts`
|
|
@@ -103,6 +103,7 @@ from entropy_data.commands.events import events_app # noqa: E402
|
|
|
103
103
|
from entropy_data.commands.example_data import example_data_app # noqa: E402
|
|
104
104
|
from entropy_data.commands.import_export import import_app # noqa: E402
|
|
105
105
|
from entropy_data.commands.lineage import lineage_app # noqa: E402
|
|
106
|
+
from entropy_data.commands.organization import organization_app # noqa: E402
|
|
106
107
|
from entropy_data.commands.search import search_app # noqa: E402
|
|
107
108
|
from entropy_data.commands.settings import settings_app # noqa: E402
|
|
108
109
|
from entropy_data.commands.sourcesystems import sourcesystems_app # noqa: E402
|
|
@@ -125,6 +126,7 @@ app.add_typer(costs_app, name="costs", help="Manage costs.")
|
|
|
125
126
|
app.add_typer(assets_app, name="assets", help="Manage data assets.")
|
|
126
127
|
app.add_typer(tags_app, name="tags", help="Manage tags.")
|
|
127
128
|
app.add_typer(api_keys_app, name="api-keys", help="Manage API keys.")
|
|
129
|
+
app.add_typer(organization_app, name="organization", help="Get organization details.")
|
|
128
130
|
app.add_typer(settings_app, name="settings", help="Manage organization settings.")
|
|
129
131
|
app.add_typer(events_app, name="events", help="Poll events.")
|
|
130
132
|
app.add_typer(lineage_app, name="lineage", help="Manage lineage (OpenLineage events).")
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Connection management commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from entropy_data import config as cfg
|
|
10
|
+
from entropy_data.output import OutputFormat, console, print_error, print_success
|
|
11
|
+
|
|
12
|
+
connection_app = typer.Typer(no_args_is_help=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _mask_api_key(api_key: str) -> str:
|
|
16
|
+
"""Mask an API key for display (first/last 4 visible)."""
|
|
17
|
+
if len(api_key) > 8:
|
|
18
|
+
return api_key[:4] + "..." + api_key[-4:]
|
|
19
|
+
return "****"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _fetch_vanity_url(api_key: str, host: str) -> str | None:
|
|
23
|
+
"""Best-effort fetch of the org vanity URL via /api/organization/settings.
|
|
24
|
+
|
|
25
|
+
Returns None on any failure (older server, network error, etc.) so callers
|
|
26
|
+
can fall back to None instead of failing the whole `connection add`.
|
|
27
|
+
"""
|
|
28
|
+
from entropy_data.client import REQUEST_TIMEOUT, EntropyDataClient
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
client = EntropyDataClient(cfg.ConnectionConfig(api_key=api_key, host=host))
|
|
32
|
+
response = client.session.get(
|
|
33
|
+
f"{client.base_url}/api/organization/settings",
|
|
34
|
+
timeout=REQUEST_TIMEOUT,
|
|
35
|
+
)
|
|
36
|
+
if response.ok:
|
|
37
|
+
return response.json().get("vanityUrl")
|
|
38
|
+
except Exception:
|
|
39
|
+
return None
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@connection_app.command("list")
|
|
44
|
+
def list_connections() -> None:
|
|
45
|
+
"""List all configured connections."""
|
|
46
|
+
connections = cfg.list_connections()
|
|
47
|
+
if not connections:
|
|
48
|
+
console.print("No connections configured. Run: entropy-data connection add <name>")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
table = Table(show_header=True)
|
|
52
|
+
table.add_column("Name")
|
|
53
|
+
table.add_column("Host")
|
|
54
|
+
table.add_column("Vanity URL")
|
|
55
|
+
table.add_column("API Key")
|
|
56
|
+
table.add_column("Default")
|
|
57
|
+
for conn in connections:
|
|
58
|
+
table.add_row(
|
|
59
|
+
conn["name"],
|
|
60
|
+
conn["host"],
|
|
61
|
+
conn.get("vanity_url") or "",
|
|
62
|
+
conn["api_key"],
|
|
63
|
+
"*" if conn["default"] else "",
|
|
64
|
+
)
|
|
65
|
+
console.print(table)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@connection_app.command("get")
|
|
69
|
+
def get_connection(
|
|
70
|
+
name: Annotated[
|
|
71
|
+
Optional[str],
|
|
72
|
+
typer.Argument(help="Connection name. Defaults to the default connection."),
|
|
73
|
+
] = None,
|
|
74
|
+
show_api_key: Annotated[
|
|
75
|
+
bool,
|
|
76
|
+
typer.Option("--show-api-key", help="Print the API key in clear text (default: masked)."),
|
|
77
|
+
] = False,
|
|
78
|
+
output: Annotated[
|
|
79
|
+
Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")
|
|
80
|
+
] = None,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Get details of a named connection (use --show-api-key to reveal the key)."""
|
|
83
|
+
from entropy_data.cli import get_output_format
|
|
84
|
+
|
|
85
|
+
config = cfg.load_config()
|
|
86
|
+
connections = config.get("connections", {})
|
|
87
|
+
|
|
88
|
+
resolved_name = name or config.get("default_connection_name")
|
|
89
|
+
if resolved_name is None:
|
|
90
|
+
print_error(
|
|
91
|
+
"No connection specified and no default set. "
|
|
92
|
+
"Run: entropy-data connection set-default <name>"
|
|
93
|
+
)
|
|
94
|
+
raise typer.Exit(1)
|
|
95
|
+
if resolved_name not in connections:
|
|
96
|
+
print_error(f"Connection '{resolved_name}' not found.")
|
|
97
|
+
raise typer.Exit(1)
|
|
98
|
+
|
|
99
|
+
conn = connections[resolved_name]
|
|
100
|
+
api_key_value = conn.get("api_key", "")
|
|
101
|
+
displayed_key = api_key_value if show_api_key else _mask_api_key(api_key_value)
|
|
102
|
+
is_default = config.get("default_connection_name") == resolved_name
|
|
103
|
+
|
|
104
|
+
fmt = output or get_output_format()
|
|
105
|
+
if fmt == OutputFormat.json:
|
|
106
|
+
payload = {
|
|
107
|
+
"name": resolved_name,
|
|
108
|
+
"host": conn.get("host", cfg.DEFAULT_HOST),
|
|
109
|
+
"vanity_url": conn.get("vanity_url"),
|
|
110
|
+
"api_key": displayed_key,
|
|
111
|
+
"default": is_default,
|
|
112
|
+
}
|
|
113
|
+
console.print_json(json.dumps(payload))
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
table = Table(show_header=False)
|
|
117
|
+
table.add_column("Field", style="cyan")
|
|
118
|
+
table.add_column("Value")
|
|
119
|
+
table.add_row("name", resolved_name)
|
|
120
|
+
table.add_row("host", conn.get("host", cfg.DEFAULT_HOST))
|
|
121
|
+
if conn.get("vanity_url"):
|
|
122
|
+
table.add_row("vanity_url", conn["vanity_url"])
|
|
123
|
+
table.add_row("api_key", displayed_key)
|
|
124
|
+
if is_default:
|
|
125
|
+
table.add_row("default", "yes")
|
|
126
|
+
console.print(table)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@connection_app.command("add")
|
|
130
|
+
def add_connection(
|
|
131
|
+
name: Annotated[str, typer.Argument(help="Connection name.")],
|
|
132
|
+
api_key: Annotated[str, typer.Option("--api-key", prompt="API key", help="The API key.")] = None,
|
|
133
|
+
host: Annotated[
|
|
134
|
+
str, typer.Option("--host", prompt="Host", prompt_required=False, help="API host URL.")
|
|
135
|
+
] = cfg.DEFAULT_HOST,
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Add or update a named connection.
|
|
138
|
+
|
|
139
|
+
The organization vanity URL is read from /api/organization/settings using the
|
|
140
|
+
provided API key. Fetching is best-effort: older servers or network errors
|
|
141
|
+
fall back to no vanity URL on the stored connection.
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
vanity_url = _fetch_vanity_url(api_key, host)
|
|
145
|
+
if vanity_url:
|
|
146
|
+
console.print(f"Fetched organization vanity URL '[cyan]{vanity_url}[/cyan]' from {host}.")
|
|
147
|
+
cfg.add_connection(name, api_key, host, vanity_url=vanity_url)
|
|
148
|
+
print_success(f"Connection '{name}' saved.")
|
|
149
|
+
except cfg.ConfigurationError as e:
|
|
150
|
+
print_error(str(e))
|
|
151
|
+
raise typer.Exit(1)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@connection_app.command("remove")
|
|
155
|
+
def remove_connection(
|
|
156
|
+
name: Annotated[str, typer.Argument(help="Connection name to remove.")],
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Remove a named connection."""
|
|
159
|
+
try:
|
|
160
|
+
cfg.remove_connection(name)
|
|
161
|
+
print_success(f"Connection '{name}' removed.")
|
|
162
|
+
except cfg.ConfigurationError as e:
|
|
163
|
+
print_error(str(e))
|
|
164
|
+
raise typer.Exit(1)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@connection_app.command("set-default")
|
|
168
|
+
def set_default(
|
|
169
|
+
name: Annotated[str, typer.Argument(help="Connection name to set as default.")],
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Set the default connection."""
|
|
172
|
+
try:
|
|
173
|
+
cfg.set_default_connection(name)
|
|
174
|
+
print_success(f"Default connection set to '{name}'.")
|
|
175
|
+
except cfg.ConfigurationError as e:
|
|
176
|
+
print_error(str(e))
|
|
177
|
+
raise typer.Exit(1)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@connection_app.command("test")
|
|
181
|
+
def test_connection() -> None:
|
|
182
|
+
"""Test the current connection by calling the API."""
|
|
183
|
+
from entropy_data.cli import get_client, handle_error
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
client = get_client()
|
|
187
|
+
client.list_resources("teams", params={"p": "0"})
|
|
188
|
+
print_success("Connection successful.")
|
|
189
|
+
except Exception as e:
|
|
190
|
+
handle_error(e)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Organization commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from entropy_data.output import OutputFormat, console
|
|
10
|
+
|
|
11
|
+
organization_app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@organization_app.command("get")
|
|
15
|
+
def get_organization(
|
|
16
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Get settings of the organization the current API key is bound to."""
|
|
19
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
20
|
+
from entropy_data.client import REQUEST_TIMEOUT, _raise_for_status
|
|
21
|
+
|
|
22
|
+
fmt = output or get_output_format()
|
|
23
|
+
try:
|
|
24
|
+
client = get_client()
|
|
25
|
+
response = client.session.get(
|
|
26
|
+
f"{client.base_url}/api/organization/settings",
|
|
27
|
+
timeout=REQUEST_TIMEOUT,
|
|
28
|
+
)
|
|
29
|
+
_raise_for_status(response)
|
|
30
|
+
data = response.json()
|
|
31
|
+
if fmt == OutputFormat.json:
|
|
32
|
+
console.print_json(json.dumps(data))
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
table = Table(show_header=False)
|
|
36
|
+
table.add_column("Field", style="cyan")
|
|
37
|
+
table.add_column("Value")
|
|
38
|
+
for key in ("vanityUrl", "host", "fullName", "logoUrl", "supportEmailAddress", "brand", "plan"):
|
|
39
|
+
value = data.get(key)
|
|
40
|
+
if value:
|
|
41
|
+
table.add_row(key, str(value))
|
|
42
|
+
sso = data.get("sso")
|
|
43
|
+
if sso:
|
|
44
|
+
table.add_row("sso.issuer", sso.get("issuer", ""))
|
|
45
|
+
if sso.get("tenant"):
|
|
46
|
+
table.add_row("sso.tenant", sso["tenant"])
|
|
47
|
+
if sso.get("autoJoin") is not None:
|
|
48
|
+
table.add_row("sso.autoJoin", str(sso["autoJoin"]))
|
|
49
|
+
console.print(table)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
handle_error(e)
|
|
@@ -21,6 +21,7 @@ class ConfigurationError(Exception):
|
|
|
21
21
|
class ConnectionConfig:
|
|
22
22
|
api_key: str
|
|
23
23
|
host: str = DEFAULT_HOST
|
|
24
|
+
vanity_url: str | None = None
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def load_config() -> dict:
|
|
@@ -47,6 +48,7 @@ def resolve_connection(
|
|
|
47
48
|
"""Resolve connection with precedence: CLI options > env vars > config file."""
|
|
48
49
|
api_key = cli_api_key
|
|
49
50
|
host = cli_host
|
|
51
|
+
vanity_url: str | None = None
|
|
50
52
|
|
|
51
53
|
# Layer 2: environment variables
|
|
52
54
|
if api_key is None:
|
|
@@ -68,6 +70,7 @@ def resolve_connection(
|
|
|
68
70
|
api_key = conn.get("api_key")
|
|
69
71
|
if host is None:
|
|
70
72
|
host = conn.get("host")
|
|
73
|
+
vanity_url = conn.get("vanity_url")
|
|
71
74
|
|
|
72
75
|
# Default host
|
|
73
76
|
if host is None:
|
|
@@ -78,17 +81,20 @@ def resolve_connection(
|
|
|
78
81
|
"No API key found. Set ENTROPY_DATA_API_KEY, use --api-key, or run: entropy-data connection add <name>"
|
|
79
82
|
)
|
|
80
83
|
|
|
81
|
-
return ConnectionConfig(api_key=api_key, host=host)
|
|
84
|
+
return ConnectionConfig(api_key=api_key, host=host, vanity_url=vanity_url)
|
|
82
85
|
|
|
83
86
|
|
|
84
|
-
def add_connection(name: str, api_key: str, host: str = DEFAULT_HOST) -> None:
|
|
87
|
+
def add_connection(name: str, api_key: str, host: str = DEFAULT_HOST, vanity_url: str | None = None) -> None:
|
|
85
88
|
"""Add or update a named connection."""
|
|
86
89
|
if not name or not name.strip():
|
|
87
90
|
raise ConfigurationError("Connection name must not be empty.")
|
|
88
91
|
config = load_config()
|
|
89
92
|
if "connections" not in config:
|
|
90
93
|
config["connections"] = {}
|
|
91
|
-
|
|
94
|
+
entry: dict = {"api_key": api_key, "host": host}
|
|
95
|
+
if vanity_url:
|
|
96
|
+
entry["vanity_url"] = vanity_url
|
|
97
|
+
config["connections"][name] = entry
|
|
92
98
|
# Set as default if it's the first connection
|
|
93
99
|
if "default_connection_name" not in config:
|
|
94
100
|
config["default_connection_name"] = name
|
|
@@ -134,6 +140,7 @@ def list_connections() -> list[dict]:
|
|
|
134
140
|
{
|
|
135
141
|
"name": name,
|
|
136
142
|
"host": conn.get("host", DEFAULT_HOST),
|
|
143
|
+
"vanity_url": conn.get("vanity_url"),
|
|
137
144
|
"api_key": masked,
|
|
138
145
|
"default": name == default_name,
|
|
139
146
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Tests for connection commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import responses
|
|
6
|
+
from typer.testing import CliRunner
|
|
7
|
+
|
|
8
|
+
import entropy_data.config as cfg
|
|
9
|
+
from entropy_data.cli import app
|
|
10
|
+
|
|
11
|
+
runner = CliRunner()
|
|
12
|
+
BASE_URL = "https://api.entropy-data.com"
|
|
13
|
+
ORG_SETTINGS_URL = f"{BASE_URL}/api/organization/settings"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_connection_list_empty(tmp_path, monkeypatch):
|
|
17
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
18
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
19
|
+
result = runner.invoke(app, ["connection", "list"])
|
|
20
|
+
assert result.exit_code == 0
|
|
21
|
+
assert "No connections" in result.output
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@responses.activate
|
|
25
|
+
def test_connection_add_and_list(tmp_path, monkeypatch):
|
|
26
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
27
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
28
|
+
# Auto-fetch fails (no responses registered) — connection still saves without vanity
|
|
29
|
+
result = runner.invoke(app, ["connection", "add", "prod", "--api-key", "mykey123456", "--host", BASE_URL])
|
|
30
|
+
assert result.exit_code == 0
|
|
31
|
+
assert "saved" in result.output
|
|
32
|
+
|
|
33
|
+
result = runner.invoke(app, ["connection", "list"])
|
|
34
|
+
assert result.exit_code == 0
|
|
35
|
+
assert "prod" in result.output
|
|
36
|
+
assert "*" in result.output # default marker
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@responses.activate
|
|
40
|
+
def test_connection_add_fetches_vanity_url(tmp_path, monkeypatch):
|
|
41
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
42
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
43
|
+
responses.add(
|
|
44
|
+
responses.GET,
|
|
45
|
+
ORG_SETTINGS_URL,
|
|
46
|
+
json={"vanityUrl": "acme", "host": BASE_URL, "fullName": "Acme Corp", "logoUrl": ""},
|
|
47
|
+
status=200,
|
|
48
|
+
)
|
|
49
|
+
result = runner.invoke(app, ["connection", "add", "prod", "--api-key", "mykey", "--host", BASE_URL])
|
|
50
|
+
assert result.exit_code == 0
|
|
51
|
+
assert "saved" in result.output
|
|
52
|
+
assert "acme" in result.output # mentioned in the fetch confirmation line
|
|
53
|
+
|
|
54
|
+
result = runner.invoke(app, ["connection", "list"])
|
|
55
|
+
assert result.exit_code == 0
|
|
56
|
+
assert "acme" in result.output
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@responses.activate
|
|
60
|
+
def test_connection_add_handles_fetch_failure(tmp_path, monkeypatch):
|
|
61
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
62
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
63
|
+
# Older server / endpoint missing → 404
|
|
64
|
+
responses.add(responses.GET, ORG_SETTINGS_URL, json={"detail": "Not found"}, status=404)
|
|
65
|
+
result = runner.invoke(app, ["connection", "add", "prod", "--api-key", "mykey", "--host", BASE_URL])
|
|
66
|
+
assert result.exit_code == 0
|
|
67
|
+
assert "saved" in result.output
|
|
68
|
+
|
|
69
|
+
result = runner.invoke(app, ["connection", "list"])
|
|
70
|
+
assert result.exit_code == 0
|
|
71
|
+
assert "prod" in result.output
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@responses.activate
|
|
75
|
+
def test_connection_remove(tmp_path, monkeypatch):
|
|
76
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
77
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
78
|
+
runner.invoke(app, ["connection", "add", "prod", "--api-key", "key1", "--host", BASE_URL])
|
|
79
|
+
result = runner.invoke(app, ["connection", "remove", "prod"])
|
|
80
|
+
assert result.exit_code == 0
|
|
81
|
+
assert "removed" in result.output
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@responses.activate
|
|
85
|
+
def test_connection_set_default(tmp_path, monkeypatch):
|
|
86
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
87
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
88
|
+
runner.invoke(app, ["connection", "add", "prod", "--api-key", "key1", "--host", BASE_URL])
|
|
89
|
+
runner.invoke(app, ["connection", "add", "dev", "--api-key", "key2", "--host", "http://localhost:8080"])
|
|
90
|
+
result = runner.invoke(app, ["connection", "set-default", "dev"])
|
|
91
|
+
assert result.exit_code == 0
|
|
92
|
+
assert "dev" in result.output
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@responses.activate
|
|
96
|
+
def test_connection_test_success(tmp_path, monkeypatch):
|
|
97
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
98
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
99
|
+
runner.invoke(app, ["connection", "add", "prod", "--api-key", "key1", "--host", BASE_URL])
|
|
100
|
+
responses.add(responses.GET, f"{BASE_URL}/api/teams", json=[], status=200)
|
|
101
|
+
result = runner.invoke(app, ["connection", "test"])
|
|
102
|
+
assert result.exit_code == 0
|
|
103
|
+
assert "successful" in result.output
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@responses.activate
|
|
107
|
+
def test_connection_get_masked_by_default(tmp_path, monkeypatch):
|
|
108
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
109
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
110
|
+
runner.invoke(app, ["connection", "add", "prod", "--api-key", "abcd1234efgh5678", "--host", BASE_URL])
|
|
111
|
+
result = runner.invoke(app, ["connection", "get", "prod"])
|
|
112
|
+
assert result.exit_code == 0
|
|
113
|
+
assert "abcd...5678" in result.output
|
|
114
|
+
assert "abcd1234efgh5678" not in result.output
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@responses.activate
|
|
118
|
+
def test_connection_get_show_api_key(tmp_path, monkeypatch):
|
|
119
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
120
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
121
|
+
runner.invoke(app, ["connection", "add", "prod", "--api-key", "abcd1234efgh5678", "--host", BASE_URL])
|
|
122
|
+
result = runner.invoke(app, ["connection", "get", "prod", "--show-api-key"])
|
|
123
|
+
assert result.exit_code == 0
|
|
124
|
+
assert "abcd1234efgh5678" in result.output
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@responses.activate
|
|
128
|
+
def test_connection_get_uses_default(tmp_path, monkeypatch):
|
|
129
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
130
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
131
|
+
runner.invoke(app, ["connection", "add", "prod", "--api-key", "abcd1234efgh5678", "--host", BASE_URL])
|
|
132
|
+
result = runner.invoke(app, ["connection", "get"])
|
|
133
|
+
assert result.exit_code == 0
|
|
134
|
+
assert "prod" in result.output
|
|
135
|
+
assert "default" in result.output
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@responses.activate
|
|
139
|
+
def test_connection_get_json(tmp_path, monkeypatch):
|
|
140
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
141
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
142
|
+
runner.invoke(app, ["connection", "add", "prod", "--api-key", "abcd1234efgh5678", "--host", BASE_URL])
|
|
143
|
+
result = runner.invoke(app, ["connection", "get", "prod", "--show-api-key", "-o", "json"])
|
|
144
|
+
assert result.exit_code == 0
|
|
145
|
+
payload = json.loads(result.output)
|
|
146
|
+
assert payload["name"] == "prod"
|
|
147
|
+
assert payload["host"] == BASE_URL
|
|
148
|
+
assert payload["api_key"] == "abcd1234efgh5678"
|
|
149
|
+
assert payload["default"] is True
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_connection_get_not_found(tmp_path, monkeypatch):
|
|
153
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
154
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
155
|
+
result = runner.invoke(app, ["connection", "get", "nope"])
|
|
156
|
+
assert result.exit_code == 1
|
|
157
|
+
assert "not found" in result.output.lower()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_connection_get_no_default(tmp_path, monkeypatch):
|
|
161
|
+
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
162
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
163
|
+
result = runner.invoke(app, ["connection", "get"])
|
|
164
|
+
assert result.exit_code == 1
|
|
165
|
+
assert "no default" in result.output.lower() or "no connection specified" in result.output.lower()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_connection_help():
|
|
169
|
+
result = runner.invoke(app, ["connection", "--help"])
|
|
170
|
+
assert result.exit_code == 0
|
|
171
|
+
assert "list" in result.output
|
|
172
|
+
assert "get" in result.output
|
|
173
|
+
assert "add" in result.output
|
|
174
|
+
assert "remove" in result.output
|
|
175
|
+
assert "set-default" in result.output
|
|
176
|
+
assert "test" in result.output
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Tests for organization commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import responses
|
|
6
|
+
from typer.testing import CliRunner
|
|
7
|
+
|
|
8
|
+
import entropy_data.config as cfg
|
|
9
|
+
from entropy_data.cli import app
|
|
10
|
+
|
|
11
|
+
runner = CliRunner()
|
|
12
|
+
BASE_URL = "https://api.entropy-data.com"
|
|
13
|
+
ORG_SETTINGS_URL = f"{BASE_URL}/api/organization/settings"
|
|
14
|
+
|
|
15
|
+
ORG_PAYLOAD = {
|
|
16
|
+
"vanityUrl": "acme",
|
|
17
|
+
"host": "https://acme.entropy-data.com",
|
|
18
|
+
"fullName": "Acme Corp",
|
|
19
|
+
"logoUrl": "https://example.com/logo.png",
|
|
20
|
+
"supportEmailAddress": "support@acme.com",
|
|
21
|
+
"brand": "default",
|
|
22
|
+
"plan": "enterprise",
|
|
23
|
+
"sso": {
|
|
24
|
+
"issuer": "azuresso",
|
|
25
|
+
"tenant": "tenant-id",
|
|
26
|
+
"autoJoin": True,
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@responses.activate
|
|
32
|
+
def test_organization_get_table(monkeypatch, tmp_path):
|
|
33
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
34
|
+
monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
|
|
35
|
+
responses.add(responses.GET, ORG_SETTINGS_URL, json=ORG_PAYLOAD, status=200)
|
|
36
|
+
result = runner.invoke(app, ["organization", "get"])
|
|
37
|
+
assert result.exit_code == 0
|
|
38
|
+
assert "acme" in result.output
|
|
39
|
+
assert "Acme Corp" in result.output
|
|
40
|
+
assert "enterprise" in result.output
|
|
41
|
+
assert "azuresso" in result.output
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@responses.activate
|
|
45
|
+
def test_organization_get_json(monkeypatch, tmp_path):
|
|
46
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
47
|
+
monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
|
|
48
|
+
responses.add(responses.GET, ORG_SETTINGS_URL, json=ORG_PAYLOAD, status=200)
|
|
49
|
+
result = runner.invoke(app, ["organization", "get", "--output", "json"])
|
|
50
|
+
assert result.exit_code == 0
|
|
51
|
+
data = json.loads(result.output)
|
|
52
|
+
assert data["vanityUrl"] == "acme"
|
|
53
|
+
assert data["plan"] == "enterprise"
|
|
54
|
+
assert data["sso"]["issuer"] == "azuresso"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@responses.activate
|
|
58
|
+
def test_organization_get_minimal_payload(monkeypatch, tmp_path):
|
|
59
|
+
"""Org without SSO / optional fields renders cleanly."""
|
|
60
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
61
|
+
monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
|
|
62
|
+
responses.add(
|
|
63
|
+
responses.GET,
|
|
64
|
+
ORG_SETTINGS_URL,
|
|
65
|
+
json={
|
|
66
|
+
"vanityUrl": "minimal",
|
|
67
|
+
"host": "https://minimal.entropy-data.com",
|
|
68
|
+
"fullName": "Minimal",
|
|
69
|
+
"logoUrl": "",
|
|
70
|
+
"supportEmailAddress": None,
|
|
71
|
+
"brand": None,
|
|
72
|
+
"plan": None,
|
|
73
|
+
"sso": None,
|
|
74
|
+
},
|
|
75
|
+
status=200,
|
|
76
|
+
)
|
|
77
|
+
result = runner.invoke(app, ["organization", "get"])
|
|
78
|
+
assert result.exit_code == 0
|
|
79
|
+
assert "minimal" in result.output
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_organization_help():
|
|
83
|
+
result = runner.invoke(app, ["organization", "--help"])
|
|
84
|
+
assert result.exit_code == 0
|
|
85
|
+
assert "get" in result.output
|
|
@@ -104,6 +104,29 @@ def test_resolve_from_config(config_dir):
|
|
|
104
104
|
conn = resolve_connection()
|
|
105
105
|
assert conn.api_key == "mykey"
|
|
106
106
|
assert conn.host == "https://custom.host"
|
|
107
|
+
assert conn.vanity_url is None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_add_and_resolve_with_vanity_url(config_dir):
|
|
111
|
+
add_connection("prod", "mykey", "https://custom.host", vanity_url="acme")
|
|
112
|
+
conn = resolve_connection()
|
|
113
|
+
assert conn.vanity_url == "acme"
|
|
114
|
+
config = load_config()
|
|
115
|
+
assert config["connections"]["prod"]["vanity_url"] == "acme"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_add_without_vanity_url_omits_key(config_dir):
|
|
119
|
+
add_connection("prod", "mykey")
|
|
120
|
+
config = load_config()
|
|
121
|
+
assert "vanity_url" not in config["connections"]["prod"]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_list_connections_includes_vanity_url(config_dir):
|
|
125
|
+
add_connection("prod", "abcd1234efgh5678", vanity_url="acme")
|
|
126
|
+
add_connection("dev", "key2")
|
|
127
|
+
result = {row["name"]: row for row in list_connections()}
|
|
128
|
+
assert result["prod"]["vanity_url"] == "acme"
|
|
129
|
+
assert result["dev"]["vanity_url"] is None
|
|
107
130
|
|
|
108
131
|
|
|
109
132
|
def test_resolve_env_overrides_config(config_dir, monkeypatch):
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"""Connection management commands."""
|
|
2
|
-
|
|
3
|
-
from typing import Annotated
|
|
4
|
-
|
|
5
|
-
import typer
|
|
6
|
-
from rich.table import Table
|
|
7
|
-
|
|
8
|
-
from entropy_data import config as cfg
|
|
9
|
-
from entropy_data.output import console, print_error, print_success
|
|
10
|
-
|
|
11
|
-
connection_app = typer.Typer(no_args_is_help=True)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@connection_app.command("list")
|
|
15
|
-
def list_connections() -> None:
|
|
16
|
-
"""List all configured connections."""
|
|
17
|
-
connections = cfg.list_connections()
|
|
18
|
-
if not connections:
|
|
19
|
-
console.print("No connections configured. Run: entropy-data connection add <name>")
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
table = Table(show_header=True)
|
|
23
|
-
table.add_column("Name")
|
|
24
|
-
table.add_column("Host")
|
|
25
|
-
table.add_column("API Key")
|
|
26
|
-
table.add_column("Default")
|
|
27
|
-
for conn in connections:
|
|
28
|
-
table.add_row(
|
|
29
|
-
conn["name"],
|
|
30
|
-
conn["host"],
|
|
31
|
-
conn["api_key"],
|
|
32
|
-
"*" if conn["default"] else "",
|
|
33
|
-
)
|
|
34
|
-
console.print(table)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@connection_app.command("add")
|
|
38
|
-
def add_connection(
|
|
39
|
-
name: Annotated[str, typer.Argument(help="Connection name.")],
|
|
40
|
-
api_key: Annotated[str, typer.Option("--api-key", prompt="API key", help="The API key.")] = None,
|
|
41
|
-
host: Annotated[
|
|
42
|
-
str, typer.Option("--host", prompt="Host", prompt_required=False, help="API host URL.")
|
|
43
|
-
] = cfg.DEFAULT_HOST,
|
|
44
|
-
) -> None:
|
|
45
|
-
"""Add or update a named connection."""
|
|
46
|
-
try:
|
|
47
|
-
cfg.add_connection(name, api_key, host)
|
|
48
|
-
print_success(f"Connection '{name}' saved.")
|
|
49
|
-
except cfg.ConfigurationError as e:
|
|
50
|
-
print_error(str(e))
|
|
51
|
-
raise typer.Exit(1)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@connection_app.command("remove")
|
|
55
|
-
def remove_connection(
|
|
56
|
-
name: Annotated[str, typer.Argument(help="Connection name to remove.")],
|
|
57
|
-
) -> None:
|
|
58
|
-
"""Remove a named connection."""
|
|
59
|
-
try:
|
|
60
|
-
cfg.remove_connection(name)
|
|
61
|
-
print_success(f"Connection '{name}' removed.")
|
|
62
|
-
except cfg.ConfigurationError as e:
|
|
63
|
-
print_error(str(e))
|
|
64
|
-
raise typer.Exit(1)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@connection_app.command("set-default")
|
|
68
|
-
def set_default(
|
|
69
|
-
name: Annotated[str, typer.Argument(help="Connection name to set as default.")],
|
|
70
|
-
) -> None:
|
|
71
|
-
"""Set the default connection."""
|
|
72
|
-
try:
|
|
73
|
-
cfg.set_default_connection(name)
|
|
74
|
-
print_success(f"Default connection set to '{name}'.")
|
|
75
|
-
except cfg.ConfigurationError as e:
|
|
76
|
-
print_error(str(e))
|
|
77
|
-
raise typer.Exit(1)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@connection_app.command("test")
|
|
81
|
-
def test_connection() -> None:
|
|
82
|
-
"""Test the current connection by calling the API."""
|
|
83
|
-
from entropy_data.cli import get_client, handle_error
|
|
84
|
-
|
|
85
|
-
try:
|
|
86
|
-
client = get_client()
|
|
87
|
-
client.list_resources("teams", params={"p": "0"})
|
|
88
|
-
print_success("Connection successful.")
|
|
89
|
-
except Exception as e:
|
|
90
|
-
handle_error(e)
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"""Tests for connection commands."""
|
|
2
|
-
|
|
3
|
-
import responses
|
|
4
|
-
from typer.testing import CliRunner
|
|
5
|
-
|
|
6
|
-
import entropy_data.config as cfg
|
|
7
|
-
from entropy_data.cli import app
|
|
8
|
-
|
|
9
|
-
runner = CliRunner()
|
|
10
|
-
BASE_URL = "https://api.entropy-data.com"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_connection_list_empty(tmp_path, monkeypatch):
|
|
14
|
-
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
15
|
-
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
16
|
-
result = runner.invoke(app, ["connection", "list"])
|
|
17
|
-
assert result.exit_code == 0
|
|
18
|
-
assert "No connections" in result.output
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def test_connection_add_and_list(tmp_path, monkeypatch):
|
|
22
|
-
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
23
|
-
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
24
|
-
result = runner.invoke(app, ["connection", "add", "prod", "--api-key", "mykey123456", "--host", BASE_URL])
|
|
25
|
-
assert result.exit_code == 0
|
|
26
|
-
assert "saved" in result.output
|
|
27
|
-
|
|
28
|
-
result = runner.invoke(app, ["connection", "list"])
|
|
29
|
-
assert result.exit_code == 0
|
|
30
|
-
assert "prod" in result.output
|
|
31
|
-
assert "*" in result.output # default marker
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def test_connection_remove(tmp_path, monkeypatch):
|
|
35
|
-
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
36
|
-
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
37
|
-
runner.invoke(app, ["connection", "add", "prod", "--api-key", "key1", "--host", BASE_URL])
|
|
38
|
-
result = runner.invoke(app, ["connection", "remove", "prod"])
|
|
39
|
-
assert result.exit_code == 0
|
|
40
|
-
assert "removed" in result.output
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_connection_set_default(tmp_path, monkeypatch):
|
|
44
|
-
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
45
|
-
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
46
|
-
runner.invoke(app, ["connection", "add", "prod", "--api-key", "key1", "--host", BASE_URL])
|
|
47
|
-
runner.invoke(app, ["connection", "add", "dev", "--api-key", "key2", "--host", "http://localhost:8080"])
|
|
48
|
-
result = runner.invoke(app, ["connection", "set-default", "dev"])
|
|
49
|
-
assert result.exit_code == 0
|
|
50
|
-
assert "dev" in result.output
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@responses.activate
|
|
54
|
-
def test_connection_test_success(tmp_path, monkeypatch):
|
|
55
|
-
monkeypatch.setattr(cfg, "CONFIG_DIR", tmp_path)
|
|
56
|
-
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
57
|
-
runner.invoke(app, ["connection", "add", "prod", "--api-key", "key1", "--host", BASE_URL])
|
|
58
|
-
responses.add(responses.GET, f"{BASE_URL}/api/teams", json=[], status=200)
|
|
59
|
-
result = runner.invoke(app, ["connection", "test"])
|
|
60
|
-
assert result.exit_code == 0
|
|
61
|
-
assert "successful" in result.output
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def test_connection_help():
|
|
65
|
-
result = runner.invoke(app, ["connection", "--help"])
|
|
66
|
-
assert result.exit_code == 0
|
|
67
|
-
assert "list" in result.output
|
|
68
|
-
assert "add" in result.output
|
|
69
|
-
assert "remove" in result.output
|
|
70
|
-
assert "set-default" in result.output
|
|
71
|
-
assert "test" in result.output
|
|
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
|