import { describe, expect, it } from "vitest"; import { explainSmtpError } from "./smtp-diagnose"; import { Ms365TokenError } from "./ms365-oauth"; function smtpError(message: string, extra: Record = {}): Error { return Object.assign(new Error(message), extra); } describe("explainSmtpError", () => { it("explains a client secret ID used instead of its value", () => { const result = explainSmtpError(new Ms365TokenError("Token-Abruf abgelehnt: AADSTS7000215: Invalid client secret provided.", "7000215")); expect(result.cause).toBe("oauth-token"); expect(result.hint).toContain("ENTRA_CLIENT_SECRET"); expect(result.hint).toContain("Wert"); }); it("explains an expired client secret and that SSO is affected too", () => { const result = explainSmtpError(new Ms365TokenError("AADSTS7000222: The provided client secret keys are expired.", "7000222")); expect(result.cause).toBe("oauth-token"); expect(result.hint).toContain("abgelaufen"); expect(result.hint).toContain("Mit Microsoft anmelden"); }); it("explains an unknown app or tenant", () => { expect(explainSmtpError(new Ms365TokenError("AADSTS700016: Application not found", "700016")).hint).toContain("ENTRA_CLIENT_ID"); expect(explainSmtpError(new Ms365TokenError("AADSTS90002: Tenant not found", "90002")).hint).toContain("ENTRA_TENANT_ID"); }); it("falls back to a general token hint for other AADSTS codes", () => { const result = explainSmtpError(new Ms365TokenError("AADSTS1234: something", "1234")); expect(result.cause).toBe("oauth-token"); expect(result.hint).toContain("ENTRA_TENANT_ID"); }); it("maps a rejected XOAUTH2 login to missing mailbox access", () => { const result = explainSmtpError(smtpError("Invalid login: 535 5.7.3 Authentication unsuccessful", { code: "EAUTH" })); expect(result.cause).toBe("oauth-mailbox-access"); expect(result.hint).toContain("SMTP.SendAsApp"); expect(result.hint).toContain("Add-MailboxPermission"); }); it("recognises SMTP AUTH disabled for the tenant or mailbox", () => { expect(explainSmtpError(smtpError("535 5.7.139 Authentication unsuccessful, SmtpClientAuthentication is disabled for the Tenant.")).cause).toBe( "smtp-auth-disabled" ); expect(explainSmtpError(smtpError("535 5.7.139 Authentication unsuccessful, SmtpClientAuthentication is disabled for the Mailbox.")).cause).toBe( "smtp-auth-disabled" ); }); it("recognises a sender address the mailbox may not use", () => { expect( explainSmtpError(smtpError("Message failed: 554 5.2.252 SendAsDenied; info@wabenhain.com not allowed to send as hallo@wabenhain.com")).cause ).toBe("send-as-denied"); expect(explainSmtpError(smtpError("550 5.7.60 SMTP; Client does not have permissions to send as this sender")).cause).toBe("send-as-denied"); }); it("recognises network problems", () => { expect(explainSmtpError(smtpError("connect ETIMEDOUT 52.97.1.1:587", { code: "ETIMEDOUT" })).cause).toBe("network"); expect(explainSmtpError(smtpError("getaddrinfo ENOTFOUND smtp.office365.com", { code: "EDNS" })).cause).toBe("network"); }); it("falls back to unknown and never throws on odd input", () => { expect(explainSmtpError("kaputt").cause).toBe("unknown"); expect(explainSmtpError(null).cause).toBe("unknown"); }); it("redacts secrets from the echoed server message", () => { const result = explainSmtpError(smtpError("AUTH XOAUTH2 eyJtoken failed secret-xyz"), { secrets: ["eyJtoken", "secret-xyz"] }); expect(result.detail).not.toContain("eyJtoken"); expect(result.detail).not.toContain("secret-xyz"); }); });