diff --git a/src/sentry/preprod/api/endpoints/organization_preprod_artifact_assemble.py b/src/sentry/preprod/api/endpoints/organization_preprod_artifact_assemble.py index 3238be8c55fe..205b729bd314 100644 --- a/src/sentry/preprod/api/endpoints/organization_preprod_artifact_assemble.py +++ b/src/sentry/preprod/api/endpoints/organization_preprod_artifact_assemble.py @@ -35,6 +35,8 @@ IntegrationProviderSlug.BITBUCKET_SERVER, ] +MAX_INSTALL_GROUPS = 16 + def validate_vcs_parameters(data: dict[str, Any]) -> str | None: head_sha = data.get("head_sha") @@ -80,8 +82,10 @@ def validate_preprod_artifact_schema(request_body: bytes) -> tuple[dict[str, Any "release_notes": {"type": "string"}, "install_groups": { "type": "array", - "items": {"type": "string", "maxLength": 255}, + "items": {"type": "string", "minLength": 1, "maxLength": 255}, "minItems": 1, + "maxItems": MAX_INSTALL_GROUPS, + "uniqueItems": True, }, **VCS_SCHEMA_PROPERTIES, }, @@ -94,7 +98,7 @@ def validate_preprod_artifact_schema(request_body: bytes) -> tuple[dict[str, Any "chunks": "The chunks field is required and must be provided as an array of 40-character hexadecimal strings.", "build_configuration": "The build_configuration field must be a string.", "release_notes": "The release_notes field must be a string.", - "install_groups": "The install_groups field must be an array of strings, each with maximum length of 255 characters.", + "install_groups": f"The install_groups field must contain between 1 and {MAX_INSTALL_GROUPS} unique, non-empty strings, each with a maximum length of 255 characters.", **VCS_ERROR_MESSAGES, } diff --git a/tests/sentry/preprod/api/endpoints/test_organization_preprod_artifact_assemble.py b/tests/sentry/preprod/api/endpoints/test_organization_preprod_artifact_assemble.py index 8f49dd2e737e..337838d2ea3c 100644 --- a/tests/sentry/preprod/api/endpoints/test_organization_preprod_artifact_assemble.py +++ b/tests/sentry/preprod/api/endpoints/test_organization_preprod_artifact_assemble.py @@ -229,6 +229,57 @@ def test_empty_strings_with_valid_data_filtered_out(self) -> None: "build_configuration": "debug", } + def test_maximum_install_groups_allowed(self) -> None: + data = { + "checksum": "a" * 40, + "chunks": [], + "install_groups": [f"group-{index}" for index in range(16)], + } + + result, error = validate_preprod_artifact_schema(orjson.dumps(data)) + + assert error is None + assert result == data + + def test_too_many_install_groups_rejected(self) -> None: + data = { + "checksum": "a" * 40, + "chunks": [], + "install_groups": [f"group-{index}" for index in range(17)], + } + + result, error = validate_preprod_artifact_schema(orjson.dumps(data)) + + assert error is not None + assert "16" in error + assert result == {} + + def test_duplicate_install_groups_rejected(self) -> None: + data = { + "checksum": "a" * 40, + "chunks": [], + "install_groups": ["alpha", "alpha"], + } + + result, error = validate_preprod_artifact_schema(orjson.dumps(data)) + + assert error is not None + assert "unique" in error + assert result == {} + + def test_empty_install_group_rejected(self) -> None: + data = { + "checksum": "a" * 40, + "chunks": [], + "install_groups": ["alpha", ""], + } + + result, error = validate_preprod_artifact_schema(orjson.dumps(data)) + + assert error is not None + assert "non-empty" in error + assert result == {} + class ValidateVcsParametersTest(TestCase): """Unit tests for VCS parameter validation function - no database required."""