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