fix(work-orders): raise media upload limit to 200 MB

This commit is contained in:
Alexandre Brandizzi 2026-09-21 15:09:51 -03:00
parent 69a92cafda
commit ca2b3b5134
5 changed files with 45 additions and 10 deletions

View file

@ -1,7 +1,7 @@
import { isWorkOrderCompletionSurfaceReadOnly } from "@/domain/work-orders/utils/work-order-lock"; import { isWorkOrderCompletionSurfaceReadOnly } from "@/domain/work-orders/utils/work-order-lock";
/** Backend RequestSizeLimit for media and completion-doc uploads. */ /** Backend RequestSizeLimit for media and completion-doc uploads. */
export const MAX_WORK_ORDER_MEDIA_BYTES = 50_000_000; export const MAX_WORK_ORDER_MEDIA_BYTES = 200_000_000;
const MEDIA_MIME_TYPES = new Set([ const MEDIA_MIME_TYPES = new Set([
"image/jpeg", "image/jpeg",

View file

@ -117,7 +117,7 @@ describe("MediaUploadModal API wiring", () => {
}); });
expect(screen.getByText("Photos & Videos")).toBeInTheDocument(); expect(screen.getByText("Photos & Videos")).toBeInTheDocument();
expect( expect(
screen.getByText("Accepted: JPG, PNG, MP4, MOV · Max 50MB per file"), screen.getByText("Accepted: JPG, PNG, MP4, MOV · Max 200MB per file"),
).toBeInTheDocument(); ).toBeInTheDocument();
expect(screen.queryByText(/categorize after upload/i)).not.toBeInTheDocument(); expect(screen.queryByText(/categorize after upload/i)).not.toBeInTheDocument();
}); });

View file

