|
| 1 | +/** |
| 2 | + * @vitest-environment node |
| 3 | + * |
| 4 | + * Differential test: narrowing the `{{ENV_VAR}}` body from `[^}]` to `[^{}]` |
| 5 | + * must not change resolution for any name the product can represent. |
| 6 | + * |
| 7 | + * The narrowing exists to remove polynomial backtracking — a class admitting |
| 8 | + * its own opening delimiter lets every offset in a run of `{` restart a full |
| 9 | + * scan. That is a real change to the accepted language, not a refactor, and it |
| 10 | + * runs on the execution path, so the divergence is pinned down here rather than |
| 11 | + * argued in a review comment. |
| 12 | + * |
| 13 | + * The one accepted divergence is enumerated below: a reference whose name |
| 14 | + * contains `{`. `PATTERNS.ENV_VAR_NAME` forbids that character, so no such name |
| 15 | + * can be stored through the secrets manager, and the divergence is unreachable. |
| 16 | + */ |
| 17 | + |
| 18 | +import { describe, expect, it } from 'vitest' |
| 19 | +import { PATTERNS, REFERENCE } from '@/executor/constants' |
| 20 | +import { createEnvVarPattern, resolveEnvVarReferences } from '@/executor/utils/reference-validation' |
| 21 | + |
| 22 | +/** The pattern as it stood before the narrowing. */ |
| 23 | +function createLegacyEnvVarPattern(): RegExp { |
| 24 | + return new RegExp(`\\${REFERENCE.ENV_VAR_START}([^}]+)\\${REFERENCE.ENV_VAR_END}`, 'g') |
| 25 | +} |
| 26 | + |
| 27 | +function matchesOf(pattern: RegExp, text: string): string[] { |
| 28 | + pattern.lastIndex = 0 |
| 29 | + const found: string[] = [] |
| 30 | + let match: RegExpExecArray | null |
| 31 | + while ((match = pattern.exec(text)) !== null) found.push(`${match.index}:${match[0]}`) |
| 32 | + return found |
| 33 | +} |
| 34 | + |
| 35 | +/** |
| 36 | + * Complete up to character equivalence: `{`, `}`, `<`, `>` are the delimiters, |
| 37 | + * and every other character behaves identically in both bodies. `_`, `A` and |
| 38 | + * the space cover the name, trim and separator cases. |
| 39 | + */ |
| 40 | +const ALPHABET = ['{', '}', '<', '>', 'A', '_', ' '] |
| 41 | +const MAX_LEN = 6 |
| 42 | + |
| 43 | +function* corpus(): Generator<string> { |
| 44 | + let level = [''] |
| 45 | + yield '' |
| 46 | + for (let length = 1; length <= MAX_LEN; length++) { |
| 47 | + const next: string[] = [] |
| 48 | + for (const prefix of level) { |
| 49 | + for (const character of ALPHABET) { |
| 50 | + const candidate = prefix + character |
| 51 | + next.push(candidate) |
| 52 | + yield candidate |
| 53 | + } |
| 54 | + } |
| 55 | + level = next |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +/** The enumerated divergence: a `{{` whose body reaches a `{` before any `}`. */ |
| 60 | +const NAME_CONTAINS_BRACE = /\{\{[^}]*\{/ |
| 61 | + |
| 62 | +describe('env var pattern narrowing', () => { |
| 63 | + it('diverges only where the reference name contains an opening brace', () => { |
| 64 | + const legacy = createLegacyEnvVarPattern() |
| 65 | + const current = createEnvVarPattern() |
| 66 | + const unexplained: string[] = [] |
| 67 | + let differed = 0 |
| 68 | + |
| 69 | + for (const text of corpus()) { |
| 70 | + const before = matchesOf(legacy, text) |
| 71 | + const after = matchesOf(current, text) |
| 72 | + if (before.join('|') === after.join('|')) continue |
| 73 | + differed++ |
| 74 | + if (!NAME_CONTAINS_BRACE.test(text) && unexplained.length < 20) unexplained.push(text) |
| 75 | + } |
| 76 | + |
| 77 | + expect(unexplained).toEqual([]) |
| 78 | + expect(differed).toBeGreaterThan(0) |
| 79 | + }) |
| 80 | + |
| 81 | + it('never diverges on a representable environment-variable name', () => { |
| 82 | + const legacy = createLegacyEnvVarPattern() |
| 83 | + const current = createEnvVarPattern() |
| 84 | + const names = ['A', '_', 'API_KEY', 'a1', '_x9', 'X'.repeat(64)] |
| 85 | + const surroundings = ['%s', 'a%sb', '%s%s', 'x {{%s}} y', '{{%s}} {{%s}}'] |
| 86 | + |
| 87 | + for (const name of names) { |
| 88 | + expect(PATTERNS.ENV_VAR_NAME.test(name)).toBe(true) |
| 89 | + for (const shape of surroundings) { |
| 90 | + const text = shape.replaceAll('%s', `{{${name}}}`).replaceAll('{{{{', '{{') |
| 91 | + expect(matchesOf(current, text)).toEqual(matchesOf(legacy, text)) |
| 92 | + } |
| 93 | + } |
| 94 | + }) |
| 95 | + |
| 96 | + it('still resolves references, including padded and embedded ones', () => { |
| 97 | + const envVars = { API_KEY: 'secret', OTHER: 'value' } |
| 98 | + expect(resolveEnvVarReferences('{{API_KEY}}', envVars)).toBe('secret') |
| 99 | + expect(resolveEnvVarReferences('{{ API_KEY }}', envVars)).toBe('secret') |
| 100 | + expect(resolveEnvVarReferences('a-{{API_KEY}}-b', envVars)).toBe('a-secret-b') |
| 101 | + expect(resolveEnvVarReferences('{{API_KEY}}/{{OTHER}}', envVars)).toBe('secret/value') |
| 102 | + }) |
| 103 | + |
| 104 | + it('scans an unterminated brace run in linear time', () => { |
| 105 | + const started = performance.now() |
| 106 | + resolveEnvVarReferences('{'.repeat(100_000), { API_KEY: 'secret' }) |
| 107 | + expect(performance.now() - started).toBeLessThan(1_000) |
| 108 | + }) |
| 109 | +}) |
0 commit comments