openapi-cli4ai 0.5.0__tar.gz → 0.5.1__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.
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/.github/dependabot.yml +1 -1
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/.github/workflows/ci.yml +8 -8
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/.github/workflows/codeql.yml +3 -3
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/.github/workflows/publish.yml +3 -3
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/CHANGELOG.md +12 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/PKG-INFO +1 -1
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/pyproject.toml +3 -2
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/src/openapi_cli4ai/cli.py +19 -4
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_auth.py +1 -1
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_auth_flow_security.py +2 -2
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_auth_flows.py +1 -1
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_caching.py +78 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_commands.py +6 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_coverage_gaps.py +1 -1
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_device_flow.py +1 -1
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_error_handling.py +3 -3
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_oidc.py +1 -1
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_security.py +7 -7
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_v040_hardening.py +3 -3
- openapi_cli4ai-0.5.1/uv.lock +704 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/.github/CODEOWNERS +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/.github/release.yml +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/.gitignore +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/.pre-commit-config.yaml +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/CONTRIBUTING.md +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/LICENSE +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/README.md +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/SECURITY.md +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/demo.gif +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/examples/profiles.toml.example +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/openapi-cli4ai +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/src/openapi_cli4ai/__init__.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/src/openapi_cli4ai/__main__.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/src/openapi_cli4ai/py.typed +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/__init__.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/conftest.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/fixtures/petstore_spec.json +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_cli_commands.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_features.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_foundation.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_integration.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_profile_management.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_run_command.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_schema_composition.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_spec_parsing.py +0 -0
- {openapi_cli4ai-0.5.0 → openapi_cli4ai-0.5.1}/tests/test_sse_streaming.py +0 -0
|
@@ -16,22 +16,22 @@ jobs:
|
|
|
16
16
|
matrix:
|
|
17
17
|
python-version: ["3.11", "3.12", "3.13"]
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
20
|
-
- uses: astral-sh/setup-uv@
|
|
19
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
20
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
21
21
|
with:
|
|
22
22
|
enable-cache: true
|
|
23
23
|
- name: Set up Python ${{ matrix.python-version }}
|
|
24
24
|
run: uv python install ${{ matrix.python-version }}
|
|
25
25
|
- name: Sync dependencies
|
|
26
|
-
run: uv sync --python ${{ matrix.python-version }}
|
|
26
|
+
run: uv sync --locked --python ${{ matrix.python-version }}
|
|
27
27
|
- name: Run unit tests with coverage
|
|
28
28
|
run: uv run --python ${{ matrix.python-version }} pytest tests/ -m "not integration" -v --cov=openapi_cli4ai --cov-report=term-missing
|
|
29
29
|
|
|
30
30
|
lint:
|
|
31
31
|
runs-on: ubuntu-latest
|
|
32
32
|
steps:
|
|
33
|
-
- uses: actions/checkout@
|
|
34
|
-
- uses: astral-sh/setup-uv@
|
|
33
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
34
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
35
35
|
- name: Ruff check
|
|
36
36
|
run: uvx ruff check .
|
|
37
37
|
- name: Ruff format check
|
|
@@ -40,9 +40,9 @@ jobs:
|
|
|
40
40
|
typecheck:
|
|
41
41
|
runs-on: ubuntu-latest
|
|
42
42
|
steps:
|
|
43
|
-
- uses: actions/checkout@
|
|
44
|
-
- uses: astral-sh/setup-uv@
|
|
43
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
44
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
45
45
|
- name: Sync dependencies
|
|
46
|
-
run: uv sync
|
|
46
|
+
run: uv sync --locked
|
|
47
47
|
- name: Run mypy
|
|
48
48
|
run: uv run mypy src/openapi_cli4ai/ --ignore-missing-imports
|
|
@@ -16,8 +16,8 @@ jobs:
|
|
|
16
16
|
analyze:
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
20
|
-
- uses: github/codeql-action/init@
|
|
19
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
20
|
+
- uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
|
21
21
|
with:
|
|
22
22
|
languages: python
|
|
23
|
-
- uses: github/codeql-action/analyze@
|
|
23
|
+
- uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
|
@@ -13,8 +13,8 @@ jobs:
|
|
|
13
13
|
permissions:
|
|
14
14
|
contents: read
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
17
|
-
- uses: astral-sh/setup-uv@
|
|
16
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
17
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
18
18
|
- name: Build sdist and wheel
|
|
19
19
|
run: uv build
|
|
20
20
|
- name: Upload artifacts
|
|
@@ -42,7 +42,7 @@ jobs:
|
|
|
42
42
|
name: dist
|
|
43
43
|
path: dist/
|
|
44
44
|
- name: Generate attestations
|
|
45
|
-
uses: actions/attest-build-provenance@
|
|
45
|
+
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
|
46
46
|
with:
|
|
47
47
|
subject-path: dist/*
|
|
48
48
|
- name: Publish to PyPI
|
|
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## [0.5.1] - 2026-07-04
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Ingest YAML OpenAPI specs served as `application/vnd.oai.openapi` (e.g. Codecov's schema endpoint), with a YAML fallback when a body advertised as JSON fails to decode (#22)
|
|
12
|
+
- `--version` now derives from installed package metadata instead of a hardcoded literal that had drifted from the real version (#22)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Commit `uv.lock` and use `uv sync --locked` in CI for reproducible builds; Dependabot now updates the lockfile via the `uv` ecosystem (#20)
|
|
17
|
+
- Dependency updates: `typer` 0.26 (with test adjustments for its vendored `click`), plus the python-deps and GitHub Actions groups (#21, #19, #16)
|
|
18
|
+
|
|
7
19
|
## [0.5.0] - 2026-04-21
|
|
8
20
|
|
|
9
21
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openapi-cli4ai
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Turn any REST API with an OpenAPI spec into an AI-ready CLI
|
|
5
5
|
Project-URL: Homepage, https://github.com/dbgorilla/openapi-cli4ai
|
|
6
6
|
Project-URL: Repository, https://github.com/dbgorilla/openapi-cli4ai
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "openapi-cli4ai"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.1"
|
|
8
8
|
description = "Turn any REST API with an OpenAPI spec into an AI-ready CLI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -44,7 +44,8 @@ openapi-cli4ai = "openapi_cli4ai.cli:app"
|
|
|
44
44
|
dev = [
|
|
45
45
|
"pytest>=8",
|
|
46
46
|
"pytest-cov>=5",
|
|
47
|
-
"
|
|
47
|
+
"click>=8,<9",
|
|
48
|
+
"mypy>=1.10,<3",
|
|
48
49
|
"types-PyYAML>=6,<7",
|
|
49
50
|
]
|
|
50
51
|
|
|
@@ -26,6 +26,7 @@ import base64
|
|
|
26
26
|
import binascii
|
|
27
27
|
from email.utils import parsedate_to_datetime
|
|
28
28
|
import hashlib
|
|
29
|
+
from importlib.metadata import version as _pkg_version, PackageNotFoundError
|
|
29
30
|
import html as html_module # noqa: F401 (used by F3: OIDC callback HTML escaping)
|
|
30
31
|
import json
|
|
31
32
|
import os
|
|
@@ -56,8 +57,11 @@ from rich.panel import Panel # noqa: E402
|
|
|
56
57
|
from rich.table import Table # noqa: E402
|
|
57
58
|
|
|
58
59
|
# ── Constants ──────────────────────────────────────────────────────────────────
|
|
59
|
-
VERSION = "0.4.0"
|
|
60
60
|
APP_NAME = "openapi-cli4ai"
|
|
61
|
+
try:
|
|
62
|
+
VERSION = _pkg_version(APP_NAME)
|
|
63
|
+
except PackageNotFoundError: # running from a source checkout without an install
|
|
64
|
+
VERSION = "0.0.0+dev"
|
|
61
65
|
CONFIG_FILE = Path.home() / ".openapi-cli4ai.toml"
|
|
62
66
|
CACHE_DIR = Path.home() / ".cache" / APP_NAME
|
|
63
67
|
CACHE_TTL = 3600 # 1 hour
|
|
@@ -458,11 +462,22 @@ def fetch_spec(profile: dict, refresh: bool = False) -> dict:
|
|
|
458
462
|
f"Got HTML instead of OpenAPI spec from {spec_url} — the server may have a frontend catch-all route"
|
|
459
463
|
)
|
|
460
464
|
|
|
461
|
-
# Handle JSON or YAML
|
|
462
|
-
|
|
465
|
+
# Handle JSON or YAML. Some servers serve a YAML spec under a
|
|
466
|
+
# non-obvious media type — e.g. Codecov's schema endpoint returns
|
|
467
|
+
# "application/vnd.oai.openapi" (the standard OpenAPI YAML type),
|
|
468
|
+
# which contains neither "yaml" nor a ".yaml" suffix. Prefer JSON
|
|
469
|
+
# when the type says so (covers the "+json" variant), route known
|
|
470
|
+
# YAML types to the YAML parser, and fall back to YAML if a body
|
|
471
|
+
# not advertised as JSON fails to decode.
|
|
472
|
+
ct = content_type.lower()
|
|
473
|
+
is_yaml = "yaml" in ct or "vnd.oai.openapi" in ct or spec_url.endswith((".yaml", ".yml"))
|
|
474
|
+
if is_yaml and "json" not in ct:
|
|
463
475
|
spec = yaml.safe_load(resp.text)
|
|
464
476
|
else:
|
|
465
|
-
|
|
477
|
+
try:
|
|
478
|
+
spec = resp.json()
|
|
479
|
+
except json.JSONDecodeError:
|
|
480
|
+
spec = yaml.safe_load(resp.text)
|
|
466
481
|
|
|
467
482
|
# Write cache atomically to prevent partial writes
|
|
468
483
|
ensure_dirs()
|
|
@@ -105,3 +105,81 @@ def test_fetch_spec_stale_cache_triggers_fetch(tmp_config, petstore_spec):
|
|
|
105
105
|
mock_make_client.return_value = mock_ctx
|
|
106
106
|
result = mod.fetch_spec(profile)
|
|
107
107
|
assert result["info"]["title"] == petstore_spec["info"]["title"]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _fetch_with_mocked_response(mod, profile, response):
|
|
111
|
+
"""Run fetch_spec with _make_client mocked to return `response` from a fresh fetch."""
|
|
112
|
+
# raise_for_status() needs a request attached to the response.
|
|
113
|
+
response.request = httpx.Request("GET", profile.get("openapi_url", profile["base_url"]))
|
|
114
|
+
mock_client = MagicMock()
|
|
115
|
+
mock_client.get = MagicMock(return_value=response)
|
|
116
|
+
mock_ctx = MagicMock()
|
|
117
|
+
mock_ctx.__enter__ = MagicMock(return_value=mock_client)
|
|
118
|
+
mock_ctx.__exit__ = MagicMock(return_value=False)
|
|
119
|
+
with patch("openapi_cli4ai.cli._make_client", return_value=mock_ctx):
|
|
120
|
+
return mod.fetch_spec(profile)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_fetch_spec_parses_yaml_vnd_oai_media_type(tmp_config):
|
|
124
|
+
"""Should parse a YAML spec served as application/vnd.oai.openapi (e.g. Codecov)."""
|
|
125
|
+
mod, _config_dir, _cache_dir = tmp_config
|
|
126
|
+
profile = {
|
|
127
|
+
"base_url": "https://api.codecov.io/api/v2",
|
|
128
|
+
"openapi_url": "https://api.codecov.io/api/v2/schema/",
|
|
129
|
+
"auth": {"type": "none"},
|
|
130
|
+
"verify_ssl": True,
|
|
131
|
+
"_name": "codecov",
|
|
132
|
+
}
|
|
133
|
+
yaml_spec = (
|
|
134
|
+
"openapi: 3.0.3\n"
|
|
135
|
+
"info:\n title: Codecov API\n version: 2.0.0\n"
|
|
136
|
+
"paths:\n /repos/:\n get:\n summary: List repos\n"
|
|
137
|
+
)
|
|
138
|
+
response = httpx.Response(
|
|
139
|
+
200,
|
|
140
|
+
content=yaml_spec.encode(),
|
|
141
|
+
headers={"content-type": "application/vnd.oai.openapi; charset=utf-8"},
|
|
142
|
+
)
|
|
143
|
+
spec = _fetch_with_mocked_response(mod, profile, response)
|
|
144
|
+
assert spec["info"]["title"] == "Codecov API"
|
|
145
|
+
assert "/repos/" in spec["paths"]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_fetch_spec_falls_back_to_yaml_on_mislabeled_json(tmp_config):
|
|
149
|
+
"""Should fall back to YAML when a body advertised as JSON is actually YAML."""
|
|
150
|
+
mod, _config_dir, _cache_dir = tmp_config
|
|
151
|
+
profile = {
|
|
152
|
+
"base_url": "https://example.com",
|
|
153
|
+
"openapi_url": "https://example.com/schema",
|
|
154
|
+
"auth": {"type": "none"},
|
|
155
|
+
"verify_ssl": True,
|
|
156
|
+
"_name": "mislabeled",
|
|
157
|
+
}
|
|
158
|
+
yaml_spec = "openapi: 3.0.3\ninfo:\n title: Mislabeled\n version: 1.0.0\npaths: {}\n"
|
|
159
|
+
response = httpx.Response(
|
|
160
|
+
200,
|
|
161
|
+
content=yaml_spec.encode(),
|
|
162
|
+
headers={"content-type": "application/json"}, # server lies
|
|
163
|
+
)
|
|
164
|
+
spec = _fetch_with_mocked_response(mod, profile, response)
|
|
165
|
+
assert spec["info"]["title"] == "Mislabeled"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_fetch_spec_parses_vnd_oai_openapi_json_variant(tmp_config):
|
|
169
|
+
"""The +json variant should still parse via the JSON path."""
|
|
170
|
+
mod, _config_dir, _cache_dir = tmp_config
|
|
171
|
+
profile = {
|
|
172
|
+
"base_url": "https://api.codecov.io/api/v2",
|
|
173
|
+
"openapi_url": "https://api.codecov.io/api/v2/schema/?format=json",
|
|
174
|
+
"auth": {"type": "none"},
|
|
175
|
+
"verify_ssl": True,
|
|
176
|
+
"_name": "codecovjson",
|
|
177
|
+
}
|
|
178
|
+
json_spec = json.dumps({"openapi": "3.0.3", "info": {"title": "Codecov API", "version": "2.0.0"}, "paths": {}})
|
|
179
|
+
response = httpx.Response(
|
|
180
|
+
200,
|
|
181
|
+
content=json_spec.encode(),
|
|
182
|
+
headers={"content-type": "application/vnd.oai.openapi+json"},
|
|
183
|
+
)
|
|
184
|
+
spec = _fetch_with_mocked_response(mod, profile, response)
|
|
185
|
+
assert spec["info"]["title"] == "Codecov API"
|
|
@@ -1210,6 +1210,12 @@ class TestMainCallback:
|
|
|
1210
1210
|
assert "openapi-cli4ai" in result.output
|
|
1211
1211
|
assert mod.VERSION in result.output
|
|
1212
1212
|
|
|
1213
|
+
def test_version_derived_from_package_metadata(self, cli_module):
|
|
1214
|
+
"""VERSION comes from installed package metadata, not a hardcoded literal."""
|
|
1215
|
+
from importlib.metadata import version
|
|
1216
|
+
|
|
1217
|
+
assert cli_module.VERSION == version("openapi-cli4ai")
|
|
1218
|
+
|
|
1213
1219
|
def test_no_subcommand_shows_help(self, tmp_config):
|
|
1214
1220
|
mod, tmp_path, cache_dir = tmp_config
|
|
1215
1221
|
|
|
@@ -10,7 +10,7 @@ from unittest.mock import MagicMock, patch
|
|
|
10
10
|
import httpx
|
|
11
11
|
import pytest
|
|
12
12
|
import tomli_w
|
|
13
|
-
from
|
|
13
|
+
from typer import Exit as ClickExit
|
|
14
14
|
from typer.testing import CliRunner
|
|
15
15
|
|
|
16
16
|
from openapi_cli4ai.cli import app
|
|
@@ -10,7 +10,7 @@ import inspect
|
|
|
10
10
|
import socket
|
|
11
11
|
from unittest.mock import MagicMock, patch
|
|
12
12
|
|
|
13
|
-
import
|
|
13
|
+
import typer
|
|
14
14
|
import pytest
|
|
15
15
|
|
|
16
16
|
from openapi_cli4ai import cli as cli_mod
|
|
@@ -41,7 +41,7 @@ class TestSpecificExceptions:
|
|
|
41
41
|
mock_client.get.side_effect = httpx.ConnectError("mocked connection error")
|
|
42
42
|
mock_mc.return_value = mock_client
|
|
43
43
|
|
|
44
|
-
with pytest.raises(
|
|
44
|
+
with pytest.raises(typer.Exit):
|
|
45
45
|
cli.fetch_spec(profile, refresh=True)
|
|
46
46
|
|
|
47
47
|
def test_try_refresh_token_catches_specific_errors(self, tmp_config):
|
|
@@ -113,7 +113,7 @@ class TestOIDCServerCleanup:
|
|
|
113
113
|
sock.listen(1)
|
|
114
114
|
|
|
115
115
|
# Calling _oidc_login_browser on this port should exit cleanly
|
|
116
|
-
with pytest.raises(
|
|
116
|
+
with pytest.raises(typer.Exit):
|
|
117
117
|
cli_mod._oidc_login_browser("http://auth.example.com/authorize", port, "test_state")
|
|
118
118
|
finally:
|
|
119
119
|
sock.close()
|
|
@@ -11,7 +11,7 @@ import stat
|
|
|
11
11
|
import urllib.parse
|
|
12
12
|
from unittest.mock import patch
|
|
13
13
|
|
|
14
|
-
import
|
|
14
|
+
import typer
|
|
15
15
|
import pytest
|
|
16
16
|
from typer.testing import CliRunner
|
|
17
17
|
|
|
@@ -317,7 +317,7 @@ class TestOIDCStateValidation:
|
|
|
317
317
|
# Mock typer.prompt to return a URL with wrong state
|
|
318
318
|
wrong_url = "http://localhost:8484/callback?code=abc123&state=WRONG"
|
|
319
319
|
with patch("typer.prompt", return_value=wrong_url):
|
|
320
|
-
with pytest.raises(
|
|
320
|
+
with pytest.raises(typer.Exit):
|
|
321
321
|
cli_mod._oidc_login_no_browser("http://auth.example.com", expected_state="CORRECT")
|
|
322
322
|
|
|
323
323
|
def test_no_browser_accepts_correct_state(self):
|
|
@@ -331,7 +331,7 @@ class TestOIDCStateValidation:
|
|
|
331
331
|
"""_oidc_login_no_browser should reject a redirect URL with no state."""
|
|
332
332
|
no_state_url = "http://localhost:8484/callback?code=abc123"
|
|
333
333
|
with patch("typer.prompt", return_value=no_state_url):
|
|
334
|
-
with pytest.raises(
|
|
334
|
+
with pytest.raises(typer.Exit):
|
|
335
335
|
cli_mod._oidc_login_no_browser("http://auth.example.com", expected_state="EXPECTED")
|
|
336
336
|
|
|
337
337
|
|
|
@@ -489,7 +489,7 @@ class TestProfileFallbackWarning:
|
|
|
489
489
|
}
|
|
490
490
|
)
|
|
491
491
|
monkeypatch.setenv("OAC_PROFILE", "nonexistent")
|
|
492
|
-
with pytest.raises(
|
|
492
|
+
with pytest.raises(typer.Exit):
|
|
493
493
|
cli.get_active_profile()
|
|
494
494
|
captured = capsys.readouterr()
|
|
495
495
|
assert "not found" in captured.err.lower()
|
|
@@ -635,21 +635,21 @@ class TestConfigCorruption:
|
|
|
635
635
|
"""A corrupt TOML config should exit with error, not silently return empty."""
|
|
636
636
|
cli, config_path, cache_dir = tmp_config
|
|
637
637
|
cli.CONFIG_FILE.write_text("this is not valid toml {{{")
|
|
638
|
-
with pytest.raises(
|
|
638
|
+
with pytest.raises(typer.Exit):
|
|
639
639
|
cli.load_profiles()
|
|
640
640
|
|
|
641
641
|
def test_wrong_shape_profiles_exits(self, tmp_config):
|
|
642
642
|
"""Config with profiles = 'string' should exit, not crash."""
|
|
643
643
|
cli, config_path, cache_dir = tmp_config
|
|
644
644
|
cli.CONFIG_FILE.write_text('profiles = "oops"\n')
|
|
645
|
-
with pytest.raises(
|
|
645
|
+
with pytest.raises(typer.Exit):
|
|
646
646
|
cli.load_profiles()
|
|
647
647
|
|
|
648
648
|
def test_wrong_shape_nested_profile_exits(self, tmp_config):
|
|
649
649
|
"""Config with [profiles] bad = 'string' should exit, not crash."""
|
|
650
650
|
cli, config_path, cache_dir = tmp_config
|
|
651
651
|
cli.CONFIG_FILE.write_text('[profiles]\nbad = "oops"\n')
|
|
652
|
-
with pytest.raises(
|
|
652
|
+
with pytest.raises(typer.Exit):
|
|
653
653
|
cli.get_active_profile()
|
|
654
654
|
|
|
655
655
|
|
|
@@ -11,7 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
import inspect
|
|
12
12
|
from unittest.mock import MagicMock, patch
|
|
13
13
|
|
|
14
|
-
import
|
|
14
|
+
import typer
|
|
15
15
|
import httpx
|
|
16
16
|
import pytest
|
|
17
17
|
|
|
@@ -65,7 +65,7 @@ class TestTokenExchangeHardening:
|
|
|
65
65
|
mock_ctx.__exit__ = MagicMock(return_value=False)
|
|
66
66
|
|
|
67
67
|
with patch.object(cli_module, "_make_client", return_value=mock_ctx):
|
|
68
|
-
with pytest.raises(
|
|
68
|
+
with pytest.raises(typer.Exit):
|
|
69
69
|
cli_module._token_exchange(
|
|
70
70
|
{"access_token": "tok"},
|
|
71
71
|
auth_config,
|
|
@@ -128,7 +128,7 @@ class TestDeviceLoginHardening:
|
|
|
128
128
|
patch.object(cli_module, "_device_discover_endpoints", return_value=endpoints),
|
|
129
129
|
patch.object(cli_module, "_make_client", return_value=mock_ctx),
|
|
130
130
|
):
|
|
131
|
-
with pytest.raises(
|
|
131
|
+
with pytest.raises(typer.Exit):
|
|
132
132
|
cli_module._device_login(auth_config, "test-profile", profile)
|
|
133
133
|
|
|
134
134
|
|