- Add test_integration_uploads.py with 12 tests for duplicate upload scenarios - Add test_ref_count.py with 7 tests for ref_count management - Fix ArtifactDetailResponse to include sha256 and checksum fields - Fix health check SQL warning by wrapping in text() - Update tests to use unique content per test run for idempotency
177 lines
6.5 KiB
Python
177 lines
6.5 KiB
Python
"""
|
|
Unit and integration tests for reference counting behavior.
|
|
|
|
Tests cover:
|
|
- ref_count is set correctly for new artifacts
|
|
- ref_count increments on duplicate uploads
|
|
- ref_count query correctly identifies existing artifacts
|
|
- Artifact lookup by SHA256 hash works correctly
|
|
"""
|
|
|
|
import pytest
|
|
import io
|
|
from tests.conftest import (
|
|
compute_sha256,
|
|
upload_test_file,
|
|
TEST_CONTENT_HELLO,
|
|
TEST_HASH_HELLO,
|
|
)
|
|
|
|
|
|
class TestRefCountQuery:
|
|
"""Tests for ref_count querying and artifact lookup."""
|
|
|
|
@pytest.mark.integration
|
|
def test_artifact_lookup_by_sha256(self, integration_client, test_package):
|
|
"""Test artifact lookup by SHA256 hash (primary key) works correctly."""
|
|
project, package = test_package
|
|
content = b"unique content for lookup test"
|
|
expected_hash = compute_sha256(content)
|
|
|
|
# Upload a file
|
|
upload_result = upload_test_file(
|
|
integration_client, project, package, content, tag="v1"
|
|
)
|
|
assert upload_result["artifact_id"] == expected_hash
|
|
|
|
# Look up artifact by ID (SHA256)
|
|
response = integration_client.get(f"/api/v1/artifact/{expected_hash}")
|
|
assert response.status_code == 200
|
|
|
|
artifact = response.json()
|
|
assert artifact["id"] == expected_hash
|
|
assert artifact["sha256"] == expected_hash
|
|
assert artifact["size"] == len(content)
|
|
|
|
@pytest.mark.integration
|
|
def test_ref_count_query_identifies_existing_artifact(
|
|
self, integration_client, test_package
|
|
):
|
|
"""Test ref_count query correctly identifies existing artifacts by hash."""
|
|
project, package = test_package
|
|
content = b"content for ref count query test"
|
|
expected_hash = compute_sha256(content)
|
|
|
|
# Upload a file with a tag
|
|
upload_result = upload_test_file(
|
|
integration_client, project, package, content, tag="v1"
|
|
)
|
|
|
|
# Query artifact and check ref_count
|
|
response = integration_client.get(f"/api/v1/artifact/{expected_hash}")
|
|
assert response.status_code == 200
|
|
|
|
artifact = response.json()
|
|
assert artifact["ref_count"] >= 1 # At least 1 from the tag
|
|
|
|
@pytest.mark.integration
|
|
def test_ref_count_set_to_1_for_new_artifact_with_tag(
|
|
self, integration_client, test_package, unique_test_id
|
|
):
|
|
"""Test ref_count is set to 1 for new artifacts when created with a tag."""
|
|
project, package = test_package
|
|
content = f"brand new content for ref count test {unique_test_id}".encode()
|
|
expected_hash = compute_sha256(content)
|
|
|
|
# Upload a new file with a tag
|
|
upload_result = upload_test_file(
|
|
integration_client, project, package, content, tag="initial"
|
|
)
|
|
|
|
assert upload_result["artifact_id"] == expected_hash
|
|
assert upload_result["ref_count"] == 1
|
|
assert upload_result["deduplicated"] is False
|
|
|
|
@pytest.mark.integration
|
|
def test_ref_count_increments_on_duplicate_upload_with_tag(
|
|
self, integration_client, test_package, unique_test_id
|
|
):
|
|
"""Test ref_count is incremented when duplicate content is uploaded with a new tag."""
|
|
project, package = test_package
|
|
content = f"content that will be uploaded twice {unique_test_id}".encode()
|
|
expected_hash = compute_sha256(content)
|
|
|
|
# First upload with tag
|
|
result1 = upload_test_file(
|
|
integration_client, project, package, content, tag="v1"
|
|
)
|
|
assert result1["ref_count"] == 1
|
|
assert result1["deduplicated"] is False
|
|
|
|
# Second upload with different tag (same content)
|
|
result2 = upload_test_file(
|
|
integration_client, project, package, content, tag="v2"
|
|
)
|
|
assert result2["artifact_id"] == expected_hash
|
|
assert result2["ref_count"] == 2
|
|
assert result2["deduplicated"] is True
|
|
|
|
@pytest.mark.integration
|
|
def test_ref_count_after_multiple_tags(self, integration_client, test_package):
|
|
"""Test ref_count correctly reflects number of tags pointing to artifact."""
|
|
project, package = test_package
|
|
content = b"content for multiple tag test"
|
|
expected_hash = compute_sha256(content)
|
|
|
|
# Upload with multiple tags
|
|
tags = ["v1", "v2", "v3", "latest"]
|
|
for i, tag in enumerate(tags):
|
|
result = upload_test_file(
|
|
integration_client, project, package, content, tag=tag
|
|
)
|
|
assert result["artifact_id"] == expected_hash
|
|
assert result["ref_count"] == i + 1
|
|
|
|
# Verify final ref_count via artifact endpoint
|
|
response = integration_client.get(f"/api/v1/artifact/{expected_hash}")
|
|
assert response.status_code == 200
|
|
assert response.json()["ref_count"] == len(tags)
|
|
|
|
|
|
class TestRefCountWithDeletion:
|
|
"""Tests for ref_count behavior when tags are deleted."""
|
|
|
|
@pytest.mark.integration
|
|
def test_ref_count_decrements_on_tag_delete(self, integration_client, test_package):
|
|
"""Test ref_count decrements when a tag is deleted."""
|
|
project, package = test_package
|
|
content = b"content for delete test"
|
|
expected_hash = compute_sha256(content)
|
|
|
|
# Upload with two tags
|
|
upload_test_file(integration_client, project, package, content, tag="v1")
|
|
upload_test_file(integration_client, project, package, content, tag="v2")
|
|
|
|
# Verify ref_count is 2
|
|
response = integration_client.get(f"/api/v1/artifact/{expected_hash}")
|
|
assert response.json()["ref_count"] == 2
|
|
|
|
# Delete one tag
|
|
delete_response = integration_client.delete(
|
|
f"/api/v1/project/{project}/{package}/tags/v1"
|
|
)
|
|
assert delete_response.status_code == 204
|
|
|
|
# Verify ref_count is now 1
|
|
response = integration_client.get(f"/api/v1/artifact/{expected_hash}")
|
|
assert response.json()["ref_count"] == 1
|
|
|
|
@pytest.mark.integration
|
|
def test_ref_count_zero_after_all_tags_deleted(
|
|
self, integration_client, test_package
|
|
):
|
|
"""Test ref_count goes to 0 when all tags are deleted."""
|
|
project, package = test_package
|
|
content = b"content that will be orphaned"
|
|
expected_hash = compute_sha256(content)
|
|
|
|
# Upload with one tag
|
|
upload_test_file(integration_client, project, package, content, tag="only-tag")
|
|
|
|
# Delete the tag
|
|
integration_client.delete(f"/api/v1/project/{project}/{package}/tags/only-tag")
|
|
|
|
# Verify ref_count is 0
|
|
response = integration_client.get(f"/api/v1/artifact/{expected_hash}")
|
|
assert response.json()["ref_count"] == 0
|