fix(backup): say when a restored archive lands without an owner (#2656)

`created_by_id` is not attribution, it is the column the access check runs
on: `_ensure_archive_visible` fails closed on NULL, so an ownerless archive
is a 404 for every caller without `archives:read_all` and never appears in
the ownership-scoped list queries.

On the overwrite path an absent key correctly leaves the local owner alone
— that rule is deliberate and unchanged. On the insert path there is no
local row to fall back on, so the archive lands ownerless, and nothing said
so. The restore reported N archives restored while the user who asked for
them saw none. Two ways in, both silent: a commit taken before the
collector recorded the column (every pre-#2656 backup), and an archive that
genuinely had no owner on the source instance.

Adds `archivesOwnerUnknown`, emitted on insert only, and suppressed when
the stale-id branch has already spoken for that row so one cause does not
produce two notes. Wording mirrors `archivesOwnerCleared` because the
consequence and the remedy are the same; the cause is not, so it is a
separate code rather than a reuse.

Five tests, plus the existing `test_a_backup_without_the_key_still_restores`
renamed and tightened — it asserted the silence this fixes. 13 locales back
in parity at 5772 leaves. No modal change: notes render through
`translateCoded`, which resolves by code.
This commit is contained in:
jmoore-skild
2026-08-04 08:57:35 -04:00
parent 329b506240
commit a85fa66dcc
15 changed files with 104 additions and 2 deletions
+71 -2
View File
@@ -2063,8 +2063,14 @@ class TestRestoredArchiveOwnership:
assert sum(1 for note in _messages(tally) if "owner cleared" in note) == 1
@pytest.mark.asyncio
async def test_a_backup_without_the_key_still_restores(self, db_session):
"""Backups taken before the collector recorded it just can't know the owner."""
async def test_a_backup_without_the_key_still_restores_and_says_so(self, db_session):
"""Backups taken before the collector recorded it just can't know the owner.
The archive is worth restoring anyway, but it lands ownerless — which is
a 404 for everyone without ``archives:read_all``. Reporting N restored
while the user who asked for them sees none is the failure mode the note
exists to prevent.
"""
tally = _CategoryTally()
await _service()._restore_archives(db_session, {"archives": [self._entry()]}, False, tally, {})
@@ -2072,7 +2078,70 @@ class TestRestoredArchiveOwnership:
row = (await db_session.execute(select(PrintArchive))).scalar_one()
assert row.created_by_id is None
assert tally.restored == 1
assert not any("owner cleared" in note for note in _messages(tally))
assert any("without an owner" in note and "archives:read_all" in note for note in _messages(tally))
@pytest.mark.asyncio
async def test_an_explicitly_ownerless_archive_is_reported_too(self, db_session):
"""Same consequence, so the same note: the source row had no owner either."""
tally = _CategoryTally()
await _service()._restore_archives(
db_session, {"archives": [self._entry(created_by_id=None)]}, False, tally, {}
)
await db_session.commit()
assert any("without an owner" in note for note in _messages(tally))
@pytest.mark.asyncio
async def test_a_stale_owner_is_not_reported_twice(self, db_session):
"""One row, one cause, one note — the cleared-owner branch already spoke."""
tally = _CategoryTally()
await _service()._restore_archives(
db_session, {"archives": [self._entry(created_by_id=4242)]}, False, tally, {}
)
await db_session.commit()
assert any("owner cleared" in note for note in _messages(tally))
assert not any("without an owner" in note for note in _messages(tally))
@pytest.mark.asyncio
async def test_a_known_owner_is_not_reported(self, db_session):
user = await self._user(db_session)
tally = _CategoryTally()
await _service()._restore_archives(
db_session, {"archives": [self._entry(created_by_id=user.id)]}, False, tally, {}
)
await db_session.commit()
assert not any("without an owner" in note for note in _messages(tally))
@pytest.mark.asyncio
async def test_the_unknown_owner_note_is_not_emitted_on_overwrite(self, db_session):
"""Overwrite keeps the local owner, so there is nothing to warn about."""
bob = await self._user(db_session, "bob")
db_session.add(
PrintArchive(
filename="benchy.3mf",
file_path="/data/benchy.3mf",
file_size=2048,
content_hash="abc123",
started_at=datetime(2026, 3, 1, 10, 0, 0),
created_by_id=bob.id,
)
)
await db_session.commit()
tally = _CategoryTally()
await _service()._restore_archives(db_session, {"archives": [self._entry()]}, True, tally, {})
await db_session.commit()
row = (await db_session.execute(select(PrintArchive))).scalar_one()
assert row.created_by_id == bob.id
assert not any("without an owner" in note for note in _messages(tally))
@pytest.mark.asyncio
async def test_overwrite_makes_the_local_owner_match_the_backup(self, db_session):