Skip to content

Commit b34a7f1

Browse files
committed
tests(hooks): add comprehensive bulk operations test grid
why: Ensure bulk operations API works correctly with parametrized tests what: - Add BulkOpTestCase NamedTuple with test_id, operation, setup, expected - Add categorized test cases: GET_INDICES, GET_VALUES, SET_BULK, CLEAR, APPEND - Create parametrized test_bulk_hook_operation() for 12 data-driven tests - Add standalone tests for iteration, SparseArray input, method chaining
1 parent 953e3f3 commit b34a7f1

File tree

1 file changed

+290
-0
lines changed

1 file changed

+290
-0
lines changed

tests/test_hooks.py

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,3 +525,293 @@ def test_new_hooks_version_gated(server: Server, test_case: HookTestCase) -> Non
525525

526526
# Cleanup
527527
session.unset_hook(f"{test_case.hook}[0]")
528+
529+
530+
# =============================================================================
531+
# Bulk Operations API Tests
532+
# =============================================================================
533+
534+
535+
class BulkOpTestCase(t.NamedTuple):
536+
"""Test case for bulk hook operations."""
537+
538+
test_id: str
539+
operation: str # "get_indices", "get_values", "set_bulk", "clear", "append"
540+
hook: str # Hook name to test
541+
setup_hooks: dict[int, str] # Initial hooks to set (index -> value)
542+
operation_args: dict[str, t.Any] # Args for operation
543+
expected_indices: list[int] # Expected indices after operation
544+
expected_contains: list[str] | None = None # Strings expected in values
545+
546+
547+
# --- get_hook_indices tests ---
548+
GET_INDICES_TESTS: list[BulkOpTestCase] = [
549+
BulkOpTestCase(
550+
"get_indices_empty",
551+
"get_indices",
552+
"session-renamed",
553+
{},
554+
{},
555+
[],
556+
),
557+
BulkOpTestCase(
558+
"get_indices_sequential",
559+
"get_indices",
560+
"session-renamed",
561+
{
562+
0: "display-message 'hook 0'",
563+
1: "display-message 'hook 1'",
564+
2: "display-message 'hook 2'",
565+
},
566+
{},
567+
[0, 1, 2],
568+
),
569+
BulkOpTestCase(
570+
"get_indices_sparse",
571+
"get_indices",
572+
"session-renamed",
573+
{
574+
0: "display-message 'hook 0'",
575+
5: "display-message 'hook 5'",
576+
10: "display-message 'hook 10'",
577+
},
578+
{},
579+
[0, 5, 10],
580+
),
581+
]
582+
583+
# --- get_hook_values tests ---
584+
GET_VALUES_TESTS: list[BulkOpTestCase] = [
585+
BulkOpTestCase(
586+
"get_values_empty",
587+
"get_values",
588+
"session-renamed",
589+
{},
590+
{},
591+
[],
592+
),
593+
BulkOpTestCase(
594+
"get_values_sparse",
595+
"get_values",
596+
"session-renamed",
597+
{0: "display-message 'hook 0'", 5: "display-message 'hook 5'"},
598+
{},
599+
[0, 5],
600+
["display-message"],
601+
),
602+
]
603+
604+
# --- set_hooks_bulk tests ---
605+
SET_BULK_TESTS: list[BulkOpTestCase] = [
606+
BulkOpTestCase(
607+
"set_bulk_with_dict",
608+
"set_bulk",
609+
"session-renamed",
610+
{},
611+
{
612+
"values": {
613+
0: "display-message 'hook 0'",
614+
1: "display-message 'hook 1'",
615+
5: "display-message 'hook 5'",
616+
},
617+
},
618+
[0, 1, 5],
619+
["hook 0", "hook 1", "hook 5"],
620+
),
621+
BulkOpTestCase(
622+
"set_bulk_with_list",
623+
"set_bulk",
624+
"session-renamed",
625+
{},
626+
{
627+
"values": [
628+
"display-message 'hook 0'",
629+
"display-message 'hook 1'",
630+
"display-message 'hook 2'",
631+
],
632+
},
633+
[0, 1, 2],
634+
),
635+
BulkOpTestCase(
636+
"set_bulk_clear_existing",
637+
"set_bulk",
638+
"session-renamed",
639+
{0: "display-message 'old 0'", 1: "display-message 'old 1'"},
640+
{"values": {0: "display-message 'new 0'"}, "clear_existing": True},
641+
[0],
642+
["new 0"],
643+
),
644+
]
645+
646+
# --- clear_hook tests ---
647+
CLEAR_TESTS: list[BulkOpTestCase] = [
648+
BulkOpTestCase(
649+
"clear_hook",
650+
"clear",
651+
"session-renamed",
652+
{0: "display-message 'hook 0'", 5: "display-message 'hook 5'"},
653+
{},
654+
[],
655+
),
656+
]
657+
658+
# --- append_hook tests ---
659+
APPEND_TESTS: list[BulkOpTestCase] = [
660+
BulkOpTestCase(
661+
"append_to_empty",
662+
"append",
663+
"session-renamed",
664+
{},
665+
{"value": "display-message 'appended'"},
666+
[0],
667+
["appended"],
668+
),
669+
BulkOpTestCase(
670+
"append_sequential",
671+
"append",
672+
"session-renamed",
673+
{0: "display-message 'initial'"},
674+
{"value": "display-message 'appended'"},
675+
[0, 1],
676+
),
677+
BulkOpTestCase(
678+
"append_after_sparse",
679+
"append",
680+
"session-renamed",
681+
{0: "display-message 'at 0'", 10: "display-message 'at 10'"},
682+
{"value": "display-message 'appended'"},
683+
[0, 10, 11],
684+
["appended"],
685+
),
686+
]
687+
688+
# Combine all bulk operation test cases
689+
ALL_BULK_OP_TESTS: list[BulkOpTestCase] = (
690+
GET_INDICES_TESTS + GET_VALUES_TESTS + SET_BULK_TESTS + CLEAR_TESTS + APPEND_TESTS
691+
)
692+
693+
694+
def _build_bulk_op_params() -> list[t.Any]:
695+
"""Build pytest params for bulk operation tests."""
696+
return [pytest.param(tc, id=tc.test_id) for tc in ALL_BULK_OP_TESTS]
697+
698+
699+
@pytest.mark.parametrize("test_case", _build_bulk_op_params())
700+
def test_bulk_hook_operation(server: Server, test_case: BulkOpTestCase) -> None:
701+
"""Test bulk hook operations.
702+
703+
This parametrized test ensures all bulk operations work correctly:
704+
- get_hook_indices: returns sorted list of existing indices
705+
- get_hook_values: returns SparseArray with values
706+
- set_hooks_bulk: sets multiple hooks at once
707+
- clear_hook: removes all indexed values
708+
- append_hook: appends at next available index
709+
"""
710+
session = server.new_session(session_name="test_bulk_ops")
711+
712+
# Setup initial hooks
713+
for idx, val in test_case.setup_hooks.items():
714+
session.set_hook(f"{test_case.hook}[{idx}]", val)
715+
716+
# Perform operation based on type
717+
if test_case.operation == "get_indices":
718+
result = session.get_hook_indices(test_case.hook)
719+
assert result == test_case.expected_indices
720+
721+
elif test_case.operation == "get_values":
722+
values = session.get_hook_values(test_case.hook)
723+
assert isinstance(values, SparseArray)
724+
assert sorted(values.keys()) == test_case.expected_indices
725+
if test_case.expected_contains:
726+
for expected_str in test_case.expected_contains:
727+
assert any(expected_str in v for v in values.values())
728+
729+
elif test_case.operation == "set_bulk":
730+
session.set_hooks_bulk(test_case.hook, **test_case.operation_args)
731+
indices = session.get_hook_indices(test_case.hook)
732+
assert indices == test_case.expected_indices
733+
if test_case.expected_contains:
734+
values = session.get_hook_values(test_case.hook)
735+
for expected_str in test_case.expected_contains:
736+
assert any(expected_str in v for v in values.values())
737+
738+
elif test_case.operation == "clear":
739+
session.clear_hook(test_case.hook)
740+
indices = session.get_hook_indices(test_case.hook)
741+
assert indices == test_case.expected_indices
742+
743+
elif test_case.operation == "append":
744+
session.append_hook(test_case.hook, test_case.operation_args["value"])
745+
indices = session.get_hook_indices(test_case.hook)
746+
assert indices == test_case.expected_indices
747+
if test_case.expected_contains:
748+
values = session.get_hook_values(test_case.hook)
749+
for expected_str in test_case.expected_contains:
750+
assert any(expected_str in v for v in values.values())
751+
752+
# Cleanup
753+
session.clear_hook(test_case.hook)
754+
755+
756+
def test_bulk_hook_values_iteration(server: Server) -> None:
757+
"""Test iterating over hook values in sorted order."""
758+
session = server.new_session(session_name="test_bulk_ops")
759+
760+
# Set hooks at sparse indices (out of order)
761+
session.set_hook("session-renamed[5]", "display-message 'fifth'")
762+
session.set_hook("session-renamed[0]", "display-message 'zeroth'")
763+
session.set_hook("session-renamed[2]", "display-message 'second'")
764+
765+
values = session.get_hook_values("session-renamed")
766+
value_list = list(values.iter_values())
767+
768+
# Values should be in sorted index order
769+
assert len(value_list) == 3
770+
assert "zeroth" in value_list[0]
771+
assert "second" in value_list[1]
772+
assert "fifth" in value_list[2]
773+
774+
# Cleanup
775+
session.clear_hook("session-renamed")
776+
777+
778+
def test_bulk_hook_set_with_sparse_array(server: Server) -> None:
779+
"""Test set_hooks_bulk with SparseArray input."""
780+
session = server.new_session(session_name="test_bulk_ops")
781+
782+
sparse: SparseArray[str] = SparseArray()
783+
sparse.add(0, "display-message 'from sparse 0'")
784+
sparse.add(10, "display-message 'from sparse 10'")
785+
786+
session.set_hooks_bulk("session-renamed", sparse)
787+
788+
indices = session.get_hook_indices("session-renamed")
789+
assert indices == [0, 10]
790+
791+
# Cleanup
792+
session.clear_hook("session-renamed")
793+
794+
795+
def test_bulk_hook_method_chaining(server: Server) -> None:
796+
"""Test that bulk operations support method chaining."""
797+
session = server.new_session(session_name="test_bulk_ops")
798+
799+
# Chain operations
800+
result = (
801+
session.set_hooks_bulk(
802+
"session-renamed",
803+
["display-message 'hook 0'"],
804+
)
805+
.append_hook("session-renamed", "display-message 'hook 1'")
806+
.append_hook("session-renamed", "display-message 'hook 2'")
807+
)
808+
809+
# Should return the session
810+
assert result is session
811+
812+
# Verify all hooks set
813+
indices = session.get_hook_indices("session-renamed")
814+
assert indices == [0, 1, 2]
815+
816+
# Cleanup
817+
session.clear_hook("session-renamed")

0 commit comments

Comments
 (0)