Spaces:
Build error
Build error
"""Editor-related tests for the DockerRuntime.""" | |
import os | |
from unittest.mock import MagicMock | |
from conftest import _close_test_runtime, _load_runtime | |
from openhands.core.logger import openhands_logger as logger | |
from openhands.events.action import FileEditAction, FileWriteAction | |
from openhands.runtime.action_execution_server import _execute_file_editor | |
from openhands.runtime.impl.cli.cli_runtime import CLIRuntime | |
def test_view_file(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
# Create test file | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='This is a test file.\nThis file is for testing purposes.', | |
path=test_file, | |
) | |
obs = runtime.run_action(action) | |
# Test view command | |
action = FileEditAction( | |
command='view', | |
path=test_file, | |
) | |
obs = runtime.run_action(action) | |
assert f"Here's the result of running `cat -n` on {test_file}:" in obs.content | |
assert '1\tThis is a test file.' in obs.content | |
assert '2\tThis file is for testing purposes.' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_view_directory(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
# Create test file | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='This is a test file.\nThis file is for testing purposes.', | |
path=test_file, | |
) | |
obs = runtime.run_action(action) | |
# Test view command | |
action = FileEditAction( | |
command='view', | |
path=config.workspace_mount_path_in_sandbox, | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert ( | |
obs.content | |
== f"""Here's the files and directories up to 2 levels deep in {config.workspace_mount_path_in_sandbox}, excluding hidden items: | |
{config.workspace_mount_path_in_sandbox}/ | |
{config.workspace_mount_path_in_sandbox}/test.txt""" | |
) | |
finally: | |
_close_test_runtime(runtime) | |
def test_create_file(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
new_file = os.path.join(config.workspace_mount_path_in_sandbox, 'new_file.txt') | |
action = FileEditAction( | |
command='create', | |
path=new_file, | |
file_text='New file content', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'File created successfully' in obs.content | |
# Verify file content | |
action = FileEditAction( | |
command='view', | |
path=new_file, | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'New file content' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_create_file_with_empty_content(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
new_file = os.path.join(config.workspace_mount_path_in_sandbox, 'new_file.txt') | |
action = FileEditAction( | |
command='create', | |
path=new_file, | |
file_text='', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'File created successfully' in obs.content | |
# Verify file content | |
action = FileEditAction( | |
command='view', | |
path=new_file, | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert '1\t' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_create_with_none_file_text(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
new_file = os.path.join( | |
config.workspace_mount_path_in_sandbox, 'none_content.txt' | |
) | |
action = FileEditAction( | |
command='create', | |
path=new_file, | |
file_text=None, | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert ( | |
obs.content | |
== 'ERROR:\nParameter `file_text` is required for command: create.' | |
) | |
finally: | |
_close_test_runtime(runtime) | |
def test_str_replace(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
# Create test file | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='This is a test file.\nThis file is for testing purposes.', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
# Test str_replace command | |
action = FileEditAction( | |
command='str_replace', | |
path=test_file, | |
old_str='test file', | |
new_str='sample file', | |
) | |
obs = runtime.run_action(action) | |
assert f'The file {test_file} has been edited' in obs.content | |
# Verify file content | |
action = FileEditAction( | |
command='view', | |
path=test_file, | |
) | |
obs = runtime.run_action(action) | |
assert 'This is a sample file.' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_str_replace_multi_line(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='This is a test file.\nThis file is for testing purposes.', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
# Test str_replace command | |
action = FileEditAction( | |
command='str_replace', | |
path=test_file, | |
old_str='This is a test file.\nThis file is for testing purposes.', | |
new_str='This is a sample file.\nThis file is for testing purposes.', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert f'The file {test_file} has been edited.' in obs.content | |
assert 'This is a sample file.' in obs.content | |
assert 'This file is for testing purposes.' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_str_replace_multi_line_with_tabs(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileEditAction( | |
command='create', | |
path=test_file, | |
file_text='def test():\n\tprint("Hello, World!")', | |
) | |
runtime.run_action(action) | |
# Test str_replace command | |
action = FileEditAction( | |
command='str_replace', | |
path=test_file, | |
old_str='def test():\n\tprint("Hello, World!")', | |
new_str='def test():\n\tprint("Hello, Universe!")', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert ( | |
obs.content | |
== f"""The file {test_file} has been edited. Here's the result of running `cat -n` on a snippet of {test_file}: | |
1\tdef test(): | |
2\t\tprint("Hello, Universe!") | |
Review the changes and make sure they are as expected. Edit the file again if necessary.""" | |
) | |
finally: | |
_close_test_runtime(runtime) | |
def test_str_replace_error_multiple_occurrences( | |
temp_dir, runtime_cls, run_as_openhands | |
): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='This is a test file.\nThis file is for testing purposes.', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='str_replace', path=test_file, old_str='test', new_str='sample' | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'Multiple occurrences of old_str `test`' in obs.content | |
assert '[1, 2]' in obs.content # Should show both line numbers | |
finally: | |
_close_test_runtime(runtime) | |
def test_str_replace_error_multiple_multiline_occurrences( | |
temp_dir, runtime_cls, run_as_openhands | |
): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
# Create a file with two identical multi-line blocks | |
multi_block = """def example(): | |
print("Hello") | |
return True""" | |
content = f"{multi_block}\n\nprint('separator')\n\n{multi_block}" | |
action = FileWriteAction( | |
content=content, | |
path=test_file, | |
) | |
runtime.run_action(action) | |
# Test str_replace command | |
action = FileEditAction( | |
command='str_replace', | |
path=test_file, | |
old_str=multi_block, | |
new_str='def new():\n print("World")', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'Multiple occurrences of old_str' in obs.content | |
assert '[1, 7]' in obs.content # Should show correct starting line numbers | |
finally: | |
_close_test_runtime(runtime) | |
def test_str_replace_nonexistent_string(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine 2', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='str_replace', | |
path=test_file, | |
old_str='Non-existent Line', | |
new_str='New Line', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'No replacement was performed' in obs.content | |
assert ( | |
f'old_str `Non-existent Line` did not appear verbatim in {test_file}' | |
in obs.content | |
) | |
finally: | |
_close_test_runtime(runtime) | |
def test_str_replace_with_empty_new_str(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine to remove\nLine 3', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='str_replace', | |
path=test_file, | |
old_str='Line to remove\n', | |
new_str='', | |
) | |
obs = runtime.run_action(action) | |
assert 'Line to remove' not in obs.content | |
assert 'Line 1' in obs.content | |
assert 'Line 3' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_str_replace_with_empty_old_str(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine 2\nLine 3', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='str_replace', | |
path=test_file, | |
old_str='', | |
new_str='New string', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
if isinstance(runtime, CLIRuntime): | |
# CLIRuntime with a 3-line file without a trailing newline reports 3 occurrences for an empty old_str | |
assert ( | |
'No replacement was performed. Multiple occurrences of old_str `` in lines [1, 2, 3]. Please ensure it is unique.' | |
in obs.content | |
) | |
else: | |
# Other runtimes might behave differently (e.g., implicitly add a newline, leading to 4 matches) | |
# TODO: Why do they have 4 lines? | |
assert ( | |
'No replacement was performed. Multiple occurrences of old_str `` in lines [1, 2, 3, 4]. Please ensure it is unique.' | |
in obs.content | |
) | |
finally: | |
_close_test_runtime(runtime) | |
def test_str_replace_with_none_old_str(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine 2\nLine 3', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='str_replace', | |
path=test_file, | |
old_str=None, | |
new_str='new content', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'old_str' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_insert(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
# Create test file | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine 2', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
# Test insert command | |
action = FileEditAction( | |
command='insert', | |
path=test_file, | |
insert_line=1, | |
new_str='Inserted line', | |
) | |
obs = runtime.run_action(action) | |
assert f'The file {test_file} has been edited' in obs.content | |
# Verify file content | |
action = FileEditAction( | |
command='view', | |
path=test_file, | |
) | |
obs = runtime.run_action(action) | |
assert 'Line 1' in obs.content | |
assert 'Inserted line' in obs.content | |
assert 'Line 2' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_insert_invalid_line(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine 2', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='insert', | |
path=test_file, | |
insert_line=10, | |
new_str='Invalid Insert', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'Invalid `insert_line` parameter' in obs.content | |
assert 'It should be within the range of allowed values' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_insert_with_empty_string(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine 2', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='insert', | |
path=test_file, | |
insert_line=1, | |
new_str='', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert '1\tLine 1' in obs.content | |
assert '2\t\n' in obs.content | |
assert '3\tLine 2' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_insert_with_none_new_str(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine 2', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='insert', | |
path=test_file, | |
insert_line=1, | |
new_str=None, | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'ERROR' in obs.content | |
assert 'Parameter `new_str` is required for command: insert' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_undo_edit(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
# Create test file | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='This is a test file.', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
# Make an edit | |
action = FileEditAction( | |
command='str_replace', | |
path=test_file, | |
old_str='test', | |
new_str='sample', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'This is a sample file.' in obs.content | |
# Undo the edit | |
action = FileEditAction( | |
command='undo_edit', | |
path=test_file, | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'Last edit to' in obs.content | |
assert 'This is a test file.' in obs.content | |
# Verify file content | |
action = FileEditAction( | |
command='view', | |
path=test_file, | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'This is a test file.' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_validate_path_invalid(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
invalid_file = os.path.join( | |
config.workspace_mount_path_in_sandbox, 'nonexistent.txt' | |
) | |
action = FileEditAction( | |
command='view', | |
path=invalid_file, | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'Invalid `path` parameter' in obs.content | |
assert f'The path {invalid_file} does not exist' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_create_existing_file_error(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine 2', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='create', | |
path=test_file, | |
file_text='New content', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'File already exists' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_str_replace_missing_old_str(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine 2', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='str_replace', | |
path=test_file, | |
old_str='', | |
new_str='sample', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert ( | |
'No replacement was performed. Multiple occurrences of old_str ``' | |
in obs.content | |
) | |
finally: | |
_close_test_runtime(runtime) | |
def test_str_replace_new_str_and_old_str_same(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine 2', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='str_replace', | |
path=test_file, | |
old_str='test file', | |
new_str='test file', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert ( | |
'No replacement was performed. `new_str` and `old_str` must be different.' | |
in obs.content | |
) | |
finally: | |
_close_test_runtime(runtime) | |
def test_insert_missing_line_param(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
test_file = os.path.join(config.workspace_mount_path_in_sandbox, 'test.txt') | |
action = FileWriteAction( | |
content='Line 1\nLine 2', | |
path=test_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='insert', | |
path=test_file, | |
new_str='Missing insert line', | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'Parameter `insert_line` is required for command: insert' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_undo_edit_no_history_error(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
empty_file = os.path.join(config.workspace_mount_path_in_sandbox, 'empty.txt') | |
action = FileWriteAction( | |
content='', | |
path=empty_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='undo_edit', | |
path=empty_file, | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert 'No edit history found for' in obs.content | |
finally: | |
_close_test_runtime(runtime) | |
def test_view_large_file_with_truncation(temp_dir, runtime_cls, run_as_openhands): | |
runtime, config = _load_runtime(temp_dir, runtime_cls, run_as_openhands) | |
try: | |
# Create a large file to trigger truncation | |
large_file = os.path.join( | |
config.workspace_mount_path_in_sandbox, 'large_test.txt' | |
) | |
large_content = 'Line 1\n' * 16000 # 16000 lines should trigger truncation | |
action = FileWriteAction( | |
content=large_content, | |
path=large_file, | |
) | |
runtime.run_action(action) | |
action = FileEditAction( | |
command='view', | |
path=large_file, | |
) | |
obs = runtime.run_action(action) | |
logger.info(obs, extra={'msg_type': 'OBSERVATION'}) | |
assert ( | |
'Due to the max output limit, only part of this file has been shown to you.' | |
in obs.content | |
) | |
finally: | |
_close_test_runtime(runtime) | |
def test_insert_line_string_conversion(): | |
"""Test that insert_line is properly converted from string to int. | |
This test reproduces issue #8369 Example 2 where a string value for insert_line | |
causes a TypeError in the editor. | |
""" | |
# Mock the OHEditor | |
mock_editor = MagicMock() | |
mock_editor.return_value = MagicMock( | |
error=None, output='Success', old_content=None, new_content=None | |
) | |
# Test with string insert_line | |
result, _ = _execute_file_editor( | |
editor=mock_editor, | |
command='insert', | |
path='/test/path.py', | |
insert_line='185', # String instead of int | |
new_str='test content', | |
) | |
# Verify the editor was called with the correct parameters (insert_line converted to int) | |
mock_editor.assert_called_once() | |
args, kwargs = mock_editor.call_args | |
assert isinstance(kwargs['insert_line'], int) | |
assert kwargs['insert_line'] == 185 | |
assert result == 'Success' | |