Skip to content

Test the dashboard bundle — the two buttons that mutate a live project are unverified #9

Description

@leggetter

Split out of #2, which closed with everything else it named addressed:
tools.py 29% → 100%, cli.py 34% → 82%, tunnel.py 41% → 96%,
plugin_api.py 64% → 83%, total 67% → 86%.

hookdeck/dashboard/dist/index.js is the one item that was deprioritised and
is still at nothing. It is 194 lines of hand-written IIFE with no tests of
any kind
, and it is invisible in the 86% — that figure is Python only, so
the number reads better than the coverage is.

It ships in the wheel, so this is not dead weight in the repo: it is code
installed on every gateway.

Why it is worth more than "it's just UI"

The original reasoning for deprioritising was that its failure mode is visible
rather than silent. That holds for rendering. It does not hold for the two
buttons, which mutate a live Hookdeck project:

onClick: function () {
  act("/connections/" + c.id + (c.paused ? "/resume" : "/pause"));
}

Both the button's label and the endpoint it posts to are derived from the same
c.paused flag. Invert that expression and the UI reads correctly while doing
the opposite — a button labelled "Pause" that resumes. Nothing in the repo
would catch it, and the operator finds out from traffic, not from the screen.

The same shape applies to retry: act("/events/" + e.id + "/retry") is a
redelivery, and this plugin exists to avoid running the agent twice for one
event.

Worth covering, roughly in this order

  • The pause/resume toggle agrees with its own label. paused: true must
    render "Resume" and post to /resume; paused: false the mirror. One test,
    and it is the one that stops a mislabelled button pausing production traffic.
  • busy disables every action button. load, both retry buttons and the
    pause/resume button all take disabled: busy. Losing that on the retry path
    turns a double-click into a double redelivery.
  • act() clears busy when the POST fails. The .catch sets it back;
    without that the panel is permanently disabled after one failed retry and the
    only fix is a page reload.
  • The SDK guard returns quietly. if (!SDK || !PLUGINS || !PLUGINS.register) return; is what stops this throwing into the host's bundle loader on a
    dashboard whose SDK predates what it uses — i.e. an older Hermes with a newer
    plugin, which is the normal upgrade order. A test that loads the bundle with
    an empty window and asserts nothing throws is cheap.
  • Error rendering handles a non-Error rejection.
    String((e && e.message) || e) is written for both shapes; only one of them
    is likely to have been tried.
  • Empty states. failed.length === 0 and conns.length === 0 have their
    own branches, and an empty queue is the common case.

How, given there is no JS toolchain here

There is no package.json, no test runner, and no build step — deliberately,
which is why dist/index.js is committed rather than generated. Adding a
toolchain to test one file is most of the cost of this issue, so it is worth
choosing before starting.

Preferred: no dependencies. The bundle takes React, its hooks and its
components from window.__HERMES_PLUGIN_SDK__, and talks to the server only
through SDK.fetchJSON. Everything it touches is injected. So it can be loaded
under node:vm with a hand-written fake SDK whose createElement returns plain
objects and whose fetchJSON records calls — then assert on the recorded tree
and the recorded requests. No DOM, no React, no npm install. The fiddly part is
faking useState/useEffect/useCallback well enough for a component this
shape; for one component with three pieces of state that is a small amount of
code, and it keeps the "no build step" property the file was written for.

Alternative: vitest + jsdom + real React as devDependencies. More faithful,
and the hooks come for free. The cost is a package.json, a lockfile, a Node
version to pin, and a second ecosystem in a repo that currently has one.

Either way it needs a job in ci.yml; today nothing runs against this file at
all.

Not covered by this issue

plugin_api.py (the Python half serving /api/plugins/hookdeck) is at 83% and
was addressed under #2. This is only the browser half.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions