oed-cli 0.1.2__tar.gz → 0.1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oed-cli
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: oed — openEuler Infra command line. Auto-discovered, AI-friendly.
5
5
  Author: oed-cli contributors
6
6
  License: Apache-2.0
@@ -97,7 +97,7 @@ pip install -e .
97
97
  Verify:
98
98
 
99
99
  ```bash
100
- oed --version # → oed, version 0.1.2
100
+ oed --version # → oed, version 0.1.3
101
101
  ```
102
102
 
103
103
  ---
@@ -63,7 +63,7 @@ pip install -e .
63
63
  Verify:
64
64
 
65
65
  ```bash
66
- oed --version # → oed, version 0.1.2
66
+ oed --version # → oed, version 0.1.3
67
67
  ```
68
68
 
69
69
  ---
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "oed-cli"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "oed — openEuler Infra command line. Auto-discovered, AI-friendly."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
- """oed-cli — openEuler Infra command line, auto-discovered, AI-friendly."""
2
-
3
- from __future__ import annotations
4
-
5
- __version__ = "0.1.2"
6
- __all__ = ["__version__"]
1
+ """oed-cli — openEuler Infra command line, auto-discovered, AI-friendly."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.1.3"
6
+ __all__ = ["__version__"]
@@ -160,6 +160,12 @@ def call_operation(
160
160
  query_params = {k: v for k, v in query_params.items() if v is not None}
161
161
  url = f"{op.base_url}{filled_path}"
162
162
 
163
+ # Discourse (forum) authenticates via Api-Key/Api-Username; inert sentinel
164
+ # until login lands, so the request shape stays visible without leaking a real credential.
165
+ api_headers: dict[str, str] = {}
166
+ if op.service_name == "forum":
167
+ api_headers = {"Api-Key": "oed-placeholder", "Api-Username": "oed-placeholder"}
168
+
163
169
  request_headers: dict[str, str] = {"User-Agent": _resolve_user_agent(user_agent)}
164
170
  if body is not None:
165
171
  request_headers["Content-Type"] = "application/json"
@@ -167,7 +173,7 @@ def call_operation(
167
173
  "method": op.backend.method,
168
174
  "url": url,
169
175
  "query": query_params,
170
- "headers": request_headers,
176
+ "headers": {**request_headers, **api_headers},
171
177
  "body": body,
172
178
  }
173
179
 
@@ -191,6 +197,7 @@ def call_operation(
191
197
  url,
192
198
  params=query_params if query_params else None,
193
199
  body=body,
200
+ headers=api_headers,
194
201
  timeout=timeout,
195
202
  user_agent=user_agent,
196
203
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oed-cli
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: oed — openEuler Infra command line. Auto-discovered, AI-friendly.
5
5
  Author: oed-cli contributors
6
6
  License: Apache-2.0
@@ -97,7 +97,7 @@ pip install -e .
97
97
  Verify:
98
98
 
99
99
  ```bash
100
- oed --version # → oed, version 0.1.2
100
+ oed --version # → oed, version 0.1.3
101
101
  ```
102
102
 
103
103
  ---
@@ -220,7 +220,7 @@ def test_help_enumerates_discovered_services(runner, patched_discovery):
220
220
  # Quickstart now leads with the per-parameter flag style and the canonical
221
221
  # CVE example; it should not mention the legacy API_<NAME> placeholder.
222
222
  assert "API_<NAME>" not in text
223
- assert "--<flag>" in text
223
+ assert "--<kebab-case>" in text
224
224
  assert "getSecurityNoticeByCveId --cve-id" in text
225
225
 
226
226
 
@@ -6,7 +6,6 @@ layers, so they need no gateway access.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- import json
10
9
  import sys
11
10
  from pathlib import Path
12
11
 
@@ -158,7 +157,9 @@ def patched(monkeypatch):
158
157
 
159
158
  captured = {}
160
159
 
161
- def _fake_do_call(method, url, params=None, body=None, timeout=30.0, headers=None, user_agent=None):
160
+ def _fake_do_call(
161
+ method, url, params=None, body=None, timeout=30.0, headers=None, user_agent=None
162
+ ):
162
163
  class _R:
163
164
  status_code = 200
164
165
  content = b'{"code":"","msg":"","data":{"ok":true}}'
@@ -172,6 +173,7 @@ def patched(monkeypatch):
172
173
  captured["url"] = url
173
174
  captured["params"] = params
174
175
  captured["body"] = body
176
+ captured["headers"] = headers or {}
175
177
  captured["user_agent"] = user_agent
176
178
  return _R()
177
179
 
@@ -515,6 +517,44 @@ def test_call_operation_post_sends_body(patched):
515
517
  assert patched["captured"]["body"] == body
516
518
 
517
519
 
520
+ def test_call_operation_forum_sends_api_headers(patched):
521
+ """Forum (Discourse) requests carry the Api-Key / Api-Username sentinels
522
+ on the real request and in the dry-run view."""
523
+
524
+ from oed_cli.dynamic import operations_table
525
+ from oed_cli.invoke import call_operation
526
+
527
+ table = operations_table(SAMPLE_SPEC, "forum", base_url="https://apig.osinfra.cn")
528
+ op = table["API_getSoftwarePackage"]
529
+
530
+ call_operation(op, params={"id": "1"})
531
+ assert patched["captured"]["headers"] == {
532
+ "Api-Key": "oed-placeholder",
533
+ "Api-Username": "oed-placeholder",
534
+ }
535
+
536
+ dry = call_operation(op, params={"id": "1"}, dry_run=True)
537
+ assert dry["request"]["headers"]["Api-Key"] == "oed-placeholder"
538
+ assert dry["request"]["headers"]["Api-Username"] == "oed-placeholder"
539
+
540
+
541
+ def test_call_operation_non_forum_omits_api_headers(patched):
542
+ """Non-forum services get no extra auth headers."""
543
+
544
+ from oed_cli.dynamic import operations_table
545
+ from oed_cli.invoke import call_operation
546
+
547
+ table = operations_table(
548
+ SAMPLE_SPEC, "software-package-server", base_url="https://apig.osinfra.cn"
549
+ )
550
+ op = table["API_getSoftwarePackage"]
551
+
552
+ call_operation(op, params={"id": "1"})
553
+ headers = patched["captured"]["headers"]
554
+ assert "Api-Key" not in headers
555
+ assert "Api-Username" not in headers
556
+
557
+
518
558
  def test_call_operation_missing_path_param_raises_user_error(patched):
519
559
  from oed_cli.dynamic import operations_table
520
560
  from oed_cli.errors import UserError
@@ -620,7 +660,6 @@ def test_service_level_help_with_method_rejected(patched, monkeypatch, capsys):
620
660
 
621
661
 
622
662
  def test_spec_missing_returns_exit_4(patched, runner, monkeypatch, capsys):
623
- from oed_cli import dynamic as dyn
624
663
  from oed_cli import main as oed_main
625
664
  from oed_cli.errors import NotFoundError
626
665
 
@@ -646,14 +685,17 @@ def test_spec_missing_returns_exit_4(patched, runner, monkeypatch, capsys):
646
685
  def test_describe_helpers_and_main_dispatch(patched, monkeypatch, capsys):
647
686
  """Compact sweep covering describe_*, main per-param flag dispatch,
