mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-14 21:15:06 +00:00
refactor(remote)!: send service options only in service_params
A service option named like a dl option (e.g. --profile) is no longer dropped or mixed with the dl value; the server casts nested values and keeps cli defaults nested.
This commit is contained in:
@@ -103,3 +103,19 @@ def test_client_sent_keys_uses_the_stamp():
|
||||
|
||||
def test_client_sent_keys_without_stamp_counts_all_keys():
|
||||
assert dm.client_sent_keys({"workers": 1, "quality": None}) == {"workers", "quality"}
|
||||
|
||||
|
||||
def test_service_defaults_and_options_travel_nested(env, monkeypatch):
|
||||
import click
|
||||
|
||||
@click.command()
|
||||
@click.option("--profile", type=int, default=1)
|
||||
@click.option("--device", default="chrome")
|
||||
def cli(**kwargs):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(handlers.Services, "load", lambda service: SimpleNamespace(cli=cli))
|
||||
params = env(body={"profile": "work", "service_params": {"profile": 2}})
|
||||
assert params["profile"] == "work"
|
||||
assert params["service_params"] == {"profile": 2, "device": "chrome"}
|
||||
assert env(body={"device": "tv"})["service_params"] == {"profile": 1, "device": "tv"}
|
||||
|
||||
@@ -136,6 +136,12 @@ def test_make_remote_service_passes_only_cli_options(remote_config, monkeypatch)
|
||||
assert built["args"][5]["EXAMPLE"] == {"x": 1}
|
||||
|
||||
|
||||
def test_a_service_option_named_like_a_dl_value_passes_only_nested():
|
||||
info = {"cli_params": [{"kind": "option", "name": "profile"}]}
|
||||
assert remote_service.remote_service_params(info, {"service_params": {"profile": "QC"}}) == {"profile": "QC"}
|
||||
assert remote_service.remote_service_params(info, {"profile": "QC"}) == {}
|
||||
|
||||
|
||||
def test_remote_error_carries_its_reason_and_exits_a_cli_run():
|
||||
err = remote_service.RemoteError("Server error [SESSION_NOT_FOUND]: gone", "SESSION_NOT_FOUND")
|
||||
assert isinstance(err, SystemExit) and err.code == 1 and err.kind == "SESSION_NOT_FOUND"
|
||||
|
||||
@@ -645,9 +645,15 @@ def test_dl_selection_travels_nested_and_a_service_option_cannot_override_it() -
|
||||
"forced_subs": True,
|
||||
"exact_lang": True,
|
||||
}
|
||||
assert data["lang"] == 3
|
||||
assert "lang" not in data and "a_lang" not in data
|
||||
assert data["service_params"] == {"lang": 3, "a_lang": "x"}
|
||||
|
||||
|
||||
def test_a_service_profile_option_travels_nested_and_keeps_the_credential_profile() -> None:
|
||||
data = _sent_create_data({"profile": "work"}, {"profile": "QC"})
|
||||
assert data["profile"] == "work"
|
||||
assert data["service_params"] == {"profile": "QC"}
|
||||
|
||||
|
||||
def test_no_dl_params_when_the_ctx_has_no_selection() -> None:
|
||||
assert "dl_params" not in _sent_create_data({}, {})
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
from uuid import UUID
|
||||
|
||||
@@ -97,8 +98,8 @@ def test_a_server_device_option_keeps_the_server_cdm_against_the_clients_choice(
|
||||
|
||||
nested = {"cdm_type": "widevine", "server_cdm": False, "service_params": {"server_side": True}}
|
||||
assert handlers.choose_session_cdm(request("flat"), "EXAMPLE1", nested) == (True, 1080)
|
||||
# service_params wins over the flat key, as it does when the service is built
|
||||
nested["server_side"], nested["service_params"] = True, {"server_side": False}
|
||||
# with service_params present a flat key is a dl value, as it is when the service is built
|
||||
nested["server_side"], nested["service_params"] = True, {}
|
||||
assert handlers.choose_session_cdm(request("flat"), "EXAMPLE1", nested) == (False, None)
|
||||
|
||||
|
||||
@@ -318,8 +319,8 @@ def test_a_capped_download_job_cannot_ask_for_hybrid(hdr_range):
|
||||
handlers.enforce_download_gates({"service": "EXAMPLE1", "quality": [1080], "range": ["HDR10"]}, request("flat"))
|
||||
|
||||
|
||||
def test_the_download_gate_reads_the_service_click_defaults(monkeypatch):
|
||||
"""A service's own click default overrides the serve default, and the gate sees what the job runs with."""
|
||||
def test_a_service_click_default_stays_out_of_the_download_gate(monkeypatch):
|
||||
"""A service's own option is not the dl option of the same name, so its default never trips the gate."""
|
||||
import asyncio
|
||||
|
||||
from unshackle.core.api import download_manager as dm
|
||||
@@ -329,15 +330,21 @@ def test_the_download_gate_reads_the_service_click_defaults(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
handlers.Services, "load", lambda service: SimpleNamespace(cli=SimpleNamespace(params=[option]))
|
||||
)
|
||||
jobs = []
|
||||
|
||||
def no_job():
|
||||
raise AssertionError("a refused job must not reach the download manager")
|
||||
class Manager:
|
||||
async def start_workers(self):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(dm, "get_download_manager", no_job)
|
||||
def create_job(self, service, title_id, owner_key=None, **params):
|
||||
jobs.append(params)
|
||||
return SimpleNamespace(job_id="j", status=SimpleNamespace(value="queued"), created_time=datetime.now())
|
||||
|
||||
monkeypatch.setattr(dm, "get_download_manager", Manager)
|
||||
body = {"service": "EXAMPLE1", "title_id": "t1", "quality": [1080]}
|
||||
with pytest.raises(APIError) as ei:
|
||||
asyncio.run(handlers.download_handler(body, request("flat")))
|
||||
assert ei.value.error_code is APIErrorCode.SERVER_CDM_CAPPED
|
||||
asyncio.run(handlers.download_handler(body, request("flat")))
|
||||
assert jobs[0]["service_params"] == {"best_available": True}
|
||||
assert not jobs[0].get("best_available")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -121,6 +121,31 @@ def test_transport_keys_block_profile_and_service_params() -> None:
|
||||
assert "service_params" in LIST_HANDLER_TRANSPORT_KEYS
|
||||
|
||||
|
||||
def test_service_params_are_the_only_service_options_when_present() -> None:
|
||||
import click
|
||||
|
||||
from unshackle.core.api.errors import APIError
|
||||
from unshackle.core.api.handlers import SESSION_TRANSPORT_KEYS, instantiate_service
|
||||
|
||||
@click.command()
|
||||
@click.argument("title")
|
||||
@click.option("--profile", default=None)
|
||||
@click.option("--device", default=None)
|
||||
@click.option("--count", type=int, default=1)
|
||||
def cli(title: str, **kwargs: object) -> dict:
|
||||
return kwargs
|
||||
|
||||
def build(data: dict) -> dict:
|
||||
return instantiate_service(click.Context(click.Command("dl")), module, "t", data, SESSION_TRANSPORT_KEYS)
|
||||
|
||||
module = type("Svc", (), {"cli": cli})
|
||||
nested = {"profile": "work", "device": "tv", "service_params": {"profile": "QC", "count": "2"}}
|
||||
assert build(nested) == {"profile": "QC", "device": None, "count": 2}
|
||||
assert build({"profile": "work", "device": "tv"}) == {"profile": None, "device": "tv", "count": 1}
|
||||
with pytest.raises(APIError):
|
||||
build({"service_params": {"count": "x"}})
|
||||
|
||||
|
||||
def test_drain_server_logs_survives_client_systemexit() -> None:
|
||||
"""RemoteClient raises SystemExit on transport errors; the drain must swallow it."""
|
||||
from unshackle.core.remote_service import RemoteService
|
||||
|
||||
Reference in New Issue
Block a user