Add initial testing script

This commit is contained in:
ConfusedPolarBear 2022-06-13 01:52:41 -05:00
parent a29f2a94bb
commit cac2094b4c
5 changed files with 228 additions and 3 deletions

7
.gitignore vendored
View File

@ -1,7 +1,8 @@
bin/
obj/
.vs/
.idea/
artifacts
# Ignore pre compiled web interface
docker/dist
# Ignore user provided end to end test results
ConfusedPolarBear.Plugin.IntroSkipper.Tests/e2e_tests/expected/*.json

View File

@ -0,0 +1,14 @@
# End to end testing framework
This folder holds scripts used in performing end to end testing of the plugin. The script:
1. **Erases all currently discovered introduction timestamps**
2. Runs the Analyze episodes task
3. Waits for the analysis to complete
4. Checks that the current results are within one second of a previous result
## Usage
1. Save the response returned by `/Intros/All?api_key=KEY` to a file somewhere.
2. Set the environment variable `JELLYFIN_TOKEN` to the access token of an administrator.
3. Run `python3 main.py -f FILENAME`

View File

@ -0,0 +1,197 @@
import argparse, json, os, time
import requests
# Server address
addr = ""
# Authentication token
token = ""
# GUID of the analyze episodes scheduled task
taskId = "8863329048cc357f7dfebf080f2fe204"
# Parse CLI arguments
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"-s",
help="Server address (if different than http://127.0.0.1:8096)",
type=str,
dest="address",
default="http://127.0.0.1:8096",
metavar="ADDRESS",
)
parser.add_argument(
"-freq",
help="Interval to poll task completion state at (default is 10 seconds)",
type=int,
dest="frequency",
default=10,
)
parser.add_argument(
"-f",
help="Expected intro timestamps (as previously retrieved from /Intros/All)",
type=str,
dest="expected",
default="expected/dev.json",
metavar="FILENAME",
)
return parser.parse_args()
# Send an HTTP request and return the response
def send(url, method="GET", log=True):
global addr, token
# Construct URL
r = None
url = addr + url
# Log request
if log:
print(f"{method} {url} ", end="")
# Send auth token
headers = {"Authorization": f"MediaBrowser Token={token}"}
# Send the request
if method == "GET":
r = requests.get(url, headers=headers)
elif method == "POST":
r = requests.post(url, headers=headers)
else:
raise ValueError(f"Unknown method {method}")
# Log status code
if log:
print(f"{r.status_code}\n")
# Check status code
r.raise_for_status()
return r
def close_enough(expected, actual):
# TODO: make customizable
return abs(expected - actual) <= 2
# Validate that all episodes in actual have a similar entry in expected.
def validate(expected, actual):
good = 0
bad = 0
total = len(expected)
for i in expected:
if i not in actual:
print(f"[!] Cound not find episode {i}")
bad += 1
continue
ex = expected[i]
ac = actual[i]
start = close_enough(ex["IntroStart"], ac["IntroStart"])
end = close_enough(ex["IntroEnd"], ac["IntroEnd"])
# If both the start and end times are close enough, keep going
if start and end:
good += 1
continue
# Oops
bad += 1
print(f"[!] Episode {i} is not correct")
print(
f"expected {ex['IntroStart']} => {ex['IntroEnd']} but found {ac['IntroStart']} => {ac['IntroEnd']}"
)
print()
print("Statistics:")
print(f"Correct: {good} ({int((good * 100) / total)}%)")
print(f"Incorrect: {bad}")
print(f"Total: {total}")
def main():
global addr, token
# Validate arguments
args = parse_args()
addr = args.address
# Validate token
token = os.environ.get("JELLYFIN_TOKEN")
if token is None:
print(
"Administrator access token is required, set environment variable JELLYFIN_TOKEN and try again"
)
exit(1)
# Validate expected timestamps
expected = []
with open(args.expected, "r") as f:
expected = json.load(f)
print(f"[+] Found {len(expected)} expected timestamps\n")
# Erase old intro timestamps
print("[+] Erasing previously discovered introduction timestamps")
send("/Intros/EraseTimestamps", "POST")
# Run analyze episodes task
print("[+] Starting episode analysis task")
send(f"/ScheduledTasks/Running/{taskId}", "POST")
# Poll for completion
print("[+] Waiting for analysis task to complete")
while True:
time.sleep(args.frequency)
task = send(f"/ScheduledTasks/{taskId}", "GET", False).json()
state = task["State"]
# Calculate percentage analyzed
percent = 0
if state == "Idle":
percent = 100
elif state == "Running":
percent = 0
if "CurrentProgressPercentage" in task:
percent = task["CurrentProgressPercentage"]
# Print percentage analyzed
print(f"\r[+] Episodes analyzed: {percent}%", end="")
if percent == 100:
print("\n")
break
# Download actual intro timestamps
print("[+] Getting actual timestamps")
intros = send("/Intros/All")
actual = intros.json()
# Store actual episodes to the filesystem
with open("/tmp/actual.json", "w") as f:
f.write(intros.text)
# Verify timestamps
print(f"[+] Found {len(actual)} actual timestamps\n")
validate(expected, actual)
# TODO: Pick 5 random intros and verify deeply equal to v1
# this should be done by getting the versioned endpoint and non-versioned
main()

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Net.Mime;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -64,4 +65,16 @@ public class SkipIntroController : ControllerBase
Plugin.Instance!.SaveTimestamps();
return NoContent();
}
/// <summary>
/// Get all introductions. Only used by the end to end testing script.
/// </summary>
/// <response code="200">All introductions have been returned.</response>
/// <returns>Dictionary of Intro objects.</returns>
[Authorize(Policy = "RequiresElevation")]
[HttpGet("Intros/All")]
public ActionResult<Dictionary<Guid, Intro>> GetAllIntros()
{
return Plugin.Instance!.Intros;
}
}