-
Notifications
You must be signed in to change notification settings - Fork 1
test(integration): skip tier-gated suites when GitLab license unavailable #429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d4fbd01
79882ce
795218e
5d60eec
8ba764f
3fe58bb
b6ff1ce
88a9edc
7267c3b
64feb2b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| /** | ||
| * Tier-gating helper for integration tests. | ||
| * | ||
| * Some integration tests exercise features that require a paid GitLab tier | ||
| * (Premium/Ultimate). When the target instance is GitLab Free (or EE binary | ||
| * without a license), those tests must be SKIPPED rather than failed — the | ||
| * underlying behavior is "feature unavailable", not "code broken". | ||
| * | ||
| * Tier is detected once in globalSetup.js (one HTTP call against currentLicense) | ||
| * and written to a tmp file. This module reads it synchronously at module load | ||
| * so describeIfTier can be used at module top-level inside test files. | ||
| * | ||
| * Usage: | ||
| * | ||
| * import { describeIfTier, itIfTier } from '../setup/tierGate'; | ||
| * | ||
| * describeIfTier('ultimate', 'Requirements verification', () => { | ||
| * it('verifies a requirement', async () => { ... }); | ||
| * }); | ||
| * | ||
| * describe('mixed-tier suite', () => { | ||
| * it('runs on all tiers', () => { ... }); | ||
| * itIfTier('premium', 'runs on premium and ultimate', () => { ... }); | ||
| * }); | ||
| */ | ||
|
|
||
| import * as crypto from 'crypto'; | ||
|
Check warning on line 27 in tests/setup/tierGate.ts
|
||
| import * as fs from 'fs'; | ||
|
Check warning on line 28 in tests/setup/tierGate.ts
|
||
| import * as os from 'os'; | ||
|
Check warning on line 29 in tests/setup/tierGate.ts
|
||
| import * as path from 'path'; | ||
|
Check warning on line 30 in tests/setup/tierGate.ts
|
||
| import { z } from 'zod'; | ||
|
|
||
| export type GitLabTier = 'free' | 'premium' | 'ultimate'; | ||
|
|
||
| /** | ||
| * Internal detection result. 'unknown' is a sentinel meaning globalSetup | ||
| * could not determine the tier (network failure / 4xx / GraphQL error). | ||
| * We never gate on 'unknown' — it bypasses skip logic so the suite runs | ||
| * and fails loudly with the real underlying error instead of producing a | ||
| * misleading green run with silent skips. | ||
| */ | ||
| type DetectedTier = GitLabTier | 'unknown'; | ||
|
|
||
| const TIER_RANK: Record<GitLabTier, number> = { free: 0, premium: 1, ultimate: 2 }; | ||
|
|
||
| // Cache file is written by globalSetup.js across a process boundary, so treat | ||
| // it as untrusted input and validate with Zod (project convention for any | ||
| // external/runtime-loaded payload). | ||
| const TierCacheSchema = z.object({ | ||
| tier: z.enum(['free', 'premium', 'ultimate', 'unknown']).optional(), | ||
| detectionFailed: z.boolean().optional(), | ||
| reason: z.string().optional(), | ||
| }); | ||
|
|
||
| // Mirror globalSetup.js: namespace by checkout root hash so concurrent runs | ||
| // across worktrees (or NFS-shared tmpdirs) don't collide on the same cache file. | ||
| // sha256 here is a cache-key digest, not a security primitive. | ||
| const REPO_HASH = crypto | ||
| .createHash('sha256') | ||
| .update(path.resolve(__dirname, '../..')) | ||
| .digest('hex') | ||
| .slice(0, 12); | ||
|
|
||
| const TIER_FILE = path.join(os.tmpdir(), `gitlab-mcp-detected-tier-${REPO_HASH}.json`); | ||
|
|
||
| function readDetectedTier(): DetectedTier { | ||
| try { | ||
| if (!fs.existsSync(TIER_FILE)) return 'unknown'; | ||
| const parsed = TierCacheSchema.safeParse(JSON.parse(fs.readFileSync(TIER_FILE, 'utf8'))); | ||
| if (!parsed.success || !parsed.data.tier) return 'unknown'; | ||
| return parsed.data.tier; | ||
| } catch { | ||
| return 'unknown'; | ||
| } | ||
| } | ||
|
|
||
| const DETECTED_TIER: DetectedTier = readDetectedTier(); | ||
|
|
||
| /** | ||
| * Return the tier detected during globalSetup. Returns 'unknown' when | ||
| * detection failed (network/auth/cache-miss) — caller can distinguish a | ||
| * confirmed Free instance from a failed detection. | ||
| */ | ||
| export function getDetectedTier(): DetectedTier { | ||
| return DETECTED_TIER; | ||
| } | ||
|
|
||
| /** | ||
| * True if the detected tier satisfies (>=) the required tier. | ||
| * When detection failed ('unknown'), returns true — we'd rather run the | ||
| * gated suite and fail loudly with the real error than silently skip and | ||
| * hide regressions behind a misleading green-with-pending run. | ||
| */ | ||
| export function tierSatisfies(required: GitLabTier): boolean { | ||
| if (DETECTED_TIER === 'unknown') return true; | ||
| return TIER_RANK[DETECTED_TIER] >= TIER_RANK[required]; | ||
| } | ||
|
|
||
| /** | ||
| * Describe block that runs only when the detected GitLab tier meets the | ||
| * required minimum. Otherwise emits describe.skip so the suite reports as | ||
| * pending instead of failing. | ||
| */ | ||
| export function describeIfTier(required: GitLabTier, name: string, fn: () => void): void { | ||
| if (tierSatisfies(required)) { | ||
| describe(name, fn); | ||
| } else { | ||
| describe.skip(`${name} [skipped: requires ${required}, detected ${DETECTED_TIER}]`, fn); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * It block that runs only when the detected GitLab tier meets the required | ||
| * minimum. Useful for a single tier-gated assertion inside an otherwise | ||
| * tier-agnostic describe block. | ||
| */ | ||
| export function itIfTier( | ||
| required: GitLabTier, | ||
| name: string, | ||
| fn: jest.ProvidesCallback, | ||
| timeout?: number, | ||
| ): void { | ||
| if (tierSatisfies(required)) { | ||
| it(name, fn, timeout); | ||
| } else { | ||
| it.skip(`${name} [skipped: requires ${required}, detected ${DETECTED_TIER}]`, fn, timeout); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.