Files
daily-blackout-check/tests/test_main.sh
MeghdadFadaee 301e57fe70
Some checks failed
Test blackout notifier / test (push) Failing after 2s
Hourly blackout check / check (push) Successful in 3s
fix(cli): make status dividers span terminal width
2026-07-21 17:01:00 +03:30

223 lines
7.2 KiB
Bash
Executable File

#!/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
WIDE_OUTPUT="$(NO_COLOR=1 COLUMNS=180 render_dashboard "$NOW_EPOCH")"
WIDE_TOP_LINE="${WIDE_OUTPUT%%$'\n'*}"
assert_equal "180" "${#WIDE_TOP_LINE}" \
"the countdown divider should span the full terminal width"
WIDE_BOTTOM_LINE="${WIDE_OUTPUT##*$'\n'}"
assert_equal "180" "${#WIDE_BOTTOM_LINE}" \
"the dashboard bottom divider should span the full terminal width"
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"
WIDE_EMPTY_OUTPUT="$(NO_COLOR=1 COLUMNS=180 render_dashboard "$EMPTY_NOW")"
WIDE_EMPTY_TOP_LINE="${WIDE_EMPTY_OUTPUT%%$'\n'*}"
WIDE_EMPTY_BOTTOM_LINE="${WIDE_EMPTY_OUTPUT##*$'\n'}"
assert_equal "180" "${#WIDE_EMPTY_TOP_LINE}" \
"the all-clear top divider should span the full terminal width"
assert_equal "180" "${#WIDE_EMPTY_BOTTOM_LINE}" \
"the all-clear bottom divider should span the full terminal width"
WIDE_UNAVAILABLE_OUTPUT="$(NO_COLOR=1 COLUMNS=80 render_unavailable 180)"
WIDE_UNAVAILABLE_TOP_LINE="${WIDE_UNAVAILABLE_OUTPUT%%$'\n'*}"
WIDE_UNAVAILABLE_BOTTOM_LINE="${WIDE_UNAVAILABLE_OUTPUT##*$'\n'}"
assert_equal "180" "${#WIDE_UNAVAILABLE_TOP_LINE}" \
"the unavailable top divider should span the full terminal width"
assert_equal "180" "${#WIDE_UNAVAILABLE_BOTTOM_LINE}" \
"the unavailable bottom divider should use the detected terminal width"
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'