test: splits monolith into a bats file for each subcommand

This commit is contained in:
Lewis Wynne 2026-04-02 11:39:48 +01:00
parent b6fde582bb
commit 2a3c9f264f
10 changed files with 535 additions and 458 deletions

81
test/at.bats Normal file
View file

@ -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: id<TAB>timestamp<TAB><TAB>message
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 ]
}

126
test/check.bats Normal file
View file

@ -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) ))
}

43
test/every.bats Normal file
View file

@ -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 ]
}

76
test/help.bats Normal file
View file

@ -0,0 +1,76 @@
#!/usr/bin/env bats
load test_helper
@test "help shows usage" {
run_nag help
[ "${status}" -eq 0 ]
[[ "${output}" =~ "<time> <message...>" ]]
[[ "${output}" =~ "every <rules> <time> <message...>" ]]
[[ "${output}" =~ "stop <id>" ]]
[[ "${output}" =~ "skip <id>" ]]
[[ "${output}" =~ "check" ]]
[[ "${output}" =~ "help [<subcommand>]" ]]
[[ "${output}" =~ "Options:" ]]
}
@test "help list shows list usage" {
run_nag help list
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "nag list" ]]
}
@test "help at shows at usage" {
run_nag help at
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "[at] <time> <message...>" ]]
}
@test "help version shows version usage" {
run_nag help version
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "( version | --version )" ]]
}
@test "help stop shows stop usage" {
run_nag help stop
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "stop <id>" ]]
}
@test "help skip shows skip usage" {
run_nag help skip
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "skip <id>" ]]
}
@test "help every shows every usage" {
run_nag help every
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "every <rules> <time> <message...>" ]]
}
@test "help check shows check usage" {
run_nag help check
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "check" ]]
}
@test "version shows current version" {
run_nag version
[ "${status}" -eq 0 ]
[[ "${output}" =~ ^[0-9]+\.[0-9]+(_[a-zA-Z0-9]+)*$ ]]
}
@test "NAG_DEFAULT overrides the default subcommand" {
NAG_DEFAULT="version" run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ ^[0-9]+\.[0-9]+(_[a-zA-Z0-9]+)*$ ]]
}

112
test/list.bats Normal file
View file

@ -0,0 +1,112 @@
#!/usr/bin/env bats
load test_helper
@test "list with no alarms prints nothing-to-nag message" {
run_nag
[ "${status}" -eq 0 ]
[ "${output}" = "Nothing to nag about." ]
}
@test "list is the default subcommand" {
run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Nothing to nag about" ]]
}
@test "list shows formatted alarms with smart dates" {
run_nag at "tomorrow 3pm" "take a break"
run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ "[1] Tomorrow, 3pm — take a break" ]]
}
@test "list shows empty message after stopping all alarms" {
run_nag at "tomorrow 3pm" "test alarm"
run_nag stop 1
[ "${status}" -eq 0 ]
run_nag
[[ "${output}" =~ "Nothing to nag about" ]]
}
@test "list shows repeating alarm with rule in parens" {
run_nag every weekday "tomorrow 3pm" standup
run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ "(weekday)" ]]
[[ "${output}" =~ "standup" ]]
}
@test "list sorts alarms by time" {
run_nag at "tomorrow 3pm" "take a break"
run_nag at "tomorrow 9am" "standup"
run_nag
[ "${status}" -eq 0 ]
# 9am should come before 3pm in output.
local _first_line _second_line
_first_line="$(printf "%s\n" "${output}" | head -1)"
_second_line="$(printf "%s\n" "${output}" | sed -n '2p')"
[[ "${_first_line}" =~ "standup" ]]
[[ "${_second_line}" =~ "take a break" ]]
}
@test "list formats multiple alarms correctly" {
run_nag at "tomorrow 3pm" "first alarm"
run_nag at "tomorrow 9am" "second alarm"
run_nag at "tomorrow 5pm" "third alarm"
run_nag
[ "${status}" -eq 0 ]
local _first _second _third
_first="$(printf "%s\n" "${output}" | sed -n '1p')"
_second="$(printf "%s\n" "${output}" | sed -n '2p')"
_third="$(printf "%s\n" "${output}" | sed -n '3p')"
[[ "${_first}" =~ "Tomorrow, 9am" ]]
[[ "${_first}" =~ "second alarm" ]]
[[ "${_second}" =~ "Tomorrow, 3pm" ]]
[[ "${_second}" =~ "first alarm" ]]
[[ "${_third}" =~ "Tomorrow, 5pm" ]]
[[ "${_third}" =~ "third alarm" ]]
}
@test "list shows Today for alarm due today" {
local _ts=$(( $(date -d "$(date +%Y-%m-%d)" +%s) + 82800 ))
write_alarm "$(printf '1\t%s\t\ttoday alarm' "${_ts}")"
run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Today,".*"today alarm" ]]
}
@test "list shows Tomorrow for alarm due tomorrow" {
local _ts=$(( $(date -d "$(date +%Y-%m-%d)" +%s) + 86400 + 43200 ))
write_alarm "$(printf '1\t%s\t\ttomorrow alarm' "${_ts}")"
run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Tomorrow,".*"tomorrow alarm" ]]
}
@test "list shows This <day> for alarm 3 days away" {
local _ts=$(( $(date -d "$(date +%Y-%m-%d)" +%s) + 86400 * 3 + 43200 ))
local _day="$(date -d "@${_ts}" +%A)"
write_alarm "$(printf '1\t%s\t\tthis alarm' "${_ts}")"
run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ "This ${_day},".*"this alarm" ]]
}
@test "list shows Next <day> for alarm 10 days away" {
local _ts=$(( $(date -d "$(date +%Y-%m-%d)" +%s) + 86400 * 10 + 43200 ))
local _day="$(date -d "@${_ts}" +%A)"
write_alarm "$(printf '1\t%s\t\tnext alarm' "${_ts}")"
run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Next ${_day},".*"next alarm" ]]
}
@test "list shows date for alarm 30 days away" {
local _ts=$(( $(date -d "$(date +%Y-%m-%d)" +%s) + 86400 * 30 + 43200 ))
local _date="$(date -d "@${_ts}" "+%a %b %-d")"
write_alarm "$(printf '1\t%s\t\tfar alarm' "${_ts}")"
run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ "${_date},".*"far alarm" ]]
}

