You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2026/04/23 16:03:58 UTC

(tooling-trusted-releases) branch sbp updated (857bbd54 -> 99ef386e)

This is an automated email from the ASF dual-hosted git repository.

sbp pushed a change to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


    from 857bbd54 Use the download publication URL by default in announcement emails
     add c8229307 Rename distribute to publish in the announcement UI
     add 12dfbfd1 Add missing JS
     add 60bfe60a #1071 - Add SBOM generation
     add 2196f1e0 #1071 - SBOM generation: docker caching
     add 279815ab #1071 - SBOM generation: add to main CI
     new 99ef386e Use shared field validation for policy form data

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/generatesbom.yml                 | 39 +++++++++++
 atr/get/announce.py                                |  4 +-
 atr/get/finish.py                                  |  4 +-
 atr/models/api.py                                  | 24 ++++++-
 atr/models/validation.py                           | 16 +++++
 atr/shared/projects.py                             |  6 +-
 .../src/{ignore-form-change.js => form-confirm.js} | 18 ++---
 atr/storage/writers/policy.py                      |  7 +-
 atr/templates/tutorial.html                        |  4 +-
 atr/util.py                                        |  8 +--
 tests/unit/test_policy_update_args.py              | 79 ++++++++++++++++++++++
 11 files changed, 173 insertions(+), 36 deletions(-)
 create mode 100644 .github/workflows/generatesbom.yml
 copy atr/static/js/src/{ignore-form-change.js => form-confirm.js} (68%)
 create mode 100644 tests/unit/test_policy_update_args.py


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org


(tooling-trusted-releases) 01/01: Use shared field validation for policy form data

Posted by sb...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git

commit 99ef386e7a5f46a05de3da4335357f8eaa5ad7ec
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Thu Apr 23 17:01:45 2026 +0100

    Use shared field validation for policy form data
---
 atr/models/api.py                     | 24 ++++++++++-
 atr/models/validation.py              | 16 +++++++
 atr/shared/projects.py                |  6 +--
 atr/storage/writers/policy.py         |  7 +---
 atr/util.py                           |  8 +---
 tests/unit/test_policy_update_args.py | 79 +++++++++++++++++++++++++++++++++++
 6 files changed, 123 insertions(+), 17 deletions(-)

diff --git a/atr/models/api.py b/atr/models/api.py
index 3280d570..d32cb888 100644
--- a/atr/models/api.py
+++ b/atr/models/api.py
@@ -326,7 +326,7 @@ class PolicyUpdateArgs(schema.Strict):
     github_repository_name: str | None = None
     github_vote_workflow_path: list[str] | None = None
     license_check_mode: sql.LicenseCheckMode | None = None
-    mailto_addresses: list[str] | None = None
+    mailto_addresses: list[pydantic.EmailStr] | None = None
     manual_vote: bool | None = None
     min_hours: int | None = None
     preserve_download_files: bool | None = None
@@ -338,6 +338,28 @@ class PolicyUpdateArgs(schema.Strict):
     start_vote_template: str | None = None
     vote_comment_template: str | None = None
 
+    @pydantic.model_validator(mode="after")
+    def validate_policy_fields(self) -> "PolicyUpdateArgs":
+        if self.min_hours is not None:
+            validation.validate_policy_min_hours(self.min_hours)
+
+        github_repository_name = self.github_repository_name
+        if github_repository_name is not None:
+            validation.validate_github_repository_name(github_repository_name.strip())
+
+        workflow_paths: list[str] = []
+        for paths in (
+            self.github_compose_workflow_path,
+            self.github_vote_workflow_path,
+            self.github_finish_workflow_path,
+        ):
+            if paths is None:
+                continue
+            workflow_paths.extend(path.strip() for path in paths if path.strip())
+        validation.validate_trusted_publishing_workflow_paths(workflow_paths)
+
+        return self
+
 
 class PolicyUpdateResults(schema.Strict):
     endpoint: Literal["/policy/update"] = schema.alias("endpoint")
diff --git a/atr/models/validation.py b/atr/models/validation.py
index 6c6c511f..fb68b565 100644
--- a/atr/models/validation.py
+++ b/atr/models/validation.py
@@ -86,6 +86,11 @@ def pagination_args_validate(query_args: Any) -> None:
             raise ValueError("Minimum offset less than 0 is nonsense")
 
 
+def validate_github_repository_name(github_repository_name: str | None) -> None:
+    if github_repository_name and ("/" in github_repository_name):
+        raise ValueError("GitHub repository name must not contain a slash.")
+
+
 def validate_ignore_pattern(pattern: str) -> None:
     """Raise an exception if the pattern is invalid."""
     if pattern == "!":
@@ -94,3 +99,14 @@ def validate_ignore_pattern(pattern: str) -> None:
     if raw_pattern.startswith("!"):
         raw_pattern = raw_pattern[1:]
     compile_ignore_pattern(raw_pattern)
+
+
+def validate_policy_min_hours(min_hours: int) -> None:
+    if (min_hours != 0) and ((min_hours < 72) or (min_hours > 144)):
+        raise ValueError("Minimum voting period must be 0 or between 72 and 144 hours inclusive.")
+
+
+def validate_trusted_publishing_workflow_paths(paths: list[str]) -> None:
+    for path in paths:
+        if not path.startswith(".github/workflows/"):
+            raise ValueError("GitHub workflow paths must start with '.github/workflows/'.")
diff --git a/atr/shared/projects.py b/atr/shared/projects.py
index 92c3974b..ff31b103 100644
--- a/atr/shared/projects.py
+++ b/atr/shared/projects.py
@@ -25,6 +25,7 @@ import pydantic
 import atr.form as form
 import atr.models.safe as safe
 import atr.models.sql as sql
