0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
CI / Shell Format Check (push) Waiting to run
CI / Check Ruby (3.4) (push) Waiting to run
CI / CI Config (push) Waiting to run
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Blocked by required conditions
CI / Build on Node ${{ matrix.node }} (push) Blocked by required conditions
CI / Style Check (push) Waiting to run
CI / Generate Assets (push) Waiting to run
CI / Check Python (3.14) (push) Waiting to run
CI / Check Python (3.9) (push) Waiting to run
CI / Build Docs (push) Waiting to run
CI / Code Scan Action (push) Waiting to run
CI / Site tests (push) Waiting to run
CI / webui tests (push) Waiting to run
CI / Run Integration Tests (push) Waiting to run
CI / Run Smoke Tests (push) Waiting to run
CI / Go Tests (push) Waiting to run
CI / Share Test (push) Waiting to run
CI / Redteam (Production API) (push) Waiting to run
CI / Redteam (Staging API) (push) Waiting to run
CI / GitHub Actions Lint (push) Waiting to run
CI / Check Ruby (3.0) (push) Waiting to run
release-please / release-please (push) Waiting to run
release-please / build (push) Blocked by required conditions
release-please / publish-npm (push) Blocked by required conditions
release-please / publish-npm-backfill (push) Waiting to run
release-please / docker (push) Blocked by required conditions
release-please / publish-code-scan-action (push) Blocked by required conditions
release-please / attest-code-scan-action (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
93 lines
3.8 KiB
TypeScript
93 lines
3.8 KiB
TypeScript
import { InvalidArgumentError } from 'commander';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { collectKeyValueOption, normalizeTagOption } from '../../src/util/cliOptions';
|
|
|
|
describe('collectKeyValueOption', () => {
|
|
it('parses a key=value pair into a record', () => {
|
|
expect(collectKeyValueOption('--tag', 'env=ci', undefined)).toEqual({ env: 'ci' });
|
|
});
|
|
|
|
it('merges onto the previous accumulator (repeatable option)', () => {
|
|
expect(collectKeyValueOption('--tag', 'run=123', { env: 'ci' })).toEqual({
|
|
env: 'ci',
|
|
run: '123',
|
|
});
|
|
});
|
|
|
|
it('keeps everything after the first "=" so values may contain "="', () => {
|
|
expect(collectKeyValueOption('--tag', 'token=a=b=c', undefined)).toEqual({ token: 'a=b=c' });
|
|
});
|
|
|
|
it('lets a later occurrence of the same key override an earlier one', () => {
|
|
const first = collectKeyValueOption('--tag', 'build=first', undefined);
|
|
expect(collectKeyValueOption('--tag', 'build=second', first)).toEqual({ build: 'second' });
|
|
});
|
|
|
|
it('accepts an explicitly empty value (key=)', () => {
|
|
expect(collectKeyValueOption('--tag', 'empty=', undefined)).toEqual({ empty: '' });
|
|
});
|
|
|
|
it.each(['invalid', '=value'])('throws InvalidArgumentError for malformed input %p', (value) => {
|
|
expect(() => collectKeyValueOption('--tag', value, undefined)).toThrow(InvalidArgumentError);
|
|
expect(() => collectKeyValueOption('--tag', value, undefined)).toThrow(
|
|
'--tag must be specified in key=value format.',
|
|
);
|
|
});
|
|
|
|
it('includes the option name in the error message', () => {
|
|
expect(() => collectKeyValueOption('--var', 'nope', undefined)).toThrow(
|
|
'--var must be specified in key=value format.',
|
|
);
|
|
});
|
|
|
|
it('stores a "__proto__" key as a harmless own property without polluting Object.prototype', () => {
|
|
const result = collectKeyValueOption('--tag', '__proto__=evil', { a: '1' });
|
|
|
|
expect(Object.prototype.hasOwnProperty.call(result, '__proto__')).toBe(true);
|
|
expect(Object.getOwnPropertyDescriptor(result, '__proto__')?.value).toBe('evil');
|
|
// The prototype chain must remain untouched.
|
|
expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
|
|
expect(({} as Record<string, unknown>).evil).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('normalizeTagOption', () => {
|
|
it('rewrites the Commander "tag" alias to the canonical "tags" field', () => {
|
|
const result = normalizeTagOption({ tag: { env: 'ci' } });
|
|
|
|
expect(result).toEqual({ tags: { env: 'ci' } });
|
|
expect(result).not.toHaveProperty('tag');
|
|
});
|
|
|
|
it('passes through an existing "tags" field when no "tag" alias is present', () => {
|
|
expect(normalizeTagOption({ tags: { env: 'ci' } })).toEqual({ tags: { env: 'ci' } });
|
|
});
|
|
|
|
it('prefers the CLI "tag" alias over a pre-existing "tags" field', () => {
|
|
const result = normalizeTagOption({ tag: { env: 'cli' }, tags: { env: 'config' } });
|
|
|
|
expect(result).toEqual({ tags: { env: 'cli' } });
|
|
expect(result).not.toHaveProperty('tag');
|
|
});
|
|
|
|
it('adds no "tags" key when neither "tag" nor "tags" is present', () => {
|
|
const input: { config: string; tag?: Record<string, string>; tags?: Record<string, string> } = {
|
|
config: 'promptfooconfig.yaml',
|
|
};
|
|
const result = normalizeTagOption(input);
|
|
|
|
expect(result).toEqual({ config: 'promptfooconfig.yaml' });
|
|
expect(result).not.toHaveProperty('tags');
|
|
expect(result).not.toHaveProperty('tag');
|
|
});
|
|
|
|
it('preserves unrelated options and does not mutate the input', () => {
|
|
const input = { config: 'a.yaml', verbose: true, tag: { env: 'ci' } };
|
|
const result = normalizeTagOption(input);
|
|
|
|
expect(result).toEqual({ config: 'a.yaml', verbose: true, tags: { env: 'ci' } });
|
|
// Input is left untouched.
|
|
expect(input).toEqual({ config: 'a.yaml', verbose: true, tag: { env: 'ci' } });
|
|
});
|
|
});
|