feat: download - fase 02
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -4,6 +4,8 @@ import asyncio
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
@@ -55,6 +57,75 @@ class DownloadApiGoldenTest(unittest.TestCase):
|
||||
|
||||
def test_download_directory_type_conflict(self) -> None:
|
||||
(self.root / "docs").mkdir()
|
||||
(self.root / "docs" / "a.txt").write_text("a", encoding="utf-8")
|
||||
|
||||
response = self._get("/api/files/download?path=storage1/docs")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('attachment; filename="docs.zip"', response.headers.get("content-disposition", ""))
|
||||
with zipfile.ZipFile(BytesIO(response.content)) as archive:
|
||||
self.assertIn("docs/", archive.namelist())
|
||||
self.assertIn("docs/a.txt", archive.namelist())
|
||||
self.assertEqual(archive.read("docs/a.txt"), b"a")
|
||||
|
||||
def test_download_multi_file_selection_as_zip(self) -> None:
|
||||
(self.root / "a.txt").write_text("A", encoding="utf-8")
|
||||
(self.root / "b.txt").write_text("B", encoding="utf-8")
|
||||
|
||||
response = self._get("/api/files/download?path=storage1/a.txt&path=storage1/b.txt")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertRegex(
|
||||
response.headers.get("content-disposition", ""),
|
||||
r'attachment; filename="kodidownload-\d{8}-\d{6}\.zip"',
|
||||
)
|
||||
with zipfile.ZipFile(BytesIO(response.content)) as archive:
|
||||
self.assertIn("a.txt", archive.namelist())
|
||||
self.assertIn("b.txt", archive.namelist())
|
||||
self.assertEqual(archive.read("a.txt"), b"A")
|
||||
self.assertEqual(archive.read("b.txt"), b"B")
|
||||
|
||||
def test_download_multi_directory_selection_as_zip(self) -> None:
|
||||
(self.root / "dir1" / "sub").mkdir(parents=True)
|
||||
(self.root / "dir2").mkdir()
|
||||
(self.root / "dir1" / "sub" / "a.txt").write_text("A", encoding="utf-8")
|
||||
(self.root / "dir2" / "b.txt").write_text("B", encoding="utf-8")
|
||||
|
||||
response = self._get("/api/files/download?path=storage1/dir1&path=storage1/dir2")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertRegex(
|
||||
response.headers.get("content-disposition", ""),
|
||||
r'attachment; filename="kodidownload-\d{8}-\d{6}\.zip"',
|
||||
)
|
||||
with zipfile.ZipFile(BytesIO(response.content)) as archive:
|
||||
self.assertIn("dir1/", archive.namelist())
|
||||
self.assertIn("dir1/sub/", archive.namelist())
|
||||
self.assertIn("dir1/sub/a.txt", archive.namelist())
|
||||
self.assertIn("dir2/b.txt", archive.namelist())
|
||||
|
||||
def test_download_mixed_file_and_directory_selection_as_zip(self) -> None:
|
||||
(self.root / "readme.txt").write_text("R", encoding="utf-8")
|
||||
(self.root / "photos" / "nested").mkdir(parents=True)
|
||||
(self.root / "photos" / "nested" / "img.txt").write_text("P", encoding="utf-8")
|
||||
|
||||
response = self._get("/api/files/download?path=storage1/readme.txt&path=storage1/photos")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertRegex(
|
||||
response.headers.get("content-disposition", ""),
|
||||
r'attachment; filename="kodidownload-\d{8}-\d{6}\.zip"',
|
||||
)
|
||||
with zipfile.ZipFile(BytesIO(response.content)) as archive:
|
||||
self.assertIn("readme.txt", archive.namelist())
|
||||
self.assertIn("photos/", archive.namelist())
|
||||
self.assertIn("photos/nested/img.txt", archive.namelist())
|
||||
|
||||
def test_download_directory_with_symlink_rejected(self) -> None:
|
||||
target = self.root / "real.txt"
|
||||
target.write_text("x", encoding="utf-8")
|
||||
(self.root / "docs").mkdir()
|
||||
(self.root / "docs" / "link.txt").symlink_to(target)
|
||||
|
||||
response = self._get("/api/files/download?path=storage1/docs")
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
||||
self.assertIn('function openContextMenu(pane, entry, event)', app_js)
|
||||
self.assertIn('function closeContextMenu()', app_js)
|
||||
self.assertIn('function isOpenableSelection(item)', app_js)
|
||||
self.assertIn('async function downloadFileRequest(path)', app_js)
|
||||
self.assertIn('async function downloadFileRequest(paths)', app_js)
|
||||
self.assertIn('function applyContextMenuSelection()', app_js)
|
||||
self.assertIn('function startContextMenuOpen()', app_js)
|
||||
self.assertIn('function startContextMenuEdit()', app_js)
|
||||
@@ -239,9 +239,9 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
||||
self.assertIn('const editableSingle = items.length === 1 && isEditableSelection(items[0]);', app_js)
|
||||
self.assertIn('elements.editButton.classList.toggle("hidden", isMulti || items.length !== 1 || items[0].kind !== "file");', app_js)
|
||||
self.assertIn('elements.editButton.disabled = !editableSingle;', app_js)
|
||||
self.assertIn('const downloadableSingle = items.length === 1 && items[0].kind === "file";', app_js)
|
||||
self.assertIn('elements.downloadButton.classList.toggle("hidden", !downloadableSingle);', app_js)
|
||||
self.assertIn('elements.downloadButton.disabled = !downloadableSingle;', app_js)
|
||||
self.assertIn('const downloadableSelection = items.length > 0;', app_js)
|
||||
self.assertIn('elements.downloadButton.classList.remove("hidden");', app_js)
|
||||
self.assertIn('elements.downloadButton.disabled = !downloadableSelection;', app_js)
|
||||
self.assertIn('elements.renameButton.classList.toggle("hidden", isMulti);', app_js)
|
||||
self.assertIn('elements.copyButton.classList.remove("hidden");', app_js)
|
||||
self.assertIn('elements.copyButton.disabled = items.length === 0;', app_js)
|
||||
@@ -250,8 +250,8 @@ class UiSmokeGoldenTest(unittest.TestCase):
|
||||
self.assertIn('elements.propertiesButton.disabled = items.length === 0;', app_js)
|
||||
self.assertIn('openCurrentDirectory();', app_js)
|
||||
self.assertIn('openEditor();', app_js)
|
||||
self.assertIn('downloadFileRequest(selected.path);', app_js)
|
||||
self.assertIn('anchor.download = selected.name;', app_js)
|
||||
self.assertIn('downloadFileRequest(selectedItems.map((item) => item.path));', app_js)
|
||||
self.assertIn('anchor.download = fileName || selected.name;', app_js)
|
||||
self.assertIn('openRenamePopup();', app_js)
|
||||
self.assertIn('startCopySelected();', app_js)
|
||||
self.assertIn('openF6Flow();', app_js)
|
||||
|
||||
Reference in New Issue
Block a user