""" Integration tests for version API endpoints. Tests cover: - Version creation via upload - Version auto-detection from filename - Version listing with pagination - Version deletion - Download by version ref - ref_count behavior with version operations """ import pytest from tests.factories import upload_test_file class TestVersionCreation: """Tests for version creation during upload.""" @pytest.mark.integration def test_upload_with_explicit_version(self, integration_client, test_package): """Test creating a version via explicit version parameter.""" project_name, package_name = test_package result = upload_test_file( integration_client, project_name, package_name, b"version create test", tag="latest", version="1.0.0", ) assert result["tag"] == "latest" assert result["version"] == "1.0.0" assert result["version_source"] == "explicit" assert result["artifact_id"] @pytest.mark.integration def test_upload_with_version_auto_detect_from_tarball( self, integration_client, test_package ): """Test version auto-detection from tarball filename pattern.""" project_name, package_name = test_package result = upload_test_file( integration_client, project_name, package_name, b"auto version test", filename="myapp-2.1.0.tar.gz", ) assert result["version"] == "2.1.0" # Tarball metadata extractor parses version from filename assert result["version_source"] == "metadata" @pytest.mark.integration def test_upload_with_version_auto_detect_v_prefix( self, integration_client, test_package ): """Test version auto-detection strips 'v' prefix from tarball filename.""" project_name, package_name = test_package result = upload_test_file( integration_client, project_name, package_name, b"v prefix test", filename="package-v3.0.0.tar.gz", ) assert result["version"] == "3.0.0" # Tarball metadata extractor parses version from filename assert result["version_source"] == "metadata" @pytest.mark.integration def test_upload_duplicate_version_warning(self, integration_client, test_package): """Test that duplicate version during upload returns response without error.""" project_name, package_name = test_package # Upload with version 1.0.0 upload_test_file( integration_client, project_name, package_name, b"first upload", version="1.0.0", ) # Upload different content with same version - should succeed but no new version result = upload_test_file( integration_client, project_name, package_name, b"second upload different content", version="1.0.0", ) # Upload succeeds but version may not be set (duplicate) assert result["artifact_id"] class TestVersionCRUD: """Tests for version list, get, delete operations.""" @pytest.mark.integration def test_list_versions(self, integration_client, test_package): """Test listing versions for a package.""" project_name, package_name = test_package # Create some versions upload_test_file( integration_client, project_name, package_name, b"v1 content", version="1.0.0", ) upload_test_file( integration_client, project_name, package_name, b"v2 content", version="2.0.0", ) response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/versions" ) assert response.status_code == 200 data = response.json() assert "items" in data assert "pagination" in data versions = [v["version"] for v in data["items"]] assert "1.0.0" in versions assert "2.0.0" in versions @pytest.mark.integration def test_list_versions_with_artifact_info(self, integration_client, test_package): """Test that version list includes artifact metadata.""" project_name, package_name = test_package upload_test_file( integration_client, project_name, package_name, b"version with info", version="1.0.0", tag="release", ) response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/versions" ) assert response.status_code == 200 data = response.json() assert len(data["items"]) >= 1 version_item = next( (v for v in data["items"] if v["version"] == "1.0.0"), None ) assert version_item is not None assert "size" in version_item assert "artifact_id" in version_item assert "tags" in version_item assert "release" in version_item["tags"] @pytest.mark.integration def test_get_version(self, integration_client, test_package): """Test getting a specific version.""" project_name, package_name = test_package upload_result = upload_test_file( integration_client, project_name, package_name, b"get version test", version="3.0.0", ) response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/versions/3.0.0" ) assert response.status_code == 200 data = response.json() assert data["version"] == "3.0.0" assert data["artifact_id"] == upload_result["artifact_id"] assert data["version_source"] == "explicit" @pytest.mark.integration def test_get_version_not_found(self, integration_client, test_package): """Test getting a non-existent version returns 404.""" project_name, package_name = test_package response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/versions/99.99.99" ) assert response.status_code == 404 @pytest.mark.integration def test_delete_version(self, integration_client, test_package): """Test deleting a version.""" project_name, package_name = test_package upload_test_file( integration_client, project_name, package_name, b"delete version test", version="4.0.0", ) # Delete version response = integration_client.delete( f"/api/v1/project/{project_name}/{package_name}/versions/4.0.0" ) assert response.status_code == 204 # Verify deleted response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/versions/4.0.0" ) assert response.status_code == 404 class TestVersionDownload: """Tests for downloading artifacts by version reference.""" @pytest.mark.integration def test_download_by_version_prefix(self, integration_client, test_package): """Test downloading an artifact using version: prefix.""" project_name, package_name = test_package content = b"download by version test" upload_test_file( integration_client, project_name, package_name, content, version="5.0.0", ) response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/+/version:5.0.0", follow_redirects=False, ) # Should either redirect or return content assert response.status_code in [200, 302, 307] @pytest.mark.integration def test_download_by_implicit_version(self, integration_client, test_package): """Test downloading an artifact using version number directly (no prefix).""" project_name, package_name = test_package content = b"implicit version download test" upload_test_file( integration_client, project_name, package_name, content, version="6.0.0", ) response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/+/6.0.0", follow_redirects=False, ) # Should resolve version first (before tag) assert response.status_code in [200, 302, 307] @pytest.mark.integration def test_version_takes_precedence_over_tag(self, integration_client, test_package): """Test that version is checked before tag when resolving refs.""" project_name, package_name = test_package # Upload with version "1.0" version_result = upload_test_file( integration_client, project_name, package_name, b"version content", version="1.0", ) # Create a tag with the same name "1.0" pointing to different artifact tag_result = upload_test_file( integration_client, project_name, package_name, b"tag content different", tag="1.0", ) # Download by "1.0" should resolve to version, not tag # Since version:1.0 artifact was uploaded first response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/+/1.0", follow_redirects=False, ) assert response.status_code in [200, 302, 307] class TestTagVersionEnrichment: """Tests for tag responses including version information.""" @pytest.mark.integration def test_tag_response_includes_version(self, integration_client, test_package): """Test that tag responses include version of the artifact.""" project_name, package_name = test_package # Upload with both version and tag upload_test_file( integration_client, project_name, package_name, b"enriched tag test", version="7.0.0", tag="stable", ) # Get tag and check version field response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/tags/stable" ) assert response.status_code == 200 data = response.json() assert data["name"] == "stable" assert data["version"] == "7.0.0" @pytest.mark.integration def test_tag_list_includes_versions(self, integration_client, test_package): """Test that tag list responses include version for each tag.""" project_name, package_name = test_package upload_test_file( integration_client, project_name, package_name, b"list version test", version="8.0.0", tag="latest", ) response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/tags" ) assert response.status_code == 200 data = response.json() tag_item = next((t for t in data["items"] if t["name"] == "latest"), None) assert tag_item is not None assert tag_item.get("version") == "8.0.0" class TestVersionPagination: """Tests for version listing pagination and sorting.""" @pytest.mark.integration def test_versions_pagination(self, integration_client, test_package): """Test version listing respects pagination.""" project_name, package_name = test_package response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/versions?limit=5" ) assert response.status_code == 200 data = response.json() assert "pagination" in data assert data["pagination"]["limit"] == 5 @pytest.mark.integration def test_versions_sorting(self, integration_client, test_package): """Test version listing can be sorted.""" project_name, package_name = test_package # Create versions with different timestamps upload_test_file( integration_client, project_name, package_name, b"sort test 1", version="1.0.0", ) upload_test_file( integration_client, project_name, package_name, b"sort test 2", version="2.0.0", ) # Test ascending sort response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/versions?sort=version&order=asc" ) assert response.status_code == 200 data = response.json() versions = [v["version"] for v in data["items"]] # First version should be 1.0.0 when sorted ascending if len(versions) >= 2: assert versions.index("1.0.0") < versions.index("2.0.0")