sanityops-cli 0.1.7__tar.gz → 0.1.8__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 (121) hide show
  1. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/PKG-INFO +1 -1
  2. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/pyproject.toml +1 -1
  3. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/__init__.py +1 -1
  4. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/defect_checker/llm_config.py +5 -2
  5. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/defect_checker/test_llm_config.py +100 -67
  6. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  7. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  8. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  9. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  10. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/.github/dependabot.yml +0 -0
  11. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/.github/scripts/pr_review_bot.py +0 -0
  12. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/.github/workflows/ai-review.yml +0 -0
  13. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/.github/workflows/ci.yml +0 -0
  14. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/.github/workflows/release.yml +0 -0
  15. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/.gitignore +0 -0
  16. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/CHANGELOG.md +0 -0
  17. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/CODE_OF_CONDUCT.md +0 -0
  18. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/CONTRIBUTING.md +0 -0
  19. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/LICENSE +0 -0
  20. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/NOTICE +0 -0
  21. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/README.md +0 -0
  22. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/SECURITY.md +0 -0
  23. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/plans/2026-08-13-model-config-comments-in-init-template.md +0 -0
  24. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/plans/2026-08-14-loading-animations.md +0 -0
  25. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/plans/2026-08-14-skills-files-banner-readme.md +0 -0
  26. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/plans/2026-08-28-copyright-compliance.md +0 -0
  27. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/plans/2026-09-08-config-command.md +0 -0
  28. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/plans/2026-09-20-cross-artifact-rendering.md +0 -0
  29. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/plans/2026-09-20-help-panel-migration.md +0 -0
  30. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/plans/2026-09-20-repair-feature.md +0 -0
  31. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/specs/2026-08-13-model-config-comments-in-init-template-design.md +0 -0
  32. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/specs/2026-08-14-loading-animations-design.md +0 -0
  33. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/specs/2026-08-14-skills-files-banner-readme-design.md +0 -0
  34. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/specs/2026-08-28-copyright-compliance-design.md +0 -0
  35. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/specs/2026-09-08-config-command-design.md +0 -0
  36. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/specs/2026-09-20-cross-artifact-rendering-design.md +0 -0
  37. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/specs/2026-09-20-help-panel-migration-design.md +0 -0
  38. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/docs/superpowers/specs/2026-09-20-repair-feature-design.md +0 -0
  39. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/entry.py +0 -0
  40. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/release.sh +0 -0
  41. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/scripts/generate-checksums.ps1 +0 -0
  42. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/scripts/install.ps1 +0 -0
  43. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/scripts/install.sh +0 -0
  44. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/__init__.py +0 -0
  45. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/repair_agent/__init__.py +0 -0
  46. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/repair_agent/agent.py +0 -0
  47. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/repair_agent/prompts.py +0 -0
  48. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/repair_agent/tools/__init__.py +0 -0
  49. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/repair_agent/tools/store_repairs_tool.py +0 -0
  50. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/scanner_agent/agent.py +0 -0
  51. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/scanner_agent/hooks/progress_hook.py +0 -0
  52. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/scanner_agent/models/finding.py +0 -0
  53. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/scanner_agent/prompts.py +0 -0
  54. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/scanner_agent/tools/grep_tool.py +0 -0
  55. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/scanner_agent/tools/listfiles_tool.py +0 -0
  56. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/scanner_agent/tools/readfile_tool.py +0 -0
  57. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/agents/scanner_agent/tools/storefindings_tool.py +0 -0
  58. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/api/__init__.py +0 -0
  59. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/api/client.py +0 -0
  60. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/commands/__init__.py +0 -0
  61. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/commands/config.py +0 -0
  62. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/commands/init.py +0 -0
  63. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/commands/inspect.py +0 -0
  64. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/constants/__init__.py +0 -0
  65. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/constants/config_defaults.py +0 -0
  66. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/constants/exit_codes.py +0 -0
  67. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/defect_checker/__init__.py +0 -0
  68. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/defect_checker/checker.py +0 -0
  69. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/defect_checker/markdown_reporter.py +0 -0
  70. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/defect_checker/renderer.py +0 -0
  71. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/exceptions/__init__.py +0 -0
  72. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/exceptions/api_exceptions.py +0 -0
  73. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/exceptions/base_exceptions.py +0 -0
  74. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/help_panel.py +0 -0
  75. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/logging/__init__.py +0 -0
  76. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/logging/logger.py +0 -0
  77. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/main.py +0 -0
  78. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/progress/__init__.py +0 -0
  79. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/progress/tracker.py +0 -0
  80. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/renderers/__init__.py +0 -0
  81. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/renderers/command_renderer/inspect_command_renderer.py +0 -0
  82. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/templates/__init__.py +0 -0
  83. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/templates/inspect_config.yaml +0 -0
  84. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/utils/__init__.py +0 -0
  85. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/utils/artifact_packer.py +0 -0
  86. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/utils/config_loader.py +0 -0
  87. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/utils/config_resolver.py +0 -0
  88. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/src/sanityops_cli/utils/validators.py +0 -0
  89. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/__init__.py +0 -0
  90. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/commands/__init__.py +0 -0
  91. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/commands/test_config.py +0 -0
  92. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/commands/test_init.py +0 -0
  93. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/commands/test_inspect_repair.py +0 -0
  94. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/test_help_panel.py +0 -0
  95. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/__init__.py +0 -0
  96. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/agents/__init__.py +0 -0
  97. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/agents/repair_agent/__init__.py +0 -0
  98. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/agents/repair_agent/test_agent.py +0 -0
  99. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/agents/repair_agent/test_store_repairs_tool.py +0 -0
  100. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/agents/scanner_agent/__init__.py +0 -0
  101. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/agents/scanner_agent/models/__init__.py +0 -0
  102. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/agents/scanner_agent/models/test_finding.py +0 -0
  103. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/agents/scanner_agent/test_agent.py +0 -0
  104. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/agents/scanner_agent/test_deterministic_capture.py +0 -0
  105. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/agents/scanner_agent/test_progress_hook.py +0 -0
  106. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/agents/scanner_agent/test_prompts.py +0 -0
  107. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/api/__init__.py +0 -0
  108. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/api/test_client.py +0 -0
  109. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/commands/test_inspect.py +0 -0
  110. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/commands/test_version.py +0 -0
  111. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/defect_checker/__init__.py +0 -0
  112. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/defect_checker/test_checker.py +0 -0
  113. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/defect_checker/test_markdown_reporter.py +0 -0
  114. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/defect_checker/test_renderer.py +0 -0
  115. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/logging/__init__.py +0 -0
  116. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/logging/test_logger.py +0 -0
  117. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/progress/__init__.py +0 -0
  118. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/progress/test_tracker.py +0 -0
  119. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/utils/test_artifact_packer.py +0 -0
  120. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/unit/utils/test_config_loader.py +0 -0
  121. {sanityops_cli-0.1.7 → sanityops_cli-0.1.8}/tests/utils/test_config_resolver.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sanityops-cli
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: CLI tool for static defect inspection of AI logical artifacts
5
5
  Project-URL: Homepage, https://sanityops.org