648
687
  and parse_json_arg / coerce_flag_value edge cases."""
688
+ from oed_cli import main as oed_main
649
689
  from oed_cli.discovery import ServiceMeta
650
690
  from oed_cli.dynamic import (
651
- collect_operations, operations_table, parse_json_arg,
652
- coerce_flag_value, resolve_operation,
691
+ coerce_flag_value,
692
+ collect_operations,
693
+ operations_table,
694
+ parse_json_arg,
695
+ resolve_operation,
653
696
  )
654
- from oed_cli.invoke import describe_service, describe_operation_help, call_operation
655
- from oed_cli import main as oed_main
656
697
  from oed_cli.errors import UserError
698
+ from oed_cli.invoke import call_operation, describe_operation_help, describe_service
657
699
 
658
700
  svc = ServiceMeta.from_raw(SAMPLE_FEED["communities"]["openeuler"][0])
659
701
  ops = collect_operations(SAMPLE_SPEC, svc.service_name)
@@ -705,14 +747,84 @@ def test_describe_helpers_and_main_dispatch(patched, monkeypatch, capsys):
705
747
  assert coerce_flag_value({"schema": {"type": "number"}}, "abc") == "abc"
706
748
 
707
749
 
750
+ def test_resolve_json_body_schema_edge_cases():
751
+ """Non-dict media / schema blocks resolve to ``None`` instead of crashing."""
752
+
753
+ from oed_cli.dynamic import _resolve_json_body_schema
754
+
755
+ assert _resolve_json_body_schema({"content": {}}, SAMPLE_SPEC) is None
756
+ assert _resolve_json_body_schema(
757
+ {"content": {"application/json": {"schema": "not-a-dict"}}}, SAMPLE_SPEC
758
+ ) is None
759
+
760
+
761
+ def test_operations_for_one_shot_helper(patched):
762
+ """``operations_for`` resolves, fetches, and bakes the runtime base_url in."""
763
+
764
+ from oed_cli.dynamic import operations_for
765
+
766
+ svc, table, spec = operations_for("software-package-server")
767
+ assert svc.service_name == "software-package-server"
768
+ assert spec is SAMPLE_SPEC
769
+ op = table["API_getSoftwarePackage"]
770
+ assert op.base_url == "https://apig.osinfra.cn"
771
+
772
+
773
+ def test_body_field_summary_edge_cases():
774
+ """Non-dict schema returns [] and non-dict properties are skipped."""
775
+
776
+ from oed_cli.invoke import _body_field_summary
777
+
778
+ assert _body_field_summary(None) == []
779
+ out = _body_field_summary(
780
+ {
781
+ "properties": {
782
+ "bad": "not-a-dict",
783
+ "good": {"type": "string", "description": "a description"},
784
+ "plain": {"type": "integer"},
785
+ }
786
+ }
787
+ )
788
+ assert [e["name"] for e in out] == ["good", "plain"]
789
+ assert out[0]["description"] == "a description"
790
+ assert "description" not in out[1]
791
+
792
+
793
+ def test_describe_operation_help_with_body(patched):
794
+ """Operation help for a POST with a body exposes params + body field summary."""
795
+
796
+ from oed_cli.dynamic import operations_table
797
+ from oed_cli.invoke import describe_operation_help
798
+
799
+ svc = patched["feed"].services[0]
800
+ op = operations_table(SAMPLE_SPEC, svc.service_name)["API_applyNewSoftwarePackage"]
801
+ doc = describe_operation_help(op, svc)
802
+ assert doc["ok"] is True
803
+ assert doc["usage"] and doc["examples"]
804
+ assert doc["body_required"] is True
805
+ assert doc["parameters"] == []
806
+ assert doc["body_required_fields"] == []
807
+ assert doc["body_schema"] == {"type": "object"}
808
+
809
+
810
+ def test_dispatch_dynamic_help_legacy_wrapper(patched, capsys):
811
+ """Legacy ``_dispatch_dynamic_help`` resolves + collects and lists operations."""
812
+
813
+ from oed_cli.main import _dispatch_dynamic_help
814
+
815
+ code = _dispatch_dynamic_help("software-package-server", [])
816
+ out = capsys.readouterr()
817
+ assert code == 0 and "listSoftwarePackages" in (out.out + out.err)
818
+
819
+
708
820
  # ---------- main() dispatch edges ----------
709
821
 
710
822
 
711
823
  def test_main_dispatch_comprehensive(patched, monkeypatch, capsys):
712
824
  """Sweep main dispatch surfaces: reserved routes, --key=value, parse-error,
