mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
API is a plain mapping, so an inventory that configures three providers replaces the whole declaration from group_vars/all/18_api.yml instead of merging into it. inventories/development/default.yml declares openai, anthropic and openrouter, so github, google and jira did not exist in CI at all and lookup('api', 'github.client_id') raised.
A raising lookup inside a value the applications renderer is templating does not fail the render: _templar_render_best_effort returns the string untouched and, for a value carrying a non-env lookup(, refuses the fallback as well. The raw template text reached plugin_value.enabled | bool, which coerced it to False and emitted the deprecation warning that fails the deploy gate. Run 34893363518, job 104153194109 lost web-app-nextcloud to exactly that, for three integrations at once. The configvalue entries would have stored template text as the client id.
utils/api.py merges the scope's override over the declaration, so an override narrows values and never the provider set, and holds the enable rule: every credential a provider declares must carry a value. The api lookup reads through it; the new api_enabled lookup gates on it and returns a real boolean instead of a string a filter has to coerce.
The same class sat in group_vars/all/16_ai.yml and roles/svc-ai-litellm/meta/secrets.yml as bare API.<provider>.<key> dereferences; both now go through the lookups.
Two lints keep it shut. test_addon_enabled_renders_to_bool renders the whole applications map against a scope with API blanked, the most hostile override an inventory may legally write, and requires every addon gate to come out a boolean. test_api_accessed_via_lookup forbids a bare API dereference in Jinja under roles/, group_vars/ and inventories/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
52 lines
1.8 KiB
Python
52 lines
1.8 KiB
Python
import unittest
|
|
|
|
from utils.api import declared_api, provider_enabled, resolve_api
|
|
|
|
|
|
class TestResolveApi(unittest.TestCase):
|
|
def test_a_partial_override_keeps_the_declared_providers(self) -> None:
|
|
declared = declared_api()
|
|
self.assertIn("github", declared)
|
|
|
|
resolved = resolve_api({"API": {"openai": {"api_key": "sk-test"}}})
|
|
|
|
self.assertEqual(set(resolved), set(declared))
|
|
self.assertEqual(resolved["openai"]["api_key"], "sk-test")
|
|
self.assertEqual(resolved["github"]["client_id"], "")
|
|
|
|
def test_an_override_wins_per_key(self) -> None:
|
|
resolved = resolve_api({"API": {"github": {"client_id": "ID42"}}})
|
|
|
|
self.assertEqual(resolved["github"]["client_id"], "ID42")
|
|
self.assertEqual(resolved["github"]["client_secret"], "")
|
|
|
|
def test_a_missing_api_falls_back_to_the_declaration(self) -> None:
|
|
self.assertEqual(resolve_api({}), declared_api())
|
|
|
|
def test_a_non_mapping_api_is_an_error(self) -> None:
|
|
with self.assertRaises(TypeError):
|
|
resolve_api({"API": "nope"})
|
|
|
|
|
|
class TestProviderEnabled(unittest.TestCase):
|
|
def test_every_credential_must_carry_a_value(self) -> None:
|
|
api = {"github": {"client_id": "ID42", "client_secret": "SEC42"}}
|
|
|
|
self.assertTrue(provider_enabled(api, "github"))
|
|
|
|
def test_one_empty_credential_disables_the_provider(self) -> None:
|
|
api = {"github": {"client_id": "ID42", "client_secret": " "}}
|
|
|
|
self.assertFalse(provider_enabled(api, "github"))
|
|
|
|
def test_a_provider_without_credentials_is_disabled(self) -> None:
|
|
self.assertFalse(provider_enabled({"github": {}}, "github"))
|
|
|
|
def test_an_undeclared_provider_is_an_error(self) -> None:
|
|
with self.assertRaises(KeyError):
|
|
provider_enabled({}, "github")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|