@ -1,10 +1,17 @@
import { createRef } from "react"; import { createRef } from "react";
import { screen } from "@testing-library/react"; import { fireEvent, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { toast } from "react-toastify";
import { MediaUploader } from "@/app/(protected)/workorders/_components/media/media-uploader";
import { MediaUploaderDropzone } from "@/app/(protected)/workorders/_components/media/media-uploader-dropzone"; import { MediaUploaderDropzone } from "@/app/(protected)/workorders/_components/media/media-uploader-dropzone";
import { workOrderMediaAcceptAttribute } from "@/domain/work-orders/utils/work-order-media-limits"; import {
MAX_WORK_ORDER_MEDIA_BYTES,
workOrderMediaAcceptAttribute,
} from "@/domain/work-orders/utils/work-order-media-limits";
import { renderWithProviders } from "@/test/test-utils"; import { renderWithProviders } from "@/test/test-utils";
vi.mock("react-toastify", () => ({ toast: { error: vi.fn() } }));
describe("MediaUploaderDropzone", () => { describe("MediaUploaderDropzone", () => {
it("matches the AAP Photos accept copy and keeps it media-only", () => { it("matches the AAP Photos accept copy and keeps it media-only", () => {
renderWithProviders( renderWithProviders(
@ -16,7 +23,7 @@ describe("MediaUploaderDropzone", () => {
); );
expect( expect(
screen.getByText("Accepted: JPG, PNG, MP4, MOV · Max 50MB per file"), screen.getByText("Accepted: JPG, PNG, MP4, MOV · Max 200MB per file"),
).toBeInTheDocument(); ).toBeInTheDocument();
expect(screen.queryByText(/categorize after upload/i)).not.toBeInTheDocument(); expect(screen.queryByText(/categorize after upload/i)).not.toBeInTheDocument();
expect(screen.queryByText(/PDF/)).not.toBeInTheDocument(); expect(screen.queryByText(/PDF/)).not.toBeInTheDocument();
@ -25,4 +32,19 @@ describe("MediaUploaderDropzone", () => {
expect(input?.getAttribute("accept")).toBe(workOrderMediaAcceptAttribute()); expect(input?.getAttribute("accept")).toBe(workOrderMediaAcceptAttribute());
expect(input?.getAttribute("accept")).not.toContain(".pdf"); expect(input?.getAttribute("accept")).not.toContain(".pdf");
}); });
it("rejects an oversized media file with the generic 200 MB toast", () => {
renderWithProviders(<MediaUploader uploads={[]} />);
const oversizedFile = {
name: "mobile-clip.MP4",
type: "application/octet-stream",
size: MAX_WORK_ORDER_MEDIA_BYTES + 1,
} as File;
const input = document.querySelector('input[type="file"]');
fireEvent.change(input as HTMLInputElement, { target: { files: [oversizedFile] } });
expect(toast.error).toHaveBeenCalledWith('"mobile-clip.MP4" exceeds the 200 MB limit.');
});
}); });

View file

@ -140,13 +140,13 @@ describe("SlideOverExtrasTab Aveta", () => {
expect(getMediaContent).not.toHaveBeenCalled(); expect(getMediaContent).not.toHaveBeenCalled();
}); });
it("shows Extra Docs accept copy with PDF DOC and 50MB", () => { it("shows Extra Docs accept copy with PDF DOC and 200MB", () => {
renderWithProviders( renderWithProviders(
<SlideOverExtrasTab uploads={[]} isReadOnly={false} onUpload={vi.fn()} onRemove={vi.fn()} />, <SlideOverExtrasTab uploads={[]} isReadOnly={false} onUpload={vi.fn()} onRemove={vi.fn()} />,
); );
expect( expect(
screen.getByText("Accepted: JPG, PNG, MP4, MOV, PDF, DOC · Max 50MB per file"), screen.getByText("Accepted: JPG, PNG, MP4, MOV, PDF, DOC · Max 200MB per file"),
).toBeInTheDocument(); ).toBeInTheDocument();
const input = document.querySelector('input[type="file"]'); const input = document.querySelector('input[type="file"]');
expect(input?.getAttribute("accept")).toContain(".pdf"); expect(input?.getAttribute("accept")).toContain(".pdf");

View file

@ -24,9 +24,10 @@ describe("work-order-media-limits", () => {
expect(canDeleteWorkOrderMedia({ id: "0" })).toBe(false); expect(canDeleteWorkOrderMedia({ id: "0" })).toBe(false);
}); });
it("caps uploads at 50_000_000 bytes and formats decimal MB", () => { it("caps uploads at 200_000_000 bytes and formats decimal MB", () => {
expect(MAX_WORK_ORDER_MEDIA_BYTES).toBe(50_000_000); expect(MAX_WORK_ORDER_MEDIA_BYTES).toBe(200_000_000);
expect(formatMediaSizeLimit()).toBe("50 MB"); expect(formatMediaSizeLimit()).toBe("200 MB");
expect(isWorkOrderFileTooLarge({ size: MAX_WORK_ORDER_MEDIA_BYTES } as File)).toBe(false);
expect(isWorkOrderFileTooLarge({ size: MAX_WORK_ORDER_MEDIA_BYTES + 1 } as File)).toBe(true); expect(isWorkOrderFileTooLarge({ size: MAX_WORK_ORDER_MEDIA_BYTES + 1 } as File)).toBe(true);
expect(isWorkOrderFileTooLarge({ size: 10 } as File)).toBe(false); expect(isWorkOrderFileTooLarge({ size: 10 } as File)).toBe(false);
}); });
@ -39,7 +40,19 @@ describe("work-order-media-limits", () => {
expect(isWorkOrderMediaTypeAllowed({ name: "a.mov", type: "video/quicktime" } as File)).toBe( expect(isWorkOrderMediaTypeAllowed({ name: "a.mov", type: "video/quicktime" } as File)).toBe(
true, true,
); );
expect(isWorkOrderMediaTypeAllowed({ name: "a.MP4", type: "" } as File)).toBe(true);
expect(isWorkOrderMediaTypeAllowed({ name: "a.MOV", type: "" } as File)).toBe(true);
expect(isWorkOrderMediaTypeAllowed({ name: "a.mov", type: "video/quicktime" } as File)).toBe(
true,
);
expect(
isWorkOrderMediaTypeAllowed({ name: "a.mp4", type: "application/octet-stream" } as File),
).toBe(true);
expect(isWorkOrderMediaTypeAllowed({ name: "a.gif", type: "image/gif" } as File)).toBe(false); expect(isWorkOrderMediaTypeAllowed({ name: "a.gif", type: "image/gif" } as File)).toBe(false);
expect(isWorkOrderMediaTypeAllowed({ name: "a.webm", type: "video/webm" } as File)).toBe(false);
expect(
isWorkOrderMediaTypeAllowed({ name: "a.txt", type: "application/octet-stream" } as File),
).toBe(false);
expect(isWorkOrderMediaTypeAllowed({ name: "a.pdf", type: "application/pdf" } as File)).toBe( expect(isWorkOrderMediaTypeAllowed({ name: "a.pdf", type: "application/pdf" } as File)).toBe(
false, false,
); );