Skip to content

Commit 03e909b

Browse files
dugshubclaude
andcommitted
test(core): add collection size limit tests for DoS protection
Implements Priority 3 (MEDIUM) security testing: collection size limits. Tests Added: - Branch actions limit: max 100 actions - Branch options limit: max 50 options - Branch menus limit: max 20 menus - Wizard branches limit: max 100 branches - SessionState option_values limit: max 1000 items - SessionState variables limit: max 1000 items Coverage: - 12 new collection limit tests - Tests both boundary conditions (at limit) and violations (over limit) These tests verify the collection size validators already implemented in models.py prevent DoS attacks via memory exhaustion. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1340819 commit 03e909b

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed

tests/unit/core/test_models.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,3 +759,161 @@ def test_json_deserialization(self) -> None:
759759
config = BashActionConfig(**json_data)
760760
assert config.id == make_action_id("deploy")
761761
assert config.name == "Deploy"
762+
763+
764+
class TestCollectionLimits:
765+
"""Test collection size limits for DoS protection."""
766+
767+
def test_rejects_too_many_actions_in_branch(self) -> None:
768+
"""Should reject branch with too many actions (>100)."""
769+
with pytest.raises(ValidationError, match="Too many actions"):
770+
BranchConfig(
771+
id=make_branch_id("test"),
772+
title="Test",
773+
actions=[
774+
BashActionConfig(
775+
id=make_action_id(f"action{i}"),
776+
name=f"Action {i}",
777+
command="echo test",
778+
)
779+
for i in range(101) # Over limit
780+
],
781+
)
782+
783+
def test_accepts_max_actions_in_branch(self) -> None:
784+
"""Should accept branch with exactly 100 actions."""
785+
config = BranchConfig(
786+
id=make_branch_id("test"),
787+
title="Test",
788+
actions=[
789+
BashActionConfig(
790+
id=make_action_id(f"action{i}"),
791+
name=f"Action {i}",
792+
command="echo test",
793+
)
794+
for i in range(100) # At limit
795+
],
796+
)
797+
assert len(config.actions) == 100
798+
799+
def test_rejects_too_many_options_in_branch(self) -> None:
800+
"""Should reject branch with too many options (>50)."""
801+
with pytest.raises(ValidationError, match="Too many options"):
802+
BranchConfig(
803+
id=make_branch_id("test"),
804+
title="Test",
805+
options=[
806+
StringOptionConfig(
807+
id=make_option_key(f"option{i}"),
808+
name=f"Option {i}",
809+
description="Test option",
810+
)
811+
for i in range(51) # Over limit
812+
],
813+
)
814+
815+
def test_accepts_max_options_in_branch(self) -> None:
816+
"""Should accept branch with exactly 50 options."""
817+
config = BranchConfig(
818+
id=make_branch_id("test"),
819+
title="Test",
820+
options=[
821+
StringOptionConfig(
822+
id=make_option_key(f"option{i}"),
823+
name=f"Option {i}",
824+
description="Test option",
825+
)
826+
for i in range(50) # At limit
827+
],
828+
)
829+
assert len(config.options) == 50
830+
831+
def test_rejects_too_many_menus_in_branch(self) -> None:
832+
"""Should reject branch with too many menus (>20)."""
833+
with pytest.raises(ValidationError, match="Too many menus"):
834+
BranchConfig(
835+
id=make_branch_id("test"),
836+
title="Test",
837+
menus=[
838+
MenuConfig(
839+
id=make_menu_id(f"menu{i}"),
840+
label=f"Menu {i}",
841+
target=make_branch_id("target"),
842+
)
843+
for i in range(21) # Over limit
844+
],
845+
)
846+
847+
def test_accepts_max_menus_in_branch(self) -> None:
848+
"""Should accept branch with exactly 20 menus."""
849+
config = BranchConfig(
850+
id=make_branch_id("test"),
851+
title="Test",
852+
menus=[
853+
MenuConfig(
854+
id=make_menu_id(f"menu{i}"),
855+
label=f"Menu {i}",
856+
target=make_branch_id("target"),
857+
)
858+
for i in range(20) # At limit
859+
],
860+
)
861+
assert len(config.menus) == 20
862+
863+
def test_rejects_too_many_branches_in_wizard(self) -> None:
864+
"""Should reject wizard with too many branches (>100)."""
865+
with pytest.raises(ValidationError, match="Too many branches"):
866+
WizardConfig(
867+
name="test",
868+
version="1.0.0",
869+
entry_branch=make_branch_id("branch0"),
870+
branches=[
871+
BranchConfig(
872+
id=make_branch_id(f"branch{i}"),
873+
title=f"Branch {i}",
874+
)
875+
for i in range(101) # Over limit
876+
],
877+
)
878+
879+
def test_accepts_max_branches_in_wizard(self) -> None:
880+
"""Should accept wizard with exactly 100 branches."""
881+
config = WizardConfig(
882+
name="test",
883+
version="1.0.0",
884+
entry_branch=make_branch_id("branch0"),
885+
branches=[
886+
BranchConfig(
887+
id=make_branch_id(f"branch{i}"),
888+
title=f"Branch {i}",
889+
)
890+
for i in range(100) # At limit
891+
],
892+
)
893+
assert len(config.branches) == 100
894+
895+
def test_rejects_too_many_option_values_in_session(self) -> None:
896+
"""Should reject session with too many option values (>1000)."""
897+
with pytest.raises(ValidationError, match="Too many options"):
898+
SessionState(
899+
option_values={
900+
make_option_key(f"option{i}"): "value" for i in range(1001)
901+
}
902+
)
903+
904+
def test_accepts_max_option_values_in_session(self) -> None:
905+
"""Should accept session with exactly 1000 option values."""
906+
state = SessionState(
907+
option_values={make_option_key(f"option{i}"): "value" for i in range(1000)}
908+
)
909+
assert len(state.option_values) == 1000
910+
911+
def test_rejects_too_many_variables_in_session(self) -> None:
912+
"""Should reject session with too many variables (>1000)."""
913+
with pytest.raises(ValidationError, match="Too many variables"):
914+
SessionState(variables={f"var{i}": "value" for i in range(1001)})
915+
916+
def test_accepts_max_variables_in_session(self) -> None:
917+
"""Should accept session with exactly 1000 variables."""
918+
state = SessionState(variables={f"var{i}": "value" for i in range(1000)})
919+
assert len(state.variables) == 1000

0 commit comments

Comments
 (0)