The lcov reports only feed SonarQube's Coverage metric — the "Unit Tests" tile
stayed "-" because we never provided a test-execution report (a separate input
via sonar.testExecutionReportPaths, in SonarQube's own Generic Test Execution
XML format, which the lcov/junit reporters don't produce).
Add a dependency-free custom node:test reporter (scripts/sonar-test-reporter.mjs)
that emits that XML — repo-root-relative <file path> entries matching sonar.tests,
integer-ms durations — and wire it into both the server and client coverage runs
in sonarqube.yml, plus sonar.testExecutionReportPaths in sonar-project.properties.
Verified locally: server 380 + client 43 test cases, well-formed XML, all three
reporters (spec/lcov/sonar) coexist in one `node --test` invocation.
Co-Authored-By: Claude <noreply@anthropic.com>
Stand up a client test suite on Node's built-in runner (no vitest/jsdom — the
targeted modules are plain ESM with no browser/DOM deps) and cover the
meaningful client logic, not presentational components:
- api/client.js: the fetch wrapper — always sends the session cookie, maps a
non-2xx response to a thrown ApiError (body.message → statusText fallback),
resolves an empty body to null, sets Content-Type for JSON but NOT for raw
FormData uploads, and builds/encodes query strings + path params.
- lib/shardEvents.js: describe() across event kinds (payload vs live frame,
actor name→acct→"Someone" fallback, sale pluralization, champ.update
branches) and the categoryOf table-consistency check.
- data/regionBuckets.js: the presence roll-up, incl. the first-match-wins
ordering and the "buckets always reconcile to the total" invariant.
- lib/heroLayout.js: parseLayout's version/shape guard and heroBackground's
default-vs-custom branch.
- lib/format.js: the date/label formatters + relative-time buckets.
Wiring so these actually count:
- client/package.json gains a `test` script (node --test);
- pr-checks.yml runs the client tests as a PR gate;
- sonarqube.yml generates a client LCOV report and sonar-project.properties
feeds it alongside the server report (SF paths resolve to client/src/...).
43 client tests; coverage on the tested modules: format/regionBuckets/
heroLayout 100%, api client 86%, shardEvents 80%.
Co-Authored-By: Claude <noreply@anthropic.com>
SonarQube reported 0% coverage because the analysis workflow never ran
the test suite — the scanner does static analysis only and was handed no
coverage report, and sonar-project.properties defined no report path.
Generate an LCOV report in sonarqube.yml before the scan using Node's
built-in test coverage (run from the repo root so SF: paths are
server/src/... and resolve against the project base dir), and point
the scanner at it via sonar.javascript.lcov.reportPaths. Node's lcov
coverage reporter needs Node >= 22, so the coverage job pins node 22.
Co-Authored-By: Claude <noreply@anthropic.com>
Wire the self-hosted SonarQube server into Gitea via a Gitea Actions
workflow. Runs on push to `main` (post-merge) and workflow_dispatch, so
it feeds the dashboard without gating any PR. Adds sonar-project.properties
(project key runic-gateway-website; server/client/bot sources, server tests,
node_modules/dist/generated excluded).
Requires two one-time Gitea settings: secret SONAR_TOKEN and variable
SONAR_HOST_URL. The scan does not wait on the Quality Gate, keeping it
fully non-blocking.
Co-Authored-By: Claude <noreply@anthropic.com>