24
test/mute.bats Normal file
View file

@ -0,0 +1,24 @@
#!/usr/bin/env bats
load test_helper
@test "mute creates muted file" {
run_nag mute
[ "${status}" -eq 0 ]
[[ "${output}" =~ "muted" ]]
[ -f "${NAG_DIR}/muted" ]
}
@test "unmute removes muted file" {
run_nag mute
run_nag unmute
[ "${status}" -eq 0 ]
[[ "${output}" =~ "unmuted" ]]
[ ! -f "${NAG_DIR}/muted" ]
}
@test "unmute when not muted says so" {
run_nag unmute
[ "${status}" -eq 0 ]
[[ "${output}" =~ "not muted" ]]
}

View file

@ -1,458 +0,0 @@
#!/usr/bin/env bats
load test_helper
@test "list with no alarms prints nothing-to-nag message" {
run_nag
[ "${status}" -eq 0 ]
[ "${output}" = "Nothing to nag about." ]
}
@test "help shows usage" {
run_nag help
[ "${status}" -eq 0 ]
[[ "${output}" =~ "<time> <message...>" ]]
[[ "${output}" =~ "every <rules> <time> <message...>" ]]
[[ "${output}" =~ "stop <id>" ]]
[[ "${output}" =~ "skip <id>" ]]
[[ "${output}" =~ "check" ]]
[[ "${output}" =~ "help [<subcommand>]" ]]
[[ "${output}" =~ "Options:" ]]
}
@test "help list shows list usage" {
run_nag help list
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "nag list" ]]
}
@test "help at shows at usage" {
run_nag help at
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "[at] <time> <message...>" ]]
}
@test "help version shows version usage" {
run_nag help version
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "( version | --version )" ]]
}
@test "version shows current version" {
run_nag version
[ "${status}" -eq 0 ]
[[ "${output}" =~ ^[0-9]+\.[0-9]+(_[a-zA-Z0-9]+)*$ ]]
}
@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: id<TAB>timestamp<TAB><TAB>message
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 ]
}
@test "list is the default subcommand" {
run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Nothing to nag about" ]]
}
@test "NAG_DEFAULT overrides the default subcommand" {
NAG_DEFAULT="version" run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ ^[0-9]+\.[0-9]+(_[a-zA-Z0-9]+)*$ ]]
}
@test "list shows formatted alarms with smart dates" {
run_nag at "tomorrow 3pm" "take a break"
run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ "[1] Tomorrow, 3pm — take a break" ]]
}
@test "list shows empty message after stopping all alarms" {
run_nag at "tomorrow 3pm" "test alarm"
run_nag stop 1
[ "${status}" -eq 0 ]
run_nag
[[ "${output}" =~ "Nothing to nag about" ]]
}
@test "help stop shows stop usage" {
run_nag help stop
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "stop <id>" ]]
}
@test "stop removes an alarm by ID" {
run_nag at "tomorrow 3pm" "take a break"
run_nag stop 1
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Stopped alarm 1" ]]
run_nag
[[ "${output}" =~ "Nothing to nag about" ]]
}
@test "stop removes only the targeted alarm" {
run_nag at "tomorrow 3pm" "first"
run_nag at "tomorrow 4pm" "second"
run_nag at "tomorrow 5pm" "third"
run_nag stop 2
[ "${status}" -eq 0 ]
run_nag
[[ "${output}" =~ "first" ]]
[[ ! "${output}" =~ "second" ]]
[[ "${output}" =~ "third" ]]
}
@test "stop with nonexistent ID fails" {
run_nag stop 99
[ "${status}" -eq 1 ]
}
@test "stop without ID fails" {
run_nag stop
[ "${status}" -eq 1 ]
}
@test "help skip shows skip usage" {
run_nag help skip
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "skip <id>" ]]
}
@test "skip reschedules a repeating alarm" {
run_nag every day "tomorrow 3pm" daily task
run_nag skip 1
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Skipped" ]]
[ -s "${NAG_DIR}/alarms" ]
local _ts _now
_ts="$(cut -f2 "${NAG_DIR}/alarms")"
_now="$(date +%s)"
(( _ts > _now ))
}
@test "skip deletes a one-shot alarm" {
run_nag at "tomorrow 3pm" "one-shot"
run_nag skip 1
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Stopped" ]]
run_nag
[[ "${output}" =~ "Nothing to nag about" ]]
}
@test "skip with nonexistent ID fails" {
run_nag skip 99
[ "${status}" -eq 1 ]
}
@test "skip without ID fails" {
run_nag skip
[ "${status}" -eq 1 ]
}
@test "help every shows every usage" {
run_nag help every
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "every <rules> <time> <message...>" ]]
}
@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 ]
}
@test "list shows repeating alarm with rule in parens" {
run_nag every weekday "tomorrow 3pm" standup
run_nag
[ "${status}" -eq 0 ]
[[ "${output}" =~ "(weekday)" ]]
[[ "${output}" =~ "standup" ]]
}
@test "list sorts alarms by time" {
run_nag at "tomorrow 3pm" "take a break"
run_nag at "tomorrow 9am" "standup"
run_nag
[ "${status}" -eq 0 ]
# 9am should come before 3pm in output.
local _first_line _second_line
_first_line="$(printf "%s\n" "${output}" | head -1)"
_second_line="$(printf "%s\n" "${output}" | sed -n '2p')"
[[ "${_first_line}" =~ "standup" ]]
[[ "${_second_line}" =~ "take a break" ]]
}
# check #######################################################################
# Helper: write a raw alarm line directly to the alarms file.
write_alarm() {
mkdir -p "${NAG_DIR}"
printf "%s\\n" "$1" >> "${NAG_DIR}/alarms"
}
@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) ))
}
@test "help check shows check usage" {
run_nag help check
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Usage:" ]]
[[ "${output}" =~ "check" ]]
}
# mute/unmute #################################################################
@test "mute creates muted file" {
run_nag mute
[ "${status}" -eq 0 ]
[[ "${output}" =~ "muted" ]]
[ -f "${NAG_DIR}/muted" ]
}
@test "unmute removes muted file" {
run_nag mute
run_nag unmute
[ "${status}" -eq 0 ]
[[ "${output}" =~ "unmuted" ]]
[ ! -f "${NAG_DIR}/muted" ]
}
@test "unmute when not muted says so" {
run_nag unmute
[ "${status}" -eq 0 ]
[[ "${output}" =~ "not muted" ]]
}

