PronounsPage/test/smoketest-diff.sh
Adaline Simonian 23a3862ca0
test: introduce snapshot-based smoke tests
- Adds a new test suite with Docker-based smoke tests for all locales.
  Can be run using the ./smoketest.sh script.
- Replaces all calls to Math.random() with a new helper that returns 0.5
  in snapshot testing mode, ensuring deterministic snapshots.
- Similarly replaces all calls to new Date() and Date.now() with new
  helpers that return a fixed date in snapshot testing mode.
- Replaces checks against NODE_ENV with APP_ENV, to ensure that the
  bundles can be built with Nuxt for testing without losing code that
  would otherwise be stripped out by production optimizations.
- Adds a database init script that can be used to initialize the
  database with a single admin user and a long-lived JWT token for use
  in automation tests.
- Adds a JWT decoding/encoding CLI tool for debugging JWTs.

Note: Snapshots are not checked in, and must be generated manually. See
test/__snapshots__/.gitignore for more information.
2025-02-02 23:11:19 -08:00

496 lines
15 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# This script is invoked by smoketest.sh when the diff command is run:
# ./smoketest.sh diff
#
# It provides a simple interactive TUI for reviewing snapshot diffs.
# Path to the folder containing this script.
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Get current working directory
cwd="$(pwd)"
# If script_dir is the same as cwd, set script_rel_path to "./$(basename "$0")"
if [[ "$script_dir" == "$cwd" ]]; then
main_script_rel_path="./smoketest.sh"
else
main_script_rel_path="${script_dir#"$cwd"/}/smoketest.sh"
fi
usage() {
cat <<EOF
Usage: ${main_script_rel_path} diff [LOCALE]
Interactive TUI for reviewing snapshot diffs. If LOCALE is provided, only
snapshots for that locale will be shown. Otherwise, all locales will be shown.
Options:
LOCALE Locale to show snapshots for. If not provided, all locales are shown.
Examples:
${main_script_rel_path} diff # Show all snapshots.
${main_script_rel_path} diff en # Show snapshots for locale 'en'.
${main_script_rel_path} diff --help # Show this help message.
EOF
}
# Path to the folder containing this script.
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# If any of the arguments are -h or --help, show usage and exit.
for arg in "$@"; do
if [[ "$arg" == "-h" || "$arg" == "--help" ]]; then
usage
exit 0
fi
done
# Directory where snapshots are stored.
SNAPSHOT_DIR="${script_dir}/__snapshots__"
# Colors for TUI.
CLR_RESET="\033[0m"
CLR_GREEN="\033[1;32m"
CLR_RED="\033[1;31m"
CLR_YELLOW="\033[1;33m"
CLR_CYAN="\033[1;36m"
CLR_DIM="\033[2m"
# Trap ctrl+c so we can do a clean exit from the TUI.
trap ctrl_c INT
function ctrl_c() {
exit 0
}
# Build a list of locales that exist in test/__snapshots__, including any
# snapshots (passed or failed).
declare -A LOCALE_SNAPSHOTS=() # Maps: "locale" -> all snapshot base names.
declare -A LOCALE_FAILED_COUNT=() # Maps: "locale" -> integer # of .error.html.
declare -A LOCALE_TOTAL_COUNT=() # Maps: "locale" -> total # of snapshots.
# If a locale was passed as an argument, only show snapshots for that locale.
# Otherwise, show all locales.
locale_dirs=()
for_single_locale=false
if [ $# -eq 1 ]; then
# If a locale was passed, only show snapshots for that locale.
locale_dirs=( "$SNAPSHOT_DIR/$1" )
for_single_locale=true
# Check if the locale directory exists. If it doesn't, print an error and exit.
if [ ! -d "${locale_dirs[0]}" ]; then
echo "No snapshot directory found for locale '$1' in $SNAPSHOT_DIR." >&2
exit 1
fi
else
# Otherwise, gather a list of possible locale subdirectories. Only consider
# directories, ignoring anything else.
locale_dirs=( $(find "$SNAPSHOT_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort) )
# If none found, just say so.
if [ ${#locale_dirs[@]} -eq 0 ]; then
echo "No snapshot directories found in $SNAPSHOT_DIR." >&2
exit 0
fi
fi
for dir in "${locale_dirs[@]}"; do
loc="$(basename "$dir")"
# Gather all *.html inside that directory. Separates expected and actual
# snapshots, and counts the total number of snapshots.
html_files=( $(find "$dir" -maxdepth 1 -type f -name '*.html' 2>/dev/null | sort) )
if [ ${#html_files[@]} -eq 0 ]; then
LOCALE_SNAPSHOTS["$loc"]=""
LOCALE_FAILED_COUNT["$loc"]=0
LOCALE_TOTAL_COUNT["$loc"]=0
continue
fi
total_count=0
failed_count=0
found_bases=()
failed_idx=0
for file in "${html_files[@]}"; do
base="$(basename "$file")"
if [[ "$base" == *".error.html" ]]; then
short="${base%.error.html}"
failed_count=$((failed_count + 1))
# Ensure we only add the base once.
if [[ ! " ${found_bases[*]} " =~ " $short " ]]; then
# Insert failed snapshots at the beginning.
found_bases=( "${found_bases[@]:0:$failed_idx}" "$short" "${found_bases[@]:$failed_idx}" )
failed_idx=$((failed_idx + 1))
total_count=$((total_count + 1))
fi
else
short="${base%.html}"
if [[ ! " ${found_bases[*]} " =~ " $short " ]]; then
found_bases+=( "$short" )
total_count=$((total_count + 1))
fi
fi
done
LOCALE_SNAPSHOTS["$loc"]="${found_bases[*]}"
LOCALE_FAILED_COUNT["$loc"]=$failed_count
LOCALE_TOTAL_COUNT["$loc"]=$total_count
done
locales_sorted=( $(printf "%s\n" "${!LOCALE_SNAPSHOTS[@]}" | sort) )
# Hide cursor for a less jumpy interface.
tput civis
cleanup() {
# Re-enable cursor on exit.
tput cnorm
}
trap cleanup EXIT
# Reads one keypress (arrow keys, enter, esc, pgup, pgdn, etc.) in raw mode.
# Returns a code: up/down/pgup/pgdn/enter/esc/backspace/ctrl_c/other
#
# Common escape sequences:
# ↑: ESC [ A
# ↓: ESC [ B
# PageUp: ESC [ 5 ~
# PageDown: ESC [ 6 ~
read_keypress() {
IFS= read -rsn1 key 2>/dev/null || true
case "$key" in
$'\x1b') # ESC
# Try reading up to two (or three) more bytes to detect special keys
read -rsn2 -t 0.001 rest 2>/dev/null || true
case "$rest" in
'[A') echo "up" ;;
'[B') echo "down" ;;
'[5')
# Possibly PageUp if next byte is '~'.
read -rsn1 -t 0.001 t3 2>/dev/null || true
if [[ "$t3" == "~" ]]; then
echo "pgup"
else
echo "esc"
fi
;;
'[6')
# Possibly PageDown if next byte is '~'.
read -rsn1 -t 0.001 t3 2>/dev/null || true
if [[ "$t3" == "~" ]]; then
echo "pgdn"
else
echo "esc"
fi
;;
*)
# If it's not recognized, treat as ESC.
echo "esc"
;;
esac
;;
'') # Enter sends empty string.
echo "enter"
;;
$'\x7f') # Backspace.
echo "backspace"
;;
q) # Treat q as backspace.
echo "backspace"
;;
*) # Could be ctrl_c, or random keys.
# Check for ^C specifically.
if [[ $key == $'\x03' ]]; then
echo "ctrl_c"
else
echo "other"
fi
;;
esac
}
# print_menu <title> <items[@]> <highlightIndex>
#
# Supports paging/scrolling so that long lists don't overflow the screen.
# PageUp/PageDown will jump the highlight by max_items at a time.
print_menu() {
local title="$1"
shift
local -a items=("${!1}") # Indirect expansion: if you passed lines[@], this captures its elements
shift
local highlightIndex="$1"
# How many total items?
local count="${#items[@]}"
# Clear screen.
clear
# Print header.
echo -e "${CLR_CYAN}${title}${CLR_RESET}"
echo "Use ↑/↓ or PgUp/PgDn to navigate, Enter to select, ESC/Backspace/q to go back, CTRL+C to quit."
echo
# Get terminal height (number of rows).
local term_height
term_height=$(tput lines)
# We want to leave some rows for the title/instructions at top, and maybe 1
# row of margin at the bottom. title+instructions so far = 3 lines, so:
local menu_start=4
local max_items=$(( term_height - menu_start - 2 ))
if (( max_items < 1 )); then
# In case the terminal is very small.
max_items=1
fi
# Make sure highlightIndex is in range.
if (( highlightIndex < 0 )); then
highlightIndex=0
elif (( highlightIndex >= count )); then
highlightIndex=$(( count - 1 ))
fi
# Center the highlight in the menu if possible.
# First guess: start printing from highlightIndex - half the window.
local half=$(( max_items / 2 ))
local start=$(( highlightIndex - half ))
if (( start < 0 )); then
start=0
fi
local end=$(( start + max_items - 1 ))
if (( end >= count )); then
end=$(( count - 1 ))
fi
# If highlightIndex is below start or above end, adjust so it's visible.
if (( highlightIndex < start )); then
start=$highlightIndex
elif (( highlightIndex > end )); then
start=$(( highlightIndex - max_items + 1 ))
fi
if (( start < 0 )); then
start=0
fi
end=$(( start + max_items - 1 ))
if (( end >= count )); then
end=$(( count - 1 ))
fi
# If there are items outside this window, show an indicator.
if (( start > 0 )); then
echo -e "${CLR_DIM} ↑ ... more above ...${CLR_RESET}"
else
echo
fi
# Print only items[start..end].
for (( i = start; i <= end; i++ )); do
local line="${items[$i]}"
if (( i == highlightIndex )); then
# Highlighted.
echo -e " ${CLR_YELLOW}$line${CLR_RESET}"
else
# Normal or dim.
if [[ "$line" == *"[No snapshots]"* ]]; then
echo -e " ${CLR_DIM}$line${CLR_RESET}"
else
echo " $line"
fi
fi
done
if (( end < count - 1 )); then
echo -e "${CLR_DIM} ↓ ... more below ...${CLR_RESET}"
fi
}
# Locale selection menu.
choose_locale() {
local idx=0
while true; do
# Build an array of menu lines.
local lines=()
for loc in "${locales_sorted[@]}"; do
local total="${LOCALE_TOTAL_COUNT["$loc"]}"
local fails="${LOCALE_FAILED_COUNT["$loc"]}"
if [ "$total" -eq 0 ]; then
lines+=("$loc [No snapshots]")
else
if [ "$fails" -gt 0 ]; then
lines+=("$loc ($fails failed / $total total)")
else
lines+=("$loc (all passed / $total total)")
fi
fi
done
# Show the menu.
print_menu "Locales with Snapshots" lines[@] "$idx" >&2
# Wait for a key.
key="$(read_keypress)"
case "$key" in
up)
# Decrement idx carefully.
if [ "$idx" -le 0 ]; then
idx=$(( ${#lines[@]} - 1 ))
else
idx=$((idx - 1))
fi
;;
down)
# Increment idx carefully.
if [ "$idx" -ge $(( ${#lines[@]} - 1 )) ]; then
idx=0
else
idx=$((idx + 1))
fi
;;
pgup)
# Jump up by one page.
idx=$(( idx - ( $(tput lines) - 5 ) ))
if (( idx < 0 )); then
idx=0
fi
;;
pgdn)
# Jump down by one page.
idx=$(( idx + ( $(tput lines) - 5 ) ))
if (( idx >= ${#lines[@]} )); then
idx=$(( ${#lines[@]} - 1 ))
fi
;;
enter)
local line="${lines[$idx]}"
# If it's "[No snapshots]", ignore selection.
if [[ "$line" == *"[No snapshots]"* ]]; then
continue
fi
# Otherwise proceed.
echo "${locales_sorted[$idx]}"
return 0
;;
esc|backspace|ctrl_c)
# Return empty to signal user wants to quit.
echo ""
return 0
;;
esac
done
}
# Snapshot selection menu.
choose_snapshot() {
local loc="$1"
# Reconstruct the snapshot base names.
IFS=' ' read -r -a bases <<< "${LOCALE_SNAPSHOTS[$loc]}"
local idx=0
while true; do
# Build lines[] for display.
local lines=()
for base in "${bases[@]}"; do
local expected="${SNAPSHOT_DIR}/${loc}/${base}.html"
local actual="${SNAPSHOT_DIR}/${loc}/${base}.error.html"
# URL Decode any %2F, %3A, etc. for display.
base="$(echo -e "${base//%/\\x}")"
if [ -f "$actual" ]; then
lines+=("$base")
else
lines+=("$base")
fi
done
print_menu "Snapshots in locale '$loc'" lines[@] "$idx" >&2
key="$(read_keypress)"
case "$key" in
up)
if [ "$idx" -le 0 ]; then
idx=$(( ${#lines[@]} - 1 ))
else
idx=$((idx - 1))
fi
;;
down)
if [ "$idx" -ge $(( ${#lines[@]} - 1 )) ]; then
idx=0
else
idx=$((idx + 1))
fi
;;
pgup)
idx=$(( idx - ( $(tput lines) - 5 ) ))
if (( idx < 0 )); then
idx=0
fi
;;
pgdn)
idx=$(( idx + ( $(tput lines) - 5 ) ))
if (( idx >= ${#lines[@]} )); then
idx=$(( ${#lines[@]} - 1 ))
fi
;;
enter)
local base="${bases[$idx]}"
local expected="${SNAPSHOT_DIR}/${loc}/${base}.html"
local actual="${SNAPSHOT_DIR}/${loc}/${base}.error.html"
# URL Decode any %2F, %3A, etc. for display.
base="$(echo -e "${base//%/\\x}")"
if [ ! -f "$actual" ]; then
# Passed snapshot.
clear
echo -e "${CLR_GREEN}Snapshot '$base' PASSED. No .error.html to compare.${CLR_RESET}" >&2
echo "Press any key to go back..." >&2
read -rsn1
else
# Compare with git diff.
clear
echo "Running diff on '$expected' vs '$actual'..." >&2
tput cnorm
git diff --no-index --color=always --color-words "$expected" "$actual" | less -R || true
# Hide cursor again on return.
tput civis
fi
;;
esc|backspace|ctrl_c)
return 0
;;
esac
done
}
# Main interactive flow
# If we're only showing snapshots for a single locale, skip the locale menu.
if [ "$for_single_locale" = true ]; then
choose_snapshot "${locale_dirs[0]##*/}"
# Exit if user pressed ESC/back at the snapshot menu.
clear
exit 0
fi
# Otherwise, show the locale menu first.
while true; do
chosen_locale="$(choose_locale)"
if [ -z "$chosen_locale" ]; then
# Exit if user pressed ESC/back at the locale menu.
clear
exit 0
fi
choose_snapshot "$chosen_locale"
done