#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
檔案上傳驗證測試腳本
測試 app.py 中的檔案上傳驗證函數
"""
import sys
import os
from io import BytesIO
# 確保可以導入專案模組
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from app import allowed_file, validate_upload_file
from werkzeug.datastructures import FileStorage
def test_allowed_file():
"""測試檔案副檔名驗證"""
print("="*60)
print("測試 1: 檔案副檔名驗證 (allowed_file)")
print("="*60)
test_cases = [
# (檔案名, 是否應該通過, 描述)
('report.xlsx', True, 'Excel 2007+ 格式'),
('data.xls', True, 'Excel 97-2003 格式'),
('export.csv', True, 'CSV 格式'),
('REPORT.XLSX', True, '大寫副檔名'),
('file.XLS', True, '混合大小寫'),
('malware.exe', False, '執行檔'),
('script.php', False, 'PHP 腳本'),
('shell.sh', False, 'Shell 腳本'),
('image.jpg', False, '圖片檔'),
('document.pdf', False, 'PDF 檔案'),
('archive.zip', False, '壓縮檔'),
('file', False, '無副檔名'),
('', False, '空檔名'),
('.xlsx', False, '只有副檔名'),
('../../etc/passwd.csv', True, '路徑遍歷但副檔名正確'),
('file.csv.exe', False, '雙副檔名攻擊'),
]
passed = 0
failed = 0
for filename, should_pass, description in test_cases:
result = allowed_file(filename)
if result == should_pass:
print(f"✅ 通過: {description} | 檔名: '{filename}' | 結果: {result}")
passed += 1
else:
print(f"❌ 失敗: {description} | 檔名: '{filename}' | 期望: {should_pass} | 實際: {result}")
failed += 1
assert failed == 0
def test_validate_upload_file():
"""測試完整的檔案上傳驗證"""
print("\n" + "="*60)
print("測試 2: 完整檔案上傳驗證 (validate_upload_file)")
print("="*60)
test_cases = [
# (檔名, 是否應該通過, 描述)
('即時業績(全月).xlsx', True, '正常中文檔名'),
('sales_report.csv', True, '正常英文檔名'),
('data 2024.xls', True, '包含空格的檔名'),
('../../../etc/passwd.csv', False, '路徑遍歷攻擊'),
('../../config.py.xlsx', False, '路徑遍歷攻擊'),
('malware.exe', False, '不允許的副檔名'),
('', False, '空檔名'),
('.xlsx', True, 'XSS 嘗試(會被清理)'),
('file|pipe.csv', True, '特殊字元(會被清理)'),
('file\x00null.xlsx', True, 'Null 字元(會被清理)'),
]
passed = 0
failed = 0
for filename, should_pass, description in test_cases:
# 建立模擬的 FileStorage 物件
fake_file = FileStorage(
stream=BytesIO(b"test data"),
filename=filename,
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
is_valid, error_msg, safe_name = validate_upload_file(fake_file)
if is_valid == should_pass:
print(f"✅ 通過: {description}")
print(f" 原始檔名: '{filename}'")
if is_valid:
print(f" 安全檔名: '{safe_name}'")
else:
print(f" 拒絕原因: {error_msg}")
passed += 1
else:
print(f"❌ 失敗: {description}")
print(f" 原始檔名: '{filename}'")
print(f" 期望: {'通過' if should_pass else '拒絕'}")
print(f" 實際: {'通過' if is_valid else '拒絕'}")
if not is_valid:
print(f" 錯誤訊息: {error_msg}")
failed += 1
print()
assert failed == 0
def test_secure_filename_cleaning():
"""測試 secure_filename 的清理效果"""
print("="*60)
print("測試 3: 檔案名稱清理效果")
print("="*60)
from werkzeug.utils import secure_filename
test_cases = [
('即時業績(全月).xlsx', '中文與括號'),
('../../../etc/passwd.csv', '路徑遍歷'),
('file|pipe<>:*.csv', '特殊字元'),
(' spaces .xlsx', '空格'),
('file\x00null.csv', 'Null 字元'),
('.xlsx', 'HTML 標籤'),
]
print("\n檔案名稱清理範例:")
print("-"*60)
for original, description in test_cases:
cleaned = secure_filename(original)
print(f"描述: {description}")
print(f" 原始: '{original}'")
print(f" 清理後: '{cleaned}'")
print()
assert True # 這個測試只是展示,不計入通過/失敗
def main():
"""主測試函數"""
print("="*60)
print("MOMO 監控系統 - 檔案上傳驗證測試")
print("="*60)
print()
# 執行所有測試
try:
test_allowed_file()
test_validate_upload_file()
except AssertionError:
print("\n⚠️ 測試未通過,請檢查!")
return 1
# 展示清理效果(不計入結果)
test_secure_filename_cleaning()
# 顯示總結
print("="*60)
print("測試結果摘要")
print("="*60)
print("✅ 通過: 測試函式完成")
print("❌ 失敗: 0")
print("\n🎉 所有檔案上傳驗證測試通過!")
return 0
if __name__ == "__main__":
sys.exit(main())