b6e4e38833
When the user left Username blank and clicked Save, the dialog persisted
the literal string "(api-key)" as the username. On reopen, that value
loaded back and the next Test Connection / save would send a poisoned
Espo-Authorization Basic header — base64("(api-key)":<apiKey>) — which
the server correctly rejects with 401, overshadowing the valid
X-Api-Key header.
Fix:
- DpapiCredentialStore.Save accepts a null/empty username and stores it
as an empty string. ApiKey is still mandatory.
- SettingsViewModel passes null when the field is blank instead of the
placeholder.
- EspoCrmClient already skips the Espo-Authorization header when
username is blank — combined with this fix, no fallback header is
sent for API-only credentials.
Tests: added Save_AcceptsEmptyUsername; updated Save_RejectsBlankInput
→ Save_RejectsBlankApiKey to match the new contract. 39/39 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>