mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-26 22:25:40 +00:00
feat: add support for interpolation on mockDataFunctions (#6393)
feat: implement `prepareMockObj` function for enhanced mock data processing in interpolation
This commit is contained in:
@@ -375,6 +375,62 @@ describe('interpolate - recursive', () => {
|
||||
"x": "baz bar"
|
||||
}`);
|
||||
});
|
||||
|
||||
it('should replace variables pointing to mock data functions', () => {
|
||||
const inputString = 'Timestamp: {{folderVar}}';
|
||||
const inputObject = {
|
||||
folderVar: '{{$isoTimestamp}}'
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
// Validate that the result is a valid ISO timestamp
|
||||
const timestampPattern = /^Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
||||
expect(timestampPattern.test(result)).toBe(true);
|
||||
});
|
||||
|
||||
it('should replace nested variables pointing to mock data functions', () => {
|
||||
const inputString = 'Random values: {{var1}} and {{var2}}';
|
||||
const inputObject = {
|
||||
var1: '{{nestedVar}}',
|
||||
nestedVar: '{{$randomInt}}',
|
||||
var2: '{{$randomBoolean}}'
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
|
||||
// Validate the result
|
||||
const parts = result.split(' and ');
|
||||
expect(parts.length).toBe(2);
|
||||
|
||||
const randomInt = parts[0].replace('Random values: ', '');
|
||||
const randomBoolean = parts[1];
|
||||
|
||||
// Check if randomInt is a number
|
||||
expect(!isNaN(Number(randomInt))).toBe(true);
|
||||
expect(Number(randomInt)).toBeGreaterThanOrEqual(0);
|
||||
expect(Number(randomInt)).toBeLessThanOrEqual(1000);
|
||||
|
||||
// Check if randomBoolean is a boolean
|
||||
expect(['true', 'false'].includes(randomBoolean)).toBe(true);
|
||||
});
|
||||
|
||||
it('should replace variables pointing to mock data functions with escapeJSONStrings option', () => {
|
||||
const inputString = '{"timestamp": "{{folderVar}}"}';
|
||||
const inputObject = {
|
||||
folderVar: '{{$isoTimestamp}}'
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject, { escapeJSONStrings: true });
|
||||
|
||||
// Should produce valid JSON
|
||||
expect(() => {
|
||||
const parsed = JSON.parse(result);
|
||||
// Validate that the timestamp is a valid ISO timestamp
|
||||
const timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
||||
expect(timestampPattern.test(parsed.timestamp)).toBe(true);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('interpolate - object handling', () => {
|
||||
@@ -534,6 +590,37 @@ describe('interpolate - mock variable interpolation', () => {
|
||||
JSON.parse(result); // This should throw an error
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should process mock variables in nested objects', () => {
|
||||
const inputString = '{{user.data}}';
|
||||
const inputObject = {
|
||||
user: {
|
||||
data: {
|
||||
id: '{{$randomUUID}}',
|
||||
timestamp: '{{$isoTimestamp}}',
|
||||
nested: {
|
||||
randomInt: '{{$randomInt}}'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const result = interpolate(inputString, inputObject);
|
||||
const parsed = JSON.parse(result);
|
||||
|
||||
// Validate UUID format
|
||||
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
expect(uuidPattern.test(parsed.id)).toBe(true);
|
||||
|
||||
// Validate ISO timestamp format
|
||||
const isoTimestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
||||
expect(isoTimestampPattern.test(parsed.timestamp)).toBe(true);
|
||||
|
||||
// Validate nested randomInt
|
||||
expect(!isNaN(Number(parsed.nested.randomInt))).toBe(true);
|
||||
expect(Number(parsed.nested.randomInt)).toBeGreaterThanOrEqual(0);
|
||||
expect(Number(parsed.nested.randomInt)).toBeLessThanOrEqual(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('interpolate - Date() handling', () => {
|
||||
|
||||
@@ -12,7 +12,58 @@
|
||||
*/
|
||||
|
||||
import { mockDataFunctions } from '../utils/faker-functions';
|
||||
import { get } from 'lodash-es';
|
||||
import { get, isPlainObject } from 'lodash-es';
|
||||
|
||||
// regex to match {{$keyword}}
|
||||
const MOCK_PATTERN = /\{\{\$(\w+)\}\}/g;
|
||||
const JSON_SPECIAL_CHARS = /[\\\n\r\t\"]/;
|
||||
|
||||
const escapeJSONString = (str: string): string => {
|
||||
if (!JSON_SPECIAL_CHARS.test(str)) {
|
||||
return str;
|
||||
}
|
||||
|
||||
return str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\t/g, '\\t')
|
||||
.replace(/\"/g, '\\"');
|
||||
};
|
||||
|
||||
const prepareMock = (str: string, escapeJSONStrings: boolean): string => {
|
||||
return str.replace(MOCK_PATTERN, (match, keyword) => {
|
||||
let generatedValue = mockDataFunctions[keyword as keyof typeof mockDataFunctions]?.();
|
||||
|
||||
if (generatedValue === undefined) {
|
||||
return match;
|
||||
}
|
||||
|
||||
generatedValue = String(generatedValue);
|
||||
|
||||
return escapeJSONStrings ? escapeJSONString(generatedValue) : generatedValue;
|
||||
});
|
||||
};
|
||||
|
||||
const prepareMockObj = (
|
||||
obj: Record<string, any>,
|
||||
escapeJSONStrings: boolean
|
||||
): Record<string, any> => {
|
||||
const processed: Record<string, any> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (typeof value === 'string') {
|
||||
processed[key] = prepareMock(value, escapeJSONStrings);
|
||||
} else if (isPlainObject(value)) {
|
||||
// plain object is used to skip special objects like Date, RegExp, etc.
|
||||
processed[key] = prepareMockObj(value, escapeJSONStrings);
|
||||
} else {
|
||||
processed[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return processed;
|
||||
};
|
||||
|
||||
const interpolate = (
|
||||
str: string,
|
||||
@@ -25,32 +76,14 @@ const interpolate = (
|
||||
|
||||
const { escapeJSONStrings } = options;
|
||||
|
||||
const patternRegex = /\{\{\$(\w+)\}\}/g;
|
||||
str = str.replace(patternRegex, (match, keyword) => {
|
||||
let replacement = mockDataFunctions[keyword as keyof typeof mockDataFunctions]?.();
|
||||
|
||||
if (replacement === undefined) return match;
|
||||
replacement = String(replacement);
|
||||
|
||||
if (!escapeJSONStrings) return replacement;
|
||||
|
||||
// All the below chars inside of a JSON String field
|
||||
// will make it invalid JSON. So we will have to escape them with `\`.
|
||||
// This is not exhaustive but selective to what faker-js can output.
|
||||
if (!/[\\\n\r\t\"]/.test(replacement)) return replacement;
|
||||
return replacement
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\t/g, '\\t')
|
||||
.replace(/\"/g, '\\"');
|
||||
});
|
||||
const preparedStr = prepareMock(str, escapeJSONStrings ?? false);
|
||||
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return str;
|
||||
return preparedStr;
|
||||
}
|
||||
|
||||
return replace(str, obj);
|
||||
// process the object with the mock data functions
|
||||
const preparedObj = prepareMockObj(obj, escapeJSONStrings ?? false);
|
||||
return replace(preparedStr, preparedObj);
|
||||
};
|
||||
|
||||
const replace = (
|
||||
|
||||
Reference in New Issue
Block a user