6
6
  Project-URL: Documentation, https://sanityops.org
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sanityops-cli"
7
- version = "0.1.7"
7
+ version = "0.1.8"
8
8
  description = "CLI tool for static defect inspection of AI logical artifacts"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -13,4 +13,4 @@
13
13
  # limitations under the License.
14
14
  #
15
15
 
16
- __version__ = "0.1.7"
16
+ __version__ = "0.1.8"
@@ -102,10 +102,13 @@ def resolve_llm_config(config_path: str | None = None) -> dict[str, str]:
102
102
  if config_values:
103
103
  return config_values
104
104
 
105
- # Fall back to ProviderConfig (env vars)
105
+ # Fall back to ProviderConfig (env vars). PROVIDER is the field that reads
106
+ # the documented LLM_PROVIDER variable (env_prefix is "LLM_"); the
107
+ # LLM_PROVIDER *alias* field listens on LLM_LLM_PROVIDER, so reading it here
108
+ # left llm_provider None and made the SDK refuse every env-configured run.
106
109
  config = ProviderConfig()
107
110
  return {
108
- "llm_provider": config.LLM_PROVIDER,
111
+ "llm_provider": config.PROVIDER,
109
112
  "llm_api_key": config.API_KEY,
110
113
  "llm_model_id": config.MODEL_ID,
111
114
  "llm_base_url": config.BASE_URL or "",
@@ -1,47 +1,85 @@
1
1
  """Unit tests for LLM config resolution."""
2
2
  from pathlib import Path
3
- from unittest import mock
4
3
 
5
4
  import yaml
6
5
 
7
6
  from sanityops_cli.defect_checker.llm_config import resolve_llm_config
8
7
 
8
+ LLM_ENV_NAMES = (
9
+ "LLM_PROVIDER",
10
+ "LLM_LLM_PROVIDER",
11
+ "LLM_API_KEY",
12
+ "LLM_MODEL_ID",
13
+ "LLM_BASE_URL",
14
+ )
9
15
 
10
- class TestResolveLlmConfig:
11
- def test_returns_four_expected_keys(self):
16
+
17
+ def _set_llm_env(monkeypatch, tmp_path: Path, **values: str) -> None:
18
+ """Point the LLM_* environment variables at `values`, clearing the rest.
19
+
20
+ Also chdirs into tmp_path so ProviderConfig's `.env` lookup (env_file)
21
+ cannot leak values from the repository into the test.
22
+ """
23
+ monkeypatch.chdir(tmp_path)
24
+ for name in LLM_ENV_NAMES:
25
+ monkeypatch.delenv(name, raising=False)
26
+ for name, value in values.items():
27
+ monkeypatch.setenv(name, value)
28
+
29
+
30
+ class TestResolveLlmConfigFromEnvironment:
31
+ """The env fallback must read the documented LLM_* variable names.
32
+
33
+ Regression: resolve_llm_config read ``ProviderConfig.LLM_PROVIDER``, the
34
+ deprecated constructor alias, instead of ``ProviderConfig.PROVIDER``. The
35
+ settings model sets ``env_prefix="LLM_"``, so that alias listens on
36
+ ``LLM_LLM_PROVIDER``; the documented ``LLM_PROVIDER`` never reached the
37
+ defect-check SDK, which refused the run with LLM_CONFIGURATION_MISSING and
38
+ left the report at zero defects with a PASS gate.
39
+ """
40
+
41
+ def test_returns_four_expected_keys(self, tmp_path: Path, monkeypatch):
42
+ _set_llm_env(monkeypatch, tmp_path)
12
43
  cfg = resolve_llm_config()
13
44
  assert set(cfg.keys()) == {"llm_provider", "llm_api_key", "llm_model_id", "llm_base_url"}
14
45
 
15
- def test_maps_provider_config_fields(self):
16
- fake = mock.Mock()
17
- fake.LLM_PROVIDER = "anthropic"
18
- fake.API_KEY = "sk-test"
19
- fake.MODEL_ID = "claude-sonnet-4-20250514"
20
- fake.BASE_URL = "https://api.example.com"
21
- with mock.patch(
22
- "sanityops_cli.defect_checker.llm_config.ProviderConfig",
23
- return_value=fake,
24
- ):
25
- cfg = resolve_llm_config()
26
- assert cfg == {
46
+ def test_reads_documented_env_var_names(self, tmp_path: Path, monkeypatch):
47
+ """LLM_PROVIDER and friends resolve into the provider the SDK receives."""
48
+ _set_llm_env(
49
+ monkeypatch,
50
+ tmp_path,
51
+ LLM_PROVIDER="anthropic",
52
+ LLM_API_KEY="sk-test",
53
+ LLM_MODEL_ID="claude-sonnet-4-20250514",
54
+ LLM_BASE_URL="https://api.example.com",
55
+ )
56
+ assert resolve_llm_config() == {
27
57
  "llm_provider": "anthropic",
28
58
  "llm_api_key": "sk-test",
29
59
  "llm_model_id": "claude-sonnet-4-20250514",
30
60
  "llm_base_url": "https://api.example.com",
31
61
  }
32
62
 
33
- def test_none_base_url_becomes_empty_string(self):
34
- fake = mock.Mock()
35
- fake.LLM_PROVIDER = "openai"
36
- fake.API_KEY = "k"
37
- fake.MODEL_ID = "gpt-4o"
38
- fake.BASE_URL = None
39
- with mock.patch(
40
- "sanityops_cli.defect_checker.llm_config.ProviderConfig",
41
- return_value=fake,
42
- ):
43
- cfg = resolve_llm_config()
44
- assert cfg["llm_base_url"] == ""
63
+ def test_deprecated_double_prefixed_provider_name_still_resolves(self, tmp_path: Path, monkeypatch):
64
+ """LLM_LLM_PROVIDER is the deprecated alias' own env name; keep it working."""
65
+ _set_llm_env(
66
+ monkeypatch,
67
+ tmp_path,
68
+ LLM_LLM_PROVIDER="openai",
69
+ LLM_API_KEY="sk-test",
70
+ LLM_MODEL_ID="gpt-4o",
71
+ )
72
+ assert resolve_llm_config()["llm_provider"] == "openai"
73
+
74
+ def test_unset_base_url_becomes_empty_string(self, tmp_path: Path, monkeypatch):
75
+ _set_llm_env(
76
+ monkeypatch,
77
+ tmp_path,
78
+ LLM_PROVIDER="openai",
79
+ LLM_API_KEY="k",
80
+ LLM_MODEL_ID="gpt-4o",
81
+ )
82
+ assert resolve_llm_config()["llm_base_url"] == ""
45
83
 
46
84
 
47
85
  class TestResolveLlmConfigFromConfigFile:
@@ -103,7 +141,7 @@ class TestResolveLlmConfigFromConfigFile:
103
141
 
104
142
  def test_model_section_absent_falls_back_to_env_vars(self, tmp_path: Path, monkeypatch):
105
143
  """Should fall back to ProviderConfig when model section absent."""
106
- monkeypatch.chdir(tmp_path)
144
+ _set_llm_env(monkeypatch, tmp_path)
107
145
  config_dir = tmp_path / ".sanityops"
108
146
  config_dir.mkdir()
109
147
  config_file = config_dir / "inspect_config.yaml"
@@ -117,17 +155,15 @@ class TestResolveLlmConfigFromConfigFile:
117
155
  with open(config_file, "w") as f:
118
156
  yaml.dump(config_content, f)
119
157
 
120
- fake = mock.Mock()
121
- fake.LLM_PROVIDER = "env-provider"
122
- fake.API_KEY = "env-key"
123
- fake.MODEL_ID = "env-model"
124
- fake.BASE_URL = "https://env.url"
125
- with mock.patch(
126
- "sanityops_cli.defect_checker.llm_config.ProviderConfig",
127
- return_value=fake,
128
- ):
129
- cfg = resolve_llm_config(str(config_file))
130
- assert cfg == {
158
+ _set_llm_env(
159
+ monkeypatch,
160
+ tmp_path,
161
+ LLM_PROVIDER="env-provider",
162
+ LLM_API_KEY="env-key",
163
+ LLM_MODEL_ID="env-model",
164
+ LLM_BASE_URL="https://env.url",
165
+ )
166
+ assert resolve_llm_config(str(config_file)) == {
131
167
  "llm_provider": "env-provider",
132
168
  "llm_api_key": "env-key",
133
169
  "llm_model_id": "env-model",
@@ -136,19 +172,14 @@ class TestResolveLlmConfigFromConfigFile:
136
172
 
137
173
  def test_config_file_missing_falls_back_to_env_vars(self, tmp_path: Path, monkeypatch):
138
174
  """Should fall back to ProviderConfig when config file doesn't exist."""
139
- monkeypatch.chdir(tmp_path)
140
-
141
- fake = mock.Mock()
142
- fake.LLM_PROVIDER = "fallback-provider"
143
- fake.API_KEY = "fallback-key"
144
- fake.MODEL_ID = "fallback-model"
145
- fake.BASE_URL = None
146
- with mock.patch(
147
- "sanityops_cli.defect_checker.llm_config.ProviderConfig",
148
- return_value=fake,
149
- ):
150
- cfg = resolve_llm_config("/nonexistent/config.yaml")
151
- assert cfg == {
175
+ _set_llm_env(
176
+ monkeypatch,
177
+ tmp_path,
178
+ LLM_PROVIDER="fallback-provider",
179
+ LLM_API_KEY="fallback-key",
180
+ LLM_MODEL_ID="fallback-model",
181
+ )
182
+ assert resolve_llm_config("/nonexistent/config.yaml") == {
152
183
  "llm_provider": "fallback-provider",
153
184
  "llm_api_key": "fallback-key",
154
185
  "llm_model_id": "fallback-model",
@@ -206,20 +237,22 @@ class TestResolveLlmConfigFromConfigFile:
206
237
  with open(config_file, "w") as f:
207
238
  yaml.dump(config_content, f)
208
239
 
209
- # Set up env vars that should be ignored
210
- fake = mock.Mock()
211
- fake.LLM_PROVIDER = "env-provider-should-be-ignored"
212
- fake.API_KEY = "env-key-should-be-ignored"
213
- fake.MODEL_ID = "env-model-should-be-ignored"
214
- fake.BASE_URL = "https://env.url.ignored"
215
- with mock.patch(
216
- "sanityops_cli.defect_checker.llm_config.ProviderConfig",
217
- return_value=fake,
218
- ):
219
- cfg = resolve_llm_config(str(config_file))
240
+ # Env vars that must be ignored in favour of the config file's model: section
241
+ _set_llm_env(
242
+ monkeypatch,
243
+ tmp_path,
244
+ LLM_PROVIDER="env-provider-should-be-ignored",
245
+ LLM_API_KEY="env-key-should-be-ignored",
246
+ LLM_MODEL_ID="env-model-should-be-ignored",
247
+ LLM_BASE_URL="https://env.url.ignored",
248
+ )
249
+
250
+ cfg = resolve_llm_config(str(config_file))
220
251
 
221
252
  # Config values win
222
- assert cfg["llm_provider"] == "config-provider"
223
- assert cfg["llm_api_key"] == "config-key"
224
- assert cfg["llm_model_id"] == "config-model"
225
- assert cfg["llm_base_url"] == ""
253
+ assert cfg == {
254
+ "llm_provider": "config-provider",
255
+ "llm_api_key": "config-key",
256
+ "llm_model_id": "config-model",
257
+ "llm_base_url": "",
258
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes