mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-08 20:30:53 -07:00
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
154 lines
4.2 KiB
TypeScript
154 lines
4.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { expect, describe, it } from 'vitest';
|
|
import {
|
|
hydrateString,
|
|
recursivelyHydrateStrings,
|
|
validateVariables,
|
|
type VariableContext,
|
|
} from './variables.js';
|
|
|
|
describe('validateVariables', () => {
|
|
it('should not throw if all required variables are present', () => {
|
|
const schema = {
|
|
extensionPath: { type: 'string', description: 'test', required: true },
|
|
} as const;
|
|
const context = { extensionPath: 'value' };
|
|
expect(() => validateVariables(context, schema)).not.toThrow();
|
|
});
|
|
|
|
it('should throw if a required variable is missing', () => {
|
|
const schema = {
|
|
extensionPath: { type: 'string', description: 'test', required: true },
|
|
} as const;
|
|
const context = {};
|
|
expect(() => validateVariables(context, schema)).toThrow(
|
|
'Missing required variable: extensionPath',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('hydrateString', () => {
|
|
it('should replace a single variable', () => {
|
|
const context = {
|
|
extensionPath: 'path/my-extension',
|
|
};
|
|
const result = hydrateString('Hello, ${extensionPath}!', context);
|
|
expect(result).toBe('Hello, path/my-extension!');
|
|
});
|
|
|
|
it('should replace multiple variables', () => {
|
|
const context = {
|
|
extensionPath: 'path/my-extension',
|
|
workspacePath: '/ws',
|
|
};
|
|
const result = hydrateString(
|
|
'Ext: ${extensionPath}, WS: ${workspacePath}',
|
|
context,
|
|
);
|
|
expect(result).toBe('Ext: path/my-extension, WS: /ws');
|
|
});
|
|
|
|
it('should ignore unknown variables', () => {
|
|
const context = {
|
|
extensionPath: 'path/my-extension',
|
|
};
|
|
const result = hydrateString('Hello, ${unknown}!', context);
|
|
expect(result).toBe('Hello, ${unknown}!');
|
|
});
|
|
|
|
it('should handle null and undefined context values', () => {
|
|
const context: VariableContext = {
|
|
extensionPath: undefined,
|
|
};
|
|
const result = hydrateString(
|
|
'Ext: ${extensionPath}, WS: ${workspacePath}',
|
|
context,
|
|
);
|
|
expect(result).toBe('Ext: ${extensionPath}, WS: ${workspacePath}');
|
|
});
|
|
});
|
|
|
|
describe('recursivelyHydrateStrings', () => {
|
|
const context = {
|
|
extensionPath: 'path/my-extension',
|
|
workspacePath: '/ws',
|
|
};
|
|
|
|
it('should hydrate strings in a flat object', () => {
|
|
const obj = {
|
|
a: 'Hello, ${workspacePath}',
|
|
b: 'Hi, ${extensionPath}',
|
|
};
|
|
const result = recursivelyHydrateStrings(obj, context);
|
|
expect(result).toEqual({
|
|
a: 'Hello, /ws',
|
|
b: 'Hi, path/my-extension',
|
|
});
|
|
});
|
|
|
|
it('should hydrate strings in an array', () => {
|
|
const arr = ['${workspacePath}', '${extensionPath}'];
|
|
const result = recursivelyHydrateStrings(arr, context);
|
|
expect(result).toEqual(['/ws', 'path/my-extension']);
|
|
});
|
|
|
|
it('should hydrate strings in a nested object', () => {
|
|
const obj = {
|
|
a: 'Hello, ${workspacePath}',
|
|
b: {
|
|
c: 'Hi, ${extensionPath}',
|
|
d: ['${workspacePath}/foo'],
|
|
},
|
|
};
|
|
const result = recursivelyHydrateStrings(obj, context);
|
|
expect(result).toEqual({
|
|
a: 'Hello, /ws',
|
|
b: {
|
|
c: 'Hi, path/my-extension',
|
|
d: ['/ws/foo'],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should not modify non-string values', () => {
|
|
const obj = {
|
|
a: 123,
|
|
b: true,
|
|
c: null,
|
|
};
|
|
const result = recursivelyHydrateStrings(obj, context);
|
|
expect(result).toEqual(obj);
|
|
});
|
|
|
|
it('should not allow prototype pollution via __proto__', () => {
|
|
const payload = JSON.parse('{"__proto__": {"polluted": "yes"}}');
|
|
const result = recursivelyHydrateStrings(payload, context);
|
|
|
|
expect(result.polluted).toBeUndefined();
|
|
expect(Object.prototype.hasOwnProperty.call(result, 'polluted')).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it('should not allow prototype pollution via constructor', () => {
|
|
const payload = JSON.parse(
|
|
'{"constructor": {"prototype": {"polluted": "yes"}}}',
|
|
);
|
|
const result = recursivelyHydrateStrings(payload, context);
|
|
|
|
expect(result.polluted).toBeUndefined();
|
|
});
|
|
|
|
it('should not allow prototype pollution via prototype', () => {
|
|
const payload = JSON.parse('{"prototype": {"polluted": "yes"}}');
|
|
const result = recursivelyHydrateStrings(payload, context);
|
|
|
|
expect(result.polluted).toBeUndefined();
|
|
});
|
|
});
|