Browse Source
* add tests for adding nzbs * restore clean_cache_dir fixture, unbreak utils tests * include tests for partial and malformed nzbs * test handling of prio from nzb metadata category * update params of test_adding_nzbs_malformed * add metadata to sabnews nzb creator * also test with size_limit * test prio with dupe detection * remove leftover todo entry * move pause and cleanup to fixture; rename functionspull/1783/head
committed by
GitHub
10 changed files with 739 additions and 58 deletions
@ -0,0 +1,625 @@ |
|||||
|
#!/usr/bin/python3 -OO |
||||
|
# Copyright 2007-2020 The SABnzbd-Team <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_functional_adding_nzbs - Tests for settings interaction when adding NZBs |
||||
|
""" |
||||
|
|
||||
|
import os |
||||
|
import shutil |
||||
|
import stat |
||||
|
import sys |
||||
|
from random import choice, randint, sample |
||||
|
from string import ascii_lowercase, digits |
||||
|
|
||||
|
import sabnzbd.config |
||||
|
from sabnzbd.constants import ( |
||||
|
DUP_PRIORITY, |
||||
|
PAUSED_PRIORITY, |
||||
|
DEFAULT_PRIORITY, |
||||
|
LOW_PRIORITY, |
||||
|
NORMAL_PRIORITY, |
||||
|
HIGH_PRIORITY, |
||||
|
FORCE_PRIORITY, |
||||
|
REPAIR_PRIORITY, |
||||
|
) |
||||
|
from sabnzbd.database import _PP_LOOKUP |
||||
|
|
||||
|
from tests.testhelper import * |
||||
|
|
||||
|
|
||||
|
# Repair priority is out of scope for the purpose of these tests: it cannot be |
||||
|
# set as a default, upon adding a job, or from a pre-queue script. |
||||
|
# "None" is used to *not* set any particular priority at a given stage. |
||||
|
|
||||
|
# Define valid options for various stages |
||||
|
PRIO_OPTS_ADD = [ |
||||
|
DEFAULT_PRIORITY, |
||||
|
DUP_PRIORITY, |
||||
|
PAUSED_PRIORITY, |
||||
|
LOW_PRIORITY, |
||||
|
NORMAL_PRIORITY, |
||||
|
HIGH_PRIORITY, |
||||
|
FORCE_PRIORITY, |
||||
|
None, |
||||
|
] |
||||
|
PRIO_OPTS_PREQ = [ |
||||
|
DEFAULT_PRIORITY, |
||||
|
DUP_PRIORITY, |
||||
|
PAUSED_PRIORITY, |
||||
|
LOW_PRIORITY, |
||||
|
NORMAL_PRIORITY, |
||||
|
HIGH_PRIORITY, |
||||
|
FORCE_PRIORITY, |
||||
|
None, |
||||
|
] |
||||
|
PRIO_OPTS_ADD_CAT = [ |
||||
|
DEFAULT_PRIORITY, |
||||
|
PAUSED_PRIORITY, |
||||
|
LOW_PRIORITY, |
||||
|
NORMAL_PRIORITY, |
||||
|
HIGH_PRIORITY, |
||||
|
FORCE_PRIORITY, |
||||
|
None, |
||||
|
] |
||||
|
PRIO_OPTS_PREQ_CAT = [ |
||||
|
DEFAULT_PRIORITY, |
||||
|
PAUSED_PRIORITY, |
||||
|
LOW_PRIORITY, |
||||
|
NORMAL_PRIORITY, |
||||
|
HIGH_PRIORITY, |
||||
|
FORCE_PRIORITY, |
||||
|
None, |
||||
|
] |
||||
|
PRIO_OPTS_META_CAT = [ |
||||
|
DEFAULT_PRIORITY, |
||||
|
PAUSED_PRIORITY, |
||||
|
LOW_PRIORITY, |
||||
|
NORMAL_PRIORITY, |
||||
|
HIGH_PRIORITY, |
||||
|
FORCE_PRIORITY, |
||||
|
None, |
||||
|
] |
||||
|
# Valid priority values for the Default category (as determined by their availability from the interface) |
||||
|
VALID_DEFAULT_PRIORITIES = [PAUSED_PRIORITY, LOW_PRIORITY, NORMAL_PRIORITY, HIGH_PRIORITY, FORCE_PRIORITY] |
||||
|
|
||||
|
# Priorities that do *not* set a job state |
||||
|
REGULAR_PRIOS = [LOW_PRIORITY, NORMAL_PRIORITY, HIGH_PRIORITY, FORCE_PRIORITY] |
||||
|
# Priorities that set job states |
||||
|
STATE_PRIOS = [DUP_PRIORITY, PAUSED_PRIORITY] |
||||
|
|
||||
|
# Needed for translating priority values to names |
||||
|
ALL_PRIOS = { |
||||
|
DEFAULT_PRIORITY: "Default", |
||||
|
DUP_PRIORITY: "Duplicate", |
||||
|
PAUSED_PRIORITY: "Paused", |
||||
|
LOW_PRIORITY: "Low", |
||||
|
NORMAL_PRIORITY: "Normal", |
||||
|
HIGH_PRIORITY: "High", |
||||
|
FORCE_PRIORITY: "Force", |
||||
|
REPAIR_PRIORITY: "Repair", |
||||
|
} |
||||
|
|
||||
|
# Min/max size for random files used in generated NZBs (bytes) |
||||
|
MIN_FILESIZE = 128 |
||||
|
MAX_FILESIZE = 1024 |
||||
|
|
||||
|
# Tags to randomise category/script/nzb name |
||||
|
CAT_RANDOM = os.urandom(4).hex() |
||||
|
SCRIPT_RANDOM = os.urandom(4).hex() |
||||
|
NZB_RANDOM = os.urandom(4).hex() |
||||
|
|
||||
|
|
||||
|
class ModuleVars: |
||||
|
# Full path to script directory resp. nzb files, once in place/generated |
||||
|
SCRIPT_DIR = None |
||||
|
NZB_FILE = None |
||||
|
META_NZB_FILE = None |
||||
|
# Pre-queue script setup marker |
||||
|
PRE_QUEUE_SETUP_DONE = False |
||||
|
|
||||
|
|
||||
|
# Shared variables at module-level |
||||
|
VAR = ModuleVars() |
||||
|
|
||||
|
|
||||
|
@pytest.fixture(scope="function") |
||||
|
def pause_and_clear(): |
||||
|
# Pause the queue |
||||
|
assert get_api_result(mode="pause")["status"] is True |
||||
|
|
||||
|
yield |
||||
|
|
||||
|
# Delete all jobs from queue and history |
||||
|
for mode in ("queue", "history"): |
||||
|
get_api_result(mode=mode, extra_arguments={"name": "delete", "value": "all", "del_files": 1}) |
||||
|
|
||||
|
# Unpause the queue |
||||
|
assert get_api_result(mode="resume")["status"] is True |
||||
|
|
||||
|
|
||||
|
@pytest.mark.usefixtures("run_sabnzbd", "pause_and_clear") |
||||
|
class TestAddingNZBs: |
||||
|
def _setup_script_dir(self): |
||||
|
VAR.SCRIPT_DIR = os.path.join(SAB_CACHE_DIR, "scripts" + SCRIPT_RANDOM) |
||||
|
try: |
||||
|
os.makedirs(VAR.SCRIPT_DIR, exist_ok=True) |
||||
|
except Exception: |
||||
|
pytest.fail("Cannot create script_dir %s" % VAR.SCRIPT_DIR) |
||||
|
|
||||
|
json = get_api_result( |
||||
|
mode="set_config", |
||||
|
extra_arguments={ |
||||
|
"section": "misc", |
||||
|
"keyword": "script_dir", |
||||
|
"value": VAR.SCRIPT_DIR, |
||||
|
}, |
||||
|
) |
||||
|
assert VAR.SCRIPT_DIR in json["config"]["misc"]["script_dir"] |
||||
|
|
||||
|
def _customize_pre_queue_script(self, priority, category): |
||||
|
""" Add a script that accepts the job and sets priority & category """ |
||||
|
script_name = "SCRIPT%s.py" % SCRIPT_RANDOM |
||||
|
try: |
||||
|
script_path = os.path.join(VAR.SCRIPT_DIR, script_name) |
||||
|
with open(script_path, "w") as f: |
||||
|
# line 1 = accept; 4 = category; 6 = priority |
||||
|
f.write( |
||||
|
"#!%s\n\nprint('1\\n\\n\\n%s\\n\\n%s\\n')" |
||||
|
% ( |
||||
|
sys.executable, |
||||
|
(category if category else ""), |
||||
|
(str(priority) if priority != None else ""), |
||||
|
) |
||||
|
) |
||||
|
if not sys.platform.startswith("win"): |
||||
|
os.chmod(script_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) |
||||
|
except Exception: |
||||
|
pytest.fail("Cannot add script %s to script_dir %s" % (script_name, VAR.SCRIPT_DIR)) |
||||
|
|
||||
|
if not VAR.PRE_QUEUE_SETUP_DONE: |
||||
|
# Set as pre-queue script |
||||
|
json = get_api_result( |
||||
|
mode="set_config", |
||||
|
extra_arguments={ |
||||
|
"section": "misc", |
||||
|
"keyword": "pre_script", |
||||
|
"value": script_name, |
||||
|
}, |
||||
|
) |
||||
|
assert script_name in json["config"]["misc"]["pre_script"] |
||||
|
VAR.PRE_QUEUE_SETUP_DONE = True |
||||
|
|
||||
|
def _configure_cat(self, priority, tag): |
||||
|
category_name = "cat" + tag + CAT_RANDOM |
||||
|
category_config = { |
||||
|
"section": "categories", |
||||
|
"name": category_name, |
||||
|
"pp": choice(list(_PP_LOOKUP.keys())), |
||||
|
"script": "None", |
||||
|
"priority": priority if priority != None else DEFAULT_PRIORITY, |
||||
|
} |
||||
|
|
||||
|
# Add the category |
||||
|
json = get_api_result(mode="set_config", extra_arguments=category_config) |
||||
|
assert json["config"]["categories"][0]["name"] == category_name |
||||
|
if priority != None: |
||||
|
assert json["config"]["categories"][0]["priority"] == priority |
||||
|
|
||||
|
return category_name |
||||
|
|
||||
|
def _configure_default_category_priority(self, priority): |
||||
|
if priority not in VALID_DEFAULT_PRIORITIES: |
||||
|
priority = DEFAULT_PRIORITY |
||||
|
json = get_api_result( |
||||
|
mode="set_config", |
||||
|
extra_arguments={ |
||||
|
"section": "categories", |
||||
|
"name": "*", |
||||
|
"priority": priority, |
||||
|
}, |
||||
|
) |
||||
|
assert ("*", priority) == (json["config"]["categories"][0]["name"], json["config"]["categories"][0]["priority"]) |
||||
|
|
||||
|
def _create_random_nzb(self, metadata=None): |
||||
|
# Create some simple, unique nzb |
||||
|
job_dir = os.path.join(SAB_CACHE_DIR, "NZB" + os.urandom(8).hex()) |
||||
|
try: |
||||
|
os.mkdir(job_dir) |
||||
|
job_file = "%s.%s" % ( |
||||
|
"".join(choice(ascii_lowercase + digits) for i in range(randint(6, 18))), |
||||
|
"".join(sample(ascii_lowercase, 3)), |
||||
|
) |
||||
|
with open(os.path.join(job_dir, job_file), "wb") as f: |
||||
|
f.write(os.urandom(randint(MIN_FILESIZE, MAX_FILESIZE))) |
||||
|
except Exception: |
||||
|
pytest.fail("Failed to create random nzb") |
||||
|
|
||||
|
return create_nzb(job_dir, metadata=metadata) |
||||
|
|
||||
|
def _create_meta_nzb(self, cat_meta): |
||||
|
return self._create_random_nzb(metadata={"category": cat_meta}) |
||||
|
|
||||
|
def _expected_results(self, STAGES, return_state=None): |
||||
|
""" Figure out what priority and state the job should end up with """ |
||||
|
# Define a bunch of helpers |
||||
|
def sanitize_stages(hit_stage, STAGES): |
||||
|
# Fallback is always category-based, so nix any explicit priorities (stages 1, 3). |
||||
|
# This is conditional only because explicit priority-upon-adding takes precedence |
||||
|
# over implicit-from-pre-queue, as discussed in #1703. |
||||
|
if not (hit_stage == 4 and STAGES[1] != None): |
||||
|
STAGES[1] = None |
||||
|
STAGES[3] = None |
||||
|
|
||||
|
# If the category was set from pre-queue, it replaces any category set earlier |
||||
|
if hit_stage == 4: |
||||
|
STAGES[2] = None |
||||
|
STAGES[5] = None |
||||
|
if hit_stage == 2: |
||||
|
STAGES[5] = None |
||||
|
|
||||
|
return STAGES |
||||
|
|
||||
|
def handle_state_prio(hit_stage, STAGES, return_state): |
||||
|
""" Find the priority that should to be set after changing the job state """ |
||||
|
# Keep record of the priority that caused the initial hit (for verification of the job state later on) |
||||
|
if not return_state: |
||||
|
return_state = STAGES[hit_stage] |
||||
|
|
||||
|
# No point in trying to find a fallback |
||||
|
if hit_stage == 0: |
||||
|
return NORMAL_PRIORITY, return_state |
||||
|
|
||||
|
STAGES = sanitize_stages(hit_stage, STAGES) |
||||
|
|
||||
|
# Work forward to find the priority prior to the hit_stage |
||||
|
pre_state_prio = None |
||||
|
pre_state_stage = None |
||||
|
# default cat -> implicit meta -> implicit on add -> explicit on add -> implicit pre-q -> explicit pre-q |
||||
|
for stage in (0, 5, 2, 1, 4, 3): |
||||
|
if stage == hit_stage: |
||||
|
if hit_stage == 1 and STAGES[4] != None: |
||||
|
# An explicit state-setting priority still has to deal with the category from pre-queue |
||||
|
# for fallback purposes, unlike non-state-setting priorities-on-add that override it. |
||||
|
continue |
||||
|
else: |
||||
|
break |
||||
|
if STAGES[stage] != None: |
||||
|
pre_state_prio = STAGES[stage] |
||||
|
pre_state_stage = stage |
||||
|
|
||||
|
if pre_state_prio != None and LOW_PRIORITY <= pre_state_prio <= HIGH_PRIORITY: |
||||
|
return pre_state_prio, return_state |
||||
|
else: |
||||
|
# The next-in-line prio is unsuitable; recurse with relevant stages zero'ed out |
||||
|
STAGES[hit_stage] = None |
||||
|
if pre_state_stage: |
||||
|
if pre_state_prio == DEFAULT_PRIORITY: |
||||
|
handle_default_cat(pre_state_stage, STAGES, return_state) |
||||
|
else: |
||||
|
STAGES[pre_state_stage] = None |
||||
|
# Sanitize again, with 'pre_state_stage' as the new hit_stage. This is needed again |
||||
|
# in cases such as hit_stage 3 setting a job state, with a fallback from stage 4. |
||||
|
sanitize_stages(pre_state_stage, STAGES) |
||||
|
return self._expected_results(STAGES, return_state) |
||||
|
|
||||
|
def handle_default_cat(hit_stage, STAGES, return_state): |
||||
|
""" Figure out the (category) default priority """ |
||||
|
STAGES = sanitize_stages(hit_stage, STAGES) |
||||
|
|
||||
|
# Strip the current -100 hit before recursing |
||||
|
STAGES[hit_stage] = None |
||||
|
|
||||
|
return self._expected_results(STAGES, return_state) |
||||
|
|
||||
|
# Work backwards through all stages: |
||||
|
# explicit pre-q -> implicit pre-q -> explicit on add -> implicit on add -> implicit meta |
||||
|
for stage in (3, 4, 1, 2, 5): |
||||
|
if STAGES[stage] != None: |
||||
|
if stage == 4 and STAGES[1] != None: |
||||
|
# Explicit priority on add takes precedence over implicit-from-pre-queue |
||||
|
continue |
||||
|
if STAGES[stage] in REGULAR_PRIOS: |
||||
|
return STAGES[stage], return_state |
||||
|
if STAGES[stage] in STATE_PRIOS: |
||||
|
return handle_state_prio(stage, STAGES, return_state) |
||||
|
if STAGES[stage] == DEFAULT_PRIORITY: |
||||
|
return handle_default_cat(stage, STAGES, return_state) |
||||
|
|
||||
|
# # ...and finally the Default category (stage 0) |
||||
|
if STAGES[0] not in (None, DEFAULT_PRIORITY): |
||||
|
if STAGES[0] in REGULAR_PRIOS: |
||||
|
# Avoid falling back to priority Force after setting a job state |
||||
|
if not (return_state in STATE_PRIOS and STAGES[0] == FORCE_PRIORITY): |
||||
|
return STAGES[0], return_state |
||||
|
else: |
||||
|
return NORMAL_PRIORITY, return_state |
||||
|
if STAGES[0] in STATE_PRIOS: |
||||
|
return handle_state_prio(0, STAGES, return_state) |
||||
|
|
||||
|
# The default of defaults... |
||||
|
return NORMAL_PRIORITY, return_state |
||||
|
|
||||
|
def _prep_priority_tester(self, prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, prio_meta_cat): |
||||
|
if not VAR.SCRIPT_DIR: |
||||
|
self._setup_script_dir() |
||||
|
if not VAR.NZB_FILE: |
||||
|
VAR.NZB_FILE = self._create_random_nzb() |
||||
|
|
||||
|
# Set the priority for the Default category |
||||
|
self._configure_default_category_priority(prio_def_cat) |
||||
|
|
||||
|
# Setup categories |
||||
|
cat_meta = None |
||||
|
if prio_meta_cat != None: |
||||
|
cat_meta = self._configure_cat(prio_meta_cat, "meta") |
||||
|
if not VAR.META_NZB_FILE: |
||||
|
VAR.META_NZB_FILE = self._create_meta_nzb(cat_meta) |
||||
|
cat_add = None |
||||
|
if prio_add_cat != None: |
||||
|
cat_add = self._configure_cat(prio_add_cat, "add") |
||||
|
|
||||
|
cat_preq = None |
||||
|
if prio_preq_cat != None: |
||||
|
cat_preq = self._configure_cat(prio_preq_cat, "pre") |
||||
|
|
||||
|
# Setup the pre-queue script |
||||
|
self._customize_pre_queue_script(prio_preq, cat_preq) |
||||
|
|
||||
|
# Queue the job, store the nzo_id |
||||
|
extra = {"name": VAR.META_NZB_FILE if cat_meta else VAR.NZB_FILE} |
||||
|
if cat_add: |
||||
|
extra["cat"] = cat_add |
||||
|
if prio_add != None: |
||||
|
extra["priority"] = prio_add |
||||
|
nzo_id = ",".join(get_api_result(mode="addlocalfile", extra_arguments=extra)["nzo_ids"]) |
||||
|
|
||||
|
# Fetch the queue output for the current job |
||||
|
return get_api_result(mode="queue", extra_arguments={"nzo_ids": nzo_id})["queue"]["slots"][0] |
||||
|
|
||||
|
def _priority_tester(self, prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, prio_meta_cat): |
||||
|
# Setup the current test job, and fetch its queue output |
||||
|
job = self._prep_priority_tester(prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, prio_meta_cat) |
||||
|
|
||||
|
# Determine the expected results |
||||
|
expected_prio, expected_state = self._expected_results( |
||||
|
[prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, prio_meta_cat] |
||||
|
) |
||||
|
|
||||
|
# Verify the results; queue output uses a string representation for the priority |
||||
|
assert ALL_PRIOS.get(expected_prio) == job["priority"] |
||||
|
if expected_state: |
||||
|
# Also check the correct state or label was set |
||||
|
if expected_state == DUP_PRIORITY: |
||||
|
assert "DUPLICATE" in job["labels"] |
||||
|
if expected_state == PAUSED_PRIORITY: |
||||
|
assert "Paused" == job["status"] |
||||
|
|
||||
|
# Caution: a full run is good for 90k+ tests |
||||
|
# @pytest.mark.parametrize("prio_meta_cat", PRIO_OPTS_META_CAT) |
||||
|
# @pytest.mark.parametrize("prio_def_cat", VALID_DEFAULT_PRIORITIES) |
||||
|
# @pytest.mark.parametrize("prio_add", PRIO_OPTS_ADD) |
||||
|
# @pytest.mark.parametrize("prio_add_cat", PRIO_OPTS_ADD_CAT) |
||||
|
# @pytest.mark.parametrize("prio_preq", PRIO_OPTS_PREQ) |
||||
|
# @pytest.mark.parametrize("prio_preq_cat", PRIO_OPTS_PREQ_CAT) |
||||
|
|
||||
|
@pytest.mark.parametrize("prio_meta_cat", sample(PRIO_OPTS_META_CAT, 2)) |
||||
|
@pytest.mark.parametrize("prio_def_cat", sample(VALID_DEFAULT_PRIORITIES, 2)) |
||||
|
@pytest.mark.parametrize("prio_add", sample(PRIO_OPTS_ADD, 3)) |
||||
|
@pytest.mark.parametrize("prio_add_cat", sample(PRIO_OPTS_ADD_CAT, 2)) |
||||
|
@pytest.mark.parametrize("prio_preq", sample(PRIO_OPTS_PREQ, 2)) |
||||
|
@pytest.mark.parametrize("prio_preq_cat", sample(PRIO_OPTS_PREQ_CAT, 2)) |
||||
|
def test_adding_nzbs_priority_sample( |
||||
|
self, prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, prio_meta_cat |
||||
|
): |
||||
|
self._priority_tester(prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, prio_meta_cat) |
||||
|
|
||||
|
@pytest.mark.parametrize( |
||||
|
"prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, prio_meta_cat", |
||||
|
[ |
||||
|
# Specific triggers for fixed bugs |
||||
|
(-1, -2, None, None, None, None), # State-setting priorities always fell back to Normal |
||||
|
(-1, -3, None, None, None, None), |
||||
|
(1, None, -2, None, None, None), |
||||
|
(2, None, None, -2, None, None), |
||||
|
(2, None, None, -3, None, None), |
||||
|
(2, -2, None, -3, None, None), |
||||
|
(0, -3, None, None, None, None), |
||||
|
(0, 2, None, None, 1, None), # Explicit priority on add was bested by implicit from pre-queue |
||||
|
(1, None, None, None, -1, None), # Category-based values from pre-queue didn't work at all |
||||
|
# Checks for test code regressions |
||||
|
(-2, -100, 2, None, None, None), |
||||
|
(-2, 0, 2, -100, None, None), |
||||
|
(1, 2, 0, -100, None, None), |
||||
|
(-2, None, -2, None, 2, None), |
||||
|
(-2, None, -1, None, 1, None), |
||||
|
(2, None, -1, None, -2, None), |
||||
|
(-2, -3, 1, None, None, None), |
||||
|
(2, 2, None, -2, None, None), |
||||
|
(2, 1, None, -2, None, None), |
||||
|
(1, -2, 0, None, None, None), |
||||
|
(0, -3, None, None, 1, None), |
||||
|
(0, -1, -1, -3, 2, None), |
||||
|
(0, 2, None, -2, None, -1), |
||||
|
(1, -2, -100, None, None, -1), |
||||
|
(1, None, None, None, None, -1), |
||||
|
(-1, None, None, None, None, 1), |
||||
|
(0, None, None, None, None, None), |
||||
|
], |
||||
|
) |
||||
|
def test_adding_nzbs_priority_triggers( |
||||
|
self, prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, prio_meta_cat |
||||
|
): |
||||
|
self._priority_tester(prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, prio_meta_cat) |
||||
|
|
||||
|
def test_adding_nzbs_partial(self): |
||||
|
"""Test adding parts of an NZB file, cut off somewhere in the middle to simulate |
||||
|
the effects of an interrupted download or bad hardware. Should fail, of course.""" |
||||
|
if not VAR.NZB_FILE: |
||||
|
VAR.NZB_FILE = self._create_random_nzb() |
||||
|
|
||||
|
nzb_basedir, nzb_basename = os.path.split(VAR.NZB_FILE) |
||||
|
nzb_size = os.stat(VAR.NZB_FILE).st_size |
||||
|
part_size = round(randint(20, 80) / 100 * nzb_size) |
||||
|
first_part = os.path.join(nzb_basedir, "part1_of_" + nzb_basename) |
||||
|
second_part = os.path.join(nzb_basedir, "part2_of_" + nzb_basename) |
||||
|
|
||||
|
with open(VAR.NZB_FILE, "rb") as nzb_in: |
||||
|
for nzb_part, chunk in (first_part, part_size), (second_part, -1): |
||||
|
with open(nzb_part, "wb") as nzb_out: |
||||
|
nzb_out.write(nzb_in.read(chunk)) |
||||
|
|
||||
|
for nzb_part in first_part, second_part: |
||||
|
json = get_api_result(mode="addlocalfile", extra_arguments={"name": nzb_part}) |
||||
|
assert json["status"] is False |
||||
|
assert json["nzo_ids"] == [] |
||||
|
os.remove(nzb_part) |
||||
|
|
||||
|
@pytest.mark.parametrize( |
||||
|
"keep_first, keep_last, strip_first, strip_last, should_work", |
||||
|
[ |
||||
|
# Keep parts |
||||
|
(6, 3, 0, 0, False), # Remove all segments content |
||||
|
(6, 0, 0, 0, False), |
||||
|
(5, 2, 0, 0, False), # Remove all segments |
||||
|
(5, 0, 0, 0, False), |
||||
|
(4, 2, 0, 0, False), # Remove all groups |
||||
|
(3, 1, 0, 0, False), # Remove all files |
||||
|
# Strip parts |
||||
|
(0, 0, 1, 0, True), # Strip '?xml' line (survivable) |
||||
|
(0, 0, 2, 0, True), # Also strip 'doctype' line (survivable) |
||||
|
(0, 0, 3, 0, False), # Also strip 'nzb xmlns' line |
||||
|
(0, 0, 0, 1, False), # Forget the 'nzb' closing tag |
||||
|
(0, 0, 0, 2, False), # Also forget the (last) 'file' closing tag |
||||
|
(0, 0, 0, 3, False), # Also forget the (last) 'segment' closing tag |
||||
|
], |
||||
|
) |
||||
|
def test_adding_nzbs_malformed(self, keep_first, keep_last, strip_first, strip_last, should_work): |
||||
|
""" Test adding broken, empty, or otherwise malformed NZB file """ |
||||
|
if not VAR.NZB_FILE: |
||||
|
VAR.NZB_FILE = self._create_random_nzb() |
||||
|
|
||||
|
with open(VAR.NZB_FILE, "rt") as nzb_in: |
||||
|
nzb_lines = nzb_in.readlines() |
||||
|
assert len(nzb_lines) >= 9 |
||||
|
|
||||
|
broken_nzb_basename = "broken_" + os.urandom(4).hex() + ".nzb" |
||||
|
broken_nzb = os.path.join(SAB_CACHE_DIR, broken_nzb_basename) |
||||
|
with open(broken_nzb, "wt") as nzb_out: |
||||
|
# Keep only first x, last y lines |
||||
|
if keep_first: |
||||
|
nzb_out.write("".join(nzb_lines[:keep_first])) |
||||
|
elif strip_first: |
||||
|
nzb_out.write("".join(nzb_lines[strip_first:])) |
||||
|
if keep_last: |
||||
|
nzb_out.write("".join(nzb_lines[(-1 * keep_last) :])) |
||||
|
elif strip_last: |
||||
|
nzb_out.write("".join(nzb_lines[: (-1 * strip_last)])) |
||||
|
|
||||
|
json = get_api_result(mode="warnings", extra_arguments={"name": "clear"}) |
||||
|
json = get_api_result(mode="addlocalfile", extra_arguments={"name": broken_nzb}) |
||||
|
assert json["status"] is should_work |
||||
|
assert len(json["nzo_ids"]) == int(should_work) |
||||
|
|
||||
|
json = get_api_result(mode="warnings") |
||||
|
assert (len(json["warnings"]) == 0) is should_work |
||||
|
if not should_work: |
||||
|
for warning in range(0, len(json["warnings"])): |
||||
|
assert (("Empty NZB file" or "Failed to import") and broken_nzb_basename) in json["warnings"][warning][ |
||||
|
"text" |
||||
|
] |
||||
|
|
||||
|
os.remove(broken_nzb) |
||||
|
|
||||
|
@pytest.mark.parametrize("prio_meta_cat", sample(PRIO_OPTS_META_CAT, 1)) |
||||
|
@pytest.mark.parametrize("prio_def_cat", sample(VALID_DEFAULT_PRIORITIES, 1)) |
||||
|
@pytest.mark.parametrize("prio_add", PRIO_OPTS_ADD) |
||||
|
def test_adding_nzbs_size_limit(self, prio_meta_cat, prio_def_cat, prio_add): |
||||
|
""" Verify state and priority of a job exceeding the size_limit """ |
||||
|
# Set size limit |
||||
|
json = get_api_result( |
||||
|
mode="set_config", extra_arguments={"section": "misc", "keyword": "size_limit", "value": MIN_FILESIZE - 1} |
||||
|
) |
||||
|
assert int(json["config"]["misc"]["size_limit"]) < MIN_FILESIZE |
||||
|
|
||||
|
job = self._prep_priority_tester(prio_def_cat, prio_add, None, None, None, prio_meta_cat) |
||||
|
|
||||
|
# Verify job is paused and low priority, and correctly labeled |
||||
|
assert job["status"] == "Paused" |
||||
|
assert job["priority"] == ALL_PRIOS.get(-1) |
||||
|
assert "TOO LARGE" in job["labels"] |
||||
|
|
||||
|
# Unset size limit |
||||
|
json = get_api_result( |
||||
|
mode="set_config", extra_arguments={"section": "misc", "keyword": "size_limit", "value": ""} |
||||
|
) |
||||
|
|
||||
|
@pytest.mark.parametrize("prio_def_cat", sample(VALID_DEFAULT_PRIORITIES, 2)) |
||||
|
@pytest.mark.parametrize("prio_add", PRIO_OPTS_ADD) |
||||
|
@pytest.mark.parametrize("prio_add_cat", sample(PRIO_OPTS_ADD_CAT, 1)) |
||||
|
@pytest.mark.parametrize("prio_preq", sample(PRIO_OPTS_PREQ, 1)) |
||||
|
@pytest.mark.parametrize("prio_preq_cat", sample(PRIO_OPTS_PREQ_CAT, 2)) |
||||
|
def test_adding_nzbs_duplicate_pausing(self, prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat): |
||||
|
# Set an nzb backup directory |
||||
|
try: |
||||
|
backup_dir = os.path.join(SAB_CACHE_DIR, "nzb_backup_dir" + os.urandom(4).hex()) |
||||
|
assert ( |
||||
|
get_api_result( |
||||
|
mode="set_config", |
||||
|
extra_arguments={"section": "misc", "keyword": "nzb_backup_dir", "value": backup_dir}, |
||||
|
)["config"]["misc"]["nzb_backup_dir"] |
||||
|
== backup_dir |
||||
|
) |
||||
|
except Exception: |
||||
|
pytest.fail("Cannot create nzb_backup_dir %s" % backup_dir) |
||||
|
|
||||
|
# Add the job a first time |
||||
|
job = self._prep_priority_tester(None, None, None, None, None, None) |
||||
|
assert job["status"] == "Queued" |
||||
|
|
||||
|
# Setup duplicate handling to 2 (Pause) |
||||
|
assert ( |
||||
|
get_api_result(mode="set_config", extra_arguments={"section": "misc", "keyword": "no_dupes", "value": 2})[ |
||||
|
"config" |
||||
|
]["misc"]["no_dupes"] |
||||
|
== 2 |
||||
|
) |
||||
|
|
||||
|
expected_prio, _ = self._expected_results( |
||||
|
[prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, None] |
||||
|
) |
||||
|
|
||||
|
job = self._prep_priority_tester(prio_def_cat, prio_add, prio_add_cat, prio_preq, prio_preq_cat, None) |
||||
|
|
||||
|
# Verify job is paused and correctly labeled, and given the right (fallback) priority |
||||
|
assert "DUPLICATE" in job["labels"] |
||||
|
assert job["priority"] == ALL_PRIOS.get(expected_prio) |
||||
|
# Priority Force overrules the duplicate pause |
||||
|
assert job["status"] == "Paused" if expected_prio != FORCE_PRIORITY else "Downloading" |
||||
|
|
||||
|
# Reset duplicate handling (0), nzb_backup_dir ("") |
||||
|
get_api_result(mode="set_config_default", extra_arguments={"keyword": "no_dupes", "keyword": "nzb_backup_dir"}) |
||||
|
|
||||
|
# Remove backup_dir |
||||
|
for timer in range(0, 5): |
||||
|
try: |
||||
|
shutil.rmtree(backup_dir) |
||||
|
break |
||||
|
except OSError: |
||||
|
time.sleep(1) |
||||
|
else: |
||||
|
pytest.fail("Failed to erase nzb_backup_dir %s" % backup_dir) |
Loading…
Reference in new issue