def test_get_contextual_events_target_at_end():
    """
    Tests behavior when the target event_id is at the end of the stream,
    resulting in fewer than context_size + 1 events after it.
    """
    mock_event_stream = MagicMock(spec=EventStream)
    target_event_id = 5  # Target is near the end
    context_size = 4

    all_event_specs = [
        {'id': 0, 'type': MessageAction, 'content': 'message_0'},
        {'id': 1, 'type': CmdRunAction, 'command': 'command_1'},
        {
            'id': 2,
            'type': CmdOutputObservation,
            'content': 'obs_2',
            'command_id': 1,
        },  # Fetched by 'before'
        {'id': 3, 'type': MessageAction, 'content': 'message_3'},  # Fetched by 'before'
        {'id': 4, 'type': CmdRunAction, 'command': 'command_4'},  # Fetched by 'before'
        {
            'id': 5,
            'type': CmdOutputObservation,
            'content': 'obs_5_TARGET',
            'command_id': 4,
        },  # Target, Fetched by 'before'
        {
            'id': 6,
            'type': MessageAction,
            'content': 'message_6_last',
        },  # Last event, fetched by 'after'
    ]
    all_events_objects = create_test_events(all_event_specs)
    events_by_id = {e.id: e for e in all_events_objects}

    # Before: start_id=5, reverse=True, limit=4.
    # Candidates (reverse chronological from stream): [5,4,3,2,1,0]
    # search_events (after its internal filtering, assuming all visible) should return: [events_by_id[5], events_by_id[4], events_by_id[3], events_by_id[2]]
    simulated_search_before = [
        events_by_id[5],
        events_by_id[4],
        events_by_id[3],
        events_by_id[2],
    ]

    # After: start_id=6, limit=context_size + 1 = 5.
    # Candidates from stream: [events_by_id[6]]
    # search_events (after its internal filtering) should return: [events_by_id[6]]
    simulated_search_after = [events_by_id[6]]

    mock_event_stream.search_events.side_effect = [
        simulated_search_before,
        simulated_search_after,
    ]

    result_str = _get_contextual_events(mock_event_stream, target_event_id)

    # Expected final:
    # From 'before' (reversed): [event_obj_2, event_obj_3, event_obj_4, event_obj_5]
    # From 'after': [event_obj_6]
    expected_final_event_objects = [
        events_by_id[2],
        events_by_id[3],
        events_by_id[4],
        events_by_id[5],
        events_by_id[6],
    ]
    expected_output_str = '\n'.join(str(e) for e in expected_final_event_objects)

    assert result_str == expected_output_str

    calls = mock_event_stream.search_events.call_args_list
    assert len(calls) == 2
    kwargs_before = calls[0][1]
    assert kwargs_before['start_id'] == target_event_id
    assert kwargs_before['limit'] == context_size  # context_size for before
    kwargs_after = calls[1][1]
    assert kwargs_after['start_id'] == target_event_id + 1
    assert kwargs_after['limit'] == context_size + 1  # context_size + 1 for after

    Tests behavior when the target event_id is at the end of the stream,
    resulting in fewer than context_size + 1 events after it.
    
tests/unit/test_contextual_events.py