{ pkgs, lib, self, }: pkgs.testers.runNixOSTest { name = "arr-init-permanent-failure"; nodes.machine = { pkgs, lib, ... }: { imports = [ self.nixosModules.default ]; system.stateVersion = "24.11"; virtualisation.memorySize = 2048; environment.systemPackages = with pkgs; [ curl jq gnugrep ]; # Mock that always returns 503 systemd.services.mock-sonarr = let mockScript = pkgs.writeScript "mock-sonarr-fail.py" '' from http.server import HTTPServer, BaseHTTPRequestHandler class FailMock(BaseHTTPRequestHandler): def do_GET(self): self.send_response(503) self.send_header("Content-Type", "text/plain") self.end_headers() self.wfile.write(b"Service Unavailable") def do_POST(self): self.do_GET() def log_message(self, format, *args): pass HTTPServer(("0.0.0.0", 8989), FailMock).serve_forever() ''; in { description = "Mock Sonarr that never becomes ready"; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = "${pkgs.python3}/bin/python3 ${mockScript}"; Type = "simple"; }; }; # Pre-seed config.xml systemd.tmpfiles.rules = [ "d /var/lib/mock-sonarr 0755 root root -" "f /var/lib/mock-sonarr/config.xml 0644 root root - test-api-key-fail" ]; services.arrInit.sonarr = { enable = true; serviceName = "mock-sonarr"; dataDir = "/var/lib/mock-sonarr"; port = 8989; # Very short timeout so retries happen fast apiTimeout = 3; }; # Speed up retries for test systemd.services.mock-sonarr-init.serviceConfig.RestartSec = lib.mkForce 2; }; testScript = '' start_all() machine.wait_for_unit("mock-sonarr.service") with subtest("Start limit is configured correctly"): unit_content = machine.succeed("systemctl cat mock-sonarr-init.service") # StartLimitIntervalSec = 5 * (3 + 30) = 165 assert "StartLimitIntervalSec=165" in unit_content, \ f"Expected StartLimitIntervalSec=165, got:\n{unit_content}" assert "StartLimitBurst=5" in unit_content, \ f"Expected StartLimitBurst=5, got:\n{unit_content}" with subtest("Service enters permanent failure after exhausting retries"): # Wait for the start-limit to be hit: "Start request repeated too quickly" in journal machine.wait_until_succeeds( "journalctl -u mock-sonarr-init.service --no-pager | grep -q 'Start request repeated too quickly'", timeout=120, ) state = machine.succeed( "systemctl show mock-sonarr-init.service --property=ActiveState | cut -d= -f2" ).strip() assert state == "failed", f"Expected 'failed' state, got '{state}'" with subtest("Journal shows repeated timeout messages"): journal = machine.succeed("journalctl -u mock-sonarr-init.service --no-pager") # Count timeout messages - should have multiple timeout_count = journal.lower().count("not available after 3 seconds") assert timeout_count >= 2, \ f"Expected at least 2 timeout messages, got {timeout_count}. Journal:\n{journal[-1000:]}" ''; }