From 58335beef69caecd3a087ef24891672ddd7bbe6a Mon Sep 17 00:00:00 2001 From: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Wed, 11 Nov 2026 13:30:51 -0500 Subject: [PATCH] test: cover lib/getBuildContext, getCurrentBranch, getGitHubRepoUrl These three modules compute the Docusaurus build context (deploy-preview branch/repo detection and the resulting asset base URL) and had no unit coverage. Add node:test suites exercising the env-var precedence rules in getCurrentBranch, the SSH/HTTPS URL normalization and REPOSITORY_URL fallback in getGitHubRepoUrl, and the production/deploy-preview branching in getBuildContext. getBuildContext.ts imported its sibling modules without file extensions, which node --experimental-strip-types (the project's own test:unit runner) cannot resolve. Add the .ts extensions so the new tests, and any future ones, can actually run; docusaurus build still resolves these extensionless-or-not imports fine, so this does not change build output. Signed-off-by: quality <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- lib/getBuildContext.test.mts | 91 +++++++++++++++++++++++++++++++++++ lib/getBuildContext.ts | 4 +- lib/getCurrentBranch.test.mts | 66 +++++++++++++++++++++++++ lib/getGitHubRepoUrl.test.mts | 62 ++++++++++++++++++++++++ 4 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 lib/getBuildContext.test.mts create mode 100644 lib/getCurrentBranch.test.mts create mode 100644 lib/getGitHubRepoUrl.test.mts diff --git a/lib/getBuildContext.test.mts b/lib/getBuildContext.test.mts new file mode 100644 index 00000000..0a444661 --- /dev/null +++ b/lib/getBuildContext.test.mts @@ -0,0 +1,91 @@ +import assert from 'node:assert/strict'; +import { describe, it, beforeEach, afterEach } from 'node:test'; +import getBuildContext from './getBuildContext.ts'; + +const ENV_KEYS = [ + 'BUILD_ENV', + 'DEPLOY_PRIME_URL', + 'HEAD', + 'BRANCH', + 'GITHUB_HEAD_REF', + 'GITHUB_REF_NAME', + 'REPOSITORY_URL', +] as const; +const savedEnv: Record = {}; + +beforeEach(() => { + for (const key of ENV_KEYS) savedEnv[key] = process.env[key]; + for (const key of ENV_KEYS) delete process.env[key]; +}); + +afterEach(() => { + for (const key of ENV_KEYS) { + if (savedEnv[key] === undefined) delete process.env[key]; + else process.env[key] = savedEnv[key]; + } +}); + +describe('getBuildContext', () => { + it('defaults to production when BUILD_ENV is unset', () => { + const ctx = getBuildContext('https://github.com/cncf/techdocs'); + assert.strictEqual(ctx.buildEnv, 'production'); + assert.strictEqual(ctx.isProd, true); + assert.strictEqual(ctx.isDeployPreview, false); + assert.strictEqual(ctx.assetBranch, 'main'); + assert.strictEqual(ctx.assetRepo, 'https://github.com/cncf/techdocs'); + assert.strictEqual( + ctx.assetBaseUrl, + 'https://github.com/cncf/techdocs/blob/main', + ); + }); + + it('is not a deploy preview when BUILD_ENV is non-production but DEPLOY_PRIME_URL is unset', () => { + process.env.BUILD_ENV = 'dev'; + const ctx = getBuildContext('https://github.com/cncf/techdocs'); + assert.strictEqual(ctx.isProd, false); + assert.strictEqual(ctx.isDeployPreview, false); + assert.strictEqual(ctx.assetBranch, 'main'); + assert.strictEqual(ctx.assetRepo, 'https://github.com/cncf/techdocs'); + }); + + it('builds a deploy-preview context from the current branch and repo', () => { + process.env.BUILD_ENV = 'dev'; + process.env.DEPLOY_PRIME_URL = + 'https://deploy-preview-1--example.netlify.app'; + process.env.HEAD = 'my-feature-branch'; + process.env.REPOSITORY_URL = 'https://github.com/someone/techdocs-fork'; + const ctx = getBuildContext('https://github.com/cncf/techdocs'); + assert.strictEqual(ctx.buildEnv, 'dev'); + assert.strictEqual(ctx.isProd, false); + assert.strictEqual(ctx.isDeployPreview, true); + assert.strictEqual(ctx.assetBranch, 'my-feature-branch'); + assert.strictEqual( + ctx.assetRepo, + 'https://github.com/someone/techdocs-fork', + ); + assert.strictEqual( + ctx.assetBaseUrl, + 'https://github.com/someone/techdocs-fork/blob/my-feature-branch', + ); + }); + + it('falls back to main/repo arg in deploy-preview mode when branch/remote cannot be resolved', () => { + process.env.BUILD_ENV = 'dev'; + process.env.DEPLOY_PRIME_URL = + 'https://deploy-preview-1--example.netlify.app'; + // No HEAD/BRANCH/GITHUB_* env vars, no REPOSITORY_URL: getCurrentBranch() + // and getGitHubRepoUrl() fall back to git plumbing, which may or may not + // resolve in this environment, so assetBranch/assetRepo must still be + // non-empty strings built from the '|| fallback' defaults. + const ctx = getBuildContext('https://github.com/cncf/techdocs'); + assert.strictEqual(ctx.isDeployPreview, true); + assert.ok( + typeof ctx.assetBranch === 'string' && ctx.assetBranch.length > 0, + ); + assert.ok(typeof ctx.assetRepo === 'string' && ctx.assetRepo.length > 0); + assert.strictEqual( + ctx.assetBaseUrl, + `${ctx.assetRepo}/blob/${ctx.assetBranch}`, + ); + }); +}); diff --git a/lib/getBuildContext.ts b/lib/getBuildContext.ts index adf437a4..8f0cf5af 100644 --- a/lib/getBuildContext.ts +++ b/lib/getBuildContext.ts @@ -1,5 +1,5 @@ -import getCurrentBranch from './getCurrentBranch'; -import getGitHubRepoUrl from './getGitHubRepoUrl'; +import getCurrentBranch from './getCurrentBranch.ts'; +import getGitHubRepoUrl from './getGitHubRepoUrl.ts'; export interface BuildContext { buildEnv: string; diff --git a/lib/getCurrentBranch.test.mts b/lib/getCurrentBranch.test.mts new file mode 100644 index 00000000..efbd4314 --- /dev/null +++ b/lib/getCurrentBranch.test.mts @@ -0,0 +1,66 @@ +import assert from 'node:assert/strict'; +import { describe, it, beforeEach, afterEach } from 'node:test'; +import getCurrentBranch from './getCurrentBranch.ts'; + +const ENV_KEYS = [ + 'HEAD', + 'BRANCH', + 'GITHUB_HEAD_REF', + 'GITHUB_REF_NAME', +] as const; +const savedEnv: Record = {}; + +beforeEach(() => { + for (const key of ENV_KEYS) savedEnv[key] = process.env[key]; +}); + +afterEach(() => { + for (const key of ENV_KEYS) { + if (savedEnv[key] === undefined) delete process.env[key]; + else process.env[key] = savedEnv[key]; + } +}); + +function clearEnv() { + for (const key of ENV_KEYS) delete process.env[key]; +} + +describe('getCurrentBranch', () => { + it('prefers HEAD over BRANCH and the GitHub Actions refs', () => { + clearEnv(); + process.env.HEAD = 'head-branch'; + process.env.BRANCH = 'branch-branch'; + process.env.GITHUB_HEAD_REF = 'gh-head-branch'; + process.env.GITHUB_REF_NAME = 'gh-ref-branch'; + assert.strictEqual(getCurrentBranch(), 'head-branch'); + }); + + it('prefers BRANCH over the GitHub Actions refs', () => { + clearEnv(); + process.env.BRANCH = 'branch-branch'; + process.env.GITHUB_HEAD_REF = 'gh-head-branch'; + process.env.GITHUB_REF_NAME = 'gh-ref-branch'; + assert.strictEqual(getCurrentBranch(), 'branch-branch'); + }); + + it('prefers GITHUB_HEAD_REF over GITHUB_REF_NAME', () => { + clearEnv(); + process.env.GITHUB_HEAD_REF = 'gh-head-branch'; + process.env.GITHUB_REF_NAME = 'gh-ref-branch'; + assert.strictEqual(getCurrentBranch(), 'gh-head-branch'); + }); + + it('falls back to GITHUB_REF_NAME when nothing else is set', () => { + clearEnv(); + process.env.GITHUB_REF_NAME = 'gh-ref-branch'; + assert.strictEqual(getCurrentBranch(), 'gh-ref-branch'); + }); + + it('falls back to `git branch --show-current` when no env vars are set', () => { + clearEnv(); + // Running inside a real git checkout, this resolves to a non-empty + // string or undefined (detached HEAD); either is a valid result, but the + // call must not throw and must not read any of the env vars above. + assert.doesNotThrow(() => getCurrentBranch()); + }); +}); diff --git a/lib/getGitHubRepoUrl.test.mts b/lib/getGitHubRepoUrl.test.mts new file mode 100644 index 00000000..85c9f3f8 --- /dev/null +++ b/lib/getGitHubRepoUrl.test.mts @@ -0,0 +1,62 @@ +import assert from 'node:assert/strict'; +import { describe, it, beforeEach, afterEach } from 'node:test'; +import getGitHubRepoUrl from './getGitHubRepoUrl.ts'; + +const ENV_KEYS = ['REPOSITORY_URL'] as const; +const savedEnv: Record = {}; + +beforeEach(() => { + for (const key of ENV_KEYS) savedEnv[key] = process.env[key]; +}); + +afterEach(() => { + for (const key of ENV_KEYS) { + if (savedEnv[key] === undefined) delete process.env[key]; + else process.env[key] = savedEnv[key]; + } +}); + +describe('getGitHubRepoUrl', () => { + it('normalizes an HTTPS REPOSITORY_URL', () => { + process.env.REPOSITORY_URL = 'https://github.com/cncf/techdocs'; + assert.strictEqual(getGitHubRepoUrl(), 'https://github.com/cncf/techdocs'); + }); + + it('normalizes an HTTPS REPOSITORY_URL with a .git suffix', () => { + process.env.REPOSITORY_URL = 'https://github.com/cncf/techdocs.git'; + assert.strictEqual(getGitHubRepoUrl(), 'https://github.com/cncf/techdocs'); + }); + + it('normalizes an SSH REPOSITORY_URL', () => { + process.env.REPOSITORY_URL = 'git@github.com:cncf/techdocs.git'; + assert.strictEqual(getGitHubRepoUrl(), 'https://github.com/cncf/techdocs'); + }); + + it('normalizes an SSH REPOSITORY_URL without a .git suffix', () => { + process.env.REPOSITORY_URL = 'git@github.com:cncf/techdocs'; + assert.strictEqual(getGitHubRepoUrl(), 'https://github.com/cncf/techdocs'); + }); + + it('falls back to the git remote when REPOSITORY_URL is unset', () => { + delete process.env.REPOSITORY_URL; + // The checkout's origin remote points at some fork or upstream of + // techdocs on GitHub; don't hard-code the owner, which varies by fork/CI. + assert.match( + getGitHubRepoUrl() ?? '', + /^https:\/\/github\.com\/[^/]+\/techdocs$/, + ); + }); + + it('falls back to the git remote when REPOSITORY_URL does not parse', () => { + process.env.REPOSITORY_URL = 'not-a-github-url'; + assert.match( + getGitHubRepoUrl() ?? '', + /^https:\/\/github\.com\/[^/]+\/techdocs$/, + ); + }); + + it('returns undefined for a non-existent remote', () => { + delete process.env.REPOSITORY_URL; + assert.strictEqual(getGitHubRepoUrl('does-not-exist'), undefined); + }); +});