from __future__ import annotations import pytest from server.infrastructure.ssh.remote_archive import ( archive_member_list_command, archive_member_type_list_command, decompress_command, tar_compress_commands, validate_archive_member_names, validate_archive_member_types, zip_compress_commands, ) def test_tar_compress_commands_end_options_before_member_names() -> None: commands = tar_compress_commands(["/var/www/-leading"], "/tmp/out.tar.gz") assert commands assert all(" -- -leading" in command for command in commands) assert commands[0] == "tar -czf /tmp/out.tar.gz -C /var/www -- -leading" def test_zip_compress_commands_end_options_before_archive_and_members() -> None: commands = zip_compress_commands(["/var/www/-leading"], "/tmp/out.zip") assert commands assert all("zip -qr -- " in command for command in commands) assert commands[0] == "cd /var/www && zip -qr -- /tmp/out.zip -leading" def test_zip_compress_commands_allow_dash_prefixed_archive_basename() -> None: commands = zip_compress_commands(["/var/www/-leading"], "/var/www/-archive.zip") assert commands[0] == "cd /var/www && zip -qr -- -archive.zip -leading" def test_archive_member_list_commands() -> None: assert archive_member_list_command("/tmp/a.tgz", "tar.gz") == "tar -tzf /tmp/a.tgz" assert archive_member_type_list_command("/tmp/a.zip", "zip") == "zipinfo -l /tmp/a.zip" def test_decompress_command_uses_safer_tar_flags() -> None: assert decompress_command("/tmp/a.tgz", "/var/www", "tar.gz") == ( "tar --no-same-owner --no-same-permissions -xzf /tmp/a.tgz -C /var/www" ) def test_validate_archive_member_names_rejects_parent_traversal() -> None: with pytest.raises(ValueError, match="路径穿越"): validate_archive_member_names("safe.txt\n../evil.txt\n") def test_validate_archive_member_names_rejects_absolute_path() -> None: with pytest.raises(ValueError, match="绝对路径"): validate_archive_member_names("/etc/passwd\n") def test_validate_archive_member_names_rejects_windows_separator() -> None: with pytest.raises(ValueError, match="非法路径"): validate_archive_member_names("dir\\evil.txt\n") def test_validate_archive_member_names_allows_relative_files_and_dirs() -> None: assert validate_archive_member_names("dir/\ndir/file.txt\n./ok.txt\n") == [ "dir/", "dir/file.txt", "./ok.txt", ] def test_validate_archive_member_types_rejects_symlink() -> None: with pytest.raises(ValueError, match="符号链接"): validate_archive_member_types("lrwxrwxrwx root/root 0 2026-01-01 link -> /etc\n") def test_validate_archive_member_types_rejects_device_file() -> None: with pytest.raises(ValueError, match="字符设备"): validate_archive_member_types("crw-r--r-- root/root 0 2026-01-01 dev\n") def test_validate_archive_member_types_allows_files_dirs_and_zipinfo_headers() -> None: validate_archive_member_types( "Archive: a.zip\n" "drwxr-xr-x 3.0 unx 0 bx stor 26-Jan-01 00:00 dir/\n" "-rw-r--r-- 3.0 unx 1 tx stor 26-Jan-01 00:00 dir/a.txt\n" "2 files, 1 bytes uncompressed\n" ) def test_tar_compress_staged_move_uses_option_terminator() -> None: commands = tar_compress_commands(["/opt/site"], "/var/backups/-out.tar.gz") assert "&& mv -f -- " in commands[1] def test_zip_compress_staged_move_uses_option_terminator() -> None: commands = zip_compress_commands(["/opt/site"], "/var/backups/-out.zip") assert "&& mv -f -- " in commands[1]