aboutsummaryrefslogtreecommitdiff
path: root/tests/test-agenda-render-cache.bats
blob: 3e949c168b5772d483fd133c55aa43c250386f97 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/usr/bin/env bats
# Tests for scripts/agenda-render-cache — the batch writer behind the timer.
#
# The elisp tests cover the query and the row shape.  What only a shell test
# can cover is the thing that actually broke: the script runs a batch Emacs
# with -Q, so none of the editor's configuration is loaded, and anything it
# forgets to set up degrades silently rather than erroring.  The first version
# wrote a file that parsed fine and was wrong, because org did not know DOING
# was a keyword and left it glued to the front of every title.
#
# Two isolation rules, both learned the hard way:
#
#   EMACS_D points at THIS checkout, not $HOME/.emacs.d.  Without it the script
#   under test runs the installed config's elisp, so a broken tree passes.
#
#   AGENDA_RENDER_FILES points at a fixture written here.  Asserting over the
#   machine's real agenda makes the result depend on what Craig happens to have
#   scheduled today: the keyword assertion only bites if some entry carries a
#   keyword, and on an empty agenda every all() assertion passes over an empty
#   list.  The fixture makes the failure mode reachable every run.

setup() {
	SCRIPT="${BATS_TEST_DIRNAME}/../scripts/agenda-render-cache"
	export EMACS_D="${BATS_TEST_DIRNAME}/.."
	export XDG_CACHE_HOME="${BATS_TEST_TMPDIR}/cache"
	OUT="${XDG_CACHE_HOME}/settings/agenda.json"

	FIXTURE="${BATS_TEST_TMPDIR}/fixture.org"
	TODAY="$(date +%Y-%m-%d)"
	DOW="$(date +%a)"
	{
		printf '* DOING [#A] Advisor projects\nSCHEDULED: <%s %s 09:00>\n' "$TODAY" "$DOW"
		printf '* TODO Standup\nSCHEDULED: <%s %s 10:00-10:15>\n' "$TODAY" "$DOW"
		printf '* Lunch\n<%s %s 12:00-13:00>\n' "$TODAY" "$DOW"
		printf '* VERIFY [#B] Check the render\nDEADLINE: <%s %s 17:00>\n' "$TODAY" "$DOW"
	} > "$FIXTURE"
	export AGENDA_RENDER_FILES="$FIXTURE"
}

# Every assertion below runs through this, so an empty result can never pass
# vacuously — all() over an empty list is true, which is how a writer that
# produced nothing would look like a writer that produced correct rows.
assert_json() {
	python3 -c "
import json, sys
rows = json.load(open(sys.argv[1]))
assert isinstance(rows, list), 'not a JSON array'
assert len(rows) == 4, 'expected 4 fixture rows, got %d' % len(rows)
$1
" "$OUT"
}

@test "the script is executable" {
	[ -x "$SCRIPT" ]
}

@test "writes a parseable JSON array and creates its directory" {
	run "$SCRIPT"
	[ "$status" -eq 0 ]
	[ -f "$OUT" ]
	run assert_json "pass"
	[ "$status" -eq 0 ]
}

@test "every row carries the three keys the renderer reads" {
	run "$SCRIPT"
	[ "$status" -eq 0 ]
	run assert_json "
assert all({'s','e','t'} <= set(r) for r in rows), 'missing s/e/t'
assert all(isinstance(r['s'], int) and isinstance(r['e'], int) for r in rows), 'non-integer instant'
assert all(r['e'] >= r['s'] for r in rows), 'negative duration'
"
	[ "$status" -eq 0 ]
}

@test "instants are milliseconds, not seconds" {
	run "$SCRIPT"
	[ "$status" -eq 0 ]
	# Seconds since 1970 are 10 digits; milliseconds are 13.
	run assert_json "assert all(len(str(r['s'])) == 13 for r in rows), 'looks like seconds'"
	[ "$status" -eq 0 ]
}

@test "titles do not carry the TODO keyword vocabulary" {
	run "$SCRIPT"
	[ "$status" -eq 0 ]
	# The regression this file exists for.  The fixture carries DOING, TODO and
	# VERIFY entries plus two priority cookies, so a script that has not set
	# org-todo-keywords fails here every run rather than only on days when
	# Craig happens to have such an entry.
	run assert_json "
KEYWORDS = {'TODO','PROJECT','DOING','WAITING','VERIFY','STALLED',
            'DELEGATED','FAILED','DONE','CANCELLED'}
bad = [r['t'] for r in rows if r['t'].split(' ')[0] in KEYWORDS]
assert not bad, 'keyword left in title: %r' % bad
cookie = [r['t'] for r in rows if r['t'].startswith('[#')]
assert not cookie, 'priority cookie left in title: %r' % cookie
assert sorted(r['t'] for r in rows) == [
    'Advisor projects', 'Check the render', 'Lunch', 'Standup']
"
	[ "$status" -eq 0 ]
}

@test "keyword and completion state survive the batch environment" {
	run "$SCRIPT"
	[ "$status" -eq 0 ]
	run assert_json "
by_title = {r['t']: r for r in rows}
assert by_title['Advisor projects']['keyword'] == 'DOING', by_title['Advisor projects']
assert by_title['Check the render']['keyword'] == 'VERIFY'
assert by_title['Lunch']['keyword'] is None
assert all(r['done'] is False for r in rows)
assert by_title['Check the render']['type'] == 'deadline'
"
	[ "$status" -eq 0 ]
}

@test "replaces an existing file rather than appending" {
	mkdir -p "$(dirname "$OUT")"
	printf 'stale garbage that is not json at all\n' > "$OUT"
	run "$SCRIPT"
	[ "$status" -eq 0 ]
	run assert_json "pass"
	[ "$status" -eq 0 ]
}

@test "fails loudly when the config directory is missing" {
	run env EMACS_D="${BATS_TEST_TMPDIR}/nonexistent" "$SCRIPT"
	[ "$status" -ne 0 ]
	[[ "$output" == *"no modules directory"* ]]
}