#!/bin/bash
# Temporary workspace restore: do a teacher's recent tabs come back — and
# only for a while, and never with their sign-in?
#
# Each scenario launches the INSTALLED classroom-wonderink one or more times
# (tools/lab/wonderink-workspace-rig.py), against the lab's stand-in for
# WonderInk's start page and a second origin standing in for its public
# resource files, and is judged by the tabs each launch opens and by the
# kept file itself. Time between launches is made to pass by moving the
# time of last use in the file; time within a launch is real, with the
# limit shortened by the test-only override where a scenario needs it.
#
#   tools/lab/wonderink-workspace-check [SCENARIO...]
set -uo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
NAME="${LAB_NAME:-classroom-v2-lab}"
COMMAND="${WONDERINK:-/usr/local/bin/classroom-wonderink}"
DIR=/opt/wonderink-check
SITE=http://127.0.0.1:8094
SLIDE=http://localhost:8095/img/ark-wide.png
OTHER=http://localhost:8095/img/ark-tall.png
KEPT=/home/teacher/.local/share/ekklesia-classroom/wonderink-workspace.json
SHEETS="/home/teacher/Downloads/Wonder Ink"

lab() { docker exec -i "$NAME" bash -c "$1"; }

lab "mkdir -p $DIR"
for file in wonderink-workspace-rig.py wonderink-lesson-site.py wonderink-picture-measure.py; do
    docker cp "$ROOT/tools/lab/$file" "$NAME:$DIR/$file" >/dev/null
done
lab "chmod -R a+rX $DIR; id teacher >/dev/null 2>&1 || useradd -m teacher; install -d -o teacher -m 700 /tmp/teacher-rt"
lab "test -f $DIR/www/ark-wide.webp || { mkdir -p $DIR/www; QT_QPA_PLATFORM=offscreen \
     QTWEBENGINE_CHROMIUM_FLAGS='--no-sandbox --disable-gpu' python3 $DIR/wonderink-picture-measure.py make $DIR/www; }
     chmod -R a+r $DIR/www"
lab "pkill -f '[w]onderink-lesson-site.py'; : > $DIR/site.log; chmod a+rw $DIR/site.log"
docker exec -d "$NAME" python3 "$DIR/wonderink-lesson-site.py" "$DIR/www" 8094 8095 "$DIR/site.log"
lab 'for _ in $(seq 20); do curl -s -o /dev/null http://127.0.0.1:8094/app && exit 0; sleep 0.3; done; exit 1' || {
    echo "the lesson site did not start" >&2
    exit 1
}

passed=0
failed=0
out=""

judge() {
    if eval "$2"; then
        printf '  \033[32mPASS\033[0m  %s\n' "$1"
        passed=$((passed + 1))
    else
        printf '  \033[31mFAIL\033[0m  %s\n' "$1"
        failed=$((failed + 1))
    fi
}

# launch [limit=SECONDS] -- STEP...     one Wonder Ink; its output appended to $out
launch() {
    local env=""
    [[ "$1" == limit=* ]] && { env="CLASSROOM_WONDERINK_TEST_WORKSPACE_SECONDS=${1#limit=}"; shift; }
    [[ "$1" == -- ]] && shift
    out+=$'\n'"=== launch $*"$'\n'
    out+=$(lab "su teacher -c 'cd /tmp; XDG_RUNTIME_DIR=/tmp/teacher-rt QT_QPA_PLATFORM=offscreen \
        QTWEBENGINE_CHROMIUM_FLAGS=\"--no-sandbox --disable-gpu\" $env \
        timeout 180 python3 $DIR/wonderink-workspace-rig.py $COMMAND $SITE $* 2>&1'; echo \"exit=\$?\"")
}

# The time of last use in the kept file, made SECONDS older (negative: the future).
age() {
    lab "su teacher -c \"python3 -c 'import json,sys; p=sys.argv[1]; d=json.load(open(p)); d[\\\"last_activity\\\"]-=float(sys.argv[2]); json.dump(d, open(p, \\\"w\\\"))' $KEPT $1\""
}

said() { grep -q -- "$1" <<< "$out"; }
nth() { grep -o -- "$1.*" <<< "$out" | sed -n "${2}p"; }
kept_file() { lab "test -e $KEPT"; }
# The age a STATE line reports for the kept file.
age_of() { grep -o 'age=[0-9]*' <<< "$1" | cut -d= -f2; }
clean() { lab "rm -f $KEPT $KEPT.new; rm -rf \"$SHEETS\"; install -d -o teacher \"$SHEETS\""; out=""; }
# Every launch ended by itself with 0, except one the scenario killed on purpose.
no_crash() {
    ! said Traceback && ! said Segmentation && \
        ! awk '/\] CRASH/ {killed=1} /^exit=/ {if ($0 != "exit=0" && !(killed && $0 == "exit=137")) bad=1; killed=0} END {exit !bad}' <<< "$out"
}