34
test/skip.bats Normal file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env bats
load test_helper
@test "skip reschedules a repeating alarm" {
run_nag every day "tomorrow 3pm" daily task
run_nag skip 1
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Skipped" ]]
[ -s "${NAG_DIR}/alarms" ]
local _ts _now
_ts="$(cut -f2 "${NAG_DIR}/alarms")"
_now="$(date +%s)"
(( _ts > _now ))
}
@test "skip deletes a one-shot alarm" {
run_nag at "tomorrow 3pm" "one-shot"
run_nag skip 1
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Stopped" ]]
run_nag
[[ "${output}" =~ "Nothing to nag about" ]]
}
@test "skip with nonexistent ID fails" {
run_nag skip 99
[ "${status}" -eq 1 ]
}
@test "skip without ID fails" {
run_nag skip
[ "${status}" -eq 1 ]
}

34
test/stop.bats Normal file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env bats
load test_helper
@test "stop removes an alarm by ID" {
run_nag at "tomorrow 3pm" "take a break"
run_nag stop 1
[ "${status}" -eq 0 ]
[[ "${output}" =~ "Stopped alarm 1" ]]
run_nag
[[ "${output}" =~ "Nothing to nag about" ]]
}
@test "stop removes only the targeted alarm" {
run_nag at "tomorrow 3pm" "first"
run_nag at "tomorrow 4pm" "second"
run_nag at "tomorrow 5pm" "third"
run_nag stop 2
[ "${status}" -eq 0 ]
run_nag
[[ "${output}" =~ "first" ]]
[[ ! "${output}" =~ "second" ]]
[[ "${output}" =~ "third" ]]
}
@test "stop with nonexistent ID fails" {
run_nag stop 99
[ "${status}" -eq 1 ]
}
@test "stop without ID fails" {
run_nag stop
[ "${status}" -eq 1 ]
}

View file

@ -17,3 +17,8 @@ teardown() {
run_nag() { run_nag() {
run "${_NAG}" --yes "$@" run "${_NAG}" --yes "$@"
} }
write_alarm() {
mkdir -p "${NAG_DIR}"
printf "%s\\n" "$1" >> "${NAG_DIR}/alarms"
}