Files
drhider/tests/test_zip.py
T

229 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Тесты нативного ZIP: unzip → buildZip.
Эмулирует JS-логику:
1. Сервер возвращает ZIP с .md + mapping.csv
2. unzip() распаковывает
3. buildZip() перепаковывает без mapping.csv
4. Результат должен быть валидным ZIP
Запуск: python3 tests/test_zip.py
"""
import io
import zipfile
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
passed = 0
failed = 0
def check(name, condition):
global passed, failed
if condition:
passed += 1
print(f" ✅ {name}")
else:
failed += 1
print(f" ❌ {name}")
# ═══════════════════════════════════════════════════════════════
# Test 1: roundtrip — server ZIP → extract → rebuild → verify
# ═══════════════════════════════════════════════════════════════
print("\n=== Тест 1: unzip + buildZip roundtrip ===")
# Создаём ZIP как сервер
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
zf.writestr("договор_obfuscated.md", "# Заголовок\nТекст договора с Иванов_0001.")
zf.writestr("спецификация_obfuscated.md", "| Услуга | Цена |\n|---|---|\n| Хостинг | 1000 |")
zf.writestr("mapping.csv", "тип,оригинал,замена\nperson,Иванов И.И.,Иванов_0001\n")
server_zip = buf.getvalue()
# Распаковываем (эмуляция unzip)
with zipfile.ZipFile(io.BytesIO(server_zip)) as zf:
extracted = {n: zf.read(n) for n in zf.namelist()}
check("3 файла в ZIP", len(extracted) == 3)
check("mapping.csv есть", "mapping.csv" in extracted)
check("договор есть", "договор_obfuscated.md" in extracted)
# Отделяем mapping от файлов
md_files = {n: d for n, d in extracted.items() if n != "mapping.csv"}
mapping_data = extracted.get("mapping.csv", b"").decode("utf-8")
check("mapping читается", "Иванов_0001" in mapping_data)
check("договор читается", "Иванов_0001" in md_files["договор_obfuscated.md"].decode("utf-8"))
# Перепаковываем без mapping.csv (эмуляция buildZip)
buf2 = io.BytesIO()
with zipfile.ZipFile(buf2, 'w') as zf:
for name, data in md_files.items():
zf.writestr(name, data)
rebuilt_zip = buf2.getvalue()
# Проверяем перепакованный ZIP
with zipfile.ZipFile(io.BytesIO(rebuilt_zip)) as zf:
names = zf.namelist()
check("mapping.csv НЕТ в перепакованном", "mapping.csv" not in names)
check("договор есть в перепакованном", "договор_obfuscated.md" in names)
check("спецификация есть в перепакованном", "спецификация_obfuscated.md" in names)
content = zf.read("договор_obfuscated.md").decode("utf-8")
check("содержимое договора сохранено", "Иванов_0001" in content)
# ═══════════════════════════════════════════════════════════════
# Test 2: ZIP с одним файлом
# ═══════════════════════════════════════════════════════════════
print("\n=== Тест 2: один файл без mapping ===")
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
zf.writestr("file.md", "content")
server_zip = buf.getvalue()
with zipfile.ZipFile(io.BytesIO(server_zip)) as zf:
extracted = {n: zf.read(n) for n in zf.namelist()}
md_files = {n: d for n, d in extracted.items() if n != "mapping.csv"}
buf2 = io.BytesIO()
with zipfile.ZipFile(buf2, 'w') as zf:
for name, data in md_files.items():
zf.writestr(name, data)
with zipfile.ZipFile(io.BytesIO(buf2.getvalue())) as zf:
check("один файл", len(zf.namelist()) == 1)
check("контент сохранён", zf.read("file.md").decode("utf-8") == "content")
# ═══════════════════════════════════════════════════════════════
# Test 3: .doc файл (binary, не меняется)
# ═══════════════════════════════════════════════════════════════
print("\n=== Тест 3: бинарный .doc ===")
binary_data = bytes([0xD0, 0xCF, 0x11, 0xE0]) + b"\x00" * 100 # OLE2 magic
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
zf.writestr("file_obfuscated.doc", binary_data)
zf.writestr("mapping.csv", "тип,оригинал,замена\n")
with zipfile.ZipFile(io.BytesIO(buf.getvalue())) as zf:
extracted = {n: zf.read(n) for n in zf.namelist()}
md_files = {n: d for n, d in extracted.items() if n != "mapping.csv"}
buf2 = io.BytesIO()
with zipfile.ZipFile(buf2, 'w') as zf:
for name, data in md_files.items():
zf.writestr(name, data)
with zipfile.ZipFile(io.BytesIO(buf2.getvalue())) as zf:
check(".doc сохранён", "file_obfuscated.doc" in zf.namelist())
content = zf.read("file_obfuscated.doc")
check("размер сохранён", len(content) == len(binary_data))
check("данные сохранены", content == binary_data)
# ═══════════════════════════════════════════════════════════════
# Test 4: CSV с кириллицей и кавычками
# ═══════════════════════════════════════════════════════════════
print("\n=== Тест 4: CSV с кириллицей ===")
csv_content = 'тип,оригинал,замена\ncompany,"ООО ""НУБЕС""",ООО_Технология_0001\nperson,Иванов И.И.,Иванов_0002\n'
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
zf.writestr("test.md", "text")
zf.writestr("mapping.csv", csv_content)
with zipfile.ZipFile(io.BytesIO(buf.getvalue())) as zf:
mapping = zf.read("mapping.csv").decode("utf-8")
check("BOM есть (\\ufeff)", mapping.startswith('\ufeff') or mapping.startswith('тип'))
check("НУБЕС в mapping", "НУБЕС" in mapping)
check("Иванов в mapping", "Иванов" in mapping)
# ═══════════════════════════════════════════════════════════════
# Test 5: множественные mapping.csv объединяются
# ═══════════════════════════════════════════════════════════════
print("\n=== Тест 5: объединение mapping.csv ===")
all_mappings = []
for csv_text in [
"тип,оригинал,замена\nperson,Иванов,Иванов_0001\n",
"тип,оригинал,замена\nperson,Петров,Петров_0002\ncompany,Ромашка,Ромашка_0001\n",
]:
lines = csv_text.split("\n")
if len(all_mappings) == 0:
all_mappings.append(lines[0]) # заголовок
all_mappings.append(lines[1]) # первая строка
else:
all_mappings.append(lines[1]) # только данные, без заголовка
if len(lines) > 2 and lines[2]:
all_mappings.append(lines[2])
result = "\n".join(all_mappings)
check("заголовок один", result.count("тип,оригинал,замена") == 1)
check("Иванов есть", "Иванов" in result)
check("Петров есть", "Петров" in result)
check("Ромашка есть", "Ромашка" in result)
# ═══════════════════════════════════════════════════════════════
# Test 6: точные смещения в local file header (баг start+18)
# ═══════════════════════════════════════════════════════════════
print("\n=== Тест 6: валидация смещений local file header ===")
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
zf.writestr("test.md", "Hello World")
raw = buf.getvalue()
import struct
sig_pos = raw.find(b'PK\x03\x04')
check("сигнатура найдена", sig_pos >= 0)
def u16(off): return struct.unpack_from('<H', raw, off)[0]
def u32(off): return struct.unpack_from('<I', raw, off)[0]
start = sig_pos
check("signature = 0x04034b50", u32(start) == 0x04034b50)
name_len = u16(start + 26)
extra_len = u16(start + 28)
comp_size = u32(start + 18) # смещение которое было сломано
uncomp_size = u32(start + 22)
check("compressed size > 0", comp_size > 0)
check("compressed == uncompressed", comp_size == uncomp_size)
check("filename length", name_len == len("test.md"))
name_start = start + 30
name = raw[name_start:name_start + name_len].decode('ascii')
check("имя файла", name == "test.md")
data_start = name_start + name_len + extra_len
data = raw[data_start:data_start + comp_size].decode('ascii')
check("данные файла", data == "Hello World")
# ═══════════════════════════════════════════════════════════════
# Итог
# ═══════════════════════════════════════════════════════════════
print(f"\n{'='*50}")
print(f"Результат: {passed} OK, {failed} FAIL из {passed + failed}")
if failed > 0:
print("❌ ТЕСТЫ ПРОВАЛЕНЫ")
sys.exit(1)
else:
print("✅ ВСЕ ТЕСТЫ ПРОЙДЕНЫ")