"""Integration tests for audit logs and history endpoints.""" import pytest from datetime import datetime, timedelta from tests.conftest import upload_test_file class TestAuditLogsEndpoint: """Tests for /api/v1/audit-logs endpoint.""" @pytest.mark.integration def test_list_audit_logs_returns_valid_response(self, integration_client): """Test that audit logs endpoint returns valid paginated response.""" response = integration_client.get("/api/v1/audit-logs") assert response.status_code == 200 data = response.json() assert "items" in data assert "pagination" in data assert isinstance(data["items"], list) pagination = data["pagination"] assert "page" in pagination assert "limit" in pagination assert "total" in pagination assert "total_pages" in pagination @pytest.mark.integration def test_audit_logs_respects_pagination(self, integration_client): """Test that audit logs endpoint respects limit parameter.""" response = integration_client.get("/api/v1/audit-logs?limit=5") assert response.status_code == 200 data = response.json() assert len(data["items"]) <= 5 assert data["pagination"]["limit"] == 5 @pytest.mark.integration def test_audit_logs_filter_by_action(self, integration_client, test_package): """Test filtering audit logs by action type.""" # Create an action that will be logged project_name, package_name = test_package response = integration_client.get("/api/v1/audit-logs?action=project.create") assert response.status_code == 200 data = response.json() # All items should have the filtered action for item in data["items"]: assert item["action"] == "project.create" @pytest.mark.integration def test_audit_log_entry_has_required_fields( self, integration_client, test_project ): """Test that audit log entries have all required fields.""" # Force some audit logs by operations on test_project response = integration_client.get("/api/v1/audit-logs?limit=10") assert response.status_code == 200 data = response.json() if data["items"]: item = data["items"][0] assert "id" in item assert "action" in item assert "resource" in item assert "user_id" in item assert "timestamp" in item class TestProjectAuditLogs: """Tests for /api/v1/projects/{project}/audit-logs endpoint.""" @pytest.mark.integration def test_project_audit_logs_returns_200(self, integration_client, test_project): """Test that project audit logs endpoint returns 200.""" response = integration_client.get(f"/api/v1/projects/{test_project}/audit-logs") assert response.status_code == 200 data = response.json() assert "items" in data assert "pagination" in data @pytest.mark.integration def test_project_audit_logs_not_found(self, integration_client): """Test that non-existent project returns 404.""" response = integration_client.get( "/api/v1/projects/nonexistent-project/audit-logs" ) assert response.status_code == 404 class TestPackageAuditLogs: """Tests for /api/v1/project/{project}/{package}/audit-logs endpoint.""" @pytest.mark.integration def test_package_audit_logs_returns_200(self, integration_client, test_package): """Test that package audit logs endpoint returns 200.""" project_name, package_name = test_package response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/audit-logs" ) assert response.status_code == 200 data = response.json() assert "items" in data assert "pagination" in data @pytest.mark.integration def test_package_audit_logs_project_not_found(self, integration_client): """Test that non-existent project returns 404.""" response = integration_client.get( "/api/v1/project/nonexistent/nonexistent/audit-logs" ) assert response.status_code == 404 @pytest.mark.integration def test_package_audit_logs_package_not_found( self, integration_client, test_project ): """Test that non-existent package returns 404.""" response = integration_client.get( f"/api/v1/project/{test_project}/nonexistent-package/audit-logs" ) assert response.status_code == 404 class TestPackageUploads: """Tests for /api/v1/project/{project}/{package}/uploads endpoint.""" @pytest.mark.integration def test_package_uploads_returns_200(self, integration_client, test_package): """Test that package uploads endpoint returns 200.""" project_name, package_name = test_package response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/uploads" ) assert response.status_code == 200 data = response.json() assert "items" in data assert "pagination" in data @pytest.mark.integration def test_package_uploads_after_upload(self, integration_client, test_package): """Test that uploads are recorded after file upload.""" project_name, package_name = test_package # Upload a file upload_result = upload_test_file( integration_client, project_name, package_name, b"test upload content", "test.txt", ) assert upload_result["artifact_id"] # Check uploads endpoint response = integration_client.get( f"/api/v1/project/{project_name}/{package_name}/uploads" ) assert response.status_code == 200 data = response.json() assert len(data["items"]) >= 1 # Verify upload record fields upload = data["items"][0] assert "artifact_id" in upload assert "package_name" in upload assert "project_name" in upload assert "uploaded_at" in upload assert "uploaded_by" in upload @pytest.mark.integration def test_package_uploads_project_not_found(self, integration_client): """Test that non-existent project returns 404.""" response = integration_client.get( "/api/v1/project/nonexistent/nonexistent/uploads" ) assert response.status_code == 404 class TestArtifactUploads: """Tests for /api/v1/artifact/{id}/uploads endpoint.""" @pytest.mark.integration def test_artifact_uploads_returns_200(self, integration_client, test_package): """Test that artifact uploads endpoint returns 200.""" project_name, package_name = test_package # Upload a file upload_result = upload_test_file( integration_client, project_name, package_name, b"artifact upload test", "artifact.txt", ) artifact_id = upload_result["artifact_id"] response = integration_client.get(f"/api/v1/artifact/{artifact_id}/uploads") assert response.status_code == 200 data = response.json() assert "items" in data assert "pagination" in data assert len(data["items"]) >= 1 @pytest.mark.integration def test_artifact_uploads_not_found(self, integration_client): """Test that non-existent artifact returns 404.""" fake_hash = "a" * 64 response = integration_client.get(f"/api/v1/artifact/{fake_hash}/uploads") assert response.status_code == 404 class TestArtifactProvenance: """Tests for /api/v1/artifact/{id}/history endpoint.""" @pytest.mark.integration def test_artifact_history_returns_200(self, integration_client, test_package): """Test that artifact history endpoint returns 200.""" project_name, package_name = test_package # Upload a file upload_result = upload_test_file( integration_client, project_name, package_name, b"provenance test content", "prov.txt", ) artifact_id = upload_result["artifact_id"] response = integration_client.get(f"/api/v1/artifact/{artifact_id}/history") assert response.status_code == 200 @pytest.mark.integration def test_artifact_history_has_required_fields( self, integration_client, test_package ): """Test that artifact history has all required fields.""" project_name, package_name = test_package # Upload a file upload_result = upload_test_file( integration_client, project_name, package_name, b"provenance fields test", "fields.txt", ) artifact_id = upload_result["artifact_id"] response = integration_client.get(f"/api/v1/artifact/{artifact_id}/history") assert response.status_code == 200 data = response.json() assert "artifact_id" in data assert "sha256" in data assert "size" in data assert "created_at" in data assert "created_by" in data assert "ref_count" in data assert "first_uploaded_at" in data assert "first_uploaded_by" in data assert "upload_count" in data assert "packages" in data assert "tags" in data assert "uploads" in data @pytest.mark.integration def test_artifact_history_not_found(self, integration_client): """Test that non-existent artifact returns 404.""" fake_hash = "b" * 64 response = integration_client.get(f"/api/v1/artifact/{fake_hash}/history") assert response.status_code == 404 @pytest.mark.integration def test_artifact_history_with_tag(self, integration_client, test_package): """Test artifact history includes tag information when tagged.""" project_name, package_name = test_package # Upload a file with a tag upload_result = upload_test_file( integration_client, project_name, package_name, b"tagged provenance test", "tagged.txt", tag="v1.0.0", ) artifact_id = upload_result["artifact_id"] response = integration_client.get(f"/api/v1/artifact/{artifact_id}/history") assert response.status_code == 200 data = response.json() # Should have at least one tag assert len(data["tags"]) >= 1 # Tag should have required fields tag = data["tags"][0] assert "project_name" in tag assert "package_name" in tag assert "tag_name" in tag class TestGlobalUploadsEndpoint: """Tests for /api/v1/uploads endpoint (global admin).""" @pytest.mark.integration def test_global_uploads_returns_200(self, integration_client): """Test that global uploads endpoint returns 200.""" response = integration_client.get("/api/v1/uploads") assert response.status_code == 200 data = response.json() assert "items" in data assert "pagination" in data @pytest.mark.integration def test_global_uploads_pagination(self, integration_client): """Test that global uploads endpoint respects pagination.""" response = integration_client.get("/api/v1/uploads?limit=5&page=1") assert response.status_code == 200 data = response.json() assert len(data["items"]) <= 5 assert data["pagination"]["limit"] == 5 assert data["pagination"]["page"] == 1 @pytest.mark.integration def test_global_uploads_filter_by_project(self, integration_client, test_package): """Test filtering global uploads by project name.""" project_name, package_name = test_package # Upload a file upload_test_file( integration_client, project_name, package_name, b"global filter test", "global.txt", ) response = integration_client.get(f"/api/v1/uploads?project={project_name}") assert response.status_code == 200 data = response.json() for item in data["items"]: assert item["project_name"] == project_name @pytest.mark.integration def test_global_uploads_filter_by_uploader(self, integration_client, test_package): """Test filtering global uploads by uploaded_by.""" project_name, package_name = test_package # Upload a file upload_test_file( integration_client, project_name, package_name, b"uploader filter test", "uploader.txt", ) # Filter by anonymous (default user) response = integration_client.get("/api/v1/uploads?uploaded_by=anonymous") assert response.status_code == 200 data = response.json() for item in data["items"]: assert item["uploaded_by"] == "anonymous" @pytest.mark.integration def test_global_uploads_has_more_field(self, integration_client): """Test that pagination includes has_more field.""" response = integration_client.get("/api/v1/uploads?limit=1") assert response.status_code == 200 data = response.json() assert "has_more" in data["pagination"] assert isinstance(data["pagination"]["has_more"], bool) class TestProjectUploadsEndpoint: """Tests for /api/v1/project/{project}/uploads endpoint.""" @pytest.mark.integration def test_project_uploads_returns_200(self, integration_client, test_project): """Test that project uploads endpoint returns 200.""" response = integration_client.get(f"/api/v1/project/{test_project}/uploads") assert response.status_code == 200 data = response.json() assert "items" in data assert "pagination" in data @pytest.mark.integration def test_project_uploads_after_upload(self, integration_client, test_package): """Test that uploads are recorded in project uploads.""" project_name, package_name = test_package # Upload a file upload_test_file( integration_client, project_name, package_name, b"project uploads test", "project.txt", ) response = integration_client.get(f"/api/v1/project/{project_name}/uploads") assert response.status_code == 200 data = response.json() assert len(data["items"]) >= 1 # Verify project name matches for item in data["items"]: assert item["project_name"] == project_name @pytest.mark.integration def test_project_uploads_filter_by_package(self, integration_client, test_package): """Test filtering project uploads by package name.""" project_name, package_name = test_package # Upload a file upload_test_file( integration_client, project_name, package_name, b"package filter test", "pkgfilter.txt", ) response = integration_client.get( f"/api/v1/project/{project_name}/uploads?package={package_name}" ) assert response.status_code == 200 data = response.json() for item in data["items"]: assert item["package_name"] == package_name @pytest.mark.integration def test_project_uploads_not_found(self, integration_client): """Test that non-existent project returns 404.""" response = integration_client.get("/api/v1/project/nonexistent/uploads") assert response.status_code == 404 class TestUploadResponseFields: """Tests for enhanced UploadResponse fields (Issue #19).""" @pytest.mark.integration def test_upload_response_has_upload_id(self, integration_client, test_package): """Test that upload response includes upload_id.""" project_name, package_name = test_package upload_result = upload_test_file( integration_client, project_name, package_name, b"upload id test", "uploadid.txt", ) # upload_id should be present assert "upload_id" in upload_result assert upload_result["upload_id"] is not None @pytest.mark.integration def test_upload_response_has_content_type(self, integration_client, test_package): """Test that upload response includes content_type.""" project_name, package_name = test_package upload_result = upload_test_file( integration_client, project_name, package_name, b"content type test", "content.txt", ) assert "content_type" in upload_result @pytest.mark.integration def test_upload_response_has_original_name(self, integration_client, test_package): """Test that upload response includes original_name.""" project_name, package_name = test_package upload_result = upload_test_file( integration_client, project_name, package_name, b"original name test", "originalname.txt", ) assert "original_name" in upload_result assert upload_result["original_name"] == "originalname.txt" @pytest.mark.integration def test_upload_response_has_created_at(self, integration_client, test_package): """Test that upload response includes created_at.""" project_name, package_name = test_package upload_result = upload_test_file( integration_client, project_name, package_name, b"created at test", "createdat.txt", ) assert "created_at" in upload_result assert upload_result["created_at"] is not None