diff --git a/test/at.bats b/test/at.bats new file mode 100644 index 0000000..d1cdc0e --- /dev/null +++ b/test/at.bats @@ -0,0 +1,81 @@ +#!/usr/bin/env bats + +load test_helper + +@test "at creates a one-shot alarm" { + run_nag at "tomorrow 3pm" "take a break" + [ "${status}" -eq 0 ] + [[ "${output}" =~ "[1] Tomorrow, 3pm — take a break" ]] + [ -f "${NAG_DIR}/alarms" ] + [ "$(wc -l < "${NAG_DIR}/alarms")" -eq 1 ] + # Verify TSV structure: idtimestampmessage + local _line + _line="$(cat "${NAG_DIR}/alarms")" + [[ "${_line}" =~ ^1$'\t'[0-9]+$'\t'$'\t'take\ a\ break$ ]] +} + +@test "at is the implicit subcommand" { + run_nag "tomorrow 3pm" "take a break" + [ "${status}" -eq 0 ] + [[ "${output}" =~ "[1]" ]] + [[ "${output}" =~ "take a break" ]] +} + +@test "at with invalid time fails" { + run_nag at "notavalidtime" "some message" + [ "${status}" -eq 1 ] +} + +@test "at rejects yesterday" { + run_nag at "yesterday 3pm" "too late" + [ "${status}" -eq 1 ] + [[ "${output}" =~ "past" ]] +} + +@test "at rejects ago" { + run_nag at "2 hours ago" "too late" + [ "${status}" -eq 1 ] + [[ "${output}" =~ "past" ]] +} + +@test "at rejects last" { + run_nag at "last friday" "too late" + [ "${status}" -eq 1 ] + [[ "${output}" =~ "past" ]] +} + +@test "invalid time with ago says invalid, not past" { + run_nag at "sdjkfhskdjfh ago" "nope" + [ "${status}" -eq 1 ] + [[ "${output}" =~ "Invalid time" ]] +} + +@test "invalid time with yesterday says invalid, not past" { + run_nag at "sdjkfh yesterday blah" "nope" + [ "${status}" -eq 1 ] + [[ "${output}" =~ "Invalid time" ]] +} + +@test "invalid time with last says invalid, not past" { + run_nag at "last sdjkfh" "nope" + [ "${status}" -eq 1 ] + [[ "${output}" =~ "Invalid time" ]] +} + +@test "at rolls past date forward to next year" { + local _last_month + _last_month="$(date -d "last month" "+%B %-d")" + run_nag at "${_last_month}" "annual thing" + [ "${status}" -eq 0 ] + local _ts + _ts="$(cut -f2 "${NAG_DIR}/alarms")" + local _ts_year _next_year + _ts_year="$(date -d "@${_ts}" +%Y)" + _next_year="$(date -d "+1 year" +%Y)" + [ "${_ts_year}" = "${_next_year}" ] +} + +@test "at without message fails" { + run_nag at "tomorrow 3pm" + [ "${status}" -eq 1 ] +} diff --git a/test/check.bats b/test/check.bats new file mode 100644 index 0000000..7e6b68f --- /dev/null +++ b/test/check.bats @@ -0,0 +1,126 @@ +#!/usr/bin/env bats + +load test_helper + +@test "check fires expired one-shot and removes it" { + local _past_ts=$(( $(date +%s) - 60 )) + write_alarm "$(printf "1\t%s\t\tpast alarm" "${_past_ts}")" + + # Record what was fired. + local _fired="${NAG_DIR}/fired" + export NAG_CMD="${NAG_DIR}/recorder" + cat > "${NAG_CMD}" <<'SCRIPT' +#!/usr/bin/env bash +printf "%s\n" "$2" >> "${NAG_DIR}/fired" +SCRIPT + chmod +x "${NAG_CMD}" + + run_nag check + [ "${status}" -eq 0 ] + + # Alarm should have been removed. + [[ ! -s "${NAG_DIR}/alarms" ]] || [ "$(wc -l < "${NAG_DIR}/alarms")" -eq 0 ] + + # Notification should have fired. + grep -q "past alarm" "${_fired}" +} + +@test "check reschedules expired repeating alarm" { + local _past_ts=$(( $(date +%s) - 60 )) + write_alarm "$(printf "1\t%s\tday\tdaily alarm" "${_past_ts}")" + + run_nag check + [ "${status}" -eq 0 ] + + # Alarm should still exist with a future timestamp. + [ -s "${NAG_DIR}/alarms" ] + local _new_ts + _new_ts="$(cut -f2 "${NAG_DIR}/alarms" | head -1)" + local _now + _now="$(date +%s)" + (( _new_ts > _now )) +} + +@test "check does not fire future alarms" { + local _future_ts=$(( $(date +%s) + 3600 )) + write_alarm "$(printf "1\t%s\t\tfuture alarm" "${_future_ts}")" + + run_nag check + [ "${status}" -eq 0 ] + + # Alarm should still be there, unchanged. + [ -s "${NAG_DIR}/alarms" ] + grep -q "future alarm" "${NAG_DIR}/alarms" +} + +@test "check fires all missed alarms" { + local _past1=$(( $(date +%s) - 120 )) + local _past2=$(( $(date +%s) - 60 )) + write_alarm "$(printf "1\t%s\t\tmissed one" "${_past1}")" + write_alarm "$(printf "2\t%s\t\tmissed two" "${_past2}")" + + local _fired="${NAG_DIR}/fired" + export NAG_CMD="${NAG_DIR}/recorder" + cat > "${NAG_CMD}" <<'SCRIPT' +#!/usr/bin/env bash +printf "%s\n" "$2" >> "${NAG_DIR}/fired" +SCRIPT + chmod +x "${NAG_CMD}" + + run_nag check + [ "${status}" -eq 0 ] + + # Both should have fired. + grep -q "missed one" "${_fired}" + grep -q "missed two" "${_fired}" + + # Both should be removed (one-shots). + [[ ! -s "${NAG_DIR}/alarms" ]] || [ "$(wc -l < "${NAG_DIR}/alarms")" -eq 0 ] +} + +@test "check silently drops stale one-shot (older than 15 min)" { + local _stale_ts=$(( $(date +%s) - 1800 )) # 30 minutes ago + write_alarm "$(printf "1\t%s\t\tstale alarm" "${_stale_ts}")" + + local _fired="${NAG_DIR}/fired" + export NAG_CMD="${NAG_DIR}/recorder" + cat > "${NAG_CMD}" <<'SCRIPT' +#!/usr/bin/env bash +printf "%s\n" "$2" >> "${NAG_DIR}/fired" +SCRIPT + chmod +x "${NAG_CMD}" + + run_nag check + [ "${status}" -eq 0 ] + + # Should NOT have fired. + [ ! -f "${_fired}" ] + + # Should still be removed. + [[ ! -s "${NAG_DIR}/alarms" ]] || [ "$(wc -l < "${NAG_DIR}/alarms")" -eq 0 ] +} + +@test "check silently reschedules stale repeating alarm" { + local _stale_ts=$(( $(date +%s) - 1800 )) # 30 minutes ago + write_alarm "$(printf "1\t%s\tday\tstale repeater" "${_stale_ts}")" + + local _fired="${NAG_DIR}/fired" + export NAG_CMD="${NAG_DIR}/recorder" + cat > "${NAG_CMD}" <<'SCRIPT' +#!/usr/bin/env bash +printf "%s\n" "$2" >> "${NAG_DIR}/fired" +SCRIPT + chmod +x "${NAG_CMD}" + + run_nag check + [ "${status}" -eq 0 ] + + # Should NOT have fired. + [ ! -f "${_fired}" ] + + # Should be rescheduled to a future time. + [ -s "${NAG_DIR}/alarms" ] + local _new_ts + _new_ts="$(cut -f2 "${NAG_DIR}/alarms" | head -1)" + (( _new_ts > $(date +%s) )) +} diff --git a/test/every.bats b/test/every.bats new file mode 100644 index 0000000..6ed45df --- /dev/null +++ b/test/every.bats @@ -0,0 +1,43 @@ +#!/usr/bin/env bats + +load test_helper + +@test "every creates a repeating alarm" { + run_nag every weekday "tomorrow 3pm" standup meeting + [ "${status}" -eq 0 ] + [[ "${output}" =~ "[1]" ]] + [[ "${output}" =~ "(weekday)" ]] + [[ "${output}" =~ "standup meeting" ]] + # Verify TSV has rule in field 3. + local _rule + _rule="$(cut -f3 "${NAG_DIR}/alarms")" + [ "${_rule}" = "weekday" ] +} + +@test "every with comma-separated rules" { + run_nag every "tuesday,thursday" "tomorrow 3pm" standup + [ "${status}" -eq 0 ] + [[ "${output}" =~ "(tue, thu)" ]] + grep -q "tue,thu" "${NAG_DIR}/alarms" +} + +@test "every snaps to next matching day" { + # "weekend 3pm" should snap to Saturday or Sunday, not a weekday. + run_nag every weekend "tomorrow 3pm" relax + [ "${status}" -eq 0 ] + local _ts _dow + _ts="$(cut -f2 "${NAG_DIR}/alarms")" + _dow="$(date -d "@${_ts}" +%u)" + # Day-of-week should be 6 (Sat) or 7 (Sun). + [[ "${_dow}" == "6" || "${_dow}" == "7" ]] +} + +@test "every with invalid rule fails" { + run_nag every "invalid_rule" "tomorrow 3pm" some message + [ "${status}" -eq 1 ] +} + +@test "every without message fails" { + run_nag every weekday "tomorrow 3pm" + [ "${status}" -eq 1 ] +} diff --git a/test/help.bats b/test/help.bats new file mode 100644 index 0000000..8753b73 --- /dev/null +++ b/test/help.bats @@ -0,0 +1,76 @@ +#!/usr/bin/env bats + +load test_helper + +@test "help shows usage" { + run_nag help + [ "${status}" -eq 0 ] + [[ "${output}" =~ "