mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2025-12-23 15:49:15 -05:00
298 lines
13 KiB
Python
298 lines
13 KiB
Python
#!/usr/bin/python3 -OO
|
|
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
"""
|
|
tests.test_config - Tests of config methods
|
|
"""
|
|
from sabnzbd.filesystem import long_path
|
|
from tests.testhelper import *
|
|
import shutil
|
|
import zipfile
|
|
import os
|
|
|
|
import sabnzbd.cfg
|
|
from sabnzbd.constants import (
|
|
DEF_INI_FILE,
|
|
DEF_HTTPS_CERT_FILE,
|
|
DEF_HTTPS_KEY_FILE,
|
|
CONFIG_BACKUP_HTTPS,
|
|
CONFIG_BACKUP_FILES,
|
|
)
|
|
from sabnzbd import config
|
|
from sabnzbd import filesystem
|
|
|
|
|
|
DEF_CHAIN_FILE = "server.chain"
|
|
|
|
|
|
class TestOptions:
|
|
test_section = "test_section"
|
|
test_keyword = "test_keyword"
|
|
|
|
def test_base_option(self):
|
|
test_option = config.Option(self.test_section, self.test_keyword)
|
|
assert test_option.section == self.test_section
|
|
assert test_option.keyword == self.test_keyword
|
|
assert test_option.section in config.CFG_DATABASE
|
|
assert test_option.keyword in config.CFG_DATABASE[test_option.section]
|
|
assert config.CFG_DATABASE[test_option.section][test_option.keyword] == test_option
|
|
# Reset database
|
|
config.CFG_DATABASE = {}
|
|
|
|
@pytest.mark.xfail(reason="These tests should be added")
|
|
def test_all(self):
|
|
# Need to add tests for all the relevant options
|
|
raise NotImplemented
|
|
|
|
def test_non_public(self):
|
|
test_option = config.Option(self.test_section, self.test_keyword, public=True)
|
|
assert test_option.get_dict() == {self.test_keyword: None}
|
|
assert test_option.get_dict(for_public_api=False) == {self.test_keyword: None}
|
|
|
|
test_option = config.Option(self.test_section, self.test_keyword, public=False)
|
|
assert test_option.get_dict() == {self.test_keyword: None}
|
|
assert test_option.get_dict(for_public_api=True) == {}
|
|
|
|
# Password is special when using for_public_api
|
|
test_option = config.OptionPassword(self.test_section, self.test_keyword, default_val="test_password")
|
|
assert test_option.get_dict() == {self.test_keyword: "test_password"}
|
|
assert test_option.get_dict(for_public_api=True) == {self.test_keyword: "**********"}
|
|
|
|
# Reset database
|
|
config.CFG_DATABASE = {}
|
|
|
|
|
|
@pytest.mark.usefixtures("clean_cache_dir")
|
|
class TestConfig:
|
|
@staticmethod
|
|
def create_dummy_zip(filename: str) -> bytes:
|
|
with io.BytesIO() as zip_buffer:
|
|
with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_ref:
|
|
zip_ref.writestr(filename, "foobar")
|
|
return zip_buffer.getvalue()
|
|
|
|
@staticmethod
|
|
def create_and_verify_backup(admin_dir: str, must_haves: list[str]):
|
|
# Create the backup
|
|
config_backup_path = config.create_config_backup()
|
|
assert os.path.exists(config_backup_path)
|
|
assert sabnzbd.__version__ in config_backup_path
|
|
assert time.strftime("%Y.%m.%d_%H") in config_backup_path
|
|
|
|
# Verify the zipfile has the expected content
|
|
with open(config_backup_path, "rb") as fp:
|
|
# Do basic backup validation
|
|
assert config.validate_config_backup(fp.read())
|
|
# Reset the file pointer
|
|
fp.seek(0)
|
|
with zipfile.ZipFile(fp, "r") as zip:
|
|
for basename in must_haves:
|
|
assert zip.getinfo(basename)
|
|
# Make sure there's nothing else in the zip
|
|
assert (zip_len := len(zip.filelist)) == len(must_haves)
|
|
|
|
# Move the current admin dir out of the way
|
|
stowed_admin = os.path.join(SAB_CACHE_DIR, "stowed_admin")
|
|
if os.path.isdir(stowed_admin):
|
|
filesystem.remove_all(stowed_admin)
|
|
assert not os.path.exists(stowed_admin)
|
|
os.rename(admin_dir, stowed_admin)
|
|
assert os.path.exists(stowed_admin)
|
|
assert filesystem.globber(stowed_admin) != []
|
|
assert not os.path.exists(admin_dir)
|
|
filesystem.create_all_dirs(admin_dir)
|
|
assert os.path.exists(admin_dir)
|
|
assert filesystem.globber(admin_dir) == []
|
|
|
|
# Store current test settings, as these may change when restoring a backup
|
|
restore_me = {setting: getattr(sabnzbd.cfg, setting)() for setting in CONFIG_BACKUP_HTTPS.values()}
|
|
|
|
# Restore the backup
|
|
with open(config_backup_path, "rb") as config_backup_fp:
|
|
config.restore_config_backup(config_backup_fp.read())
|
|
|
|
# Check settings results
|
|
restore_changed_settings = False
|
|
for filename, setting in CONFIG_BACKUP_HTTPS.items():
|
|
if filename in must_haves:
|
|
restore_changed_settings = True
|
|
value = getattr(sabnzbd.cfg, setting)()
|
|
if setting != "https_chain":
|
|
# All https settings should point to the default basenames of the restored files...
|
|
assert value == getattr(sabnzbd.cfg, setting).default
|
|
else:
|
|
# ...except the one that doesn't have a default and uses a hardcoded filename instead
|
|
assert value == DEF_CHAIN_FILE
|
|
# Check filename results
|
|
for basename in must_haves:
|
|
# Verify all files in the backup were restored into the admin dir...
|
|
assert os.path.exists(os.path.join(admin_dir, basename))
|
|
# ...and nothing else
|
|
if not restore_changed_settings:
|
|
assert zip_len == len(filesystem.globber(admin_dir))
|
|
else:
|
|
# Account for sabnzbd.ini.bak in case settings were changed as part of the restore
|
|
assert zip_len + 1 == len(filesystem.globber(admin_dir))
|
|
|
|
# Restore the test settings
|
|
for setting, value in restore_me.items():
|
|
getattr(sabnzbd.cfg, setting).set(value)
|
|
sabnzbd.config.save_config(True)
|
|
|
|
# Purge the backup file to prevent collisions
|
|
os.unlink(config_backup_path)
|
|
assert not os.path.exists(config_backup_path)
|
|
|
|
# Call the original admin dir back into active duty
|
|
filesystem.remove_all(admin_dir)
|
|
assert not os.path.exists(admin_dir)
|
|
os.rename(stowed_admin, admin_dir)
|
|
assert os.path.exists(admin_dir)
|
|
assert filesystem.globber(admin_dir) != []
|
|
assert not os.path.exists(stowed_admin)
|
|
|
|
def test_validate_config_backup(self):
|
|
"""Validate basic dummy data"""
|
|
assert not config.validate_config_backup(b"invalid")
|
|
assert not config.validate_config_backup(self.create_dummy_zip("dummyfile"))
|
|
assert config.validate_config_backup(self.create_dummy_zip(DEF_INI_FILE))
|
|
|
|
@set_config(
|
|
{
|
|
"admin_dir": os.path.join(SAB_CACHE_DIR, "test_config_backup"),
|
|
"complete_dir": os.path.join(SAB_COMPLETE_DIR, "test_config_backup"),
|
|
}
|
|
)
|
|
def test_config_backup(self):
|
|
"""Combined tests for the config.{create,validate,restore}_config_backup functions"""
|
|
# Prepare the basics
|
|
admin_dir = sabnzbd.cfg.admin_dir.get_path()
|
|
sabnzbd.cfg.set_root_folders2()
|
|
ini_path = os.path.join(admin_dir, DEF_INI_FILE)
|
|
shutil.copyfile(os.path.join(SAB_DATA_DIR, "sabnzbd.basic.ini"), ini_path)
|
|
assert os.path.exists(ini_path)
|
|
config.read_config(ini_path)
|
|
filesystem.create_all_dirs(sabnzbd.cfg.complete_dir())
|
|
assert os.path.exists(sabnzbd.cfg.complete_dir())
|
|
|
|
# Create a backup and verify it has the expected files (ini only, as there are no admin and https config files)
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE])
|
|
|
|
# Add other admin files that qualify for inclusion in backups
|
|
for basename in CONFIG_BACKUP_FILES:
|
|
with open(admin_file := os.path.join(admin_dir, basename), "wb") as fp:
|
|
fp.write(os.urandom(128))
|
|
assert os.path.exists(admin_file)
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE] + CONFIG_BACKUP_FILES)
|
|
|
|
# Add some useless files in the admin_dir
|
|
for basename in ["totals3.sab", "Best.Movie.Ever.1951.240p.avi", "Rating.sab"]:
|
|
with open(useless_file := os.path.join(admin_dir, basename), "wb") as fp:
|
|
fp.write(os.urandom(256))
|
|
assert os.path.exists(useless_file)
|
|
# None of these should appear in the backup
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE] + CONFIG_BACKUP_FILES)
|
|
|
|
# Remove the extra admin files, but keep the useless ones around
|
|
for basename in CONFIG_BACKUP_FILES:
|
|
os.unlink(admin_file := os.path.join(admin_dir, basename))
|
|
assert not os.path.exists(admin_file)
|
|
|
|
# Generate fake HTTPS certificate and key files
|
|
cert_file = os.path.join(admin_dir, DEF_HTTPS_CERT_FILE)
|
|
key_file = os.path.join(admin_dir, DEF_HTTPS_KEY_FILE)
|
|
for filepath in (cert_file, key_file):
|
|
with open(filepath, "wb") as fp:
|
|
fp.write(os.urandom(512))
|
|
assert os.path.exists(cert_file)
|
|
assert os.path.exists(key_file)
|
|
|
|
# Copy cert and key to create a second set of https config files outside the admin dir
|
|
other_cert_file = long_path(os.path.join(SAB_CACHE_DIR, "foobar.mycert"))
|
|
other_key_file = long_path(os.path.join(SAB_CACHE_DIR, "foobar.mykey"))
|
|
shutil.copyfile(cert_file, other_key_file)
|
|
shutil.copyfile(key_file, other_cert_file)
|
|
assert os.path.exists(other_cert_file)
|
|
assert os.path.exists(other_key_file)
|
|
|
|
# Imitate a mainstream https setup (cert and key present, but no chain file)
|
|
sabnzbd.cfg.enable_https.set(True)
|
|
sabnzbd.cfg.https_cert.set(DEF_HTTPS_CERT_FILE)
|
|
sabnzbd.cfg.https_key.set(DEF_HTTPS_KEY_FILE)
|
|
sabnzbd.config.save_config(True)
|
|
assert not sabnzbd.cfg.https_chain()
|
|
assert sabnzbd.CONFIG_BACKUP_HTTPS_OK == []
|
|
|
|
# Results should remain the same, as we didn't fake the results of a startup with https enabled yet
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE])
|
|
|
|
# Results should still remain the same, the startup data lists only bogus files
|
|
sabnzbd.CONFIG_BACKUP_HTTPS_OK = ["/tmp/no.cert", "/lib/fuldstændig_falsk.nøgle", "/etc/存在しないファイル"]
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE])
|
|
|
|
# Now pretend the program started with this config (note: full paths must be used for _OK)
|
|
sabnzbd.CONFIG_BACKUP_HTTPS_OK = [cert_file, key_file]
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE, DEF_HTTPS_CERT_FILE, DEF_HTTPS_KEY_FILE])
|
|
|
|
# Pretend some other files were loaded on startup instead
|
|
sabnzbd.CONFIG_BACKUP_HTTPS_OK = [other_cert_file, other_key_file]
|
|
# Files in the settings no longer match those in _OK; no https config should be in the backup
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE])
|
|
|
|
# Set the full path to a key and cert file outside the admin dir
|
|
sabnzbd.cfg.https_cert.set(other_cert_file)
|
|
sabnzbd.cfg.https_key.set(other_key_file)
|
|
sabnzbd.config.save_config(True)
|
|
# Now the files should be included, albeit under the default names
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE, DEF_HTTPS_CERT_FILE, DEF_HTTPS_KEY_FILE])
|
|
|
|
# Repeat with the "others" removed, so there's nothing (but the ini) left to include in the first place
|
|
for f in (other_cert_file, other_key_file):
|
|
os.unlink(f)
|
|
assert not os.path.exists(other_cert_file)
|
|
assert not os.path.exists(other_key_file)
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE])
|
|
|
|
# Make up a chain file
|
|
chain_file = os.path.join(admin_dir, "ssl-chain.txt")
|
|
shutil.copyfile(cert_file, chain_file)
|
|
assert os.path.exists(chain_file)
|
|
# Update the config and the startup record (mostly)
|
|
sabnzbd.cfg.https_cert.set(cert_file)
|
|
sabnzbd.cfg.https_key.set(key_file)
|
|
sabnzbd.cfg.https_chain.set(chain_file)
|
|
sabnzbd.config.save_config(True)
|
|
sabnzbd.CONFIG_BACKUP_HTTPS_OK = [cert_file, key_file]
|
|
|
|
# There may be a chain file now, but as long as it's not listed in _OK it should be excluded from the backup
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE, DEF_HTTPS_CERT_FILE, DEF_HTTPS_KEY_FILE])
|
|
|
|
# Now it should be included
|
|
sabnzbd.CONFIG_BACKUP_HTTPS_OK.append(chain_file)
|
|
self.create_and_verify_backup(
|
|
admin_dir, [DEF_INI_FILE, DEF_HTTPS_CERT_FILE, DEF_HTTPS_KEY_FILE, DEF_CHAIN_FILE]
|
|
)
|
|
|
|
# Same same but more lonely
|
|
sabnzbd.CONFIG_BACKUP_HTTPS_OK = [chain_file, "/tmp/foobar.exe"]
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE, DEF_CHAIN_FILE])
|
|
|
|
# Disabling https shouldn't make any difference as long as the evidence shows it was active on startup
|
|
sabnzbd.cfg.enable_https.set(False)
|
|
sabnzbd.config.save_config(True)
|
|
self.create_and_verify_backup(admin_dir, [DEF_INI_FILE, DEF_CHAIN_FILE])
|