diff --git a/tests/jellyfin-qbittorrent-monitor.nix b/tests/jellyfin-qbittorrent-monitor.nix index d52be5b..45dee94 100644 --- a/tests/jellyfin-qbittorrent-monitor.nix +++ b/tests/jellyfin-qbittorrent-monitor.nix @@ -1,45 +1,10 @@ { lib, pkgs, + inputs, ... }: let - # Mock qBittorrent - simple enough to keep mocked - mockQBittorrent = pkgs.writers.writePython3Bin "mock-qbt" { flakeIgnore = [ "E501" ]; } '' - import http.server - import socketserver - - - class Handler(http.server.BaseHTTPRequestHandler): - def log_message(self, fmt, *args): - print(f"qbt: {fmt % args}") - - def do_GET(self): - if self.path == "/api/v2/transfer/speedLimitsMode": - self.send_response(200) - self.send_header("Content-type", "text/plain") - self.end_headers() - self.wfile.write(("1" if getattr(self.server, "alt_speed", False) else "0").encode()) - else: - self.send_response(404) - self.end_headers() - - def do_POST(self): - if self.path == "/api/v2/transfer/toggleSpeedLimitsMode": - self.server.alt_speed = not getattr(self.server, "alt_speed", False) - self.send_response(200) - self.end_headers() - else: - self.send_response(404) - self.end_headers() - - - socketserver.TCPServer.allow_reuse_address = True - with socketserver.TCPServer(("0.0.0.0", 8080), Handler) as s: - print("Mock qBittorrent on port 8080") - s.serve_forever() - ''; - payloads = { auth = pkgs.writeText "auth.json" (builtins.toJSON { Username = "jellyfin"; }); empty = pkgs.writeText "empty.json" (builtins.toJSON { }); @@ -49,34 +14,76 @@ pkgs.testers.runNixOSTest { name = "jellyfin-qbittorrent-monitor"; nodes = { - server = { - services.jellyfin.enable = true; - environment.systemPackages = with pkgs; [ - curl - ffmpeg - ]; - virtualisation.diskSize = 3 * 1024; - networking.firewall.allowedTCPPorts = [ - 8096 - 8080 - ]; - networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ - { - address = "192.168.1.1"; - prefixLength = 24; - } - ]; - networking.interfaces.eth1.ipv4.routes = [ - { - address = "203.0.113.0"; - prefixLength = 24; - } - ]; - systemd.services.mock-qbittorrent = { - wantedBy = [ "multi-user.target" ]; - serviceConfig.ExecStart = lib.getExe mockQBittorrent; + server = + { ... }: + { + imports = [ + inputs.vpn-confinement.nixosModules.default + ]; + + services.jellyfin.enable = true; + + # Real qBittorrent service + services.qbittorrent = { + enable = true; + webuiPort = 8080; + openFirewall = true; + + serverConfig.LegalNotice.Accepted = true; + + serverConfig.Preferences = { + WebUI = { + # Disable authentication for testing + AuthSubnetWhitelist = "0.0.0.0/0,::/0"; + AuthSubnetWhitelistEnabled = true; + LocalHostAuth = false; + }; + + Downloads = { + SavePath = "/var/lib/qbittorrent/downloads"; + TempPath = "/var/lib/qbittorrent/incomplete"; + }; + }; + + serverConfig.BitTorrent.Session = { + # Normal speed - unlimited + GlobalUPSpeedLimit = 0; + GlobalDLSpeedLimit = 0; + + # Alternate speed limits for when Jellyfin is streaming + AlternativeGlobalUPSpeedLimit = 100; + AlternativeGlobalDLSpeedLimit = 100; + }; + }; + + environment.systemPackages = with pkgs; [ + curl + ffmpeg + ]; + virtualisation.diskSize = 3 * 1024; + networking.firewall.allowedTCPPorts = [ + 8096 + 8080 + ]; + networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ + { + address = "192.168.1.1"; + prefixLength = 24; + } + ]; + networking.interfaces.eth1.ipv4.routes = [ + { + address = "203.0.113.0"; + prefixLength = 24; + } + ]; + + # Create directories for qBittorrent + systemd.tmpfiles.rules = [ + "d /var/lib/qbittorrent/downloads 0755 qbittorrent qbittorrent" + "d /var/lib/qbittorrent/incomplete 0755 qbittorrent qbittorrent" + ]; }; - }; # Public test IP (RFC 5737 TEST-NET-3) so Jellyfin sees it as external client = { @@ -123,9 +130,12 @@ pkgs.testers.runNixOSTest { server.wait_for_unit("jellyfin.service") server.wait_for_open_port(8096) server.wait_until_succeeds("curl -sf http://localhost:8096/health | grep -q Healthy", timeout=60) - server.wait_for_unit("mock-qbittorrent.service") + server.wait_for_unit("qbittorrent.service") server.wait_for_open_port(8080) + # Wait for qBittorrent WebUI to be responsive + server.wait_until_succeeds("curl -sf http://localhost:8080/api/v2/app/version", timeout=30) + with subtest("Complete Jellyfin setup wizard"): server.wait_until_succeeds(api_get("/Startup/Configuration")) server.succeed(api_get("/Startup/FirstUser")) @@ -169,13 +179,13 @@ pkgs.testers.runNixOSTest { python = "${pkgs.python3.withPackages (ps: [ ps.requests ])}/bin/python" monitor = "${../services/jellyfin-qbittorrent-monitor.py}" server.succeed(f""" - systemd-run --unit=monitor-test \\ - --setenv=JELLYFIN_URL=http://localhost:8096 \\ - --setenv=JELLYFIN_API_KEY={token} \\ - --setenv=QBITTORRENT_URL=http://localhost:8080 \\ - --setenv=CHECK_INTERVAL=1 \\ - --setenv=STREAMING_START_DELAY=1 \\ - --setenv=STREAMING_STOP_DELAY=1 \\ + systemd-run --unit=monitor-test \ + --setenv=JELLYFIN_URL=http://localhost:8096 \ + --setenv=JELLYFIN_API_KEY={token} \ + --setenv=QBITTORRENT_URL=http://localhost:8080 \ + --setenv=CHECK_INTERVAL=1 \ + --setenv=STREAMING_START_DELAY=1 \ + --setenv=STREAMING_STOP_DELAY=1 \ {python} {monitor} """) time.sleep(2) @@ -275,12 +285,13 @@ pkgs.testers.runNixOSTest { time.sleep(2) assert is_throttled(), "Should be throttled before qBittorrent restart" - # Restart mock-qbittorrent (this resets alt_speed to False) - server.succeed("systemctl restart mock-qbittorrent.service") - server.wait_for_unit("mock-qbittorrent.service") + # Restart qBittorrent (this resets alt_speed to its config default - disabled) + server.succeed("systemctl restart qbittorrent.service") + server.wait_for_unit("qbittorrent.service") server.wait_for_open_port(8080) + server.wait_until_succeeds("curl -sf http://localhost:8080/api/v2/app/version", timeout=30) - # qBittorrent restarted - alt_speed is now False (default) + # qBittorrent restarted - alt_speed is now False (default on startup) # The monitor should detect this and re-apply throttling time.sleep(3) # Give monitor time to detect and re-apply assert is_throttled(), "Monitor should re-apply throttling after qBittorrent restart" @@ -300,10 +311,11 @@ pkgs.testers.runNixOSTest { # Verify we're unthrottled (no active streams) assert not is_throttled(), "Should be unthrottled before test" - # Restart mock-qbittorrent - server.succeed("systemctl restart mock-qbittorrent.service") - server.wait_for_unit("mock-qbittorrent.service") + # Restart qBittorrent + server.succeed("systemctl restart qbittorrent.service") + server.wait_for_unit("qbittorrent.service") server.wait_for_open_port(8080) + server.wait_until_succeeds("curl -sf http://localhost:8080/api/v2/app/version", timeout=30) # Give monitor time to check state time.sleep(3)