ConnectWiseProvider implements PSAProvider with test_connection() that
calls GET /system/info to verify credentials and connectivity. All other
methods raise NotImplementedError with slice references for future work.
Provider registry (get_provider_for_account) looks up the account's
PsaConnection, decrypts stored credentials, and instantiates the
correct provider. Currently supports connectwise only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements ConnectWiseClient in services/psa/connectwise/client.py with:
- API key auth (Base64 companyId+publicKey:privateKey) + clientId header
- Accept header pinned to CW API version 2025.16
- Dynamic base URL resolution via /login/companyinfo (cloud vs on-premise)
- SSRF prevention: validates against known CW domains, rejects private IPs
- Retry with exponential backoff for timeouts and 5xx errors
- 429 rate limit handling with Retry-After header respect
- Error mapping: 401/403/404/429/5xx to typed PSA exceptions
- Paginated GET with while-loop pattern (max 1000 per page)
- JSON Patch array format for PATCH requests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>