feat: enforce configured file-size limits across all local upload paths - #934
Draft
bdart wants to merge 7 commits into
Draft
feat: enforce configured file-size limits across all local upload paths#934bdart wants to merge 7 commits into
bdart wants to merge 7 commits into
Conversation
Co-authored-by: Daniel <bdart@users.noreply.github.com>
- useFileDropzone.uploadFiles: validate sizes before setUploading, silent-chat creation, or any network request; add maxSizeBytes/maxSizeFormatted to deps - useStandaloneFileUpload: import useUploadFeature, add size preflight before FormData construction, pass maxSizeFormatted to UploadTooLargeError (fixes the 'Maximum size: -' regression on 413 responses) - useConversationDropzone: add optional maxSize, maxSizeFormatted, and onError params; surface file-too-large rejections immediately via onError - useChatFileSources.handleSelectedFiles (onSelectFiles): validate sizes before calling performDiskUpload to protect the host/custom File-object path Co-authored-by: Daniel <bdart@users.noreply.github.com>
… error state - FileUploadButton: read maxSizeBytes from useUploadFeature, pass to useDropzone; add onError prop; surface file-too-large dropzone rejections via onError - FileUpload (buttonOnly): extract setError from useFileDropzone and pass as onError to FileUploadButton so rejections update the visible error state - AssistantFileUploadSelector: read maxSizeBytes/maxSizeFormatted from useUploadFeature, pass maxSize to its hidden disk-picker dropzone, handle file-too-large rejection into the local cloudLinkError state - Chat.tsx: read maxSizeBytes/maxSizeFormatted from useUploadFeature; import useFileUploadStore to get setError; pass maxSize, maxSizeFormatted, and onError to useConversationDropzone so drag-drop rejections are visible Co-authored-by: Daniel <bdart@users.noreply.github.com>
… from library Makes these available to office-addin consumers via @erato/frontend/library Co-authored-by: Daniel <bdart@users.noreply.github.com>
…aths AddinChatInput.wrappedOnSendMessage: - After resolveSelectedFilesForSend(), validate file sizes with validateFileSizes before constructing FormData or calling fetchUploadFile; if any file exceeds the configured limit, set emailUploadError and fall through (message sent without file IDs, chips remain for retry) - In the catch block, map isUploadTooLarge(error) to UploadTooLargeError with the runtime-formatted limit instead of silently logging; surface via emailUploadError state merged onto chatInputProps.uploadError OutlookAddinChat + AddinChatCore: - Import useUploadFeature and useFileUploadStore - Pass maxSize, maxSizeFormatted, and onError to useConversationDropzone so drag-drop size rejections are immediately visible in the add-in UI Co-authored-by: Daniel <bdart@users.noreply.github.com>
New test files: - utils/__tests__/validateFileSizes.test.ts: 8 tests covering boundary conditions (1-byte below/at/above limit), batch rejection, and metadata - hooks/files/__tests__/useStandaloneFileUpload.test.tsx: preflight rejection, formatted limit in errors (not '--'), HTTP 413 fallback - hooks/files/__tests__/useConversationDropzone.test.tsx: maxSize passed to useDropzone, file-too-large routing via onError - hooks/files/__tests__/useChatFileSources.test.tsx: onSelectFiles preflight for host/custom file paths Updated test files: - hooks/files/__tests__/useFileDropzone.test.tsx: update FeatureConfigProvider mock to include maxSizeBytes/maxSizeFormatted; add 6 preflight tests - components/ui/FileUpload/__tests__/FileUploadButton.test.tsx: add 4 tests for maxSize configuration and file-too-large rejection routing Co-authored-by: Daniel <bdart@users.noreply.github.com>
The new size-limit tests allocated real multi-MiB buffers, so vitest's deep-equality in toHaveBeenCalledWith walked every byte (~3.9s per assertion) and two tests exceeded the 5s timeout. Share a fixture that reports a size without allocating it. Also fixes the tsc errors that broke both Docker builds (invalid FileCapability fixtures, untyped vi.fn mocks), the eslint/prettier violations, and the office-addin library mocks that lacked the useUploadFeature/useFileUploadStore exports the add-in hosts now read. Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes ERMAIN-34.
Prevents every local
Filelarger than the runtime-configured per-file upload limit from starting an upload, regardless of how the file entered the UI. The configured limit (maxSizeBytes/maxSizeFormattedfromuseUploadFeature()) is enforced consistently — no hard-coded 20 MB anywhere in client code.What changed
New utility —
frontend/src/utils/validateFileSizes.tsPure, side-effect-free preflight function. Accepts
File[]andmaxSizeBytes; returns a discriminated union with{ valid: true }or{ valid: false; oversizedFiles: File[] }. One byte above the limit → invalid; exactly at the limit → valid.API-facing upload hooks (correctness boundary)
useFileDropzone.uploadFilessetUploading, silent-chat creation,FormData, orfetchUploadFile.useStandaloneFileUpload.uploadFilesuseUploadFeature; size preflight before upload state or network request;UploadTooLargeErrornow always receivesmaxSizeFormatted(fixes theMaximum size: —regression on 413 responses).useChatFileSources.handleSelectedFiles(onSelectFiles)performDiskUploadprotects the host/customFile[]path used by Outlook/Teams add-menu.Selection surfaces (UX early-rejection)
FileUploadButtonmaxSizeBytesfromuseUploadFeature; passes touseDropzone; addsonErrorprop; routesfile-too-largerejections viaonError.FileUpload(buttonOnly)setErrorfromuseFileDropzoneand passes it asonErrortoFileUploadButton.useConversationDropzonemaxSize,maxSizeFormatted, andonErrorprops; surfacesfile-too-largerejections immediately viaonError.AssistantFileUploadSelectoruseUploadFeature; passesmaxSizeto its hidden disk-picker dropzone; routesfile-too-largeinto itscloudLinkErrorstate.Chat.tsxmaxSize,maxSizeFormatted, andsetUploadErrorasonErrortouseConversationDropzone.OutlookAddinChat+AddinChatCoreuseConversationDropzonewiring for both add-in conversation hosts.Outlook direct email-source upload (
AddinChatInput)resolveSelectedFilesForSend(), validates file sizes withvalidateFileSizesbefore constructingFormDataor callingfetchUploadFile. Oversized files setemailUploadErrorand skip the upload (message is still sent without the file IDs; chips remain for retry).catchblock,isUploadTooLarge(error)maps HTTP 413 toUploadTooLargeError(maxSizeFormatted)instead of silently logging.emailUploadErroris merged intochatInputProps.uploadErrorand surfaced throughAddinChatInputCore→ChatInput.Library exports
UploadTooLargeError,isUploadTooLarge, andvalidateFileSizesare now exported from@erato/frontend/libraryso add-in packages can consume them directly.Test environment fix
Added
frontend/src/__mocks__/desktop-sidecar-protocol.tsstub + vitest alias so tests that import through the library chain don't fail on the missing native package.Tests added / updated
utils/__tests__/validateFileSizes.test.tsFilereference returned)hooks/files/__tests__/useFileDropzone.test.tsxfetchUploadFileand chat creation, setsUploadTooLargeErrorwith formatted limit, accepts exact-limit file, rejects mixed batch,isUploadingstays falsehooks/files/__tests__/useStandaloneFileUpload.test.tsx—), exact-limit accept, mixed-batch rejection,isUploadingstays false, HTTP 413 with numeric and string statushooks/files/__tests__/useConversationDropzone.test.tsxmaxSizeforwarded touseDropzone,file-too-large→UploadTooLargeErrorviaonError, nouploadFilescall on rejection, valid files still uploadhooks/files/__tests__/useChatFileSources.test.tsxonSelectFilespreflight rejects oversized, formats limit correctly, accepts exact limit, rejects mixed batchcomponents/ui/FileUpload/__tests__/FileUploadButton.test.tsxmaxSizefrom feature config passed to dropzone,file-too-large→UploadTooLargeErrorviaonError, noperformFileUploadon rejection, noonErroron clean dropScope not covered in this PR (per issue)
fetchLinkFile(browser does not possess remote bytes)Testing
All affected test suites pass (
vitest run). TheFileUploadcomponent tests, Chat tests, provider tests, and utility tests are unaffected.Linear Issue: ERMAIN-34