THREE="touch open:$SLIDE open:$OTHER front:1 touch"
RESTORED="tabs=\\['/app', '$SLIDE', '$OTHER'\\] active=1"
FRESH="tabs=\\['/app'\\] active=0 kept\\[none\\]"

scenarios=("$@")
[[ ${#scenarios[@]} -gt 0 ]] || scenarios=(restore-5min restore-90min expire-2h1m expire-next-day expire-next-week
    reopen-does-not-refresh activity-refreshes background-ignored preview-ignored printing-counts
    closed-stays-closed reordered worksheet worksheet-gone crash-then-restore restore-crashes
    corrupt future-format half-written privacy restored-tabs-print)

for scenario in "${scenarios[@]}"; do
    echo "$scenario"
    clean
    before=$failed

    case "$scenario" in
    restore-5min|restore-90min)
        minutes=${scenario#restore-}; minutes=${minutes%min}
        launch -- $THREE state quit
        age $((minutes * 60))
        launch -- state front:0 state quit
        judge "three tabs, the middle one in front, closed; $minutes minutes later: all three back" \
            "nth STATE 2 | grep -q \"$RESTORED\""
        judge "in the same order, the same one in front" "nth START 2 | grep -q 'tabs=3'"
        judge "the tabs behind wait to load until they are shown (a PDF behind opened at 10%)" \
            "nth STATE 2 | grep -q 'unshown=2'"
        judge "and one clicked loads: the start page, asking to sign in" \
            "nth STATE 3 | grep -q \"pages=\\['Signed out'\\] unshown=1\"" ;;
    expire-2h1m|expire-next-day|expire-next-week)
        case "$scenario" in
            expire-2h1m) seconds=$((2 * 3600 + 60)); label="two hours and a minute" ;;
            expire-next-day) seconds=$((24 * 3600)); label="the next day" ;;
            *) seconds=$((7 * 24 * 3600)); label="the next week" ;;
        esac
        launch -- $THREE quit
        age $seconds
        launch -- state quit
        judge "$label: a fresh Wonder Ink, one tab on the start page" "nth STATE 1 | grep -q \"$FRESH\""
        judge "the old workspace is gone, and says why" "said 'starting fresh (expired)' && ! kept_file" ;;
    reopen-does-not-refresh)
        launch limit=20 -- $THREE quit
        launch limit=20 -- wait:8 quit
        launch limit=20 -- wait:8 quit
        launch limit=20 -- state quit
        judge "reopened twice, nobody touching it: restored both times" \
            "nth START 2 | grep -q 'tabs=3' && nth START 3 | grep -q 'tabs=3'"
        judge "but reopening never renewed it: fresh once the limit had passed" \
            "nth STATE 1 | grep -q \"$FRESH\"" ;;
    activity-refreshes)
        launch -- $THREE quit
        age 600
        launch -- wait:4 state touch quit
        # The time of last use is written at most every 30 s, and at quit.
        restored=$(age_of "$(nth STATE 1)"); touched=$(age_of "$(nth QUIT 2)")
        judge "restoring it did not count as using it (age $restored s, from ten minutes)" "(( restored >= 600 ))"
        judge "a click on the restored tabs does (age $touched s at quit)" "(( touched <= 5 ))" ;;
    background-ignored)
        launch -- "touch open:$SITE/app?busy=1 touch" quit
        age 600
        launch -- wait:20 state quit
        judge "a page busy by itself for 20 s (a request a second, an animation) is not activity (age $(age_of "$(nth STATE 1)") s)" \
            "(( \$(age_of \"\$(nth STATE 1)\") >= 615 ))"
        judge "and it really was busy" "[[ \$(lab 'grep -c beacon $DIR/site.log') -gt 10 ]]" ;;
    preview-ignored)
        launch -- $THREE quit
        age 600
        launch -- preview:15 state quit
        judge "a print preview left open 15 s untouched is not activity (age $(age_of "$(nth STATE 1)") s)" \
            "(( \$(age_of \"\$(nth STATE 1)\") >= 610 ))" ;;
    printing-counts)
        launch -- $THREE quit
        age 600
        before_jobs=$(lab 'lpstat -W all -o 2>/dev/null | wc -l')
        launch -- front:2 print state quit
        judge "pressing Print is using Wonder Ink (age $(age_of "$(nth STATE 1)") s)" "(( \$(age_of \"\$(nth STATE 1)\") <= 5 ))"
        judge "and the restored picture tab printed" "(( \$(lab 'lpstat -W all -o | wc -l') > before_jobs ))" ;;
    closed-stays-closed)
        launch -- $THREE close:1 touch quit
        launch -- state quit
        judge "a tab closed before closing Wonder Ink stays closed" \
            "nth STATE 1 | grep -q \"tabs=\\['/app', '$OTHER'\\]\"" ;;
    reordered)
        launch -- $THREE move:0:2 touch quit
        launch -- state quit
        judge "tabs dragged into another order come back in that order" \
            "nth STATE 1 | grep -q \"tabs=\\['$SLIDE', '$OTHER', '/app'\\]\"" ;;
    worksheet)
        launch -- touch worksheet:Colour-the-Ark open:$SLIDE front:1 touch quit
        launch -- state quit
        judge "a worksheet comes back as a worksheet, where it was, in front" \
            "nth STATE 1 | grep -q \"tabs=\\['/app', 'worksheet:Colour-the-Ark.pdf', '$SLIDE'\\] active=1\"" ;;
    worksheet-gone)
        launch -- touch worksheet:Colour-the-Ark open:$SLIDE touch quit
        lab "rm -f \"$SHEETS/Colour-the-Ark.pdf\""
        launch -- state quit
        judge "a worksheet whose file has gone is left out, and nothing else is" \
            "nth STATE 1 | grep -q \"tabs=\\['/app', '$SLIDE'\\]\"" ;;
    crash-then-restore)
        launch -- $THREE wait:2 crash
        launch -- state quit
        judge "killed outright: the tabs come back all the same" "nth STATE 1 | grep -q \"$RESTORED\"" ;;
    restore-crashes)
        launch -- $THREE quit
        launch -- crash
        launch -- crash
        launch -- state quit
        judge "a restore that crashes is tried once more" "nth START 2 | grep -q 'tabs=3' && nth START 3 | grep -q 'tabs=3 attempts=2'"
        judge "and then abandoned: fresh, without asking anybody" \
            "said 'starting fresh (it failed to restore 2 times)' && nth STATE 1 | grep -q \"tabs=\\['/app'\\]\"" ;;
    corrupt)
        lab "su teacher -c 'mkdir -p $(dirname $KEPT); echo \"{not json\" > $KEPT'"
        launch -- state quit
        judge "an unreadable file: fresh, and Wonder Ink opens" "said 'starting fresh (unreadable)' && nth STATE 1 | grep -q \"$FRESH\"" ;;
    future-format)
        launch -- $THREE quit
        lab "su teacher -c \"python3 -c 'import json,sys; p=sys.argv[1]; d=json.load(open(p)); d[\\\"format\\\"]=99; json.dump(d, open(p, \\\"w\\\"))' $KEPT\""
        launch -- state quit
        judge "a file from a later version: fresh" "said 'starting fresh (from another version)' && nth STATE 1 | grep -q \"$FRESH\"" ;;
    half-written)
        launch -- $THREE quit
        lab "su teacher -c 'echo \"{\\\"format\\\": 1, \\\"last_act\" > $KEPT.new'"
        launch -- state quit
        judge "a write cut off half way leaves the last whole one, which is restored" "nth STATE 1 | grep -q \"$RESTORED\"" ;;
    privacy)
        launch -- signin touch state open:$SLIDE open:$OTHER touch quit
        judge "signed in on the stand-in, whose sign-in is Basic auth as WonderInk's is" \
            "nth STATE 1 | grep -q \"pages=\\['Signed in as teacher'\\]\""
        judge "the kept file has no password, Basic-auth credential, cookie or header in it" \
            "kept_file && ! lab \"grep -qiE 'hunter2|Basic|dGVhY2hlc|teacher@|rrf|cookie|x-rrf|authoriz|token' $KEPT\""
        judge "only addresses, titles and times: the fields it is meant to have" \
            "lab \"python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); sys.exit(0 if set(d)=={\\\"format\\\",\\\"last_activity\\\",\\\"active\\\",\\\"attempts\\\",\\\"tabs\\\"} and all(set(t)=={\\\"kind\\\",\\\"where\\\",\\\"title\\\"} for t in d[\\\"tabs\\\"]) else 1)' $KEPT\""
        launch -- front:0 state quit
        judge "and the restored start page is signed out: the site asks again" \
            "said 'restored 3 recent tabs' && nth STATE 2 | grep -q \"pages=\\['Signed out'\\]\"" ;;
    restored-tabs-print)
        launch -- touch worksheet:Colour-the-Ark open:$SLIDE touch quit
        before_jobs=$(lab 'lpstat -W all -o 2>/dev/null | wc -l')
        launch -- front:1 print front:2 print state quit
        judge "a restored worksheet tab and a restored picture tab both print" \
            "(( \$(lab 'lpstat -W all -o | wc -l') >= before_jobs + 2 ))" ;;
    esac

    judge "Wonder Ink never crashed by itself" no_crash
    [[ "$failed" -eq "$before" ]] || \
        { echo "$out" | grep -E 'STATE|START|QUIT|CRASH|PRINTED|exit=|Traceback|starting|restored|TEST' | tail -14 | sed 's/^/      /'; }
done

echo
echo "$passed passed, $failed failed"
[[ "$failed" -eq 0 ]]
