diff --git a/tests/default.nix b/tests/default.nix index 7f7c6b9..00891d7 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -12,4 +12,5 @@ edge-cases = import ./edge-cases.nix { inherit pkgs lib self; }; health-checks = import ./health-checks.nix { inherit pkgs lib self; }; delayed-start = import ./delayed-start.nix { inherit pkgs lib self; }; + jellyseerr = import ./jellyseerr.nix { inherit pkgs lib self; }; } diff --git a/tests/jellyseerr.nix b/tests/jellyseerr.nix new file mode 100644 index 0000000..6c8214b --- /dev/null +++ b/tests/jellyseerr.nix @@ -0,0 +1,268 @@ +{ + pkgs, + lib, + self, +}: + +pkgs.testers.runNixOSTest { + name = "arr-init-jellyseerr"; + + nodes = { + # Test 1: Profile resolution, anime fallback (null -> profileName), idempotency, + # and graceful skip when settings.json is missing. + machine = + { pkgs, lib, ... }: + { + imports = [ self.nixosModules.default ]; + + system.stateVersion = "24.11"; + + virtualisation.memorySize = 4096; + + environment.systemPackages = with pkgs; [ + curl + jq + gnugrep + ]; + + services.sonarr = { + enable = true; + dataDir = "/var/lib/sonarr/.config/NzbDrone"; + settings.server.port = lib.mkDefault 8989; + }; + + services.radarr = { + enable = true; + dataDir = "/var/lib/radarr/.config/Radarr"; + settings.server.port = lib.mkDefault 7878; + }; + + # Mock Jellyseerr (not in nixpkgs); the init script only needs the + # systemd unit to exist so Requires/After are satisfied and + # `systemctl restart jellyseerr.service` succeeds. + systemd.services.jellyseerr = { + description = "Mock Jellyseerr"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + ExecStart = "${pkgs.coreutils}/bin/sleep infinity"; + }; + }; + + systemd.tmpfiles.rules = [ + "d /var/lib/jellyseerr 0755 root root -" + ]; + + services.jellyseerrInit = { + enable = true; + configDir = "/var/lib/jellyseerr"; + radarr = { + profileName = "Any"; + dataDir = "/var/lib/radarr/.config/Radarr"; + port = 7878; + serviceName = "radarr"; + }; + sonarr = { + profileName = "Any"; + # animeProfileName defaults to null -> Nix config falls back to profileName + dataDir = "/var/lib/sonarr/.config/NzbDrone"; + port = 8989; + serviceName = "sonarr"; + }; + }; + }; + + # Test 2: Explicit animeProfileName differs from profileName + explicit_anime = + { pkgs, lib, ... }: + { + imports = [ self.nixosModules.default ]; + + system.stateVersion = "24.11"; + + virtualisation.memorySize = 4096; + + environment.systemPackages = with pkgs; [ + curl + jq + gnugrep + ]; + + services.sonarr = { + enable = true; + dataDir = "/var/lib/sonarr/.config/NzbDrone"; + settings.server.port = lib.mkDefault 8989; + }; + + services.radarr = { + enable = true; + dataDir = "/var/lib/radarr/.config/Radarr"; + settings.server.port = lib.mkDefault 7878; + }; + + systemd.services.jellyseerr = { + description = "Mock Jellyseerr"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + ExecStart = "${pkgs.coreutils}/bin/sleep infinity"; + }; + }; + + systemd.tmpfiles.rules = [ + "d /var/lib/jellyseerr 0755 root root -" + ]; + + services.jellyseerrInit = { + enable = true; + configDir = "/var/lib/jellyseerr"; + radarr = { + profileName = "Any"; + dataDir = "/var/lib/radarr/.config/Radarr"; + port = 7878; + serviceName = "radarr"; + }; + sonarr = { + profileName = "Any"; + animeProfileName = "SD"; + dataDir = "/var/lib/sonarr/.config/NzbDrone"; + port = 8989; + serviceName = "sonarr"; + }; + }; + }; + }; + + testScript = '' + start_all() + + # ===== Test 1a: Basic profile resolution + anime fallback ===== + + machine.wait_for_unit("sonarr.service") + machine.wait_for_unit("radarr.service") + machine.wait_for_unit("jellyseerr.service") + + # Wait for Sonarr API + machine.wait_until_succeeds( + "API_KEY=$(grep -oP '(?<=)[^<]+' /var/lib/sonarr/.config/NzbDrone/config.xml) && " + "curl -sf http://localhost:8989/api/v3/system/status -H \"X-Api-Key: $API_KEY\"", + timeout=120, + ) + + # Wait for Radarr API + machine.wait_until_succeeds( + "API_KEY=$(grep -oP '(?<=)[^<]+' /var/lib/radarr/.config/Radarr/config.xml) && " + "curl -sf http://localhost:7878/api/v3/system/status -H \"X-Api-Key: $API_KEY\"", + timeout=120, + ) + + # Create settings.json with placeholder profile data + import json + settings = { + "radarr": [ + { + "name": "Radarr Main", + "activeProfileId": -1, + "activeProfileName": "", + } + ], + "sonarr": [ + { + "name": "Sonarr Main", + "activeProfileId": -1, + "activeProfileName": "", + "activeAnimeProfileId": -1, + "activeAnimeProfileName": "", + } + ], + } + machine.succeed( + f"cat > /var/lib/jellyseerr/settings.json << 'SETTINGS'\n" + f"{json.dumps(settings, indent=2)}\n" + f"SETTINGS" + ) + + # Trigger jellyseerr-init + machine.succeed("systemctl restart jellyseerr-init.service") + machine.wait_for_unit("jellyseerr-init.service") + + # Verify Radarr profile was resolved + machine.succeed( + "jq -e '.radarr[0].activeProfileName == \"Any\"' /var/lib/jellyseerr/settings.json" + ) + machine.succeed( + "jq -e '.radarr[0].activeProfileId > 0' /var/lib/jellyseerr/settings.json" + ) + + # Verify Sonarr profile was resolved + machine.succeed( + "jq -e '.sonarr[0].activeProfileName == \"Any\"' /var/lib/jellyseerr/settings.json" + ) + machine.succeed( + "jq -e '.sonarr[0].activeProfileId > 0' /var/lib/jellyseerr/settings.json" + ) + + # Verify anime profile defaulted to regular profile (animeProfileName was null) + machine.succeed( + "jq -e '.sonarr[0].activeAnimeProfileName == \"Any\"' /var/lib/jellyseerr/settings.json" + ) + machine.succeed( + "jq -e '.sonarr[0].activeAnimeProfileId == .sonarr[0].activeProfileId' /var/lib/jellyseerr/settings.json" + ) + + # ===== Test 1b: Idempotency — second run should not rewrite the file ===== + + machine.succeed("cp /var/lib/jellyseerr/settings.json /tmp/settings_before.json") + machine.succeed("systemctl restart jellyseerr-init.service") + machine.wait_for_unit("jellyseerr-init.service") + machine.succeed("diff /tmp/settings_before.json /var/lib/jellyseerr/settings.json") + + # ===== Test 1c: Missing settings.json — graceful skip ===== + + machine.succeed("rm /var/lib/jellyseerr/settings.json") + machine.succeed("systemctl restart jellyseerr-init.service") + machine.wait_for_unit("jellyseerr-init.service") + machine.succeed("test ! -f /var/lib/jellyseerr/settings.json") + + # ===== Test 2: Explicit anime profile (SD) differs from regular (Any) ===== + + explicit_anime.wait_for_unit("sonarr.service") + explicit_anime.wait_for_unit("radarr.service") + explicit_anime.wait_for_unit("jellyseerr.service") + + explicit_anime.wait_until_succeeds( + "API_KEY=$(grep -oP '(?<=)[^<]+' /var/lib/sonarr/.config/NzbDrone/config.xml) && " + "curl -sf http://localhost:8989/api/v3/system/status -H \"X-Api-Key: $API_KEY\"", + timeout=120, + ) + explicit_anime.wait_until_succeeds( + "API_KEY=$(grep -oP '(?<=)[^<]+' /var/lib/radarr/.config/Radarr/config.xml) && " + "curl -sf http://localhost:7878/api/v3/system/status -H \"X-Api-Key: $API_KEY\"", + timeout=120, + ) + + explicit_anime.succeed( + f"cat > /var/lib/jellyseerr/settings.json << 'SETTINGS'\n" + f"{json.dumps(settings, indent=2)}\n" + f"SETTINGS" + ) + + explicit_anime.succeed("systemctl restart jellyseerr-init.service") + explicit_anime.wait_for_unit("jellyseerr-init.service") + + # Regular profile is "Any" + explicit_anime.succeed( + "jq -e '.sonarr[0].activeProfileName == \"Any\"' /var/lib/jellyseerr/settings.json" + ) + + # Anime profile is "SD" (explicitly set) + explicit_anime.succeed( + "jq -e '.sonarr[0].activeAnimeProfileName == \"SD\"' /var/lib/jellyseerr/settings.json" + ) + + # Profile IDs must differ since the profiles are different + explicit_anime.succeed( + "jq -e '.sonarr[0].activeAnimeProfileId != .sonarr[0].activeProfileId' /var/lib/jellyseerr/settings.json" + ) + ''; +}