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

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


The following commit(s) were added to refs/heads/main by this push:
     new 1bbddbf  Add license check exclude fields to the UI and related tests
1bbddbf is described below

commit 1bbddbf438a46c0d8cc387827c033a5deb523174
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Jan 9 17:10:01 2026 +0000

    Add license check exclude fields to the UI and related tests
---
 atr/get/projects.py                    |  2 +
 atr/shared/projects.py                 | 11 ++++++
 atr/storage/writers/policy.py          |  7 ++++
 tests/e2e/policy/__init__.py           | 16 ++++++++
 tests/e2e/{sbom => policy}/conftest.py | 34 +++++++---------
 tests/e2e/policy/helpers.py            | 35 +++++++++++++++++
 tests/e2e/policy/test_get.py           | 49 +++++++++++++++++++++++
 tests/e2e/policy/test_post.py          | 72 ++++++++++++++++++++++++++++++++++
 tests/e2e/sbom/conftest.py             | 25 ++++++------
 tests/e2e/sbom/helpers.py              | 24 ++++++++++++
 tests/e2e/sbom/test_post.py            | 19 +++++----
 11 files changed, 252 insertions(+), 42 deletions(-)

diff --git a/atr/get/projects.py b/atr/get/projects.py
index 085f4ff..1b44c94 100644
--- a/atr/get/projects.py
+++ b/atr/get/projects.py
@@ -330,6 +330,8 @@ def _render_compose_form(project: sql.Project) -> 
htm.Element:
                 "project_name": project.name,
                 "source_artifact_paths": 
"\n".join(project.policy_source_artifact_paths),
                 "license_check_mode": project.policy_license_check_mode,
+                "source_excludes_lightweight": 
"\n".join(project.policy_source_excludes_lightweight),
+                "source_excludes_rat": 
"\n".join(project.policy_source_excludes_rat),
                 "binary_artifact_paths": 
"\n".join(project.policy_binary_artifact_paths),
                 "github_repository_name": 
project.policy_github_repository_name or "",
                 "github_compose_workflow_path": 
"\n".join(project.policy_github_compose_workflow_path),
diff --git a/atr/shared/projects.py b/atr/shared/projects.py
index db9951f..7eabf73 100644
--- a/atr/shared/projects.py
+++ b/atr/shared/projects.py
@@ -116,6 +116,17 @@ class ComposePolicyForm(form.Form):
         "Only affects source artifacts. Lightweight checks ALWAYS RUN on 
binary artifacts.",
         widget=form.Widget.RADIO,
     )