+import atr.models.validation as validation
 import atr.util as util
 
 type COMPOSE = Literal["compose"]
@@ -173,10 +174,7 @@ class VotePolicyForm(form.Form):
 
     @pydantic.model_validator(mode="after")
     def validate_vote_fields(self) -> VotePolicyForm:
-        min_hours = self.min_hours
-        if (min_hours != 0) and ((min_hours < 72) or (min_hours > 144)):
-            raise ValueError("Minimum voting period must be 0 or between 72 and 144 hours inclusive.")
-
+        validation.validate_policy_min_hours(self.min_hours)
         return self
 
 
diff --git a/atr/storage/writers/policy.py b/atr/storage/writers/policy.py
index 97e6428c..45c825ab 100644
--- a/atr/storage/writers/policy.py
+++ b/atr/storage/writers/policy.py
@@ -156,7 +156,7 @@ class CommitteeMember(CommitteeParticipant):
             _validate_file_tag_mappings(update.file_tag_mappings)
 
         if ("min_hours" in fields_to_update) and (update.min_hours is not None):
-            _validate_min_hours(update.min_hours)
+            models.validation.validate_policy_min_hours(update.min_hours)
 
         if ("manual_vote" in fields_to_update) and update.manual_vote:
             if project.committee and project.committee.is_podling:
@@ -363,8 +363,3 @@ def _validate_file_tag_mappings(mappings: dict[str, list[str]]) -> None:
         for value in values:
             if ".." in value:
                 raise ValueError("File tag mapping values may not contain '..'")
-
-
-def _validate_min_hours(min_hours: int) -> None:
-    if (min_hours != 0) and ((min_hours < 72) or (min_hours > 144)):
-        raise ValueError("Minimum voting period must be 0 or between 72 and 144 hours inclusive.")
diff --git a/atr/util.py b/atr/util.py
index ceb6dbd1..b46db7c4 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -1144,12 +1144,8 @@ def validate_trusted_publishing_constraints(
     if github_repository_branch and (not github_repository_name):
         raise ValueError("GitHub repository name is required when a GitHub branch is set.")
 
-    if github_repository_name and ("/" in github_repository_name):
-        raise ValueError("GitHub repository name must not contain a slash.")
-
-    for path in all_paths:
-        if not path.startswith(".github/workflows/"):
-            raise ValueError("GitHub workflow paths must start with '.github/workflows/'.")
+    validation.validate_github_repository_name(github_repository_name)
+    validation.validate_trusted_publishing_workflow_paths(all_paths)
 
 
 def validate_vote_duration(duration: int):
diff --git a/tests/unit/test_policy_update_args.py b/tests/unit/test_policy_update_args.py
new file mode 100644
index 00000000..0b87278f
--- /dev/null
+++ b/tests/unit/test_policy_update_args.py
@@ -0,0 +1,79 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pydantic
+import pytest
+
+import atr.models.api as api
+
+
+def _policy_update(**kwargs: object) -> api.PolicyUpdateArgs:
+    return api.PolicyUpdateArgs.model_validate({"project": "tooling", **kwargs})
+
+
+@pytest.mark.parametrize("min_hours", [0, 72, 144])
+def test_policy_update_args_accepts_valid_min_hours(min_hours: int) -> None:
+    args = _policy_update(min_hours=min_hours)
+
+    assert args.min_hours == min_hours
+
+
+@pytest.mark.parametrize("min_hours", [-1, 1, 71, 145])
+def test_policy_update_args_rejects_invalid_min_hours(min_hours: int) -> None:
+    with pytest.raises(pydantic.ValidationError, match="Minimum voting period"):
+        _policy_update(min_hours=min_hours)
+
+
+def test_policy_update_args_rejects_github_repository_name_with_slash() -> None:
+    with pytest.raises(pydantic.ValidationError, match="must not contain a slash"):
+        _policy_update(github_repository_name="apache/tooling")
+
+
+@pytest.mark.parametrize(
+    "field",
+    [
+        "github_compose_workflow_path",
+        "github_vote_workflow_path",
+        "github_finish_workflow_path",
+    ],
+)
+def test_policy_update_args_rejects_invalid_workflow_paths(field: str) -> None:
+    with pytest.raises(pydantic.ValidationError, match="must start with"):
+        _policy_update(**{field: ["build.yml"]})
+
+
+def test_policy_update_args_allows_partial_trusted_publishing_update() -> None:
+    args = _policy_update(github_repository_branch="release")
+
+    assert args.github_repository_branch == "release"
+
+
+def test_policy_update_args_validates_normalised_workflow_paths() -> None:
+    args = _policy_update(github_vote_workflow_path=[" .github/workflows/vote.yml ", "  "])
+
+    assert args.github_vote_workflow_path == [" .github/workflows/vote.yml ", "  "]
+
+
+def test_policy_update_args_validates_mailto_addresses() -> None:
+    args = _policy_update(mailto_addresses=[" test@example.org "])
+
+    assert args.mailto_addresses == ["test@example.org"]
+
+
+def test_policy_update_args_rejects_malformed_mailto_addresses() -> None:
+    with pytest.raises(pydantic.ValidationError, match="valid email address"):
+        _policy_update(mailto_addresses=["not-an-email"])


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org