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


The following commit(s) were added to refs/heads/sbp by this push:
     new 45f2bfd0 Add tests for file metadata, including testing an alternative 
design
45f2bfd0 is described below

commit 45f2bfd0580e93e4a2e3eb9e196902193416c608
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Mar 17 15:39:05 2026 +0000

    Add tests for file metadata, including testing an alternative design
---
 tests/unit/test_create_revision.py    |  67 +++++++++++++++++
 tests/unit/test_release_file_state.py | 132 ++++++++++++++++++++++++++++++++++
 2 files changed, 199 insertions(+)

diff --git a/tests/unit/test_create_revision.py 
b/tests/unit/test_create_revision.py
index 1b8a7ff0..ffb0f01e 100644
--- a/tests/unit/test_create_revision.py
+++ b/tests/unit/test_create_revision.py
@@ -54,6 +54,7 @@ class FakeRevision:
         self.name = ""
         self.number = ""
         self.parent_name: str | None = None
+        self.seq: int = 0
         self.phase = phase
         self.release = release
         self.release_name = release_name
@@ -85,6 +86,7 @@ class MockSafeData:
             raise RuntimeError("Expected data.add to set _new_revision before 
flush")
         self._new_revision.name = f"{self._new_revision.release_name} 
{self._new_number}"
         self._new_revision.number = self._new_number
+        self._new_revision.seq = int(self._new_number)
         self._new_revision.parent_name = self._parent_name
 
     async def _merge(self, obj: object) -> object:
@@ -284,6 +286,71 @@ async def 
test_modify_failed_error_propagates_and_cleans_up(tmp_path: pathlib.Pa
     assert not os.listdir(tmp_path)
 
 
[email protected]
+async def test_v1_previous_attestable_suppresses_file_state_rows(tmp_path: 
pathlib.Path):
+    release = mock.MagicMock()
+    release.phase = sql.ReleasePhase.RELEASE_CANDIDATE_DRAFT
+    release.project = mock.MagicMock()
+    release.project.release_policy = None
+    release.release_policy = None
+    release_name = sql.release_name("proj", "1.0")
+
+    old_revision = mock.MagicMock()
+    old_revision.name = f"{release_name} 00001"
+    old_revision.number = "00001"
+    old_revision.safe_number = safe.RevisionNumber("00001")
+
+    import contextlib
+
+    import atr.models.attestable as attestable
+
+    v1_attestable = attestable.AttestableV1(paths={"example.txt": "hash1"})
+
+    mock_session = _mock_db_session(release)
+    participant = _make_participant()
+    safe_data = MockSafeData(parent_name=old_revision.name, new_number="00002")
+
+    patches = [
+        mock.patch.object(revision.aiofiles.os, "makedirs", 
new_callable=mock.AsyncMock),
+        mock.patch.object(revision.aiofiles.os, "rename", 
new_callable=mock.AsyncMock),
+        mock.patch.object(revision.attestable, "load", 
new_callable=mock.AsyncMock, return_value=v1_attestable),
+        mock.patch.object(
+            revision.attestable,
+            "paths_to_hashes_and_sizes",
+            new_callable=mock.AsyncMock,
+            return_value=({"example.txt": "hash2"}, {"example.txt": 100}),
+        ),
+        mock.patch.object(revision.attestable, "write_files_data", 
new_callable=mock.AsyncMock),
+        mock.patch.object(revision.attestable, "compute_classifications", 
return_value={"example.txt": "binary"}),
+        mock.patch.object(revision.db, "session", return_value=mock_session),
+        mock.patch.object(revision.detection, 
"detect_archives_requiring_quarantine", return_value=[]),
+        mock.patch.object(revision.detection, "validate_directory", 
return_value=[]),
+        mock.patch.object(
+            revision.interaction, "latest_revision", 
new_callable=mock.AsyncMock, return_value=old_revision
+        ),
+        mock.patch.object(revision.merge, "merge", 
new_callable=mock.AsyncMock),
+        mock.patch.object(revision.sql, "Revision", 
side_effect=_make_fake_revision),
+        mock.patch.object(revision, "SafeSession", 
return_value=MockSafeSession(safe_data)),
+        mock.patch.object(revision.tasks, "draft_checks", 
new_callable=mock.AsyncMock),
+        mock.patch.object(revision.util, "chmod_directories"),
+        mock.patch.object(revision.util, "chmod_files"),
+        mock.patch.object(revision.util, "create_hard_link_clone", 
new_callable=mock.AsyncMock),
+        mock.patch.object(revision.paths, "get_tmp_dir", 
return_value=tmp_path),
+        mock.patch.object(revision.util, "paths_to_inodes", return_value={}),
+        mock.patch.object(revision.paths, "release_directory", 
return_value=tmp_path / "releases" / "00002"),
+        mock.patch.object(revision.paths, "release_directory_base", 
return_value=tmp_path / "releases"),
+    ]
+
+    with contextlib.ExitStack() as stack:
+        for patch in patches:
+            stack.enter_context(patch)
+        await participant.create_revision_with_quarantine("proj", "1.0", 
"test")
+
+    added_objects = [call.args[0] for call in safe_data.add.call_args_list]
+    file_state_rows = [obj for obj in added_objects if isinstance(obj, 
sql.ReleaseFileState)]
+    assert file_state_rows == []
+
+
 def _make_fake_revision(**kwargs) -> FakeRevision:
     return FakeRevision(**kwargs)
 
diff --git a/tests/unit/test_release_file_state.py 
b/tests/unit/test_release_file_state.py
index b84c8945..034d9a5e 100644
--- a/tests/unit/test_release_file_state.py
+++ b/tests/unit/test_release_file_state.py
@@ -15,9 +15,141 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import atr.attestable as attestable
+import atr.models.attestable as models
 import atr.models.sql as sql
 
 
+def test_can_write_file_state_rows_first_revision():
+    assert attestable.can_write_file_state_rows(previous=None, 
parent_name=None) is True
+
+
+def test_can_write_file_state_rows_missing_attestable_with_parent():
+    assert attestable.can_write_file_state_rows(previous=None, 
parent_name="proj-1.0 00005") is False
+
+
+def test_can_write_file_state_rows_v1_previous():
+    previous = models.AttestableV1(paths={"a.tar.gz": "h1"})
+    assert attestable.can_write_file_state_rows(previous=previous, 
parent_name="proj-1.0 00002") is False
+
+
+def test_can_write_file_state_rows_v2_previous():
+    previous = models.AttestableV2(
+        paths={
+            "a.tar.gz": models.PathEntryV2(content_hash="h1", 
classification="source"),
+        }
+    )
+    assert attestable.can_write_file_state_rows(previous=previous, 
parent_name="proj-1.0 00002") is True
+
+
+def test_compute_file_state_rows_changed_classification():
+    previous = models.AttestableV2(
+        paths={
+            "app.tar.gz": models.PathEntryV2(content_hash="hash1", 
classification="metadata"),
+        },
+    )
+    rows = attestable.compute_file_state_rows(
+        "example-0.0.1",
+        2,
+        {"app.tar.gz": "hash1"},
+        {"app.tar.gz": "source"},
+        previous,
+    )
+
+    assert len(rows) == 1
+    assert rows[0].path == "app.tar.gz"
+    assert rows[0].present is True
+    assert rows[0].content_hash == "hash1"
+    assert rows[0].classification == "source"
+
+
+def test_compute_file_state_rows_changed_hash():
+    previous = models.AttestableV2(
+        paths={
+            "README.md": models.PathEntryV2(content_hash="hash1", 
classification="metadata"),
+        },
+    )
+    rows = attestable.compute_file_state_rows(
+        "example-0.0.1",
+        2,
+        {"README.md": "hash2"},
+        {"README.md": "metadata"},
+        previous,
+    )
+
+    assert len(rows) == 1
+    assert rows[0].path == "README.md"
+    assert rows[0].present is True
+    assert rows[0].content_hash == "hash2"
+    assert rows[0].classification == "metadata"
+
+
+def test_compute_file_state_rows_deleted_path():
+    previous = models.AttestableV2(
+        paths={
+            "old-file.tar.gz": models.PathEntryV2(content_hash="hash1", 
classification="source"),
+        },
+    )
+    rows = attestable.compute_file_state_rows("example-0.0.1", 2, {}, {}, 
previous)
+
+    assert len(rows) == 1
+    assert rows[0].path == "old-file.tar.gz"
+    assert rows[0].present is False
+    assert rows[0].content_hash is None
+    assert rows[0].classification is None
+
+
+def test_compute_file_state_rows_new_path():
+    rows = attestable.compute_file_state_rows(
+        "example-0.0.1",
+        1,
+        {"README.md": "hash1"},
+        {"README.md": "metadata"},
+        None,
+    )
+
+    assert len(rows) == 1
+    assert rows[0].release_name == "example-0.0.1"
+    assert rows[0].path == "README.md"
+    assert rows[0].since_revision_seq == 1
+    assert rows[0].present is True
+    assert rows[0].content_hash == "hash1"
+    assert rows[0].classification == "metadata"
+
+
+def test_compute_file_state_rows_unchanged_path():
+    previous = models.AttestableV2(
+        paths={
+            "README.md": models.PathEntryV2(content_hash="hash1", 
classification="metadata"),
+        },
+    )
+    rows = attestable.compute_file_state_rows(
+        "example-0.0.1",
+        2,
+        {"README.md": "hash1"},
+        {"README.md": "metadata"},
+        previous,
+    )
+
+    assert len(rows) == 0
+
+
+def test_compute_file_state_rows_v1_previous():
+    previous = models.AttestableV1(paths={"README.md": "hash1"})
+    rows = attestable.compute_file_state_rows(
+        "example-0.0.1",
+        2,
+        {"README.md": "hash1"},
+        {"README.md": "metadata"},
+        previous,
+    )
+
+    assert len(rows) == 1
+    assert rows[0].present is True
+    assert rows[0].content_hash == "hash1"
+    assert rows[0].classification == "metadata"
+
+
 def test_release_file_state_deleted():
     state = sql.ReleaseFileState(
         release_name="example-0.0.1",


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

Reply via email to