Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ classifiers = [
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = [
"seclab-taskflow-agent==0.3.1",
"seclab-taskflow-agent==0.4.0",
]

[project.urls]
Expand Down
12 changes: 6 additions & 6 deletions tests/test_container_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,15 @@ def test_shell_exec_lazy_start(self):
patch("subprocess.run", side_effect=[start_proc, exec_proc]),
):
assert cs_mod._container_name is None
result = cs_mod.shell_exec.fn(command="echo hello")
result = cs_mod.shell_exec(command="echo hello")
assert cs_mod._container_name is not None
assert "hello" in result

def test_shell_exec_runs_command(self):
cs_mod._container_name = "seclab-shell-testtest"
exec_proc = _make_proc(returncode=0, stdout="output\n")
with patch("subprocess.run", return_value=exec_proc) as mock_run:
result = cs_mod.shell_exec.fn(command="echo output", workdir="/workspace")
result = cs_mod.shell_exec(command="echo output", workdir="/workspace")
cmd = mock_run.call_args[0][0]
assert "docker" in cmd
assert "exec" in cmd
Expand All @@ -129,21 +129,21 @@ def test_shell_exec_includes_exit_code(self):
cs_mod._container_name = "seclab-shell-testtest"
exec_proc = _make_proc(returncode=0, stdout="done\n")
with patch("subprocess.run", return_value=exec_proc):
result = cs_mod.shell_exec.fn(command="true")
result = cs_mod.shell_exec(command="true")
assert "[exit code: 0]" in result

def test_shell_exec_nonzero_exit(self):
cs_mod._container_name = "seclab-shell-testtest"
exec_proc = _make_proc(returncode=1, stdout="", stderr="error\n")
with patch("subprocess.run", return_value=exec_proc):
result = cs_mod.shell_exec.fn(command="false")
result = cs_mod.shell_exec(command="false")
assert "[exit code: 1]" in result
assert "error" in result

def test_shell_exec_timeout(self):
cs_mod._container_name = "seclab-shell-testtest"
with patch("subprocess.run", side_effect=subprocess.TimeoutExpired(cmd="docker", timeout=5)):
result = cs_mod.shell_exec.fn(command="sleep 999", timeout=5)
result = cs_mod.shell_exec(command="sleep 999", timeout=5)
assert "timeout" in result

def test_shell_exec_start_failure_returns_error(self):
Expand All @@ -153,7 +153,7 @@ def test_shell_exec_start_failure_returns_error(self):
patch.object(cs_mod, "CONTAINER_WORKSPACE", ""),
patch("subprocess.run", return_value=_make_proc(returncode=1, stderr="image not found")),
):
result = cs_mod.shell_exec.fn(command="echo hi")
result = cs_mod.shell_exec(command="echo hi")
assert "Failed to start container" in result
assert cs_mod._container_name is None

Expand Down
48 changes: 24 additions & 24 deletions tests/test_gh_file_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,22 @@ class TestFetchFileFromGh:
async def test_fetch_file_success(self):
resp = _make_response(text=SAMPLE_FILE_CONTENT)
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.fetch_file_from_gh.fn(owner="Owner", repo="Repo", path="src/main.py")
result = await gfv_mod.fetch_file_from_gh(owner="Owner", repo="Repo", path="src/main.py")
assert "1: import os" in result
assert "5: print(\"Setec Astronomy\")" in result

@pytest.mark.asyncio
async def test_fetch_file_lowercases_owner_repo(self):
resp = _make_response(text="line1\nline2\n")
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp) as mock_api:
await gfv_mod.fetch_file_from_gh.fn(owner="OWNER", repo="REPO", path="file.py")
await gfv_mod.fetch_file_from_gh(owner="OWNER", repo="REPO", path="file.py")
url = mock_api.call_args[1]["url"]
assert "/owner/repo/" in url

@pytest.mark.asyncio
async def test_fetch_file_api_error(self):
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value="HTTP error: 404"):
result = await gfv_mod.fetch_file_from_gh.fn(owner="owner", repo="repo", path="missing.py")
result = await gfv_mod.fetch_file_from_gh(owner="owner", repo="repo", path="missing.py")
assert result == "HTTP error: 404"


