From 91b00abc67fc22e3c2fa08e9417e881e075ac948 Mon Sep 17 00:00:00 2001 From: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Tue, 10 Nov 2026 01:18:20 -0500 Subject: [PATCH] test(runtime): pin the Dockerfile's node major and single digest to action.yml's runs.using runtime.test.ts already holds engines, devEngines, @types/node, the README, the contributing guide and every setup-node step to the major of runs.using, and dependabot.yml ignores the Dockerfile's node major with the note that it is bumped together with action.yml. The Dockerfile itself was the one pinned-together reference no test read: a FROM node:26-slim on either stage, or two stages on different digests, rendered and shipped silently. Both now fail the suite. Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- __tests__/runtime.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/__tests__/runtime.test.ts b/__tests__/runtime.test.ts index 99391cd5..7e0e43ad 100644 --- a/__tests__/runtime.test.ts +++ b/__tests__/runtime.test.ts @@ -68,6 +68,26 @@ describe(`action.yml runs the bundle under ${action.runs.using}`, () => { it('is the runtime the README tells self-hosted runner operators to provide', () => { expect(read('README.md')).toContain(`\`${action.runs.using}\` runtime`) }) + + // The Dockerfile is the runtime the server and the chart's CronJob actually run on, and + // .github/dependabot.yml ignores its major on purpose ("bump them together"); nothing but + // this test notices when one of the two moves without the other. + describe('is the major of the node image the Dockerfile builds and runs on', () => { + const stages = read('Dockerfile').split('\n').filter(line => line.startsWith('FROM ')) + + it('has a build stage and a runtime stage', () => { + expect(stages).toHaveLength(2) + }) + + it.each(stages)('%s', (line) => { + expect(line).toMatch(new RegExp(`^FROM node:${runtimeMajor}-slim@sha256:[0-9a-f]{64}( AS \\w+)?$`)) + }) + + it('runs on the same digest it was built on, so dependabot cannot move one stage alone', () => { + const digests = stages.map(line => /@(sha256:[0-9a-f]{64})/.exec(line)?.[1]) + expect(new Set(digests).size).toBe(1) + }) + }) }) describe('every setup-node step follows package.json', () => {