|
12 | 12 | from sentry.models.orgauthtoken import OrgAuthToken |
13 | 13 | from sentry.preprod.api.endpoints.organization_preprod_artifact_assemble import ( |
14 | 14 | validate_preprod_artifact_schema, |
| 15 | + validate_vcs_parameters, |
15 | 16 | ) |
16 | 17 | from sentry.preprod.tasks import create_preprod_artifact |
17 | 18 | from sentry.silo.base import SiloMode |
@@ -171,6 +172,116 @@ def test_additional_properties_rejected(self) -> None: |
171 | 172 | assert result == {} |
172 | 173 |
|
173 | 174 |
|
| 175 | +class ValidateVcsParametersTest(TestCase): |
| 176 | + """Unit tests for VCS parameter validation function - no database required.""" |
| 177 | + |
| 178 | + def test_valid_minimal_no_vcs_params(self) -> None: |
| 179 | + """Test that validation passes when no VCS params are provided.""" |
| 180 | + data = {"checksum": "a" * 40, "chunks": []} |
| 181 | + error = validate_vcs_parameters(data) |
| 182 | + assert error is None |
| 183 | + |
| 184 | + def test_valid_complete_vcs_params(self) -> None: |
| 185 | + """Test that validation passes when all required VCS params are provided.""" |
| 186 | + data = { |
| 187 | + "checksum": "a" * 40, |
| 188 | + "chunks": [], |
| 189 | + "head_sha": "e" * 40, |
| 190 | + "head_repo_name": "owner/repo", |
| 191 | + "provider": "github", |
| 192 | + "head_ref": "feature/xyz", |
| 193 | + } |
| 194 | + error = validate_vcs_parameters(data) |
| 195 | + assert error is None |
| 196 | + |
| 197 | + def test_valid_complete_vcs_params_with_base_sha(self) -> None: |
| 198 | + """Test that validation passes when all VCS params including base_sha are provided.""" |
| 199 | + data = { |
| 200 | + "checksum": "a" * 40, |
| 201 | + "chunks": [], |
| 202 | + "head_sha": "e" * 40, |
| 203 | + "base_sha": "f" * 40, |
| 204 | + "head_repo_name": "owner/repo", |
| 205 | + "provider": "github", |
| 206 | + "head_ref": "feature/xyz", |
| 207 | + } |
| 208 | + error = validate_vcs_parameters(data) |
| 209 | + assert error is None |
| 210 | + |
| 211 | + def test_same_head_and_base_sha(self) -> None: |
| 212 | + """Test that validation fails when head_sha and base_sha are the same.""" |
| 213 | + same_sha = "e" * 40 |
| 214 | + data = { |
| 215 | + "checksum": "a" * 40, |
| 216 | + "chunks": [], |
| 217 | + "head_sha": same_sha, |
| 218 | + "base_sha": same_sha, |
| 219 | + } |
| 220 | + error = validate_vcs_parameters(data) |
| 221 | + assert error is not None |
| 222 | + assert "Head SHA and base SHA cannot be the same" in error |
| 223 | + assert same_sha in error |
| 224 | + |
| 225 | + def test_base_sha_without_head_sha(self) -> None: |
| 226 | + """Test that validation fails when base_sha is provided without head_sha.""" |
| 227 | + data = {"checksum": "a" * 40, "chunks": [], "base_sha": "f" * 40} |
| 228 | + error = validate_vcs_parameters(data) |
| 229 | + assert error is not None |
| 230 | + assert "Head SHA is required when base SHA is provided" in error |
| 231 | + |
| 232 | + def test_missing_head_repo_name(self) -> None: |
| 233 | + """Test that validation fails when head_repo_name is missing.""" |
| 234 | + data = { |
| 235 | + "checksum": "a" * 40, |
| 236 | + "chunks": [], |
| 237 | + "head_sha": "e" * 40, |
| 238 | + "provider": "github", |
| 239 | + "head_ref": "feature/xyz", |
| 240 | + } |
| 241 | + error = validate_vcs_parameters(data) |
| 242 | + assert error is not None |
| 243 | + assert "Missing parameters" in error |
| 244 | + assert "head_repo_name" in error |
| 245 | + |
| 246 | + def test_missing_provider(self) -> None: |
| 247 | + """Test that validation fails when provider is missing.""" |
| 248 | + data = { |
| 249 | + "checksum": "a" * 40, |
| 250 | + "chunks": [], |
| 251 | + "head_sha": "e" * 40, |
| 252 | + "head_repo_name": "owner/repo", |
| 253 | + "head_ref": "feature/xyz", |
| 254 | + } |
| 255 | + error = validate_vcs_parameters(data) |
| 256 | + assert error is not None |
| 257 | + assert "Missing parameters" in error |
| 258 | + assert "provider" in error |
| 259 | + |
| 260 | + def test_missing_head_ref(self) -> None: |
| 261 | + """Test that validation fails when head_ref is missing.""" |
| 262 | + data = { |
| 263 | + "checksum": "a" * 40, |
| 264 | + "chunks": [], |
| 265 | + "head_sha": "e" * 40, |
| 266 | + "head_repo_name": "owner/repo", |
| 267 | + "provider": "github", |
| 268 | + } |
| 269 | + error = validate_vcs_parameters(data) |
| 270 | + assert error is not None |
| 271 | + assert "Missing parameters" in error |
| 272 | + assert "head_ref" in error |
| 273 | + |
| 274 | + def test_missing_multiple_params(self) -> None: |
| 275 | + """Test that validation fails and reports all missing params.""" |
| 276 | + data = {"checksum": "a" * 40, "chunks": [], "head_sha": "e" * 40} |
| 277 | + error = validate_vcs_parameters(data) |
| 278 | + assert error is not None |
| 279 | + assert "Missing parameters" in error |
| 280 | + assert "head_repo_name" in error |
| 281 | + assert "provider" in error |
| 282 | + assert "head_ref" in error |
| 283 | + |
| 284 | + |
174 | 285 | class ProjectPreprodArtifactAssembleTest(APITestCase): |
175 | 286 | """Integration tests for the full endpoint - requires database.""" |
176 | 287 |
|
@@ -804,3 +915,53 @@ def test_assemble_missing_vcs_parameters(self) -> None: |
804 | 915 | assert "head_repo_name" in response.data["error"] |
805 | 916 | assert "provider" in response.data["error"] |
806 | 917 | assert "head_ref" in response.data["error"] |
| 918 | + |
| 919 | + def test_assemble_same_head_and_base_sha(self) -> None: |
| 920 | + """Test that providing the same value for head_sha and base_sha returns a 400 error.""" |
| 921 | + content = b"test same sha" |
| 922 | + total_checksum = sha1(content).hexdigest() |
| 923 | + |
| 924 | + blob = FileBlob.from_file(ContentFile(content)) |
| 925 | + FileBlobOwner.objects.get_or_create(organization_id=self.organization.id, blob=blob) |
| 926 | + |
| 927 | + same_sha = "e" * 40 |
| 928 | + |
| 929 | + response = self.client.post( |
| 930 | + self.url, |
| 931 | + data={ |
| 932 | + "checksum": total_checksum, |
| 933 | + "chunks": [blob.checksum], |
| 934 | + "head_sha": same_sha, |
| 935 | + "base_sha": same_sha, |
| 936 | + "provider": "github", |
| 937 | + "head_repo_name": "owner/repo", |
| 938 | + "head_ref": "feature/xyz", |
| 939 | + }, |
| 940 | + HTTP_AUTHORIZATION=f"Bearer {self.token.token}", |
| 941 | + ) |
| 942 | + assert response.status_code == 400, response.content |
| 943 | + assert "error" in response.data |
| 944 | + assert "Head SHA and base SHA cannot be the same" in response.data["error"] |
| 945 | + assert same_sha in response.data["error"] |
| 946 | + |
| 947 | + def test_assemble_base_sha_without_head_sha(self) -> None: |
| 948 | + """Test that providing base_sha without head_sha returns a 400 error.""" |
| 949 | + content = b"test base sha without head sha" |
| 950 | + total_checksum = sha1(content).hexdigest() |
| 951 | + |
| 952 | + blob = FileBlob.from_file(ContentFile(content)) |
| 953 | + FileBlobOwner.objects.get_or_create(organization_id=self.organization.id, blob=blob) |
| 954 | + |
| 955 | + response = self.client.post( |
| 956 | + self.url, |
| 957 | + data={ |
| 958 | + "checksum": total_checksum, |
| 959 | + "chunks": [blob.checksum], |
| 960 | + "base_sha": "f" * 40, |
| 961 | + # Missing head_sha |
| 962 | + }, |
| 963 | + HTTP_AUTHORIZATION=f"Bearer {self.token.token}", |
| 964 | + ) |
| 965 | + assert response.status_code == 400, response.content |
| 966 | + assert "error" in response.data |
| 967 | + assert "Head SHA is required when base SHA is provided" in response.data["error"] |
0 commit comments