+    source_excludes_lightweight: str = form.label(
+        "Lightweight source excludes",
+        "Patterns using .gitignore syntax for files to exclude"
+        " from lightweight license header checks on source artifacts.",
+        widget=form.Widget.TEXTAREA,
+    )
+    source_excludes_rat: str = form.label(
+        "RAT source excludes",
+        "RAT exclude file contents for source artifacts. Used only when no 
.rat-excludes file exists in the archive.",
+        widget=form.Widget.TEXTAREA,
+    )
     binary_artifact_paths: str = form.label(
         "Binary artifact paths",
         "Paths to binary artifacts to be included in the release.",
diff --git a/atr/storage/writers/policy.py b/atr/storage/writers/policy.py
index d49b97a..b7f79c1 100644
--- a/atr/storage/writers/policy.py
+++ b/atr/storage/writers/policy.py
@@ -97,6 +97,8 @@ class CommitteeMember(CommitteeParticipant):
 
         release_policy.source_artifact_paths = 
_split_lines(form.source_artifact_paths)
         release_policy.license_check_mode = form.license_check_mode  # 
pyright: ignore[reportAttributeAccessIssue]
+        release_policy.source_excludes_lightweight = 
_split_lines_verbatim(form.source_excludes_lightweight)
+        release_policy.source_excludes_rat = 
_split_lines_verbatim(form.source_excludes_rat)
         release_policy.binary_artifact_paths = 
_split_lines(form.binary_artifact_paths)
         release_policy.github_repository_name = 
form.github_repository_name.strip()
         release_policy.github_compose_workflow_path = 
_split_lines(form.github_compose_workflow_path)
@@ -235,3 +237,8 @@ class CommitteeMember(CommitteeParticipant):
 
 def _split_lines(text: str) -> list[str]:
     return [line.strip() for line in text.split("\n") if line.strip()]
+
+
+def _split_lines_verbatim(text: str) -> list[str]:
+    # This still excludes empty lines
+    return [line for line in text.split("\n") if line]
diff --git a/tests/e2e/policy/__init__.py b/tests/e2e/policy/__init__.py
new file mode 100644
index 0000000..13a8339
--- /dev/null
+++ b/tests/e2e/policy/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/tests/e2e/sbom/conftest.py b/tests/e2e/policy/conftest.py
similarity index 51%
copy from tests/e2e/sbom/conftest.py
copy to tests/e2e/policy/conftest.py
index 0801444..2918137 100644
--- a/tests/e2e/sbom/conftest.py
+++ b/tests/e2e/policy/conftest.py
@@ -17,10 +17,10 @@
 
 from __future__ import annotations
 
-import pathlib
-from typing import TYPE_CHECKING, Final
+from typing import TYPE_CHECKING
 
 import e2e.helpers as helpers
+import e2e.policy.helpers as policy_helpers
 import pytest
 
 if TYPE_CHECKING:
@@ -28,25 +28,19 @@ if TYPE_CHECKING:
 
     from playwright.sync_api import Page
 
-PROJECT_NAME: Final[str] = "test"
-VERSION_NAME: Final[str] = "0.1+e2e-sbom"
-FILE_NAME: Final[str] = "apache-test-0.2.tar.gz"
-CURRENT_DIR: Final[pathlib.Path] = pathlib.Path(__file__).parent.resolve()
-
 
 @pytest.fixture
-def page_release_with_file(page: Page) -> Generator[Page]:
+def page_project(page: Page) -> Generator[Page]:
     helpers.log_in(page)
-
-    helpers.delete_release_if_exists(page, PROJECT_NAME, VERSION_NAME)
-
-    helpers.visit(page, f"/start/{PROJECT_NAME}")
-    page.get_by_role("textbox").type(VERSION_NAME)
-    page.get_by_role("button", name="Start new release").click()
-    helpers.visit(page, f"/upload/{PROJECT_NAME}/{VERSION_NAME}")
-    
page.locator('input[name="file_data"]').set_input_files(f"{CURRENT_DIR}/../test_files/{FILE_NAME}")
-    page.get_by_role("button", name="Add files").click()
-    page.wait_for_url(f"**/compose/{PROJECT_NAME}/{VERSION_NAME}")
-    helpers.visit(page, f"/compose/{PROJECT_NAME}/{VERSION_NAME}")
-    page.wait_for_selector("#ongoing-tasks-banner", state="hidden")
+    _clear_policy_excludes(page)
+    helpers.visit(page, policy_helpers.PROJECT_URL)
     yield page
+    _clear_policy_excludes(page)
+
+
+def _clear_policy_excludes(page: Page) -> None:
+    helpers.visit(page, policy_helpers.PROJECT_URL)
+    policy_helpers.textarea_source_excludes_lightweight(page).fill("")
+    policy_helpers.textarea_source_excludes_rat(page).fill("")
+    policy_helpers.compose_form_save_button(page).click()
+    page.wait_for_load_state()
diff --git a/tests/e2e/policy/helpers.py b/tests/e2e/policy/helpers.py
new file mode 100644
index 0000000..c9bcc09
--- /dev/null
+++ b/tests/e2e/policy/helpers.py
@@ -0,0 +1,35 @@
+# 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.
+
+from typing import Final
+
+from playwright.sync_api import Locator, Page
+
+PROJECT_NAME: Final[str] = "test"
+PROJECT_URL: Final[str] = f"/projects/{PROJECT_NAME}"
+
+
+def compose_form_save_button(page: Page) -> Locator:
+    return page.locator('form.atr-canary button[type="submit"]').first
+
+
+def textarea_source_excludes_lightweight(page: Page) -> Locator:
+    return page.locator('textarea[name="source_excludes_lightweight"]')
+
+
+def textarea_source_excludes_rat(page: Page) -> Locator:
+    return page.locator('textarea[name="source_excludes_rat"]')
diff --git a/tests/e2e/policy/test_get.py b/tests/e2e/policy/test_get.py
new file mode 100644
index 0000000..f447442
--- /dev/null
+++ b/tests/e2e/policy/test_get.py
@@ -0,0 +1,49 @@
+# 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 e2e.policy.helpers as helpers
+from playwright.sync_api import Page, expect
+
+
+def test_source_excludes_lightweight_initially_empty(page_project: Page) -> 
None:
+    textarea = helpers.textarea_source_excludes_lightweight(page_project)
+    expect(textarea).to_have_value("")
+
+
+def test_source_excludes_lightweight_textarea_is_editable(page_project: Page) 
-> None:
+    textarea = helpers.textarea_source_excludes_lightweight(page_project)
+    expect(textarea).to_be_editable()
+
+
+def test_source_excludes_lightweight_textarea_is_visible(page_project: Page) 
-> None:
+    textarea = helpers.textarea_source_excludes_lightweight(page_project)
+    expect(textarea).to_be_visible()
+
+
+def test_source_excludes_rat_initially_empty(page_project: Page) -> None:
+    textarea = helpers.textarea_source_excludes_rat(page_project)
+    expect(textarea).to_have_value("")
+
+
+def test_source_excludes_rat_textarea_is_editable(page_project: Page) -> None:
+    textarea = helpers.textarea_source_excludes_rat(page_project)
+    expect(textarea).to_be_editable()
+
+
+def test_source_excludes_rat_textarea_is_visible(page_project: Page) -> None:
+    textarea = helpers.textarea_source_excludes_rat(page_project)
+    expect(textarea).to_be_visible()
diff --git a/tests/e2e/policy/test_post.py b/tests/e2e/policy/test_post.py
new file mode 100644
index 0000000..d3f6218
--- /dev/null
+++ b/tests/e2e/policy/test_post.py
@@ -0,0 +1,72 @@
+# 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 e2e.helpers as root_helpers
+import e2e.policy.helpers as helpers
+from playwright.sync_api import Page, expect
+
+
+def test_source_excludes_lightweight_can_be_cleared(page_project: Page) -> 
None:
+    textarea = helpers.textarea_source_excludes_lightweight(page_project)
+    textarea.fill("*.min.js")
+    helpers.compose_form_save_button(page_project).click()
+    page_project.wait_for_load_state()
+
+    root_helpers.visit(page_project, helpers.PROJECT_URL)
+    textarea = helpers.textarea_source_excludes_lightweight(page_project)
+    textarea.fill("")
+    helpers.compose_form_save_button(page_project).click()
+    page_project.wait_for_load_state()
+
+    root_helpers.visit(page_project, helpers.PROJECT_URL)
+    textarea = helpers.textarea_source_excludes_lightweight(page_project)
+    expect(textarea).to_have_value("")
+
+
+def 
test_source_excludes_lightweight_preserves_internal_whitespace(page_project: 
Page) -> None:
+    # TODO: There is a problem with leading and trailing whitespace in the form
+    # Anyway, this is an edge case, and perhaps normalisation would even be 
better
+    textarea = helpers.textarea_source_excludes_lightweight(page_project)
+    textarea.fill("first\n  middle with spaces  \nlast")
+    helpers.compose_form_save_button(page_project).click()
+    page_project.wait_for_load_state()
+
+    root_helpers.visit(page_project, helpers.PROJECT_URL)
+    textarea = helpers.textarea_source_excludes_lightweight(page_project)
+    expect(textarea).to_have_value("first\n  middle with spaces  \nlast")
+
+
+def test_source_excludes_lightweight_value_persists(page_project: Page) -> 
None:
+    textarea = helpers.textarea_source_excludes_lightweight(page_project)
+    textarea.fill("*.min.js\nvendor/**")
+    helpers.compose_form_save_button(page_project).click()
+    page_project.wait_for_load_state()
+
+    root_helpers.visit(page_project, helpers.PROJECT_URL)
+    textarea = helpers.textarea_source_excludes_lightweight(page_project)
+    expect(textarea).to_have_value("*.min.js\nvendor/**")
+
+
+def test_source_excludes_rat_value_persists(page_project: Page) -> None:
+    textarea = helpers.textarea_source_excludes_rat(page_project)
+    textarea.fill("third-party/**\n*.generated")
+    helpers.compose_form_save_button(page_project).click()
+    page_project.wait_for_load_state()
+
+    root_helpers.visit(page_project, helpers.PROJECT_URL)
+    textarea = helpers.textarea_source_excludes_rat(page_project)
+    expect(textarea).to_have_value("third-party/**\n*.generated")
diff --git a/tests/e2e/sbom/conftest.py b/tests/e2e/sbom/conftest.py
index 0801444..b118d95 100644
--- a/tests/e2e/sbom/conftest.py
+++ b/tests/e2e/sbom/conftest.py
@@ -17,10 +17,10 @@
 
 from __future__ import annotations
 
-import pathlib
-from typing import TYPE_CHECKING, Final
+from typing import TYPE_CHECKING
 
 import e2e.helpers as helpers
+import e2e.sbom.helpers as sbom_helpers
 import pytest
 
 if TYPE_CHECKING:
@@ -28,25 +28,22 @@ if TYPE_CHECKING:
 
     from playwright.sync_api import Page
 
-PROJECT_NAME: Final[str] = "test"
-VERSION_NAME: Final[str] = "0.1+e2e-sbom"
-FILE_NAME: Final[str] = "apache-test-0.2.tar.gz"
-CURRENT_DIR: Final[pathlib.Path] = pathlib.Path(__file__).parent.resolve()
-
 
 @pytest.fixture
 def page_release_with_file(page: Page) -> Generator[Page]:
     helpers.log_in(page)
 
-    helpers.delete_release_if_exists(page, PROJECT_NAME, VERSION_NAME)
+    helpers.delete_release_if_exists(page, sbom_helpers.PROJECT_NAME, 
sbom_helpers.VERSION_NAME)
 
-    helpers.visit(page, f"/start/{PROJECT_NAME}")
-    page.get_by_role("textbox").type(VERSION_NAME)
+    helpers.visit(page, f"/start/{sbom_helpers.PROJECT_NAME}")
+    page.get_by_role("textbox").type(sbom_helpers.VERSION_NAME)
     page.get_by_role("button", name="Start new release").click()
-    helpers.visit(page, f"/upload/{PROJECT_NAME}/{VERSION_NAME}")
-    
page.locator('input[name="file_data"]').set_input_files(f"{CURRENT_DIR}/../test_files/{FILE_NAME}")
+    helpers.visit(page, 
f"/upload/{sbom_helpers.PROJECT_NAME}/{sbom_helpers.VERSION_NAME}")
+    page.locator('input[name="file_data"]').set_input_files(
+        f"{sbom_helpers.CURRENT_DIR}/../test_files/{sbom_helpers.FILE_NAME}"
+    )
     page.get_by_role("button", name="Add files").click()
-    page.wait_for_url(f"**/compose/{PROJECT_NAME}/{VERSION_NAME}")
-    helpers.visit(page, f"/compose/{PROJECT_NAME}/{VERSION_NAME}")
+    
page.wait_for_url(f"**/compose/{sbom_helpers.PROJECT_NAME}/{sbom_helpers.VERSION_NAME}")
+    helpers.visit(page, 
f"/compose/{sbom_helpers.PROJECT_NAME}/{sbom_helpers.VERSION_NAME}")
     page.wait_for_selector("#ongoing-tasks-banner", state="hidden")
     yield page
diff --git a/tests/e2e/sbom/helpers.py b/tests/e2e/sbom/helpers.py
new file mode 100644
index 0000000..d5dc363
--- /dev/null
+++ b/tests/e2e/sbom/helpers.py
@@ -0,0 +1,24 @@
+# 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 pathlib
+from typing import Final
+
+PROJECT_NAME: Final[str] = "test"
+VERSION_NAME: Final[str] = "0.1+e2e-sbom"
+FILE_NAME: Final[str] = "apache-test-0.2.tar.gz"
+CURRENT_DIR: Final[pathlib.Path] = pathlib.Path(__file__).parent.resolve()
diff --git a/tests/e2e/sbom/test_post.py b/tests/e2e/sbom/test_post.py
index cbc2109..207bef3 100644
--- a/tests/e2e/sbom/test_post.py
+++ b/tests/e2e/sbom/test_post.py
@@ -15,25 +15,28 @@
 # specific language governing permissions and limitations
 # under the License.
 
-import e2e.helpers as helpers  # type: ignore[reportMissingImports]
-from e2e.sbom.conftest import FILE_NAME, PROJECT_NAME, VERSION_NAME  # type: 
ignore[reportMissingImports]
+import e2e.helpers as helpers
+import e2e.sbom.helpers as sbom_helpers
 from playwright.sync_api import Page, expect
 
 
 def test_sbom_generate(page_release_with_file: Page) -> None:
-    # Make sure test file exists
-    file_cell = page_release_with_file.get_by_role("cell", name=FILE_NAME)
+    # Make sure that the test file exists
+    file_cell = page_release_with_file.get_by_role("cell", 
name=sbom_helpers.FILE_NAME)
     expect(file_cell).to_be_visible()
 
     # Generate an SBOM for the file
-    helpers.visit(page_release_with_file, 
f"/draft/tools/{PROJECT_NAME}/{VERSION_NAME}/{FILE_NAME}")
+    helpers.visit(
+        page_release_with_file,
+        
f"/draft/tools/{sbom_helpers.PROJECT_NAME}/{sbom_helpers.VERSION_NAME}/{sbom_helpers.FILE_NAME}",
+    )
     generate_button = page_release_with_file.get_by_role("button", name="SBOM")
     generate_button.click()
 
-    # Check the generated SBOM exists now
-    helpers.visit(page_release_with_file, 
f"/compose/{PROJECT_NAME}/{VERSION_NAME}")
+    # Check that the generated SBOM exists now
+    helpers.visit(page_release_with_file, 
f"/compose/{sbom_helpers.PROJECT_NAME}/{sbom_helpers.VERSION_NAME}")
     page_release_with_file.wait_for_selector("#ongoing-tasks-banner", 
state="hidden")
     page_release_with_file.reload()
 
-    sbom_cell = page_release_with_file.get_by_role("cell", 
name=f"{FILE_NAME}.cdx.json")
+    sbom_cell = page_release_with_file.get_by_role("cell", 
name=f"{sbom_helpers.FILE_NAME}.cdx.json")
     expect(sbom_cell).to_be_visible()


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to