Upgrade Harper to v5: add integration tests, CI workflow, and dev tooling#48
Upgrade Harper to v5: add integration tests, CI workflow, and dev tooling#48BboyAkers wants to merge 5 commits into
Conversation
- Install harper@5.0.28 and @harperfast/integration-testing as devDependencies - Add TypeScript devDependency and tsconfig.json - Add integrationTests/metrics.test.ts covering the /metrics endpoint, content-type, default Node.js process metrics, and PrometheusExporterSettings table initialization; apply harperBinPath fix (ERR_PACKAGE_PATH_NOT_EXPORTED) - Add .github/workflows/integration-tests.yml (Node matrix 22/24/26, pinned actions) - Regenerate package-lock.json with --os=linux --cpu=x64 --include=optional so bufferutil/utf-8-validate/node-gyp-build are recorded for Linux CI - Un-ignore package-lock.json (was in .gitignore; CI needs it for npm ci) - Update package.json author branding HarperDB, Inc. -> Harper - Update README note: exporter supports Harper v5 in addition to v4.2+ Migration items N/A: no harperdb imports (runtime global); no Table.get() return shape issues; no Blob.save(); no wasLoadedFromSource(); v5 plain-record access fix was already applied in commit 73e2fa6 (recordProp compat shim). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Harper returns the Prometheus string as application/json when no explicit Accept header is sent. Update the content-type test to accept that, and add an extractMetricsText() helper that unwraps JSON-encoded strings before checking for Prometheus metric comments. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
| // harper's `exports` only exposes ".", so 'harper/dist/bin/harper.js' is not resolvable. | ||
| // Resolve the CLI from the exported main entry and pass it explicitly. | ||
| const require = createRequire(import.meta.url); | ||
| const harperBinPath = resolve(dirname(require.resolve('harper')), 'bin/harper.js'); |
There was a problem hiding this comment.
Fragile bin path resolution — require.resolve('harper') returns the package's main entry (dist/index.js), so dirname() gives dist/. The resolved path dist/bin/harper.js happens to match harper 5.0.28's bin field by coincidence. If harper's main entry ever moves to a different subdirectory (or to the package root), dirname() will produce the wrong base and the path will silently point nowhere, crashing every test run with a cryptic ENOENT.
A more resilient approach resolves from the package root directly:
// Use the package.json to anchor at the package root
const harperRoot = dirname(require.resolve('harper/package.json'));
const harperBinPath = resolve(harperRoot, 'dist/bin/harper.js');(Or check whether setupHarperWithFixture already accepts a package name so the bin path lookup can live in the integration-testing library.)
| const ct = res.headers.get('content-type') ?? ''; | ||
| if (ct.includes('application/json')) { | ||
| const body = await res.json() as unknown; | ||
| return typeof body === 'string' ? body : JSON.stringify(body); |
There was a problem hiding this comment.
Silent swallow of non-string JSON body — when Harper responds with Content-Type: application/json but the parsed body is not a string (e.g. an error object like {"error": "..."}), extractMetricsText returns JSON.stringify(body), which looks like {"error":"..."}. The calling test then asserts text.includes('# HELP'), which fails with the message 'Response should contain Prometheus metric comments' — giving no indication that an error document was returned instead of metrics.
Consider throwing instead so the failure message is actionable:
if (typeof body !== 'string') {
throw new Error(`Expected string from JSON-wrapped metrics response, got: ${JSON.stringify(body)}`);
}
return body;| void test('Harper starts successfully', async () => { | ||
| const res = await authFetch(ctx, '/'); | ||
| ok( | ||
| [200, 400, 404].includes(res.status), |
There was a problem hiding this comment.
400 accepted as 'starts successfully' — including 400 Bad Request in the success set means this test passes even when Harper is running but completely misconfigured. In that state every subsequent test would run against a broken Harper and produce misleading failures, rather than this health-check surfacing the root cause.
If Harper's root / legitimately returns 400 for a well-formed GET (e.g. it only accepts POST), document why and add a comment; otherwise tighten the check:
ok(
[200, 404].includes(res.status),
`Unexpected status ${res.status}`,
);| ct.includes('text/plain') || | ||
| ct.includes('application/openmetrics-text') || | ||
| ct.includes('application/json') || | ||
| ct.includes('text/'), |
There was a problem hiding this comment.
Content-type assertion is over-broad — the final ct.includes('text/') arm matches any text/* media type, including text/html (an error page). If Harper ever returns an HTML error response with HTTP 200, this test passes while the metrics endpoint is actually broken.
Additionally, ct.includes('text/plain') is completely redundant: every text/plain value is already caught by ct.includes('text/'), so that branch is dead code.
Tighten to the three types that are actually expected:
ok(
ct.includes('text/plain') ||
ct.includes('application/openmetrics-text') ||
ct.includes('application/json'),
`Unexpected content-type: ${ct}`,
);|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 |
There was a problem hiding this comment.
Unnecessary checkout — the generate-node-version-matrix job only runs a shell if/else that inspects the NODE_VER environment variable. It never reads any file from the repository. The actions/checkout step adds a few seconds to every CI run (clone + LFS + submodule overhead) and is not needed here.
Remove the checkout step from this job to keep it lean.
- Anchor harperBinPath at package root via harper/package.json instead of main-entry dirname - Throw on non-string JSON body in extractMetricsText instead of silently stringifying - Remove HTTP 400 from accepted status set in 'Harper starts successfully' test - Remove catch-all text/ and dead text/plain arms from content-type assertion - Remove unnecessary checkout step from generate-node-version-matrix CI job Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Summary
harper@^5.0.28and@harperfast/integration-testing@^0.4.0as devDependenciestsconfig.jsonintegrationTests/metrics.test.tscovering:GET /prometheus_exporter/metricsreturns HTTP 200 with Prometheus/OpenMetrics texttext/plainorapplication/openmetrics-textprom-client collectDefaultMetrics()) are present/metrics/fastpath returns 200PrometheusExporterSettingstable is accessible via RESTforceAuthorizationsetting is initializedharperBinPathfix in test setup (workaround forERR_PACKAGE_PATH_NOT_EXPORTED—harper'sexportsmap only exposes".", soharper/dist/bin/harper.jsis not resolvable via the harness auto-resolve path).github/workflows/integration-tests.yml(Node matrix 22/24/26, pinned actions to commit hashes)package-lock.jsonwith--os=linux --cpu=x64 --include=optionalsobufferutil,utf-8-validate, andnode-gyp-buildare recorded for Linux CIpackage-lock.json(was in.gitignore; CI needs it fornpm ci)package.jsonauthor branding:HarperDB, Inc.→HarperMigration items applied / N/A
harperdb→harperimport swapharperdb/harperis a runtime-provided global, not a listed dependencyTable.get()returns plain frozen recordsrecordPropcompat shim added in commit73e2fa6wasLoadedFromSource()→target.loadedFromSourceBlob.save()→saveBeforeCommitKnown issues / Notes
EADDRNOTAVAIL: macOS loopback aliases are not configured on this machine (only127.0.0.1exists, and the integration harness binds Harper to127.0.0.2+). This is environmental — CI runs onubuntu-latestwhich supports the full127.0.0.0/8range. CI is the gate.harperBinPathworkaround: upstream issue —harperpackage should export its bin path or the harness should resolve via the package root instead of the blocked deep subpath.npm scope
The package name is
@harperdb/prometheus-exporter. If this should move to a current Harper npm scope (@harperfast/or similar), that is a manual step — flagged for the team per the upgrade plan (§11.1).Test plan
GET /prometheus_exporter/metricsreturns valid Prometheus/OpenMetrics textPrometheusExporterSettingstable initializes correctly on first boot🤖 Generated with Claude Code