fix(settings): surface real reason behind "Test connection" failures

The catch-all in TestConnectionAsync was just showing
"שגיאת חיבור: One or more errors occurred." which is useless. Walk the
InnerException chain and translate the underlying SocketError /
TaskCanceled / AuthenticationException into a specific Hebrew message:

- HostNotFound  → "השרת {host} לא נמצא ב-DNS"
- ConnectionRefused → "החיבור ל-{host}:{port} נדחה"
- TimedOut      → "חיבור פג זמן"
- HostUnreachable / NetworkUnreachable → check VPN/network
- AuthenticationException → bad TLS cert
- TaskCanceled  → 15s timeout
- otherwise     → "{OuterType}: {OuterMsg} — {InnerType}: {InnerMsg}"

Full exception (with stack) keeps going to the addin log via
_logger.Warning, so the user can still file a diagnostic bundle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
PointStar
2026-05-11 16:06:43 +03:00
parent 3bedb2cb06
commit 40c3839278
@@ -138,8 +138,8 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
}
catch (Exception ex)
{
_logger.Warning(ex, "TestConnection failed");
Fail("שגיאת חיבור: " + ex.Message);
_logger.Warning(ex, "TestConnection failed for {Url}", baseUrl);
Fail("שגיאת חיבור: " + FormatExceptionForUser(ex, baseUrl));
}
finally
{
@@ -147,6 +147,55 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
}
}
/// <summary>
/// Walks the InnerException chain and pulls out the underlying socket
/// error code so the dialog actually tells the user something useful
/// (DNS miss, connection refused, TLS failure, etc.) instead of just
/// "One or more errors occurred".
/// </summary>
private static string FormatExceptionForUser(Exception ex, Uri target)
{
var current = ex;
while (current.InnerException != null)
{
current = current.InnerException;
}
if (current is System.Net.Sockets.SocketException sock)
{
switch (sock.SocketErrorCode)
{
case System.Net.Sockets.SocketError.HostNotFound:
return $"השרת {target.Host} לא נמצא ב-DNS. ודא שהכתובת נכונה ושיש חיבור לאינטרנט.";
case System.Net.Sockets.SocketError.ConnectionRefused:
return $"החיבור ל-{target.Host}:{target.Port} נדחה. ודא שהשרת רץ ומאזין.";
case System.Net.Sockets.SocketError.TimedOut:
return $"חיבור ל-{target.Host} פג זמן. ייתכן שהשרת לא זמין או שיש חסימה ברשת.";
case System.Net.Sockets.SocketError.NetworkUnreachable:
case System.Net.Sockets.SocketError.HostUnreachable:
return "לא הצלחתי להגיע לרשת של השרת. בדוק את ה-VPN/חיבור הרשת.";
default:
return $"שגיאת רשת ({sock.SocketErrorCode}): {sock.Message}";
}
}
if (current is TaskCanceledException)
{
return "החיבור לא הגיב תוך 15 שניות. השרת אולי לא זמין או שהוא איטי.";
}
if (current is System.Security.Authentication.AuthenticationException)
{
return "אישור ה-TLS של השרת לא תקין. בדוק את הקישור (https vs http) או פנה לאדמין.";
}
// Fallback: outer + inner with newline so the user sees the chain.
var outer = ex.GetType().Name + ": " + ex.Message;
if (current != ex)
{
outer += " — " + current.GetType().Name + ": " + current.Message;
}
return outer;
}
[RelayCommand]
private void Save()
{