713
825
  dry-run short-circuit, service-only listing, and call_operation errors."""
714
- from oed_cli import main as oed_main
715
826
  from oed_cli import http as http_mod
827
+ from oed_cli import main as oed_main
716
828
  from oed_cli.errors import NetworkError
717
829
 
718
830
  monkeypatch.setenv("OED_COMMUNITY", "openeuler")
@@ -758,8 +870,13 @@ def test_main_dispatch_comprehensive(patched, monkeypatch, capsys):
758
870
  code = oed_main.main(["software-package-server", "API_getSoftwarePackage", "--id", "1"])
759
871
  out = capsys.readouterr()
760
872
  assert code == 2 and "network_error" in (out.out + out.err)
761
- monkeypatch.setattr(http_mod, "get_request",
762
- lambda *a, **k: type("R", (), {"status_code": 503, "text": "", "content": b"", "headers": {}})())
873
+ monkeypatch.setattr(
874
+ http_mod,
875
+ "get_request",
876
+ lambda *a, **k: type(
877
+ "R", (), {"status_code": 503, "text": "", "content": b"", "headers": {}}
878
+ )(),
879
+ )
763
880
  code = oed_main.main(["software-package-server", "API_getSoftwarePackage", "--id", "1"])
764
881
  assert code == 3
765
882
 
@@ -770,7 +887,7 @@ def test_main_dispatch_comprehensive(patched, monkeypatch, capsys):
770
887
  def test_resolve_user_agent_precedence(monkeypatch):
771
888
  """Explicit arg > ``OED_USER_AGENT`` env > bundled default; empty string falls through."""
772
889
 
773
- from oed_cli.http import _resolve_user_agent, DEFAULT_USER_AGENT
890
+ from oed_cli.http import DEFAULT_USER_AGENT, _resolve_user_agent
774
891
 
775
892
  monkeypatch.delenv("OED_USER_AGENT", raising=False)
776
893
  assert _resolve_user_agent(None) == DEFAULT_USER_AGENT
@@ -836,16 +953,21 @@ def test_dispatch_user_agent_flag_flows_to_http(patched, monkeypatch, capsys):
836
953
  assert patched["captured"]["user_agent"] == "browser-mock/9.9"
837
954
 
838
955
 
839
- def test_dispatch_user_agent_env_only(patched, monkeypatch):
840
- """No flag, only env → env value is forwarded."""
956
+ def test_dispatch_user_agent_env_only(patched, monkeypatch, capsys):
957
+ """No --user-agent flag, only OED_USER_AGENT env → env value lands in the
958
+ outbound User-Agent header (resolved downstream in the HTTP layer)."""
841
959
 
842
960
  from oed_cli import main as oed_main
843
961
 
844
962
  monkeypatch.setenv("OED_COMMUNITY", "openeuler")
845
963
  monkeypatch.setenv("OED_USER_AGENT", "env-only/3.0")
846
- code = oed_main.main(["software-package-server", "API_getSoftwarePackage", "--id", "1"])
964
+ code = oed_main.main([
965
+ "software-package-server", "API_getSoftwarePackage",
966
+ "--id", "1", "--dry-run",
967
+ ])
847
968
  assert code == 0
848
- assert patched["captured"]["user_agent"] == "env-only/3.0"
969
+ out = capsys.readouterr().out
970
+ assert '"User-Agent": "env-only/3.0"' in out
849
971
 
850
972
 
851
973
  def test_dispatch_user_agent_flag_overrides_env(patched, monkeypatch):
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