fix: align inquiry errors and expert review rules

This commit is contained in:
SepehrYahyaee
2026-09-19 12:11:43 +03:30
parent 45e0ad883a
commit 406b139b3d
21 changed files with 457 additions and 55 deletions

View File

@@ -4,13 +4,63 @@ import {
} from "./inquiry-error";
describe("inquiry error messages", () => {
it("turns ESG not-found responses into a contextual plate message", () => {
it("preserves a Persian ESG not-found response", () => {
expect(
getInquiryErrorMessage(
{ success: false, message: "موردی یافت نشد" },
"thirdPartyPlate",
),
).toBe("بیمه‌نامه شخص ثالثی مطابق پلاک و کد ملی واردشده یافت نشد.");
).toBe("موردی یافت نشد");
});
it.each([
["RECORD_NOT_FOUND", "رکوردی یافت نشد", "Provider request failed"],
[
"INQUIRY_NO_MATCH",
"نتیجه‌ای مطابق با اطلاعات وارد شده یافت نشد",
"Inquiry returned no matching result",
],
])(
"prefers ESG messageFa for %s over the technical message",
(code, messageFa, message) => {
expect(
getInquiryErrorMessage(
{
error: {
code,
message,
messageFa,
providerMessage: message,
providerCode: code,
},
attemptSummary: {
attempts: [{ code, message, messageFa }],
},
},
"thirdPartyPlate",
),
).toBe(messageFa);
},
);
it("finds messageFa inside an HTTP response envelope", () => {
expect(
getInquiryErrorMessage(
{
response: {
status: 404,
data: {
error: {
code: "RECORD_NOT_FOUND",
message: "Provider request failed",
messageFa: "رکوردی یافت نشد",
},
},
},
},
"thirdPartyPlate",
),
).toBe("رکوردی یافت نشد");
});
it("distinguishes VIN and car-body not-found failures", () => {

View File

@@ -87,7 +87,25 @@ export function inquiryErrorStatus(error: unknown): number | undefined {
}
export function extractInquiryProviderMessage(error: unknown): string {
for (const record of errorRecords(error)) {
const records = errorRecords(error);
// The normalized ESG/Parsian envelope carries the safe user-facing text in
// messageFa while `message` and `providerMessage` may remain technical.
// Search every envelope level for that explicit Persian field before
// considering generic message fields on an outer object.
for (const record of records) {
for (const key of [
"messageFa",
"MessageFa",
"messageFA",
"persianMessage",
] as const) {
const message = cleanMessage(record[key]);
if (message) return message;
}
}
for (const record of records) {
for (const key of ["message", "Message", "detail", "title"] as const) {
const message = cleanMessage(record[key]);
if (message) return message;
@@ -128,13 +146,21 @@ export function isInquiryTimeout(error: unknown): boolean {
const hasPersian = (value: string): boolean => /[\u0600-\u06ff]/.test(value);
const isNotFound = (error: unknown, message: string): boolean => {
const root = asRecord(error);
const responseData = asRecord(asRecord(root?.response)?.data);
const code = String(root?.code ?? responseData?.code ?? "").toUpperCase();
const codes = errorRecords(error).flatMap((record) =>
[record.code, record.providerCode]
.map((code) => String(code ?? "").toUpperCase())
.filter(Boolean),
);
return (
inquiryErrorStatus(error) === 404 ||
["NOT_FOUND", "POLICY_NOT_FOUND", "NO_POLICY", "RECORD_NOT_FOUND"].includes(
code,
codes.some((code) =>
[
"NOT_FOUND",
"POLICY_NOT_FOUND",
"NO_POLICY",
"RECORD_NOT_FOUND",
"INQUIRY_NO_MATCH",
].includes(code),
) ||
/\bnot[ -]?found\b|\bno (?:active |relevant )?(?:record|policy|item)\b|record\.not\.found|موردی یافت نشد|یافت نشد|پیدا نشد|فاقد بیمه(?:‌| )?نامه/i.test(
message,
@@ -175,6 +201,11 @@ export function getInquiryErrorMessage(
): string {
const providerMessage = extractInquiryProviderMessage(error);
// Persian text supplied by the provider is already the intended client
// message. Preserve it verbatim instead of replacing it with a local
// contextual fallback such as "inquiry not found".
if (providerMessage && hasPersian(providerMessage)) return providerMessage;
if (isNotFound(error, providerMessage)) return NOT_FOUND_MESSAGES[context];
if (
@@ -208,10 +239,6 @@ export function getInquiryErrorMessage(
return "سرویس استعلام در دسترس نیست. لطفاً کمی بعد دوباره تلاش کنید.";
}
// A provider's specific Persian validation/business message is already safe
// and more useful than replacing it with a broad local validation message.
if (providerMessage && hasPersian(providerMessage)) return providerMessage;
if (isInvalidInput(providerMessage) || inquiryErrorStatus(error) === 422) {
return INVALID_MESSAGES[context];
}