better trash; no favicon
This commit is contained in:
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user