better trash; no favicon

This commit is contained in:
Ludwig Lehnert
2026-08-19 10:07:25 +00:00
parent 1c4e07c713
commit d618957b68
14 changed files with 263 additions and 54 deletions
+18
View File
@@ -198,6 +198,11 @@ class ProgressParsingTests(unittest.TestCase):
handle.write(b"a" * 3)
with open(os.path.join(nested, "two.bin"), "wb") as handle:
handle.write(b"b" * 5)
os.makedirs(os.path.join(tmpdir, ".trash", "alice"))
with open(
os.path.join(tmpdir, ".trash", "alice", "deleted.bin"), "wb"
) as handle:
handle.write(b"x" * 100)
sizes, total = backup.measure_backup_payload([(tmpdir, "data/private")])
@@ -222,6 +227,7 @@ class GroupArchiveTests(unittest.TestCase):
source_root = os.path.join(tmpdir, "groups")
os.makedirs(os.path.join(source_root, "data", "Finance"))
os.makedirs(os.path.join(source_root, "archive", "Former"))
os.makedirs(os.path.join(source_root, "data", ".trash", "alice"))
os.makedirs(os.path.join(source_root, "metadata"))
with open(
os.path.join(source_root, "data", "Finance", "report.txt"),
@@ -241,6 +247,12 @@ class GroupArchiveTests(unittest.TestCase):
encoding="utf-8",
) as handle:
handle.write("preserve me")
with open(
os.path.join(source_root, "data", ".trash", "alice", "deleted.txt"),
"w",
encoding="utf-8",
) as handle:
handle.write("retained deletion")
with open(
os.path.join(source_root, "metadata", "index.txt"),
"w",
@@ -280,6 +292,9 @@ class GroupArchiveTests(unittest.TestCase):
self.assertFalse(
os.path.exists(os.path.join(staged_root, "data", "Finance"))
)
self.assertFalse(
os.path.exists(os.path.join(staged_root, "data", ".trash"))
)
self.assertEqual(len(commands), 2)
for command, kwargs in commands:
@@ -405,6 +420,8 @@ class BackendProgressCommandTests(unittest.TestCase):
self.assertIn("--log-level", command)
self.assertIn("INFO", command)
self.assertIn("--config", command)
self.assertIn("--exclude", command)
self.assertIn("/.trash/**", command)
self.assertEqual(
run_streaming.call_args.kwargs["progress"].backend_name, "rclone"
)
@@ -430,6 +447,7 @@ class BackendProgressCommandTests(unittest.TestCase):
self.assertIn("--mkpath", command)
self.assertIn("--progress", command)
self.assertIn("--outbuf=L", command)
self.assertIn("--exclude=/.trash/", command)
self.assertEqual(
run_streaming.call_args.kwargs["progress"].backend_name, "rsync"
)
+46
View File
@@ -142,6 +142,52 @@ class TrashTests(unittest.TestCase):
self.assertTrue(os.path.isfile(recent))
self.assertEqual(trash.list_items(now=now)["matched"], 1)
def test_temporary_and_document_lock_files_are_never_exposed_or_retained(self):
with tempfile.TemporaryDirectory() as tmpdir:
env = self.roots(tmpdir)
with mock.patch.dict(os.environ, env):
trash.ensure_trash_roots()
temporary = self.recycled_file(
env["GROUP_ROOT"], "alice/Finance/713A292F.tmp", b"temp"
)
versioned_temporary = self.recycled_file(
env["GROUP_ROOT"],
"alice/Finance/Copy #2 of 713A292F.TMP",
b"versioned temp",
)
document_lock = self.recycled_file(
env["GROUP_ROOT"],
"alice/Finance/~$RG Eingang 2026.xlsx",
b"lock",
)
versioned_lock = self.recycled_file(
env["GROUP_ROOT"],
"alice/Finance/Copy #3 of ~$RG Eingang 2026.xlsx",
b"versioned lock",
)
self.assertEqual(trash.list_items()["matched"], 0)
with self.assertRaises(FileNotFoundError):
trash.open_download(
trash.encode_item_id(
"Data", "alice/Finance/713A292F.tmp"
)
)
result = trash.cleanup_expired()
self.assertEqual(
result,
{"removed": 4, "removedBytes": 36},
)
for path in (
temporary,
versioned_temporary,
document_lock,
versioned_lock,
):
self.assertFalse(os.path.exists(path))
def test_cleanup_removes_expired_symlinks_without_following_them(self):
with tempfile.TemporaryDirectory() as tmpdir:
env = self.roots(tmpdir)
+40 -2
View File
@@ -1332,19 +1332,57 @@ class WebPresentationTests(unittest.TestCase):
share_config = config.split(f"[{share}]", 1)[1]
if next_share:
share_config = share_config.split(f"[{next_share}]", 1)[0]
self.assertIn("vfs objects = acl_xattr recycle full_audit", share_config)
# Audit must wrap recycle so an SMB deletion remains unlinkat in the
# activity log instead of becoming the recycle module's renameat.
self.assertIn("vfs objects = acl_xattr full_audit recycle", share_config)
self.assertIn("recycle:repository = .trash/%U", share_config)
self.assertIn("recycle:keeptree = yes", share_config)
self.assertIn("recycle:versions = yes", share_config)
self.assertIn("recycle:touch_mtime = yes", share_config)
self.assertIn("recycle:exclude = *.tmp,*.TMP,~$*", share_config)
self.assertIn("recycle:exclude_dir = .trash", share_config)
self.assertIn("veto files = /.trash/", share_config)
self.assertIn("acl_xattr recycle full_audit", init_script)
for module in ("acl_xattr", "recycle", "full_audit"):
self.assertIn(module, init_script)
self.assertIn("/app/trash.py --cleanup", init_script)
self.assertIn("TRASH_RETENTION_DAYS", init_script)
self.assertIn("COPY app/trash.py /app/trash.py", dockerfile)
def test_application_has_no_favicon(self):
root = os.path.join(os.path.dirname(__file__), "..")
web_root = os.path.join(root, "app", "web")
with open(os.path.join(web_root, "index.html"), encoding="utf-8") as handle:
index = handle.read()
with open(os.path.join(root, "app", "web_ui.py"), encoding="utf-8") as handle:
server = handle.read()
self.assertNotIn("favicon", index.casefold())
self.assertNotIn("favicon", server.casefold())
self.assertFalse(os.path.exists(os.path.join(web_root, "favicon.svg")))
def test_disposable_dc_is_compatible_with_rootless_podman(self):
root = os.path.join(os.path.dirname(__file__), "..")
with open(
os.path.join(root, "dev", "ad-dc.Dockerfile"),
encoding="utf-8",
) as handle:
dockerfile = handle.read()
with open(
os.path.join(root, "dev", "ad-entrypoint.sh"),
encoding="utf-8",
) as handle:
entrypoint = handle.read()
with open(os.path.join(root, "scripts", "dev"), encoding="utf-8") as handle:
launcher = handle.read()
self.assertIn("lowerBound: 10000", dockerfile)
self.assertIn("upperBound: 60000", dockerfile)
self.assertIn("xattr_tdb:file = /var/lib/samba/state/xattr.tdb", entrypoint)
self.assertIn("samba-tool ntacl sysvolcheck", entrypoint)
self.assertNotIn("--use-rfc2307", entrypoint)
self.assertNotIn("--cap-add SYS_ADMIN", launcher)
class TlsSummaryTests(unittest.TestCase):
@mock.patch("app.web_ui.ssl._ssl._test_decode_cert")