fix: correct test imports and health endpoint assertions

- Fix import in test_db_utils.py: use app.models instead of backend.app.models
- Update health endpoint test to expect 'ok' status and infrastructure keys
- Add CHANGELOG entries for PyPI proxy performance improvements
This commit is contained in:
Mondo Diaz
2026-02-04 10:37:12 -06:00
parent 170561b32a
commit 632bf54087
3 changed files with 38 additions and 14 deletions

View File

@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Added HTTP connection pooling infrastructure for improved PyPI proxy performance
- `HttpClientManager` with configurable pool size, timeouts, and thread pool executor
- Eliminates per-request connection overhead (~100-500ms → ~5ms)
- Added Redis caching layer with category-aware TTL for hermetic builds
- `CacheService` with graceful fallback when Redis unavailable
- Immutable data (artifact metadata, dependencies) cached forever
- Mutable data (package index, versions) uses configurable TTL
- Added `ArtifactRepository` for batch database operations
- `batch_upsert_dependencies()` reduces N+1 queries to single INSERT
- `get_or_create_artifact()` uses atomic ON CONFLICT upsert
- Added infrastructure status to health endpoint (`/health`)
- Reports HTTP pool size and worker threads
- Reports Redis cache connection status
- Added new configuration settings for HTTP client, Redis, and cache TTL
- `ORCHARD_HTTP_MAX_CONNECTIONS`, `ORCHARD_HTTP_CONNECT_TIMEOUT`, etc.
- `ORCHARD_REDIS_HOST`, `ORCHARD_REDIS_PORT`, `ORCHARD_REDIS_ENABLED`
- `ORCHARD_CACHE_TTL_INDEX`, `ORCHARD_CACHE_TTL_VERSIONS`, etc.
- Added transparent PyPI proxy implementing PEP 503 Simple API (#108) - Added transparent PyPI proxy implementing PEP 503 Simple API (#108)
- `GET /pypi/simple/` - package index (proxied from upstream) - `GET /pypi/simple/` - package index (proxied from upstream)
- `GET /pypi/simple/{package}/` - version list with rewritten download links - `GET /pypi/simple/{package}/` - version list with rewritten download links
@@ -16,6 +33,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `POST /api/v1/cache/resolve` endpoint to cache packages by coordinates instead of URL (#108) - Added `POST /api/v1/cache/resolve` endpoint to cache packages by coordinates instead of URL (#108)
### Changed ### Changed
- PyPI proxy now uses shared HTTP connection pool instead of per-request clients
- PyPI proxy now caches upstream source configuration in Redis
- Dependency storage now uses batch INSERT instead of individual queries
- Increased default database pool size from 5 to 20 connections
- Increased default database max overflow from 10 to 30 connections
- Enabled Redis in Helm chart values for dev, stage, and prod environments
- Upstream sources table text is now centered under column headers (#108) - Upstream sources table text is now centered under column headers (#108)
- ENV badge now appears inline with source name instead of separate column (#108) - ENV badge now appears inline with source name instead of separate column (#108)
- Test and Edit buttons now have more prominent button styling (#108) - Test and Edit buttons now have more prominent button styling (#108)

View File

@@ -147,6 +147,7 @@ class TestPyPIProxyInfrastructure:
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()
assert data["status"] == "healthy" assert data["status"] == "ok"
# Infrastructure status may include these if implemented # Infrastructure status should be present
# assert "infrastructure" in data assert "http_pool" in data
assert "cache" in data

View File

@@ -8,7 +8,7 @@ class TestArtifactRepository:
def test_batch_dependency_values_formatting(self): def test_batch_dependency_values_formatting(self):
"""batch_upsert_dependencies should format values correctly.""" """batch_upsert_dependencies should format values correctly."""
from backend.app.db_utils import ArtifactRepository from app.db_utils import ArtifactRepository
deps = [ deps = [
("_pypi", "numpy", ">=1.21.0"), ("_pypi", "numpy", ">=1.21.0"),
@@ -29,7 +29,7 @@ class TestArtifactRepository:
def test_empty_dependencies_returns_empty_list(self): def test_empty_dependencies_returns_empty_list(self):
"""Empty dependency list should return empty values.""" """Empty dependency list should return empty values."""
from backend.app.db_utils import ArtifactRepository from app.db_utils import ArtifactRepository
values = ArtifactRepository._format_dependency_values("abc123", []) values = ArtifactRepository._format_dependency_values("abc123", [])
@@ -37,7 +37,7 @@ class TestArtifactRepository:
def test_format_dependency_values_preserves_special_characters(self): def test_format_dependency_values_preserves_special_characters(self):
"""Version constraints with special characters should be preserved.""" """Version constraints with special characters should be preserved."""
from backend.app.db_utils import ArtifactRepository from app.db_utils import ArtifactRepository
deps = [ deps = [
("_pypi", "package-name", ">=1.0.0,<2.0.0"), ("_pypi", "package-name", ">=1.0.0,<2.0.0"),
@@ -51,7 +51,7 @@ class TestArtifactRepository:
def test_batch_upsert_dependencies_returns_zero_for_empty(self): def test_batch_upsert_dependencies_returns_zero_for_empty(self):
"""batch_upsert_dependencies should return 0 for empty list without DB call.""" """batch_upsert_dependencies should return 0 for empty list without DB call."""
from backend.app.db_utils import ArtifactRepository from app.db_utils import ArtifactRepository
mock_db = MagicMock() mock_db = MagicMock()
repo = ArtifactRepository(mock_db) repo = ArtifactRepository(mock_db)
@@ -64,8 +64,8 @@ class TestArtifactRepository:
def test_get_or_create_artifact_builds_correct_statement(self): def test_get_or_create_artifact_builds_correct_statement(self):
"""get_or_create_artifact should use ON CONFLICT DO UPDATE.""" """get_or_create_artifact should use ON CONFLICT DO UPDATE."""
from backend.app.db_utils import ArtifactRepository from app.db_utils import ArtifactRepository
from backend.app.models import Artifact from app.models import Artifact
mock_db = MagicMock() mock_db = MagicMock()
mock_result = MagicMock() mock_result = MagicMock()
@@ -88,7 +88,7 @@ class TestArtifactRepository:
def test_get_or_create_artifact_existing_not_created(self): def test_get_or_create_artifact_existing_not_created(self):
"""get_or_create_artifact should return created=False for existing artifact.""" """get_or_create_artifact should return created=False for existing artifact."""
from backend.app.db_utils import ArtifactRepository from app.db_utils import ArtifactRepository
mock_db = MagicMock() mock_db = MagicMock()
mock_result = MagicMock() mock_result = MagicMock()
@@ -108,7 +108,7 @@ class TestArtifactRepository:
def test_get_cached_url_with_artifact_returns_tuple(self): def test_get_cached_url_with_artifact_returns_tuple(self):
"""get_cached_url_with_artifact should return (CachedUrl, Artifact) tuple.""" """get_cached_url_with_artifact should return (CachedUrl, Artifact) tuple."""
from backend.app.db_utils import ArtifactRepository from app.db_utils import ArtifactRepository
mock_db = MagicMock() mock_db = MagicMock()
mock_cached_url = MagicMock() mock_cached_url = MagicMock()
@@ -125,7 +125,7 @@ class TestArtifactRepository:
def test_get_cached_url_with_artifact_returns_none_when_not_found(self): def test_get_cached_url_with_artifact_returns_none_when_not_found(self):
"""get_cached_url_with_artifact should return None when URL not cached.""" """get_cached_url_with_artifact should return None when URL not cached."""
from backend.app.db_utils import ArtifactRepository from app.db_utils import ArtifactRepository
mock_db = MagicMock() mock_db = MagicMock()
mock_db.query.return_value.join.return_value.filter.return_value.first.return_value = None mock_db.query.return_value.join.return_value.filter.return_value.first.return_value = None
@@ -137,7 +137,7 @@ class TestArtifactRepository:
def test_get_artifact_dependencies_returns_list(self): def test_get_artifact_dependencies_returns_list(self):
"""get_artifact_dependencies should return list of dependencies.""" """get_artifact_dependencies should return list of dependencies."""
from backend.app.db_utils import ArtifactRepository from app.db_utils import ArtifactRepository
mock_db = MagicMock() mock_db = MagicMock()
mock_dep1 = MagicMock() mock_dep1 = MagicMock()
@@ -156,7 +156,7 @@ class TestArtifactRepository:
def test_get_artifact_dependencies_returns_empty_list(self): def test_get_artifact_dependencies_returns_empty_list(self):
"""get_artifact_dependencies should return empty list when no dependencies.""" """get_artifact_dependencies should return empty list when no dependencies."""
from backend.app.db_utils import ArtifactRepository from app.db_utils import ArtifactRepository
mock_db = MagicMock() mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.all.return_value = [] mock_db.query.return_value.filter.return_value.all.return_value = []