Files
wehub-resource-sync e4dcfc49aa
Tests / Lint and Format (push) Waiting to run
Tests / Web Node Tests (push) Waiting to run
Tests / Import Check (Python 3.11) (push) Waiting to run
Tests / Import Check (Python 3.12) (push) Waiting to run
Tests / Import Check (Python 3.13) (push) Waiting to run
Tests / Import Check (Python 3.14) (push) Waiting to run
Tests / Python Tests (Python 3.11) (push) Blocked by required conditions
Tests / Python Tests (Python 3.12) (push) Blocked by required conditions
Tests / Python Tests (Python 3.13) (push) Blocked by required conditions
Tests / Python Tests (Python 3.14) (push) Blocked by required conditions
Tests / Test Summary (push) Blocked by required conditions
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

188 lines
5.2 KiB
Python

"""Unit tests for the ``github_query`` tool's pure logic.
We never spawn ``gh`` for real — every test injects its own
``command_runner`` so we can pin returncode / stdout / stderr.
"""
from __future__ import annotations
import asyncio
from typing import Any
import pytest
from deeptutor.tools.github_query import (
MAX_OUTPUT_CHARS,
GithubOutcome,
run_github_query,
)
def _runner(
*,
returncode: int = 0,
stdout: str = "",
stderr: str = "",
record: list[list[str]] | None = None,
):
async def _run(argv, timeout_s):
if record is not None:
record.append(list(argv))
return returncode, stdout, stderr
return _run
# ---------------------------------------------------------------------------
# Argument validation
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_rejects_missing_query_type() -> None:
outcome = await run_github_query(
query_type="",
target="owner/repo",
gh_available=lambda: True,
command_runner=_runner(),
)
assert outcome.ok is False
assert "query_type" in outcome.error
@pytest.mark.asyncio
async def test_rejects_missing_target() -> None:
outcome = await run_github_query(
query_type="pr",
target="",
gh_available=lambda: True,
command_runner=_runner(),
)
assert outcome.ok is False
assert "target" in outcome.error
@pytest.mark.asyncio
async def test_rejects_unsupported_query_type() -> None:
outcome = await run_github_query(
query_type="merge", # explicitly write-flavoured, not in the whitelist
target="owner/repo#1",
gh_available=lambda: True,
command_runner=_runner(),
)
assert outcome.ok is False
assert "Unsupported query_type" in outcome.error
# ---------------------------------------------------------------------------
# gh CLI availability
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_reports_gh_missing_gracefully() -> None:
outcome = await run_github_query(
query_type="pr",
target="owner/repo#1",
gh_available=lambda: False,
command_runner=_runner(), # should never be invoked
)
assert outcome.ok is False
assert "`gh`" in outcome.error or "gh" in outcome.error.lower()
# ---------------------------------------------------------------------------
# argv shape — confirms each query_type stays read-only
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_pr_argv_is_view_only() -> None:
record: list[list[str]] = []
await run_github_query(
query_type="pr",
target="owner/repo#42",
gh_available=lambda: True,
command_runner=_runner(stdout='{"title":"x"}', record=record),
)
assert record == [
[
"gh",
"pr",
"view",
"owner/repo#42",
"--json",
"title,state,statusCheckRollup,reviews,url,author,createdAt,body",
]
]
@pytest.mark.asyncio
async def test_issue_argv_is_view_only() -> None:
record: list[list[str]] = []
await run_github_query(
query_type="issue",
target="owner/repo#7",
gh_available=lambda: True,
command_runner=_runner(stdout='{"title":"x"}', record=record),
)
assert record[0][:4] == ["gh", "issue", "view", "owner/repo#7"]
@pytest.mark.asyncio
async def test_run_argv_uses_list_subcommand() -> None:
record: list[list[str]] = []
await run_github_query(
query_type="run",
target="owner/repo",
gh_available=lambda: True,
command_runner=_runner(stdout="[]", record=record),
)
assert record[0][:5] == ["gh", "run", "list", "--repo", "owner/repo"]
@pytest.mark.asyncio
async def test_api_argv_does_not_set_method() -> None:
"""`gh api` defaults to GET; we never pass --method so the tool
can't be coerced into a mutating call by creative ``target``s."""
record: list[list[str]] = []
await run_github_query(
query_type="api",
target="repos/owner/repo/issues",
gh_available=lambda: True,
command_runner=_runner(stdout="[]", record=record),
)
assert "--method" not in record[0]
assert "-X" not in record[0]
assert record[0][0:2] == ["gh", "api"]
# ---------------------------------------------------------------------------
# Output handling
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_truncates_oversized_output() -> None:
huge = "y" * (MAX_OUTPUT_CHARS + 500)
outcome = await run_github_query(
query_type="pr",
target="o/r#1",
gh_available=lambda: True,
command_runner=_runner(stdout=huge),
)
assert outcome.ok is True
assert outcome.output.endswith("[truncated]")
@pytest.mark.asyncio
async def test_surfaces_nonzero_exit_as_failure() -> None:
outcome = await run_github_query(
query_type="pr",
target="o/r#1",
gh_available=lambda: True,
command_runner=_runner(returncode=1, stderr="not found"),
)
assert outcome.ok is False
assert "not found" in outcome.error