fix(services): test connection merges stored secrets for blank fields
The credential tester (POST /api/services/test) used only body.secrets — the
values typed in the form. When editing an existing service the secret fields
are masked and intentionally left blank ("leave blank to keep current"), so the
test ran with empty credentials and failed auth even though the stored secret
was valid.
When body.id is set, look up the stored service, decrypt its secrets, and fall
back to the stored value for any known secret key that is absent or blank in
the input. The test still uses the freshly-typed config (so you can test an
edited URL) but authenticates with the effective credentials. New-service tests
(no id) are unchanged.
Test: editing a service and testing with empty secrets now authenticates with
the stored secret (asserts the stored key reaches the upstream request).
402/402 backend pass; ruff clean.
This commit is contained in:
@@ -195,12 +195,32 @@ def test_instance(
|
||||
_validate_input(body) # raises HTTPException(422) on bad config/type/secrets
|
||||
definition = require_service_definition(body.service_type)
|
||||
|
||||
# When editing an existing service, secret fields are masked and not
|
||||
# re-entered (the UI says "leave blank to keep current"), so body.secrets
|
||||
# only carries freshly-typed values. Fall back to the stored (decrypted)
|
||||
# secret for any known key that is absent or blank, so the test runs with
|
||||
# the effective credentials rather than failing auth on empty fields.
|
||||
secrets = dict(body.secrets)
|
||||
if body.id:
|
||||
existing = store.get_service(body.id)
|
||||
if existing and existing.get("service_type") == body.service_type:
|
||||
from media_library_viewer_api.services.secrets import decrypt_secrets
|
||||
|
||||
stored: dict[str, str] = {}
|
||||
try:
|
||||
stored = decrypt_secrets(existing.get("secrets") or {})
|
||||
except Exception:
|
||||
logger.exception("failed to decrypt stored secrets for test service_id=%s", body.id)
|
||||
for key in definition.secret_keys:
|
||||
if not secrets.get(key) and stored.get(key):
|
||||
secrets[key] = stored[key]
|
||||
|
||||
if definition.test_callable is None:
|
||||
logger.info("test requested type=%s ok=true (no test_callable)", body.service_type)
|
||||
return {"ok": True, "detail": "No connection test for this service type", "evidence": None}
|
||||
|
||||
try:
|
||||
result: TestResult = definition.test_callable(body.config, body.secrets, store)
|
||||
result: TestResult = definition.test_callable(body.config, secrets, store)
|
||||
except Exception as exc:
|
||||
logger.exception("test_callable raised for type=%s", body.service_type)
|
||||
result = TestResult(ok=False, detail=f"Test failed unexpectedly: {exc}")
|
||||
|
||||
Reference in New Issue
Block a user