entropy-data 0.3.4__tar.gz → 0.3.6__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 (72) hide show
  1. {entropy_data-0.3.4 → entropy_data-0.3.6}/CHANGELOG.md +9 -0
  2. {entropy_data-0.3.4 → entropy_data-0.3.6}/PKG-INFO +1 -1
  3. {entropy_data-0.3.4 → entropy_data-0.3.6}/pyproject.toml +1 -1
  4. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/access.py +75 -0
  5. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/semantics.py +46 -0
  6. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/config.py +1 -3
  7. entropy_data-0.3.6/tests/commands/test_access_request.py +118 -0
  8. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_semantics.py +75 -1
  9. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/test_config.py +2 -2
  10. {entropy_data-0.3.4 → entropy_data-0.3.6}/.editorconfig +0 -0
  11. {entropy_data-0.3.4 → entropy_data-0.3.6}/.github/dependabot.yml +0 -0
  12. {entropy_data-0.3.4 → entropy_data-0.3.6}/.github/pull_request_template.md +0 -0
  13. {entropy_data-0.3.4 → entropy_data-0.3.6}/.github/workflows/ci.yaml +0 -0
  14. {entropy_data-0.3.4 → entropy_data-0.3.6}/.github/workflows/release.yaml +0 -0
  15. {entropy_data-0.3.4 → entropy_data-0.3.6}/.gitignore +0 -0
  16. {entropy_data-0.3.4 → entropy_data-0.3.6}/.pre-commit-config.yaml +0 -0
  17. {entropy_data-0.3.4 → entropy_data-0.3.6}/CLAUDE.md +0 -0
  18. {entropy_data-0.3.4 → entropy_data-0.3.6}/Dockerfile +0 -0
  19. {entropy_data-0.3.4 → entropy_data-0.3.6}/LICENSE +0 -0
  20. {entropy_data-0.3.4 → entropy_data-0.3.6}/README.md +0 -0
  21. {entropy_data-0.3.4 → entropy_data-0.3.6}/release +0 -0
  22. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/__init__.py +0 -0
  23. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/__main__.py +0 -0
  24. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/cli.py +0 -0
  25. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/client.py +0 -0
  26. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/__init__.py +0 -0
  27. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/api_keys.py +0 -0
  28. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/assets.py +0 -0
  29. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/certifications.py +0 -0
  30. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/connection.py +0 -0
  31. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/connectors.py +0 -0
  32. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/costs.py +0 -0
  33. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/datacontracts.py +0 -0
  34. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/dataproducts.py +0 -0
  35. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/definitions.py +0 -0
  36. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/events.py +0 -0
  37. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/example_data.py +0 -0
  38. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/git_credentials.py +0 -0
  39. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/gitconnections.py +0 -0
  40. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/import_export.py +0 -0
  41. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/lineage.py +0 -0
  42. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/notification_channels.py +0 -0
  43. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/organization.py +0 -0
  44. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/search.py +0 -0
  45. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/settings.py +0 -0
  46. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/sourcesystems.py +0 -0
  47. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/tags.py +0 -0
  48. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/teams.py +0 -0
  49. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/test_results.py +0 -0
  50. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/commands/usage.py +0 -0
  51. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/output.py +0 -0
  52. {entropy_data-0.3.4 → entropy_data-0.3.6}/src/entropy_data/util.py +0 -0
  53. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/__init__.py +0 -0
  54. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/__init__.py +0 -0
  55. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_api_keys.py +0 -0
  56. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_assets.py +0 -0
  57. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_connection.py +0 -0
  58. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_connectors.py +0 -0
  59. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_costs.py +0 -0
  60. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_datacontracts.py +0 -0
  61. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_dataproducts.py +0 -0
  62. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_git_credentials.py +0 -0
  63. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_gitconnections.py +0 -0
  64. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_lineage.py +0 -0
  65. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_notification_channels.py +0 -0
  66. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_organization.py +0 -0
  67. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_settings.py +0 -0
  68. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_tags.py +0 -0
  69. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_teams.py +0 -0
  70. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/commands/test_usage.py +0 -0
  71. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/conftest.py +0 -0
  72. {entropy_data-0.3.4 → entropy_data-0.3.6}/tests/test_client.py +0 -0
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.3.6]
6
+
7
+ - `entropy-data connection add` now sets the newly added connection as the default, overriding any previously set default.
8
+
9
+ ## [0.3.5]
10
+
11
+ - Add `entropy-data semantics search <namespace> <query> [--kind <kind>] [--limit <n>]` for case-insensitive substring search across concept id, name, and description in a namespace. Implemented client-side over the existing `GET /api/semantics/experimental/namespaces/{ns}/concepts` endpoint.
12
+ - Add `entropy-data access request <data-product-id> <output-port-id> --purpose <text> --consumer-team|--consumer-user|--consumer-dataproduct <id> [--roles <comma-list>] [--id <agreement-id>]` to submit an access request for a provider output port. Wraps `PUT /api/access/{id}` and auto-generates a UUID when `--id` is omitted.
13
+
5
14
  ## [0.3.4]
