4.4 KiB
name, description
| name | description |
|---|---|
| dankvoice-behavior-scenario-authoring | Author or extend a dankvoice behavior.bats scenario: driver env knobs (DV_REWRITE/DV_FAIL/DV_CUT_MS/DV_TAG_LOG), the SPOKEN heard-words census, private-TMPDIR leak assertions, and the arm-the-precondition-before-absence trap. |
Writing a dankvoice behavior test
tests/behavior.bats drives the built bundle through a generated harness. Each @test
is: set env → run a named scenario → assert on printed census lines. Never assert on
internals; the observable vocabulary is CALLS, PLAYS, PLAYORDER, SPOKEN, NOTIFY,
CALL <url> input=… auth=… at=…, PLAYER, KILLED.
The driver lives in a heredoc inside setup(); find and read it before adding anything:
sed -n '/cat > "$DV_DRIVER" </,/^TS$/p' tests/behavior.bats
A scenario is a case "<name>": in that switch that calls the extension's own handlers
(say.handler("on", ctx), messageEnd?.(said("…"), ctx), agentEnd?.(answer("…"), ctx)).
Prefer delivering narration through the harness (agent_end/message_end) over a typed
/say: the humanizer, mood, heckle, framing and the frequency hold govern unsolicited
narration only, so a typed line cannot exercise them.
Fixture knobs (set per test, all read by the driver)
| Knob | Effect |
|---|---|
DV_REWRITE=off|echo|prose|reasoning|stall |
what the chat (humanizer) route answers; unset ⇒ it 404s and the rewrite falls back to mechanical text |
DV_STALL_MS |
delay the chat answer (timeout behaviour) |
DV_FAIL=<N> |
the first N /audio/speech requests answer HTTP 500 JSON — an endpoint outage a test can watch the queue weather |
DV_HOLD=<sec> |
how long the player stub holds each clip; sets the playback windows |
DV_CUT_MS=<ms> |
in stagecut, when another session cuts the line |
DV_TAG_LOG, DV_TIMING_LOG |
per-clip tag census (PLAYORDER, SPOKEN) and player START/END ms |
DV_CT |
content type of the answer (non-WAV routing) |
DV_KEYS |
key replayer for the settings modal |
TMPDIR="$WORK/scratch-tmp" |
scope synthesis temp dirs so a leak assertion is yours alone |
Always pass DANKVOICE_STAGE_DIR="$WORK/stage" when the test touches the stage, and use the
say_run <scenario> helper (it wraps run env … so $output is captured); a bare
scenario_name invocation prints without capturing.
Two traps that have each hidden a real bug
Assert absence only with the producer armed. DV_REWRITE unset ⇒ the chat route 404s,
so [[ "$output" != *"/chat/completions"* ]] and "payload equals the source text" pass even
when rewriting is happening. Nine tests pinned the opposite of the repo's law this way. Pin
with DV_REWRITE=prose (the fixture answers with its own words) and assert the payload you
expect — then survival is evidence, not absence.
Count what was HEARD, not what was requested. A segment whose clip was killed mid-play,
or aborted mid-synthesis, is legitimately synthesized again on resume. Laws about framing
("one intro per narration") are about hearing, so count SPOKEN lines: the driver maps each
played tag back to the /audio/speech request that produced that clip. CALLS counts
requests and will over-report across a line-cut.
Temp-dir leak assertions mirror this: TMPDIR private, then
find "$TMPDIR" -maxdepth 1 -type d -name 'dankvoice-*' | wc -l.
Pinning timing
Derive windows from DV_HOLD: with 0.5 s clips, clips occupy ~[10,310], [310,610], [610,910];
choose DV_CUT_MS so the cut lands strictly inside the clip you mean to interrupt, leaving
margin for the 250 ms stage poll and synthesis time. Default scenario timings are env-overridable
(DV_CUT_MS ?? 600) so a new test never edits an existing one's schedule.
Red-proof and gate (project invariants)
- Prove a new assertion can fail: in a private worktree,
git show HEAD:src/<file> > src/<file>,bun run build, run the filter, restore the file and rebuild. Never while another suite runs (setup()rebuilds the bundle fromsrc/). Seedankvoice-bats-serialization,dankvoice-bundle-swap-red-proof. bun run buildbefore staging:tests/packaging.batsbyte-compares committeddist/with a fresh build, sodist/index.jsmust be in the same commit as thesrc/change.- Gate with the whole suite (
bats tests/), not the filter; ~5 min, so run it unbounded/async and never write tosrc/while it runs.