Expand All @@ -95,7 +95,7 @@ class TestGetFileLinesFromGh:
async def test_get_lines_range(self):
resp = _make_response(text=SAMPLE_FILE_CONTENT)
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.get_file_lines_from_gh.fn(
result = await gfv_mod.get_file_lines_from_gh(
owner="owner", repo="repo", path="main.py", start_line=4, length=2
)
lines = result.strip().splitlines()
Expand All @@ -106,7 +106,7 @@ async def test_get_lines_range(self):
async def test_get_lines_clamps_start(self):
resp = _make_response(text=SAMPLE_FILE_CONTENT)
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.get_file_lines_from_gh.fn(
result = await gfv_mod.get_file_lines_from_gh(
owner="owner", repo="repo", path="main.py", start_line=-5, length=2
)
assert "1: import os" in result
Expand All @@ -115,15 +115,15 @@ async def test_get_lines_clamps_start(self):
async def test_get_lines_out_of_range(self):
resp = _make_response(text="one\ntwo\n")
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.get_file_lines_from_gh.fn(
result = await gfv_mod.get_file_lines_from_gh(
owner="owner", repo="repo", path="main.py", start_line=100, length=10
)
assert "No lines found" in result

@pytest.mark.asyncio
async def test_get_lines_api_error(self):
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value="Request error: timeout"):
result = await gfv_mod.get_file_lines_from_gh.fn(
result = await gfv_mod.get_file_lines_from_gh(
owner="owner", repo="repo", path="main.py", start_line=1, length=5
)
assert result == "Request error: timeout"
Expand All @@ -138,7 +138,7 @@ class TestSearchFileFromGh:
async def test_search_file_finds_matches(self):
resp = _make_response(text=SAMPLE_FILE_CONTENT)
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.search_file_from_gh.fn(
result = await gfv_mod.search_file_from_gh(
owner="owner", repo="repo", path="main.py", search_term="import"
)
assert "1: import os" in result
Expand All @@ -148,15 +148,15 @@ async def test_search_file_finds_matches(self):
async def test_search_file_no_matches(self):
resp = _make_response(text=SAMPLE_FILE_CONTENT)
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.search_file_from_gh.fn(
result = await gfv_mod.search_file_from_gh(
owner="owner", repo="repo", path="main.py", search_term="nonexistent_term"
)
assert "No matches found" in result

@pytest.mark.asyncio
async def test_search_file_api_error(self):
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value="HTTP error: 500"):
result = await gfv_mod.search_file_from_gh.fn(
result = await gfv_mod.search_file_from_gh(
owner="owner", repo="repo", path="main.py", search_term="import"
)
assert result == "HTTP error: 500"
Expand All @@ -171,7 +171,7 @@ class TestSearchFilesFromGh:
async def test_search_files_multiple_paths(self):
resp = _make_response(text=SAMPLE_FILE_CONTENT)
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.search_files_from_gh.fn(
result = await gfv_mod.search_files_from_gh(
owner="owner", repo="repo", paths="main.py, utils.py", search_term="import",
save_to_db=False,
)
Expand All @@ -183,7 +183,7 @@ async def test_search_files_multiple_paths(self):
async def test_search_files_no_paths(self):
resp = _make_response(text="")
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.search_files_from_gh.fn(
result = await gfv_mod.search_files_from_gh(
owner="owner", repo="repo", paths="", search_term="import", save_to_db=False,
)
# empty string split yields [""], which hits the API for an empty path
Expand All @@ -193,7 +193,7 @@ async def test_search_files_no_paths(self):
async def test_search_files_no_matches(self):
resp = _make_response(text="nothing here\n")
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.search_files_from_gh.fn(
result = await gfv_mod.search_files_from_gh(
owner="owner", repo="repo", paths="main.py", search_term="zzzzz"
)
assert "No matches found" in result
Expand All @@ -208,7 +208,7 @@ async def test_search_files_save_to_db(self):
mock_session = MagicMock()
mock_session_cls.return_value.__enter__ = MagicMock(return_value=mock_session)
mock_session_cls.return_value.__exit__ = MagicMock(return_value=False)
result = await gfv_mod.search_files_from_gh.fn(
result = await gfv_mod.search_files_from_gh(
owner="owner", repo="repo", paths="main.py", search_term="import", save_to_db=True
)
assert "saved to database" in result
Expand All @@ -218,7 +218,7 @@ async def test_search_files_save_to_db(self):
@pytest.mark.asyncio
async def test_search_files_api_error(self):
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value="Request error: timeout"):
result = await gfv_mod.search_files_from_gh.fn(
result = await gfv_mod.search_files_from_gh(
owner="owner", repo="repo", paths="main.py", search_term="import"
)
assert result == "Request error: timeout"
Expand All @@ -244,7 +244,7 @@ def test_fetch_last_results(self):
mock_session.query.return_value.all.return_value = [mock_result]
mock_session.query.return_value.delete.return_value = None

result = gfv_mod.fetch_last_search_results.fn()
result = gfv_mod.fetch_last_search_results()
data = json.loads(result)
assert len(data) == 1
assert data[0]["path"] == "src/main.py"
Expand All @@ -258,7 +258,7 @@ def test_fetch_last_results_empty(self):
mock_session.query.return_value.all.return_value = []
mock_session.query.return_value.delete.return_value = None

result = gfv_mod.fetch_last_search_results.fn()
result = gfv_mod.fetch_last_search_results()
assert json.loads(result) == []


Expand All @@ -271,7 +271,7 @@ class TestListDirectoryFromGh:
async def test_list_directory_success(self):
resp = _make_response(json_data=SAMPLE_DIR_JSON)
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.list_directory_from_gh.fn(owner="Owner", repo="Repo", path="src")
result = await gfv_mod.list_directory_from_gh(owner="Owner", repo="Repo", path="src")
data = json.loads(result)
assert "src/main.py" in data
assert "src/utils.py" in data
Expand All @@ -281,13 +281,13 @@ async def test_list_directory_success(self):
async def test_list_directory_empty(self):
resp = _make_response(json_data=[])
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.list_directory_from_gh.fn(owner="owner", repo="repo", path="empty")
result = await gfv_mod.list_directory_from_gh(owner="owner", repo="repo", path="empty")
assert json.loads(result) == []

@pytest.mark.asyncio
async def test_list_directory_api_error(self):
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value="HTTP error: 404"):
result = await gfv_mod.list_directory_from_gh.fn(owner="owner", repo="repo", path="missing")
result = await gfv_mod.list_directory_from_gh(owner="owner", repo="repo", path="missing")
assert result == "HTTP error: 404"

@pytest.mark.asyncio
Expand All @@ -296,7 +296,7 @@ async def test_list_directory_path_is_file(self):
file_obj = {"path": "src/main.py", "type": "file", "size": 123, "sha": "abc"}
resp = _make_response(json_data=file_obj)
with patch.object(gfv_mod, "call_api", new_callable=AsyncMock, return_value=resp):
result = await gfv_mod.list_directory_from_gh.fn(owner="owner", repo="repo", path="src/main.py")
result = await gfv_mod.list_directory_from_gh(owner="owner", repo="repo", path="src/main.py")
assert "not a directory" in result


Expand All @@ -318,7 +318,7 @@ async def fake_fetch_source_zip(owner, repo, tmp_dir):
return "source code fetched"

with patch.object(gfv_mod, "_fetch_source_zip", side_effect=fake_fetch_source_zip):
result = await gfv_mod.search_repo_from_gh.fn(
result = await gfv_mod.search_repo_from_gh(
owner="Owner", repo="Repo", search_term="import"
)
data = json.loads(result)
Expand All @@ -337,7 +337,7 @@ async def fake_fetch_source_zip(owner, repo, tmp_dir):
return "source code fetched"

with patch.object(gfv_mod, "_fetch_source_zip", side_effect=fake_fetch_source_zip):
result = await gfv_mod.search_repo_from_gh.fn(
result = await gfv_mod.search_repo_from_gh(
owner="owner", repo="repo", search_term="nonexistent"
)
assert json.loads(result) == []
Expand All @@ -348,7 +348,7 @@ async def fake_fetch_source_zip(owner, repo, tmp_dir):
return "Error: HTTP error: 404"

with patch.object(gfv_mod, "_fetch_source_zip", side_effect=fake_fetch_source_zip):
result = await gfv_mod.search_repo_from_gh.fn(
result = await gfv_mod.search_repo_from_gh(
owner="owner", repo="repo", search_term="import"
)
data = json.loads(result)
Expand Down
Loading