6
15
 
7
16
  - Add `entropy-data datacontracts yaml <id>` to fetch a data contract as ODCS YAML (writes to stdout or `--file`). Backed by `GET /api/datacontracts/{id}.yaml`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: entropy-data
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: CLI for Entropy Data
5
5
  Project-URL: Homepage, https://entropy-data.com
6
6
  Project-URL: Documentation, https://docs.entropy-data.com
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "entropy-data"
3
- version = "0.3.4"
3
+ version = "0.3.6"
4
4
  description = "CLI for Entropy Data"
5
5
  requires-python = ">=3.11"
6
6
  license = "MIT"
@@ -1,5 +1,6 @@
1
1
  """Access (data usage agreements) commands."""
2
2
 
3
+ import uuid
3
4
  from pathlib import Path
4
5
  from typing import Annotated, Optional
5
6
 
@@ -126,3 +127,77 @@ def cancel_access(
126
127
  print_link(location)
127
128
  except Exception as e:
128
129
  handle_error(e)
130
+
131
+
132
+ @access_app.command("request")
133
+ def request_access(
134
+ data_product_id: Annotated[str, typer.Argument(help="Provider data product ID.")],
135
+ output_port_id: Annotated[str, typer.Argument(help="Provider output port ID.")],
136
+ purpose: Annotated[str, typer.Option("--purpose", help="Business justification for the access.")] = ...,
137
+ consumer_team: Annotated[
138
+ Optional[str],
139
+ typer.Option("--consumer-team", help="Consumer team ID (request on behalf of a team)."),
140
+ ] = None,
141
+ consumer_user: Annotated[
142
+ Optional[str],
143
+ typer.Option("--consumer-user", help="Consumer user ID (request on behalf of a user)."),
144
+ ] = None,
145
+ consumer_dataproduct: Annotated[
146
+ Optional[str],
147
+ typer.Option("--consumer-dataproduct", help="Consumer data product ID (cross-product agreement)."),
148
+ ] = None,
149
+ roles: Annotated[
150
+ Optional[str],
151
+ typer.Option("--roles", help="Comma-separated list of roles to request (e.g. analyst,data_engineer)."),
152
+ ] = None,
153
+ id: Annotated[
154
+ Optional[str],
155
+ typer.Option("--id", help="Agreement ID. Auto-generated UUID if not provided."),
156
+ ] = None,
157
+ ) -> None:
158
+ """Submit an access request for a provider data product output port.
159
+
160
+ Creates a new access agreement in 'requested' state via PUT /api/access/{id}. Exactly
161
+ one of --consumer-team / --consumer-user / --consumer-dataproduct must be specified.
162
+ """
163
+ from entropy_data.cli import get_client, handle_error
164
+ from entropy_data.output import error_console
165
+
166
+ consumer_count = sum(c is not None for c in (consumer_team, consumer_user, consumer_dataproduct))
167
+ if consumer_count != 1:
168
+ error_console.print(
169
+ "[red]Error: provide exactly one of --consumer-team, --consumer-user, "
170
+ "or --consumer-dataproduct.[/red]"
171
+ )
172
+ raise SystemExit(2)
173
+
174
+ agreement_id = id or str(uuid.uuid4())
175
+ consumer: dict = {}
176
+ if consumer_team is not None:
177
+ consumer["teamId"] = consumer_team
178
+ if consumer_user is not None:
179
+ consumer["userId"] = consumer_user
180
+ if consumer_dataproduct is not None:
181
+ consumer["dataProductId"] = consumer_dataproduct
182
+
183
+ body: dict = {
184
+ "id": agreement_id,
185
+ "provider": {
186
+ "dataProductId": data_product_id,
187
+ "outputPortId": output_port_id,
188
+ },
189
+ "consumer": consumer,
190
+ "info": {
191
+ "purpose": purpose,
192
+ },
193
+ }
194
+ if roles:
195
+ body["roles"] = [r.strip() for r in roles.split(",") if r.strip()]
196
+
197
+ try:
198
+ client = get_client()
199
+ location = client.put_resource(RESOURCE_PATH, agreement_id, body)
200
+ print_success(f"Access request '{agreement_id}' submitted.")
201
+ print_link(location)
202
+ except Exception as e:
203
+ handle_error(e)
@@ -257,6 +257,52 @@ def delete_relationship(
257
257
  handle_error(e)
258
258
 
259
259
 
260
+ @semantics_app.command("search")
261
+ def search_concepts(
262
+ namespace: Annotated[str, typer.Argument(help="Namespace ID.")],
263
+ query: Annotated[str, typer.Argument(help="Search query (case-insensitive substring).")],
264
+ kind: Annotated[
265
+ Optional[str],
266
+ typer.Option("--kind", help="Filter by kind: entity, metric, group, shared_property, property."),
267
+ ] = None,
268
+ limit: Annotated[int, typer.Option("--limit", help="Max results.")] = 50,
269
+ output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
270
+ ) -> None:
271
+ """Search concepts in a namespace by case-insensitive substring against id, name, description.
272
+
273
+ Implemented client-side: fetches all concepts in the namespace via the experimental list
274
+ endpoint, then filters in memory. The platform has no dedicated search endpoint.
275
+ """
276
+ from entropy_data.cli import get_client, get_output_format, handle_error
277
+ from entropy_data.output import error_console
278
+
279
+ fmt = output or get_output_format()
280
+ q = query.strip().lower()
281
+ if not q:
282
+ error_console.print("[red]Error: query must not be empty.[/red]")
283
+ raise SystemExit(2)
284
+
285
+ try:
286
+ client = get_client()
287
+ all_concepts, _ = client.list_resources(_concepts_path(namespace))
288
+
289
+ def matches(c: dict) -> bool:
290
+ haystack = " ".join(
291
+ [
292
+ c.get("id") or "",
293
+ c.get("name") or "",
294
+ c.get("description") or "",
295
+ ]
296
+ ).lower()
297
+ return q in haystack
298
+
299
+ filtered = [c for c in all_concepts if matches(c) and (kind is None or c.get("kind") == kind)]
300
+ truncated = filtered[: max(1, limit)]
301
+ print_resource_list(truncated, "semantic-concepts", fmt)
302
+ except Exception as e:
303
+ handle_error(e)
304
+
305
+
260
306
  semantics_app.add_typer(namespaces_app, name="namespaces", help="Manage semantic namespaces.")
261
307
  semantics_app.add_typer(concepts_app, name="concepts", help="Manage semantic concepts.")
262
308
  semantics_app.add_typer(relationships_app, name="relationships", help="Manage semantic relationships.")
@@ -106,9 +106,7 @@ def add_connection(name: str, api_key: str, host: str = DEFAULT_HOST, vanity_url
106
106
  if vanity_url:
107
107
  entry["vanity_url"] = vanity_url
108
108
  config["connections"][name] = entry
109
- # Set as default if it's the first connection
110
- if "default_connection_name" not in config:
111
- config["default_connection_name"] = name
109
+ config["default_connection_name"] = name
112
110
  save_config(config)
113
111
 
114
112
 
@@ -0,0 +1,118 @@
1
+ """Tests for the `access request` command."""
2
+
3
+ import json
4
+ import re
5
+
6
+ import responses
7
+ from typer.testing import CliRunner
8
+
9
+ import entropy_data.config as cfg
10
+ from entropy_data.cli import app
11
+
12
+ runner = CliRunner()
13
+ BASE_URL = "https://api.entropy-data.com"
14
+
15
+ UUID_RE = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
16
+
17
+
18
+ def _setup(monkeypatch, tmp_path):
19
+ monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
20
+ monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
21
+
22
+
23
+ @responses.activate
24
+ def test_request_with_consumer_team(monkeypatch, tmp_path):
25
+ _setup(monkeypatch, tmp_path)
26
+ captured: dict = {}
27
+
28
+ def _capture(request):
29
+ captured["body"] = json.loads(request.body)
30
+ return (200, {}, "")
31
+
32
+ responses.add_callback(responses.PUT, re.compile(rf"{BASE_URL}/api/access/.+"), callback=_capture)
33
+
34
+ result = runner.invoke(
35
+ app,
36
+ [
37
+ "access",
38
+ "request",
39
+ "dp_account_master",
40
+ "accounts",
41
+ "--purpose",
42
+ "Building churn model.",
43
+ "--consumer-team",
44
+ "customer-success",
45
+ ],
46
+ )
47
+ assert result.exit_code == 0
48
+ assert "submitted" in result.output
49
+ body = captured["body"]
50
+ assert body["provider"] == {"dataProductId": "dp_account_master", "outputPortId": "accounts"}
51
+ assert body["consumer"] == {"teamId": "customer-success"}
52
+ assert body["info"] == {"purpose": "Building churn model."}
53
+ assert UUID_RE.match(body["id"])
54
+
55
+
56
+ @responses.activate
57
+ def test_request_with_explicit_id_and_roles(monkeypatch, tmp_path):
58
+ _setup(monkeypatch, tmp_path)
59
+ captured: dict = {}
60
+
61
+ def _capture(request):
62
+ captured["body"] = json.loads(request.body)
63
+ return (200, {}, "")
64
+
65
+ responses.add_callback(responses.PUT, f"{BASE_URL}/api/access/req-001", callback=_capture)
66
+
67
+ result = runner.invoke(
68
+ app,
69
+ [
70
+ "access",
71
+ "request",
72
+ "dp_a",
73
+ "op_a",
74
+ "--purpose",
75
+ "x",
76
+ "--consumer-user",
77
+ "alice@example.com",
78
+ "--roles",
79
+ "analyst, data_engineer",
80
+ "--id",
81
+ "req-001",
82
+ ],
83
+ )
84
+ assert result.exit_code == 0
85
+ body = captured["body"]
86
+ assert body["id"] == "req-001"
87
+ assert body["consumer"] == {"userId": "alice@example.com"}
88
+ assert body["roles"] == ["analyst", "data_engineer"]
89
+
90
+
91
+ def test_request_missing_consumer(monkeypatch, tmp_path):
92
+ _setup(monkeypatch, tmp_path)
93
+ result = runner.invoke(
94
+ app,
95
+ ["access", "request", "dp_a", "op_a", "--purpose", "x"],
96
+ )
97
+ assert result.exit_code == 2
98
+ assert "consumer" in result.output.lower()
99
+
100
+
101
+ def test_request_multiple_consumers(monkeypatch, tmp_path):
102
+ _setup(monkeypatch, tmp_path)
103
+ result = runner.invoke(
104
+ app,
105
+ [
106
+ "access",
107
+ "request",
108
+ "dp_a",
109
+ "op_a",
110
+ "--purpose",
111
+ "x",
112
+ "--consumer-team",
113
+ "t1",
114
+ "--consumer-user",
115
+ "u1",
116
+ ],
117
+ )
118
+ assert result.exit_code == 2
@@ -24,6 +24,27 @@ CONCEPT = {
24
24
  "status": "active",
25
25
  }
26
26
 
27
+ CONCEPTS_FOR_SEARCH = [
28
+ {
29
+ "id": "customer",
30
+ "name": "Customer",
31
+ "kind": "entity",
32
+ "description": "A person or organization that purchases.",
33
+ },
34
+ {
35
+ "id": "account",
36
+ "name": "Account",
37
+ "kind": "entity",
38
+ "description": "An ongoing relationship with a customer.",
39
+ },
40
+ {
41
+ "id": "mrr",
42
+ "name": "Monthly Recurring Revenue",
43
+ "kind": "metric",
44
+ "description": "Monthly revenue from active subscriptions.",
45
+ },
46
+ ]
47
+
27
48
  RELATIONSHIP = {
28
49
  "id": "customer-has-orders",
29
50
  "name": "Customer has Orders",
@@ -216,7 +237,60 @@ def test_semantics_relationships_delete(monkeypatch, tmp_path):
216
237
  ["semantics", "relationships", "delete", "main", "customer-has-orders"],
217
238
  )
218
239
  assert result.exit_code == 0
219
- assert "deleted" in result.output
240
+
241
+
242
+ # Search
243
+
244
+
245
+ @responses.activate
246
+ def test_semantics_search_substring(monkeypatch, tmp_path):
247
+ monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
248
+ monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
249
+ responses.add(responses.GET, f"{NS_URL}/main/concepts", json=CONCEPTS_FOR_SEARCH, status=200)
250
+ result = runner.invoke(
251
+ app, ["semantics", "search", "main", "customer", "--output", "json"]
252
+ )
253
+ assert result.exit_code == 0
254
+ data = json.loads(result.output)
255
+ ids = {c["id"] for c in data}
256
+ # 'customer' matches both the customer concept and the account description
257
+ assert ids == {"customer", "account"}
258
+
259
+
260
+ @responses.activate
261
+ def test_semantics_search_kind_filter(monkeypatch, tmp_path):
262
+ monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
263
+ monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
264
+ responses.add(responses.GET, f"{NS_URL}/main/concepts", json=CONCEPTS_FOR_SEARCH, status=200)
265
+ result = runner.invoke(
266
+ app,
267
+ ["semantics", "search", "main", "revenue", "--kind", "metric", "--output", "json"],
268
+ )
269
+ assert result.exit_code == 0
270
+ data = json.loads(result.output)
271
+ assert len(data) == 1
272
+ assert data[0]["id"] == "mrr"
273
+
274
+
275
+ @responses.activate
276
+ def test_semantics_search_limit(monkeypatch, tmp_path):
277
+ monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
278
+ monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
279
+ responses.add(responses.GET, f"{NS_URL}/main/concepts", json=CONCEPTS_FOR_SEARCH, status=200)
280
+ result = runner.invoke(
281
+ app,
282
+ ["semantics", "search", "main", "a", "--limit", "1", "--output", "json"],
283
+ )
284
+ assert result.exit_code == 0
285
+ data = json.loads(result.output)
286
+ assert len(data) == 1
287
+
288
+
289
+ def test_semantics_search_empty_query(monkeypatch, tmp_path):
290
+ monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
291
+ monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
292
+ result = runner.invoke(app, ["semantics", "search", "main", " "])
293
+ assert result.exit_code == 2
220
294
 
221
295
 
222
296
  def test_semantics_help():
@@ -43,11 +43,11 @@ def test_add_connection_sets_default(config_dir):
43
43
  assert config["connections"]["prod"]["api_key"] == "key123"
44
44
 
45
45
 
46
- def test_add_second_connection_keeps_default(config_dir):
46
+ def test_add_second_connection_becomes_default(config_dir):
47
47
  add_connection("prod", "key1")
48
48
  add_connection("dev", "key2", "http://localhost:8080")
49
49
  config = load_config()
50
- assert config["default_connection_name"] == "prod"
50
+ assert config["default_connection_name"] == "dev"
51
51
  assert len(config["connections"]) == 2
52
52
 
53
53
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes