at 25.11-pre 5.5 kB view raw
1import ./make-test-python.nix ( 2 { lib, pkgs, ... }: 3 4 { 5 name = "jellyfin"; 6 meta.maintainers = with lib.maintainers; [ minijackson ]; 7 8 nodes.machine = 9 { ... }: 10 { 11 services.jellyfin.enable = true; 12 environment.systemPackages = with pkgs; [ ffmpeg ]; 13 }; 14 15 # Documentation of the Jellyfin API: https://api.jellyfin.org/ 16 # Beware, this link can be resource intensive 17 testScript = 18 let 19 payloads = { 20 auth = pkgs.writeText "auth.json" ( 21 builtins.toJSON { 22 Username = "jellyfin"; 23 } 24 ); 25 empty = pkgs.writeText "empty.json" (builtins.toJSON { }); 26 }; 27 in 28 '' 29 import json 30 from urllib.parse import urlencode 31 32 machine.wait_for_unit("jellyfin.service") 33 machine.wait_for_open_port(8096) 34 machine.succeed("curl --fail http://localhost:8096/") 35 36 machine.wait_until_succeeds("curl --fail http://localhost:8096/health | grep Healthy") 37 38 auth_header = 'MediaBrowser Client="NixOS Integration Tests", DeviceId="1337", Device="Apple II", Version="20.09"' 39 40 41 def api_get(path): 42 return f"curl --fail 'http://localhost:8096{path}' -H 'X-Emby-Authorization:{auth_header}'" 43 44 45 def api_post(path, json_file=None): 46 if json_file: 47 return f"curl --fail -X post 'http://localhost:8096{path}' -d '@{json_file}' -H Content-Type:application/json -H 'X-Emby-Authorization:{auth_header}'" 48 else: 49 return f"curl --fail -X post 'http://localhost:8096{path}' -H 'X-Emby-Authorization:{auth_header}'" 50 51 52 with machine.nested("Wizard completes"): 53 machine.wait_until_succeeds(api_get("/Startup/Configuration")) 54 machine.succeed(api_get("/Startup/FirstUser")) 55 machine.succeed(api_post("/Startup/Complete")) 56 57 with machine.nested("Can login"): 58 auth_result_str = machine.succeed( 59 api_post( 60 "/Users/AuthenticateByName", 61 "${payloads.auth}", 62 ) 63 ) 64 auth_result = json.loads(auth_result_str) 65 auth_token = auth_result["AccessToken"] 66 auth_header += f", Token={auth_token}" 67 68 sessions_result_str = machine.succeed(api_get("/Sessions")) 69 sessions_result = json.loads(sessions_result_str) 70 71 this_session = [ 72 session for session in sessions_result if session["DeviceId"] == "1337" 73 ] 74 if len(this_session) != 1: 75 raise Exception("Session not created") 76 77 me_str = machine.succeed(api_get("/Users/Me")) 78 me = json.loads(me_str)["Id"] 79 80 with machine.nested("Can add library"): 81 tempdir = machine.succeed("mktemp -d -p /var/lib/jellyfin").strip() 82 machine.succeed(f"chmod 755 '{tempdir}'") 83 84 # Generate a dummy video that we can test later 85 videofile = f"{tempdir}/Big Buck Bunny (2008) [1080p].mkv" 86 machine.succeed(f"ffmpeg -f lavfi -i testsrc2=duration=5 '{videofile}'") 87 88 add_folder_query = urlencode( 89 { 90 "name": "My Library", 91 "collectionType": "Movies", 92 "paths": tempdir, 93 "refreshLibrary": "true", 94 } 95 ) 96 97 machine.succeed( 98 api_post( 99 f"/Library/VirtualFolders?{add_folder_query}", 100 "${payloads.empty}", 101 ) 102 ) 103 104 105 def is_refreshed(_): 106 folders_str = machine.succeed(api_get("/Library/VirtualFolders")) 107 folders = json.loads(folders_str) 108 print(folders) 109 return all(folder["RefreshStatus"] == "Idle" for folder in folders) 110 111 112 retry(is_refreshed) 113 114 with machine.nested("Can identify videos"): 115 items = [] 116 117 # For some reason, having the folder refreshed doesn't mean the 118 # movie was scanned 119 def has_movie(_): 120 global items 121 122 items_str = machine.succeed( 123 api_get(f"/Users/{me}/Items?IncludeItemTypes=Movie&Recursive=true") 124 ) 125 items = json.loads(items_str)["Items"] 126 127 return len(items) == 1 128 129 retry(has_movie) 130 131 video = items[0]["Id"] 132 133 item_info_str = machine.succeed(api_get(f"/Users/{me}/Items/{video}")) 134 item_info = json.loads(item_info_str) 135 136 if item_info["Name"] != "Big Buck Bunny": 137 raise Exception("Jellyfin failed to properly identify file") 138 139 with machine.nested("Can read videos"): 140 media_source_id = item_info["MediaSources"][0]["Id"] 141 142 machine.succeed( 143 "ffmpeg" 144 + f" -headers 'X-Emby-Authorization:{auth_header}'" 145 + f" -i http://localhost:8096/Videos/{video}/master.m3u8?mediaSourceId={media_source_id}" 146 + " /tmp/test.mkv" 147 ) 148 149 duration = machine.succeed( 150 "ffprobe /tmp/test.mkv" 151 + " -show_entries format=duration" 152 + " -of compact=print_section=0:nokey=1" 153 ) 154 155 if duration.strip() != "5.000000": 156 raise Exception("Downloaded video has wrong duration") 157 ''; 158 } 159)