From ed67862bb83f160a6508649945d2543304887c17 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 31 Jul 2026 21:18:42 +0000 Subject: [PATCH] =?UTF-8?q?=EB=8F=99=EC=A0=81=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0=20=EC=A0=9C=ED=95=9C=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=B0=8F=20=EB=8B=A4=EC=A4=91=20=ED=8C=8C=EC=9D=BC=20=EC=B4=9D?= =?UTF-8?q?=EB=9F=89=20=EA=B2=80=EC=A6=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/palette.md | 4 ++++ CHANGELOG.md | 2 ++ saas_web.py | 23 +++++++++++++++++++++-- tests/test_saas_web.py | 4 +++- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.jules/palette.md b/.jules/palette.md index a1cf208b..e552bd24 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -1,3 +1,7 @@ +## 2024-07-14 - 동적 파일 크기 제한 검증 및 일괄 폼 유효성 검사 +**Learning:** 정적으로 코딩된 업로드 제한 크기(예: 5 GiB)는 백엔드 제한이 변경될 때 클라이언트 측 검증과 불일치하게 되며, 일괄 업로드 폼에서 전체 파일 크기 검증이 없으면 사용자가 업로드 실패를 서버 측에서만 확인하게 되어 나쁜 UX를 초래합니다. +**Action:** 클라이언트 측 파일 크기 검증에서 백엔드의 최대 제한(`MAX_UPLOAD_BYTES`) 상수를 사용해 동적으로 포맷팅된 인간 친화적인 단위(예: `formatBinaryBytes(MAX_UPLOAD_BYTES)`)를 사용하고, 다중 파일 업로드 폼에도 각 파일 크기의 합을 검증하여 제출 전 즉각적인 피드백을 제공하도록 합니다. + ## 2024-07-12 - Intercepting batch form submissions for testing visual loading states **Learning:** Extending the learning from 2024-06-13, intercepting form submissions using `e.preventDefault()` via `page.evaluate()` is essential for capturing screenshot and video evidence of loading states (e.g., button disabling, spinner appearing) on forms like batch upload where the submission would normally reload the page or download an archive. **Action:** When testing visual loading states with Playwright, always inject an event listener using `page.evaluate()` to call `e.preventDefault()` on the form's `submit` event to freeze the UI in its loading state for verification. diff --git a/CHANGELOG.md b/CHANGELOG.md index ebfe94a6..6123a6ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,5 +2,7 @@ ## [Unreleased] ### Added +- 단일 및 다중 파일 업로드 폼에서 파일 크기 초과 오류 시 백엔드 제한 값에 맞춰 동적인 사이즈 한도를 표기하도록 개선 +- 다중 파일 업로드(일괄 업로드) 시 총 합산 크기가 제한(MAX_UPLOAD_BYTES)을 초과하는지 클라이언트에서 먼저 검증하여 즉시 피드백을 주도록 개선 - 다중 파일 업로드 선택 시 즉각적인 파일 개수 피드백 및 제한 초과 경고 메시지 추가 - 일괄 업로드 폼에 대상 바이트 프리셋 버튼과 총 파일 크기 미리보기를 추가하여 사용성을 개선했습니다. diff --git a/saas_web.py b/saas_web.py index 3a7b0352..4e745eed 100644 --- a/saas_web.py +++ b/saas_web.py @@ -230,9 +230,19 @@ async def add_security_headers(request: Request, call_next): } const text = formatBinaryBytes(file.size); if (file.size > MAX_UPLOAD_BYTES) { - input.setCustomValidity('File exceeds 5 GiB limit.'); + const limitStr = formatBinaryBytes(MAX_UPLOAD_BYTES); + input.setCustomValidity('File exceeds ' + limitStr + ' limit.'); input.setAttribute('aria-invalid', 'true'); - preview.innerText = 'Selected file size: ' + text + ' (exceeds 5 GiB limit)'; + preview.innerText = 'Selected file size: ' + text + ' (exceeds ' + limitStr + ' limit)'; + preview.style.color = '#dc3545'; + return; + } + + if (totalSize > MAX_UPLOAD_BYTES) { + const limitStr = formatBinaryBytes(MAX_UPLOAD_BYTES); + input.setCustomValidity('Total combined file size exceeds ' + limitStr + ' limit.'); + input.setAttribute('aria-invalid', 'true'); + preview.innerText = 'Selected ' + files.length + ' files (' + formatBinaryBytes(totalSize) + ', exceeds ' + limitStr + ' limit)'; preview.style.color = '#dc3545'; return; } @@ -325,6 +335,15 @@ async def add_security_headers(request: Request, call_next): preview.style.color = '#dc3545'; return; } + + if (totalSize > MAX_UPLOAD_BYTES) { + const limitStr = formatBinaryBytes(MAX_UPLOAD_BYTES); + input.setCustomValidity('Total combined file size exceeds ' + limitStr + ' limit.'); + input.setAttribute('aria-invalid', 'true'); + preview.innerText = 'Selected ' + files.length + ' files (' + formatBinaryBytes(totalSize) + ', exceeds ' + limitStr + ' limit)'; + preview.style.color = '#dc3545'; + return; + } preview.innerText = 'Selected ' + files.length + ' file(s) (' + formatBinaryBytes(totalSize) + ')'; } diff --git a/tests/test_saas_web.py b/tests/test_saas_web.py index 57b879d1..647c9969 100644 --- a/tests/test_saas_web.py +++ b/tests/test_saas_web.py @@ -52,7 +52,8 @@ def test_get_ui_includes_binary_file_size_validation(self): self.assertIn("const MAX_UPLOAD_BYTES = 5 * 1024 * 1024 * 1024;", html) self.assertIn("['B', 'KiB', 'MiB', 'GiB']", html) - self.assertIn("File exceeds 5 GiB limit.", html) + self.assertIn("const limitStr = formatBinaryBytes(MAX_UPLOAD_BYTES);", html) + self.assertIn("input.setCustomValidity('File exceeds ' + limitStr + ' limit.');", html) self.assertIn("preview.style.color = '#0f6674';", html) self.assertIn('onchange="updateFileSizePreview(this)"', html) @@ -594,6 +595,7 @@ def test_get_ui_includes_batch_upload_form(self): self.assertIn('onchange="updateBatchFilePreview(this)"', html) self.assertIn('id="batch_files_preview"', html) self.assertIn('function updateBatchFilePreview(input)', html) + self.assertIn("input.setCustomValidity('Total combined file size exceeds ' + limitStr + ' limit.');", html) @unittest.skipUnless(_HAS_FASTAPI, "fastapi not installed (optional integration dependency)")