Skip to content

Commit ad3a1a6

Browse files
fix httpx tests etc
1 parent 18f4dcd commit ad3a1a6

File tree

4 files changed

+35
-59
lines changed

4 files changed

+35
-59
lines changed

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ requires-python = ">= 3.8"
77
dependencies = [
88
"click>=8.0.0",
99
"fastapi[standard]>=0.109.1", # Minimum safe release (https://osv.dev/vulnerability/PYSEC-2024-38)
10-
"httpx>= 0.28.1",
10+
"httpx",
11+
"pathspec>=0.12.1",
1112
"pydantic",
1213
"python-dotenv",
1314
"slowapi",
1415
"starlette>=0.40.0", # Minimum safe release (https://osv.dev/vulnerability/GHSA-f96h-pmfr-66vw)
1516
"tiktoken>=0.7.0", # Support for o200k_base encoding
16-
"pathspec>=0.12.1",
1717
"typing_extensions>= 4.0.0; python_version < '3.10'",
18-
"uvicorn>=0.11.7", # Minimum safe release (https://osv.dev/vulnerability/PYSEC-2020-150)
18+
"uvicorn>=0.11.7", # Min£imum safe release (https://osv.dev/vulnerability/PYSEC-2020-150)
1919
]
2020

2121
license = {file = "LICENSE"}

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
click>=8.0.0
22
fastapi[standard]>=0.109.1 # Vulnerable to https://osv.dev/vulnerability/PYSEC-2024-38
3-
httpx>=0.28.1
3+
httpx
44
pathspec>=0.12.1
55
pydantic
66
python-dotenv

src/gitingest/utils/git_utils.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from starlette.status import (
1313
HTTP_200_OK,
1414
HTTP_301_MOVED_PERMANENTLY,
15-
HTTP_302_FOUND,
1615
HTTP_401_UNAUTHORIZED,
1716
HTTP_403_FORBIDDEN,
1817
HTTP_404_NOT_FOUND,
@@ -127,20 +126,18 @@ async def check_repo_exists(url: str, token: str | None = None) -> bool:
127126
async with httpx.AsyncClient(follow_redirects=True) as client:
128127
try:
129128
response = await client.head(url, headers=headers)
130-
status = response.status_code
131-
132-
if status in {HTTP_200_OK, HTTP_301_MOVED_PERMANENTLY}:
133-
return True
134-
# TODO: handle 302 redirects
135-
if status in {HTTP_404_NOT_FOUND, HTTP_302_FOUND}:
136-
return False
137-
if status in {HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN}:
138-
return False
139-
msg = f"Unexpected HTTP status {status} for {url}"
140-
raise RuntimeError(msg)
141129
except httpx.RequestError:
142130
return False
143131

132+
status_code = response.status_code
133+
134+
if status_code in {HTTP_200_OK, HTTP_301_MOVED_PERMANENTLY}:
135+
return True
136+
if status_code in {HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND}:
137+
return False
138+
msg = f"Unexpected HTTP status {status_code} for {url}"
139+
raise RuntimeError(msg)
140+
144141

145142
def _parse_github_url(url: str) -> tuple[str, str, str]:
146143
"""Parse a GitHub URL and return (hostname, owner, repo).

tests/test_clone.py

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,16 @@
99
from pathlib import Path
1010
from unittest.mock import AsyncMock
1111

12+
import httpx
1213
import pytest
1314
from pytest_mock import MockerFixture
15+
from starlette.status import (
16+
HTTP_200_OK,
17+
HTTP_301_MOVED_PERMANENTLY,
18+
HTTP_401_UNAUTHORIZED,
19+
HTTP_403_FORBIDDEN,
20+
HTTP_404_NOT_FOUND,
21+
)
1422

1523
from gitingest.clone import clone_repo
1624
from gitingest.schemas import CloneConfig
@@ -87,35 +95,25 @@ async def test_clone_nonexistent_repository(repo_exists_true: AsyncMock) -> None
8795

8896
@pytest.mark.asyncio
8997
@pytest.mark.parametrize(
90-
("mock_stdout", "return_code", "expected"),
98+
("status_code", "expected"),
9199
[
92-
(b"200\n", 0, True), # Existing repo
93-
(b"404\n", 0, False), # Non-existing repo
94-
(b"200\n", 1, False), # Failed request
100+
(HTTP_200_OK, True),
101+
(HTTP_301_MOVED_PERMANENTLY, True),
102+
(HTTP_401_UNAUTHORIZED, False),
103+
(HTTP_403_FORBIDDEN, False),
104+
(HTTP_404_NOT_FOUND, False),
95105
],
96106
)
97-
async def test_check_repo_exists(
98-
mock_stdout: bytes,
99-
*,
100-
return_code: int,
101-
expected: bool,
102-
mocker: MockerFixture,
103-
) -> None:
104-
"""Test the ``check_repo_exists`` function with different Git HTTP responses.
105-
106-
Given various stdout lines and return codes:
107-
When ``check_repo_exists`` is called,
108-
Then it should correctly indicate whether the repository exists.
109-
"""
110-
mock_exec = mocker.patch("asyncio.create_subprocess_exec", new_callable=AsyncMock)
111-
mock_process = AsyncMock()
112-
mock_process.communicate.return_value = (mock_stdout, b"")
113-
mock_process.returncode = return_code
114-
mock_exec.return_value = mock_process
107+
async def test_check_repo_exists(status_code: int, *, expected: bool, mocker: MockerFixture) -> None:
108+
"""Verify that ``check_repo_exists`` interprets httpx results correctly."""
109+
mock_client = AsyncMock()
110+
mock_client.__aenter__.return_value = mock_client # context-manager protocol
111+
mock_client.head.return_value = httpx.Response(status_code=status_code)
112+
mocker.patch("httpx.AsyncClient", return_value=mock_client)
115113

116-
repo_exists = await check_repo_exists(DEMO_URL)
114+
result = await check_repo_exists(DEMO_URL)
117115

118-
assert repo_exists is expected
116+
assert result is expected
119117

120118

121119
@pytest.mark.asyncio
@@ -218,25 +216,6 @@ async def test_check_repo_exists_with_redirect(mocker: MockerFixture) -> None:
218216
assert repo_exists is False
219217

220218

221-
@pytest.mark.asyncio
222-
async def test_check_repo_exists_with_permanent_redirect(mocker: MockerFixture) -> None:
223-
"""Test ``check_repo_exists`` when a permanent redirect (301) is returned.
224-
225-
Given a URL that responds with "301 Found":
226-
When ``check_repo_exists`` is called,
227-
Then it should return ``True``, indicating the repo may exist at the new location.
228-
"""
229-
mock_exec = mocker.patch("asyncio.create_subprocess_exec", new_callable=AsyncMock)
230-
mock_process = AsyncMock()
231-
mock_process.communicate.return_value = (b"301\n", b"")
232-
mock_process.returncode = 0 # Simulate successful request
233-
mock_exec.return_value = mock_process
234-
235-
repo_exists = await check_repo_exists(DEMO_URL)
236-
237-
assert repo_exists
238-
239-
240219
@pytest.mark.asyncio
241220
async def test_clone_with_timeout(run_command_mock: AsyncMock) -> None:
242221
"""Test cloning a repository when a timeout occurs.

0 commit comments

Comments
 (0)