From 46573935747f5eb91ed0ed2160367d33a248d12e Mon Sep 17 00:00:00 2001 From: aiwithrajan Date: Wed, 14 Oct 2026 18:54:50 +0530 Subject: [PATCH] fix(backend): validate non-blank name in CreateFolderRequest and UpdateFolderRequest ($25 bounty proposal) --- backend/models/folder.py | 20 ++++++ .../tests/unit/test_folder_name_validation.py | 72 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 backend/tests/unit/test_folder_name_validation.py diff --git a/backend/models/folder.py b/backend/models/folder.py index 5057179dcc5..f45484625ed 100644 --- a/backend/models/folder.py +++ b/backend/models/folder.py @@ -38,6 +38,16 @@ class CreateFolderRequest(BaseModel): color: Optional[str] = None icon: Optional[str] = None + @field_validator('name') + @classmethod + def validate_name(cls, value: str) -> str: + if value is not None: + stripped = value.strip() + if not stripped: + raise ValueError('folder name cannot be empty or blank') + return stripped + return value + class UpdateFolderRequest(BaseModel): """Request model for updating folder metadata.""" @@ -48,6 +58,16 @@ class UpdateFolderRequest(BaseModel): icon: Optional[str] = None order: Optional[int] = None + @field_validator('name') + @classmethod + def validate_name(cls, value: Optional[str]) -> Optional[str]: + if value is not None: + stripped = value.strip() + if not stripped: + raise ValueError('folder name cannot be empty or blank') + return stripped + return value + class MoveConversationRequest(BaseModel): """Request model for moving a conversation to a folder.""" diff --git a/backend/tests/unit/test_folder_name_validation.py b/backend/tests/unit/test_folder_name_validation.py new file mode 100644 index 00000000000..ac155bfcd0f --- /dev/null +++ b/backend/tests/unit/test_folder_name_validation.py @@ -0,0 +1,72 @@ +"""Tests for CreateFolderRequest and UpdateFolderRequest name validation and normalization. + +Validates that: +- Empty strings and whitespace-only names are rejected with ValidationError ('folder name cannot be empty or blank') on both create and update paths. +- Valid names with leading/trailing whitespace are automatically stripped and normalized. +- Update requests allow name=None (omitted field) when updating other folder attributes such as description, color, icon, or order. +- Default and valid payloads succeed as expected. +""" + +import unittest +from pydantic import ValidationError + +from models.folder import CreateFolderRequest, UpdateFolderRequest + + +class TestFolderNameValidation(unittest.TestCase): + """Test suite verifying folder name validation and whitespace normalization.""" + + def test_create_folder_request_rejects_empty_name(self): + with self.assertRaises(ValidationError) as cm: + CreateFolderRequest(name="") + self.assertTrue( + any("folder name cannot be empty or blank" in err["msg"] or "at least 1 character" in err["msg"] for err in cm.exception.errors()) + ) + + def test_create_folder_request_rejects_whitespace_only_name(self): + for whitespace in (" ", " ", "\t", "\n", " \t \n "): + with self.subTest(whitespace=whitespace): + with self.assertRaises(ValidationError) as cm: + CreateFolderRequest(name=whitespace) + self.assertTrue( + any("folder name cannot be empty or blank" in err["msg"] for err in cm.exception.errors()) + ) + + def test_create_folder_request_strips_leading_trailing_whitespace(self): + req = CreateFolderRequest(name=" Work & Projects ") + self.assertEqual(req.name, "Work & Projects") + + def test_create_folder_request_accepts_valid_name(self): + req = CreateFolderRequest(name="Personal Knowledge") + self.assertEqual(req.name, "Personal Knowledge") + self.assertIsNone(req.description) + + def test_update_folder_request_rejects_empty_name(self): + with self.assertRaises(ValidationError) as cm: + UpdateFolderRequest(name="") + self.assertTrue( + any("folder name cannot be empty or blank" in err["msg"] or "at least 1 character" in err["msg"] for err in cm.exception.errors()) + ) + + def test_update_folder_request_rejects_whitespace_only_name(self): + for whitespace in (" ", " ", "\t", "\n", " \t \n "): + with self.subTest(whitespace=whitespace): + with self.assertRaises(ValidationError) as cm: + UpdateFolderRequest(name=whitespace) + self.assertTrue( + any("folder name cannot be empty or blank" in err["msg"] for err in cm.exception.errors()) + ) + + def test_update_folder_request_allows_omitted_name(self): + req = UpdateFolderRequest(description="New folder notes", color="#10B981") + self.assertIsNone(req.name) + self.assertEqual(req.description, "New folder notes") + self.assertEqual(req.color, "#10B981") + + def test_update_folder_request_strips_leading_trailing_whitespace(self): + req = UpdateFolderRequest(name=" Archived Items ") + self.assertEqual(req.name, "Archived Items") + + +if __name__ == "__main__": + unittest.main()