Files
daily-blackout-check/tests/test_main.sh
Meghdad 42687ee651
Some checks failed
Test blackout notifier / test (push) Failing after 4s
Hourly blackout check / check (push) Successful in 3s
feat: add multi-chat routing and bill-specific dashboard countdown
2026-07-22 02:14:45 +03:30

251 lines
8.5 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"
build_schedule "$FIXTURE" "$NOW_EPOCH" "222"
assert_equal "3" "${#SCHEDULE[@]}" \
"selecting a countdown bill should not filter the schedule"
assert_equal "UPCOMING" "${HERO%%$'\t'*}" \
"the countdown should ignore another bill's ongoing outage"
assert_contains "$HERO" '"bill_id":"222"' \
"the countdown should use only the selected bill"
SELECTED_OUTPUT="$(NO_COLOR=1 COLUMNS=80 render_dashboard "$NOW_EPOCH" 80 "222")"
assert_contains "$SELECTED_OUTPUT" "COUNTDOWN FOR BILL 222" \
"the selected countdown bill should be labeled"
assert_contains "$SELECTED_OUTPUT" "OUTAGE SCHEDULE (3)" \
"the selected countdown bill should not hide other bills from the list"
build_schedule "$FIXTURE" "$NOW_EPOCH" "333"
assert_equal "" "$HERO" \
"a bill without upcoming outages should not borrow another bill's countdown"
NO_HERO_OUTPUT="$(NO_COLOR=1 COLUMNS=80 render_dashboard "$NOW_EPOCH" 80 "333")"
assert_contains "$NO_HERO_OUTPUT" "NO CURRENT OR UPCOMING OUTAGE" \
"a selected bill without outages should have a clear countdown status"
assert_contains "$NO_HERO_OUTPUT" "OUTAGE SCHEDULE (3)" \
"all bills should remain listed when the selected bill has no outage"
build_schedule "$FIXTURE" "$NOW_EPOCH"
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
if main --bill-id invalid >/dev/null 2>&1; then
printf 'FAIL: a non-decimal countdown bill ID was accepted\n' >&2
exit 1
fi
printf 'Bash CLI tests passed\n'