feat: add live outage countdown cli dashboard
This commit is contained in:
200
tests/test_main.sh
Executable file
200
tests/test_main.sh
Executable file
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=../main.sh
|
||||
source "$ROOT_DIR/main.sh"
|
||||
|
||||
TEST_TMP="$(mktemp -d "${TMPDIR:-/tmp}/blackout-cli-tests.XXXXXX")"
|
||||
trap 'rm -rf -- "$TEST_TMP"' EXIT
|
||||
|
||||
assert_equal() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local message="$3"
|
||||
|
||||
if [[ "$expected" != "$actual" ]]; then
|
||||
printf 'FAIL: %s\nExpected: %s\nActual: %s\n' "$message" "$expected" "$actual" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local text="$1"
|
||||
local expected="$2"
|
||||
local message="$3"
|
||||
|
||||
if [[ "$text" != *"$expected"* ]]; then
|
||||
printf 'FAIL: %s\nMissing: %s\n' "$message" "$expected" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
FIXTURE="$TEST_TMP/outages.json"
|
||||
cat >"$FIXTURE" <<'JSON'
|
||||
{
|
||||
"schema_version": 1,
|
||||
"updated_at": "2026-07-18T16:00:00+03:30",
|
||||
"bills": {
|
||||
"222": {
|
||||
"events": {
|
||||
"5": {
|
||||
"status": "active",
|
||||
"snapshot": {
|
||||
"outage_number": "5",
|
||||
"outage_date": "1405/04/28",
|
||||
"start_time": "09:00",
|
||||
"stop_time": "11:00",
|
||||
"address": "Future planned address",
|
||||
"reason": "Maintenance",
|
||||
"is_planned": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"111": {
|
||||
"events": {
|
||||
"1": {
|
||||
"status": "active",
|
||||
"snapshot": {
|
||||
"outage_number": "1",
|
||||
"outage_date": "1405/04/27",
|
||||
"start_time": "16:00",
|
||||
"stop_time": "18:00",
|
||||
"address": "Current address",
|
||||
"reason": "Load management",
|
||||
"is_planned": true
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"status": "active",
|
||||
"snapshot": {
|
||||
"outage_number": "2",
|
||||
"outage_date": "1405/04/27",
|
||||
"start_time": "18:00",
|
||||
"stop_time": "20:00",
|
||||
"address": "Emergency address",
|
||||
"reason": "Emergency load management",
|
||||
"is_planned": false
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"status": "cancelled",
|
||||
"snapshot": {
|
||||
"outage_number": "3",
|
||||
"outage_date": "1405/04/27",
|
||||
"start_time": "19:00",
|
||||
"stop_time": "21:00",
|
||||
"address": "Cancelled address",
|
||||
"reason": "Cancelled maintenance",
|
||||
"is_planned": true
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"status": "active",
|
||||
"snapshot": {
|
||||
"outage_number": "4",
|
||||
"outage_date": "1405/04/27",
|
||||
"start_time": "10:00",
|
||||
"stop_time": "11:00",
|
||||
"address": "Expired address",
|
||||
"reason": "Old event",
|
||||
"is_planned": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
||||
assert_equal "2026-07-18" "$(jalali_to_gregorian "1405/04/27")" \
|
||||
"Jalali dates should convert to Gregorian dates"
|
||||
|
||||
read -r cross_start cross_stop < <(event_epochs "1405/04/27" "23:00" "01:00")
|
||||
assert_equal "7200" "$((cross_stop - cross_start))" \
|
||||
"cross-midnight outages should end on the following day"
|
||||
|
||||
assert_equal "01 : 01 : 01" "$(banner_duration 3661)" \
|
||||
"countdowns should include hours, minutes, and seconds"
|
||||
assert_equal "49 : 30 : 00" "$(banner_duration 178200)" \
|
||||
"countdowns should use total hours beyond one day"
|
||||
FONT_OUTPUT="$(print_large_countdown "00 : 00 : 00" 150)"
|
||||
assert_contains "$FONT_OUTPUT" ',a8888a,' \
|
||||
"the countdown should consistently use the univers-style font"
|
||||
|
||||
if ! validate_state "$FIXTURE"; then
|
||||
printf 'FAIL: valid fixture was rejected\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NOW_EPOCH="$(TZ=Asia/Tehran date -d '2026-07-18 17:00:00' +%s)"
|
||||
build_schedule "$FIXTURE" "$NOW_EPOCH"
|
||||
assert_equal "3" "${#SCHEDULE[@]}" \
|
||||
"only active current and future events should be scheduled"
|
||||
assert_equal "ONGOING" "${HERO%%$'\t'*}" \
|
||||
"an ongoing outage should take the hero position"
|
||||
assert_contains "${SCHEDULE[0]}" '"outage_number":"1"' \
|
||||
"ongoing events should sort first"
|
||||
assert_contains "${SCHEDULE[1]}" '"outage_number":"2"' \
|
||||
"the nearest future event should sort second"
|
||||
assert_contains "${SCHEDULE[2]}" '"outage_number":"5"' \
|
||||
"later events should remain in the schedule"
|
||||
|
||||
OUTPUT="$(NO_COLOR=1 COLUMNS=80 render_dashboard "$NOW_EPOCH")"
|
||||
if [[ "$OUTPUT" == *"POWER RETURNS IN"* || "$OUTPUT" == *"NEXT OUTAGE IN"* ]]; then
|
||||
printf 'FAIL: countdown output contains a redundant heading\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
assert_contains "$OUTPUT" "PLANNED" \
|
||||
"planned events should be labeled"
|
||||
assert_contains "$OUTPUT" "EMERGENCY" \
|
||||
"unplanned events should be labeled"
|
||||
assert_contains "$OUTPUT" "OUTAGE SCHEDULE (3)" \
|
||||
"the dashboard should include every selected event"
|
||||
if [[ "$OUTPUT" == *"Address"* || "$OUTPUT" == *"Reason"* \
|
||||
|| "$OUTPUT" == *"Emergency address"* || "$OUTPUT" == *"Load management"* ]]; then
|
||||
printf 'FAIL: dashboard output exposes address or reason text\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$OUTPUT" == *$'\033['* ]]; then
|
||||
printf 'FAIL: NO_COLOR output contains ANSI escapes\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FUTURE_NOW="$(TZ=Asia/Tehran date -d '2026-07-18 15:00:00' +%s)"
|
||||
build_schedule "$FIXTURE" "$FUTURE_NOW"
|
||||
assert_equal "UPCOMING" "${HERO%%$'\t'*}" \
|
||||
"the nearest future event should be the hero when none are ongoing"
|
||||
|
||||
EMPTY_NOW="$(TZ=Asia/Tehran date -d '2026-07-20 12:00:00' +%s)"
|
||||
build_schedule "$FIXTURE" "$EMPTY_NOW"
|
||||
assert_equal "0" "${#SCHEDULE[@]}" \
|
||||
"expired schedules should become empty"
|
||||
EMPTY_OUTPUT="$(NO_COLOR=1 COLUMNS=60 render_dashboard "$EMPTY_NOW")"
|
||||
assert_contains "$EMPTY_OUTPUT" "ALL CLEAR" \
|
||||
"an empty schedule should render the all-clear screen"
|
||||
|
||||
TERMINAL_ACTIVE=1
|
||||
DRAW_OUTPUT="$(draw_frame $'short\nlonger')"
|
||||
TERMINAL_ACTIVE=0
|
||||
assert_contains "$DRAW_OUTPUT" $'short\033[K\nlonger\033[K' \
|
||||
"interactive redraws should erase old content from every line"
|
||||
|
||||
MALFORMED="$TEST_TMP/malformed.json"
|
||||
printf '{"schema_version": 999, "bills": {}}\n' >"$MALFORMED"
|
||||
if validate_state "$MALFORMED"; then
|
||||
printf 'FAIL: unsupported schema was accepted\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (
|
||||
curl() { return 22; }
|
||||
fetch_state "$TEST_TMP/download.json" "123"
|
||||
); then
|
||||
printf 'FAIL: download failure was reported as success\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'Bash CLI tests passed\n'
|
||||
Reference in New Issue
Block a user