mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 14:23:13 +00:00
fix: allowlist internal uplift denial copy
This commit is contained in:
parent
7b30895aca
commit
80640642cc
4 changed files with 49 additions and 7 deletions
|
|
@ -10,11 +10,21 @@ export class ApiError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export const REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE =
|
||||
"Your role can't request uplifts on this work order.";
|
||||
|
||||
export function mapHttpStatusToMessage(status: number, data?: unknown): string {
|
||||
if (status === 401) {
|
||||
return "You are not authorized to access this page.";
|
||||
}
|
||||
if (status === 403) {
|
||||
if (data && typeof data === "object") {
|
||||
const record = data as Record<string, unknown>;
|
||||
const message = record.message ?? record.Message ?? record.error;
|
||||
if (message === REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE) {
|
||||
return REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE;
|
||||
}
|
||||
}
|
||||
return "You do not have permission to perform this action.";
|
||||
}
|
||||
if (status === 404) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from "@tanstack/react-query";
|
||||
import { isHTTPError, isNetworkError, isTimeoutError } from "ky";
|
||||
import { toast } from "react-toastify";
|
||||
import { mapHttpStatusToMessage } from "@/api/api-error";
|
||||
import { mapHttpStatusToMessage, REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE } from "@/api/api-error";
|
||||
import { workOrderUpliftsApi } from "@/domain/work-orders/api/work-order-uplifts-api";
|
||||
import type {
|
||||
CreateWorkOrderUpliftInput,
|
||||
|
|
@ -16,15 +16,14 @@ import type {
|
|||
import { queryKeys } from "@/infra/query-key/query-key";
|
||||
import { requireQueryParam } from "@/lib/query/require-query-param";
|
||||
|
||||
const CREATE_PERMISSION_DENIED_MESSAGE = "Your role can't request uplifts on this work order.";
|
||||
const CREATE_UPLIFT_ERROR_FALLBACK = "Failed to create uplift";
|
||||
const CREATE_UPLIFT_FIXED_STATUS_COPY = new Set([401, 404, 500]);
|
||||
const CREATE_UPLIFT_FIXED_STATUS_COPY = new Set([401, 403, 404, 500]);
|
||||
|
||||
function createUpliftErrorMessage(error: unknown): string {
|
||||
if (isHTTPError(error)) {
|
||||
const status = error.response.status;
|
||||
if (status === 403) {
|
||||
return CREATE_PERMISSION_DENIED_MESSAGE;
|
||||
if (status === 403 && error.message === REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE) {
|
||||
return REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE;
|
||||
}
|
||||
return CREATE_UPLIFT_FIXED_STATUS_COPY.has(status)
|
||||
? mapHttpStatusToMessage(status)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import ky from "ky";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { normalizeApiRequestError } from "@/api/api-error";
|
||||
import { normalizeApiRequestError, REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE } from "@/api/api-error";
|
||||
|
||||
const RESOURCE_URL = "https://example.test/api/resource";
|
||||
|
||||
|
|
@ -29,6 +29,32 @@ describe("normalizeApiRequestError", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("preserves only the allowlisted public denial for 403 responses", async () => {
|
||||
const allowedFetch = vi.fn(() =>
|
||||
Promise.resolve(
|
||||
new Response(JSON.stringify({ message: REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE }), {
|
||||
status: 403,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
),
|
||||
) as unknown as typeof fetch;
|
||||
const unrelatedFetch = vi.fn(() =>
|
||||
Promise.resolve(
|
||||
new Response(JSON.stringify({ message: "accountId 42 is forbidden" }), {
|
||||
status: 403,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}),
|
||||
),
|
||||
) as unknown as typeof fetch;
|
||||
|
||||
await expect(createClient(allowedFetch).get(RESOURCE_URL).json()).rejects.toThrow(
|
||||
REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE,
|
||||
);
|
||||
await expect(createClient(unrelatedFetch).get(RESOURCE_URL).json()).rejects.toThrow(
|
||||
"You do not have permission to perform this action.",
|
||||
);
|
||||
});
|
||||
|
||||
it("reports a connection failure for network errors", async () => {
|
||||
const fetchImpl = vi.fn(() =>
|
||||
Promise.reject(new TypeError("fetch failed")),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { toast } from "react-toastify";
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { WorkOrderUpliftsDialog } from "@/app/(protected)/workorders/_components/uplifts/work-order-uplifts-dialog";
|
||||
import { REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE } from "@/api/api-error";
|
||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
import { renderWithProviders } from "@/test/test-utils";
|
||||
|
||||
|
|
@ -67,9 +68,15 @@ async function submitInternalCreate(status: number, serverMessage: string) {
|
|||
|
||||
describe("internal work-order uplift create error copy", () => {
|
||||
it("shows the exact role denial for a 403", async () => {
|
||||
const message = await submitInternalCreate(403, REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE);
|
||||
|
||||
expect(message).toBe(REQUEST_UPLIFTS_ROLE_DENIAL_MESSAGE);
|
||||
});
|
||||
|
||||
it("keeps unrelated 403 permission denials generic", async () => {
|
||||
const message = await submitInternalCreate(403, "accountId 42 is forbidden");
|
||||
|
||||
expect(message).toBe("Your role can't request uplifts on this work order.");
|
||||
expect(message).toBe("You do not have permission to perform this action.");
|
||||
});
|
||||
|
||||
it("keeps unknown non-403 server diagnostics generic", async () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue