mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
Merge pull request #5438 from Pragadesh-45/feat/multiline-values-for-env-vars
Feat/ Add Multiline Support for Enviroment Variables
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "multiline-variables",
|
||||
"type": "collection"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
meta {
|
||||
name: multiline-variables
|
||||
type: collection
|
||||
version: 1.0.0
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
vars {
|
||||
host: https://www.httpfaker.org
|
||||
multiline_data: '''
|
||||
line1
|
||||
line2
|
||||
line3
|
||||
'''
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
meta {
|
||||
name: multiline-test
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/echo
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{{multiline_data_json}}
|
||||
}
|
||||
|
||||
tests {
|
||||
test("should post multiline data successfully", function() {
|
||||
expect(res.getStatus()).to.equal(200);
|
||||
});
|
||||
|
||||
test("should resolve multiline_data_json variable correctly", function() {
|
||||
const body = res.getBody();
|
||||
// HTTP Faker echo endpoint returns the request body in body.body
|
||||
// Verify the multiline JSON variable was resolved and parsed correctly
|
||||
expect(body.body.user.name).to.equal("John Doe");
|
||||
expect(body.body.user.email).to.equal("john@example.com");
|
||||
expect(body.body.user.preferences.theme).to.equal("dark");
|
||||
expect(body.body.user.preferences.notifications).to.equal(true);
|
||||
});
|
||||
|
||||
test("should preserve JSON structure from multiline variable", function() {
|
||||
const body = res.getBody();
|
||||
// Verify the complete JSON structure was preserved
|
||||
expect(body.body.metadata.created).to.equal("2025-09-03");
|
||||
expect(body.body.metadata.version).to.equal("1.0");
|
||||
});
|
||||
|
||||
test("should resolve host variable in URL", function() {
|
||||
const body = res.getBody();
|
||||
// Verify the host variable was resolved in the request URL
|
||||
expect(body.url).to.equal("https://www.httpfaker.org/api/echo");
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
meta {
|
||||
name: request
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/echo
|
||||
body: text
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
Ping Test Request
|
||||
Host: {{host}}
|
||||
|
||||
Multiline Data:
|
||||
{{multiline_data}}
|
||||
|
||||
End of multiline content.
|
||||
}
|
||||
|
||||
body:text {
|
||||
{{host}}
|
||||
{{multiline_data}}
|
||||
}
|
||||
|
||||
tests {
|
||||
test("should get 200 response", function() {
|
||||
expect(res.getStatus()).to.equal(200);
|
||||
});
|
||||
|
||||
test("should resolve multiline_data variable correctly", function() {
|
||||
const body = res.getBody();
|
||||
// Verify the multiline variable was resolved and contains all three lines
|
||||
expect(body.body).to.equal("https://www.httpfaker.org\nline1\nline2\nline3");
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"collections": [
|
||||
{
|
||||
"path": "{{projectRoot}}/e2e-tests/environments/multiline-variables/collection",
|
||||
"securityConfig": {
|
||||
"jsSandboxMode": "developer"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": [
|
||||
"{{projectRoot}}/e2e-tests/environments/multiline-variables/collection"
|
||||
],
|
||||
"request": {
|
||||
"sslVerification": false,
|
||||
"customCaCertificate": {
|
||||
"enabled": false,
|
||||
"filePath": null
|
||||
}
|
||||
},
|
||||
"font": {
|
||||
"codeFont": "default"
|
||||
},
|
||||
"proxy": {
|
||||
"enabled": false,
|
||||
"protocol": "http",
|
||||
"hostname": "",
|
||||
"port": "",
|
||||
"auth": {
|
||||
"enabled": false,
|
||||
"username": "",
|
||||
"password": ""
|
||||
},
|
||||
"bypassProxy": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
|
||||
test.describe('Multiline Variables - Read Environment Test', () => {
|
||||
test('should read existing multiline environment variables', async ({ pageWithUserData: page }) => {
|
||||
test.setTimeout(30 * 1000);
|
||||
|
||||
// open the collection
|
||||
await expect(page.getByTitle('multiline-variables')).toBeVisible();
|
||||
await page.getByTitle('multiline-variables').click();
|
||||
|
||||
// open request
|
||||
await expect(page.getByTitle('request', { exact: true })).toBeVisible();
|
||||
await page.getByTitle('request', { exact: true }).click();
|
||||
|
||||
// open environment dropdown
|
||||
await expect(page.getByTitle('No Environment')).toBeVisible();
|
||||
await page.getByTitle('No Environment').click();
|
||||
|
||||
// select test environment
|
||||
await expect(page.locator('.dropdown-item').filter({ hasText: 'Test' })).toBeVisible();
|
||||
await page.locator('.dropdown-item').filter({ hasText: 'Test' }).click();
|
||||
await expect(page.locator('.current-environment').filter({ hasText: /Test/ })).toBeVisible();
|
||||
|
||||
// send request
|
||||
const sendButton = page.locator('#send-request').getByRole('img').nth(2);
|
||||
await expect(sendButton).toBeVisible();
|
||||
await sendButton.click();
|
||||
await expect(page.locator('.response-status-code.text-ok')).toBeVisible();
|
||||
await expect(page.locator('.response-status-code')).toContainText('200');
|
||||
|
||||
// response pane should contain the expected multiline text in JSON body
|
||||
const responsePane = page.locator('.response-pane');
|
||||
await expect(responsePane).toContainText('"body": "https://www.httpfaker.org\\nline1\\nline2\\nline3"');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
|
||||
test.describe('Multiline Variables - Write Test', () => {
|
||||
test('should create and use multiline environment variable dynamically', async ({ pageWithUserData: page }) => {
|
||||
test.setTimeout(60 * 1000);
|
||||
|
||||
// open the collection
|
||||
await expect(page.getByTitle('multiline-variables')).toBeVisible();
|
||||
await page.getByTitle('multiline-variables').click();
|
||||
|
||||
// open request
|
||||
await expect(page.getByTitle('multiline-test', { exact: true })).toBeVisible();
|
||||
await page.getByTitle('multiline-test', { exact: true }).click();
|
||||
|
||||
// open environment dropdown
|
||||
await expect(page.getByTitle('No Environment')).toBeVisible();
|
||||
await page.getByTitle('No Environment').click();
|
||||
|
||||
// select test environment
|
||||
await expect(page.locator('.dropdown-item').filter({ hasText: 'Test' })).toBeVisible();
|
||||
await page.locator('.dropdown-item').filter({ hasText: 'Test' }).click();
|
||||
await expect(page.locator('.current-environment').filter({ hasText: /Test/ })).toBeVisible();
|
||||
|
||||
// select configure button from environment dropdown
|
||||
await expect(page.getByTitle('Test', { exact: true })).toBeVisible();
|
||||
await page.getByTitle('Test', { exact: true }).click();
|
||||
|
||||
// open environment configuration
|
||||
await expect(page.locator('#Configure')).toBeVisible();
|
||||
await page.locator('#Configure').click();
|
||||
|
||||
// add variable
|
||||
await page.getByRole('button', { name: /Add.*Variable/i }).click();
|
||||
const valueTextarea = page.locator('.bruno-modal-card textarea').last();
|
||||
await expect(valueTextarea).toBeVisible();
|
||||
|
||||
|
||||
const jsonValue = `{
|
||||
"user": {
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"preferences": {
|
||||
"theme": "dark",
|
||||
"notifications": true
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"created": "2025-09-03",
|
||||
"version": "1.0"
|
||||
}
|
||||
}`;
|
||||
|
||||
// fill variable value
|
||||
await valueTextarea.fill(jsonValue);
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
await page.keyboard.type('multiline_data_json');
|
||||
|
||||
// save variable and close config
|
||||
const saveVarButton = page.getByRole('button', { name: /Save/i });
|
||||
await expect(saveVarButton).toBeVisible();
|
||||
await saveVarButton.click();
|
||||
|
||||
await expect(page.locator('.close.cursor-pointer')).toBeVisible();
|
||||
await page.locator('.close.cursor-pointer').click();
|
||||
|
||||
// send request
|
||||
const sendButton = page.locator('#send-request').getByRole('img').nth(2);
|
||||
await expect(sendButton).toBeVisible();
|
||||
await sendButton.click();
|
||||
|
||||
// wait for response status
|
||||
await expect(page.locator('.response-status-code.text-ok')).toBeVisible();
|
||||
await expect(page.locator('.response-status-code')).toContainText('200');
|
||||
|
||||
// verify multiline JSON variable resolution in response
|
||||
const expectedBody =
|
||||
'{\n "user": {\n "name": "John Doe",\n "email": "john@example.com",\n "preferences": {\n "theme": "dark",\n "notifications": true\n }\n },\n "metadata": {\n "created": "2025-09-03",\n "version": "1.0"\n }\n}';
|
||||
await expect(page.locator('.response-pane')).toContainText(`"body": ${JSON.stringify(expectedBody)}`);
|
||||
});
|
||||
|
||||
// clean up created variable after test
|
||||
test.afterEach(async () => {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const testBruPath = path.join(__dirname, 'collection/environments/Test.bru');
|
||||
let content = fs.readFileSync(testBruPath, 'utf8');
|
||||
|
||||
// remove the multiline_data_json variable and its content
|
||||
content = content.replace(/\s*multiline_data_json:\s*'''\s*[\s\S]*?\s*'''/g, '');
|
||||
|
||||
fs.writeFileSync(testBruPath, content);
|
||||
});
|
||||
});
|
||||
@@ -19,7 +19,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="current-environment flex items-center justify-center pl-3 pr-2 py-1 select-none">
|
||||
<p className="text-nowrap truncate max-w-32">{activeEnvironment ? activeEnvironment.name : 'No Environment'}</p>
|
||||
<p className="text-nowrap truncate max-w-32" title={activeEnvironment ? activeEnvironment.name : 'No Environment'}>{activeEnvironment ? activeEnvironment.name : 'No Environment'}</p>
|
||||
<IconCaretDown className="caret" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
@@ -82,7 +82,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
handleSettingsIconClick();
|
||||
dropdownTippyRef.current.hide();
|
||||
}}>
|
||||
<div className="pr-2 text-gray-600">
|
||||
<div className="pr-2 text-gray-600" id="Configure">
|
||||
<IconSettings size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span>Configure</span>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { IconTrash, IconAlertCircle, IconDeviceFloppy, IconRefresh, IconCircleCh
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import MultiLineEditor from 'components/MultiLineEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { uuid } from 'utils/common';
|
||||
import { useFormik } from 'formik';
|
||||
@@ -214,7 +214,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
</td>
|
||||
<td className="flex flex-row flex-nowrap items-center">
|
||||
<div className="overflow-hidden grow w-full relative">
|
||||
<SingleLineEditor
|
||||
<MultiLineEditor
|
||||
theme={storedTheme}
|
||||
collection={_collection}
|
||||
name={`${index}.value`}
|
||||
@@ -253,6 +253,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
ref={addButtonRef}
|
||||
className="btn-add-param text-link pr-2 py-3 mt-2 select-none"
|
||||
onClick={addVariable}
|
||||
id="add-variable"
|
||||
>
|
||||
+ Add Variable
|
||||
</button>
|
||||
|
||||
@@ -3,7 +3,7 @@ import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash, IconAlertCircle } from '@tabler/icons';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import MultiLineEditor from 'components/MultiLineEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { uuid } from 'utils/common';
|
||||
import { useFormik } from 'formik';
|
||||
@@ -147,7 +147,7 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
|
||||
</td>
|
||||
<td className="flex flex-row flex-nowrap">
|
||||
<div className="overflow-hidden grow w-full relative">
|
||||
<SingleLineEditor
|
||||
<MultiLineEditor
|
||||
theme={storedTheme}
|
||||
name={`${index}.value`}
|
||||
value={variable.value}
|
||||
|
||||
@@ -16,7 +16,7 @@ const StatusCode = ({ status }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className={getTabClassname(status)}>
|
||||
<StyledWrapper className={`response-status-code ${getTabClassname(status)}`}>
|
||||
{status} {statusCodePhraseMap[status]}
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -232,7 +232,7 @@ const Collection = ({ collection, searchText }) => {
|
||||
onClick={handleCollectionCollapse}
|
||||
onDoubleClick={handleCollectionDoubleClick}
|
||||
/>
|
||||
<div className="ml-1 w-full" id="sidebar-collection-name">
|
||||
<div className="ml-1 w-full" id="sidebar-collection-name" title={collection.name}>
|
||||
{collection.name}
|
||||
</div>
|
||||
{isLoading ? <IconLoader2 className="animate-spin mx-1" size={18} strokeWidth={1.5} /> : null}
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
const ohm = require('ohm-js');
|
||||
const _ = require('lodash');
|
||||
|
||||
// Env files use 4-space indentation for multiline content
|
||||
// vars {
|
||||
// API_KEY: '''
|
||||
// -----BEGIN PUBLIC KEY-----
|
||||
// MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8
|
||||
// HMR5LXFFrwXQFE6xUVhXrxUpx1TtfoGkRcU7LEWV
|
||||
// -----END PUBLIC KEY-----
|
||||
// '''
|
||||
// }
|
||||
const indentLevel = 4;
|
||||
const grammar = ohm.grammar(`Bru {
|
||||
BruEnvFile = (vars | secretvars)*
|
||||
|
||||
@@ -10,14 +20,20 @@ const grammar = ohm.grammar(`Bru {
|
||||
tagend = nl "}"
|
||||
optionalnl = ~tagend nl
|
||||
keychar = ~(tagend | st | nl | ":") any
|
||||
valuechar = ~(nl | tagend) any
|
||||
valuechar = ~(nl | tagend | multilinetextblockstart) any
|
||||
|
||||
multilinetextblockdelimiter = "'''"
|
||||
multilinetextblockstart = "'''" nl
|
||||
multilinetextblockend = nl st* "'''"
|
||||
multilinetextblock = multilinetextblockstart multilinetextblockcontent multilinetextblockend
|
||||
multilinetextblockcontent = (~multilinetextblockend any)*
|
||||
|
||||
// Dictionary Blocks
|
||||
dictionary = st* "{" pairlist? tagend
|
||||
pairlist = optionalnl* pair (~tagend stnl* pair)* (~tagend space)*
|
||||
pair = st* key st* ":" st* value st*
|
||||
key = keychar*
|
||||
value = valuechar*
|
||||
value = multilinetextblock | valuechar*
|
||||
|
||||
// Array Blocks
|
||||
array = st* "[" stnl* valuelist stnl* "]"
|
||||
@@ -120,8 +136,31 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
||||
return chars.sourceString ? chars.sourceString.trim() : '';
|
||||
},
|
||||
value(chars) {
|
||||
// .ctorName provides the name of the rule that matched the input
|
||||
if (chars.ctorName === 'multilinetextblock') {
|
||||
return chars.ast;
|
||||
}
|
||||
return chars.sourceString ? chars.sourceString.trim() : '';
|
||||
},
|
||||
multilinetextblockstart(_1, _2) {
|
||||
return '';
|
||||
},
|
||||
multilinetextblockend(_1, _2, _3) {
|
||||
return '';
|
||||
},
|
||||
multilinetextblockdelimiter(_) {
|
||||
return '';
|
||||
},
|
||||
multilinetextblock(_1, content, _2) {
|
||||
return content.ast
|
||||
.split('\n')
|
||||
.map((line) => line.slice(indentLevel)) // Remove 4-space indentation
|
||||
.join('\n')
|
||||
.trim();
|
||||
},
|
||||
multilinetextblockcontent(chars) {
|
||||
return chars.sourceString;
|
||||
},
|
||||
nl(_1, _2) {
|
||||
return '';
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
const { indentString } = require('./utils');
|
||||
const { indentString, getValueString } = require('./utils');
|
||||
|
||||
const enabled = (items = [], key = "enabled") => items.filter((item) => item[key]);
|
||||
const disabled = (items = [], key = "enabled") => items.filter((item) => !item[key]);
|
||||
@@ -16,23 +16,6 @@ const stripLastLine = (text) => {
|
||||
return text.replace(/(\r?\n)$/, '');
|
||||
};
|
||||
|
||||
const getValueString = (value) => {
|
||||
const hasNewLines = value?.includes('\n');
|
||||
|
||||
if (!hasNewLines) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Add one level of indentation to the contents of the multistring
|
||||
const indentedLines = value
|
||||
.split('\n')
|
||||
.map((line) => ` ${line}`)
|
||||
.join('\n');
|
||||
|
||||
// Join the lines back together with newline characters and enclose them in triple single quotes
|
||||
return `'''\n${indentedLines}\n'''`;
|
||||
};
|
||||
|
||||
const jsonToBru = (json) => {
|
||||
const { meta, http, grpc, params, headers, metadata, auth, body, script, tests, vars, assertions, settings, docs } = json;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const _ = require('lodash');
|
||||
const { getValueString, indentString } = require('./utils');
|
||||
|
||||
const envToJson = (json) => {
|
||||
const variables = _.get(json, 'variables', []);
|
||||
@@ -7,7 +8,8 @@ const envToJson = (json) => {
|
||||
.map((variable) => {
|
||||
const { name, value, enabled } = variable;
|
||||
const prefix = enabled ? '' : '~';
|
||||
return ` ${prefix}${name}: ${value}`;
|
||||
|
||||
return indentString(`${prefix}${name}: ${getValueString(value)}`);
|
||||
});
|
||||
|
||||
const secretVars = variables
|
||||
@@ -15,7 +17,7 @@ const envToJson = (json) => {
|
||||
.map((variable) => {
|
||||
const { name, enabled } = variable;
|
||||
const prefix = enabled ? '' : '~';
|
||||
return ` ${prefix}${name}`;
|
||||
return indentString(`${prefix}${name}`);
|
||||
});
|
||||
|
||||
if (!variables || !variables.length) {
|
||||
|
||||
@@ -7,12 +7,21 @@ const safeParseJson = (json) => {
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeNewlines = (str) => {
|
||||
if (!str || typeof str !== 'string') {
|
||||
return str || '';
|
||||
}
|
||||
|
||||
// "\r\n" is windows, "\r" is old mac, "\n" is linux
|
||||
return str.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
||||
};
|
||||
|
||||
const indentString = (str) => {
|
||||
if (!str || !str.length) {
|
||||
return str || '';
|
||||
}
|
||||
|
||||
return str
|
||||
return normalizeNewlines(str)
|
||||
.split('\n')
|
||||
.map((line) => ' ' + line)
|
||||
.join('\n');
|
||||
@@ -22,15 +31,33 @@ const outdentString = (str) => {
|
||||
if (!str || !str.length) {
|
||||
return str || '';
|
||||
}
|
||||
|
||||
return str
|
||||
|
||||
return normalizeNewlines(str)
|
||||
.split('\n')
|
||||
.map((line) => line.replace(/^ /, ''))
|
||||
.join('\n');
|
||||
};
|
||||
|
||||
const getValueString = (value) => {
|
||||
// Handle null, undefined, and empty strings
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const hasNewLines = value?.includes('\n') || value?.includes('\r');
|
||||
|
||||
if (!hasNewLines) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Wrap multiline values in triple quotes with 2-space indentation
|
||||
return `'''\n${indentString(value)}\n'''`;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
safeParseJson,
|
||||
normalizeNewlines,
|
||||
indentString,
|
||||
outdentString
|
||||
outdentString,
|
||||
getValueString
|
||||
};
|
||||
|
||||
@@ -313,4 +313,116 @@ vars:secret [access_key,access_secret, access_password ]
|
||||
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should parse multiline variable values', () => {
|
||||
const input = `
|
||||
vars {
|
||||
json_data: '''
|
||||
{
|
||||
"name": "test",
|
||||
"value": 123
|
||||
}
|
||||
'''
|
||||
}`;
|
||||
|
||||
const output = parser(input);
|
||||
const expected = {
|
||||
variables: [
|
||||
{
|
||||
name: 'json_data',
|
||||
value: '{\n "name": "test",\n "value": 123\n}',
|
||||
enabled: true,
|
||||
secret: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should parse multiline variable that has indentation', () => {
|
||||
const input = `
|
||||
vars {
|
||||
script: '''
|
||||
function test() {
|
||||
console.log("hello");
|
||||
return true;
|
||||
}
|
||||
'''
|
||||
}`;
|
||||
|
||||
const output = parser(input);
|
||||
const expected = {
|
||||
variables: [
|
||||
{
|
||||
name: 'script',
|
||||
value: 'function test() {\n console.log("hello");\n return true;\n}',
|
||||
enabled: true,
|
||||
secret: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should parse disabled multiline variable', () => {
|
||||
const input = `
|
||||
vars {
|
||||
~disabled_multiline: '''
|
||||
line 1
|
||||
line 2
|
||||
line 3
|
||||
'''
|
||||
}`;
|
||||
|
||||
const output = parser(input);
|
||||
const expected = {
|
||||
variables: [
|
||||
{
|
||||
name: 'disabled_multiline',
|
||||
value: 'line 1\nline 2\nline 3',
|
||||
enabled: false,
|
||||
secret: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should parse multiple multiline variables', () => {
|
||||
const input = `
|
||||
vars {
|
||||
config: '''
|
||||
debug=true
|
||||
port=3000
|
||||
'''
|
||||
template: '''
|
||||
<html>
|
||||
<body>Hello World</body>
|
||||
</html>
|
||||
'''
|
||||
}`;
|
||||
|
||||
const output = parser(input);
|
||||
const expected = {
|
||||
variables: [
|
||||
{
|
||||
name: 'config',
|
||||
value: 'debug=true\nport=3000',
|
||||
enabled: true,
|
||||
secret: false
|
||||
},
|
||||
{
|
||||
name: 'template',
|
||||
value: '<html>\n <body>Hello World</body>\n</html>',
|
||||
enabled: true,
|
||||
secret: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const parser = require('../src/jsonToEnv');
|
||||
|
||||
describe('env parser', () => {
|
||||
it('should parse empty vars', () => {
|
||||
describe('jsonToEnv', () => {
|
||||
it('should stringify empty vars', () => {
|
||||
const input = {
|
||||
variables: []
|
||||
};
|
||||
@@ -14,7 +14,7 @@ describe('env parser', () => {
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should parse single var line', () => {
|
||||
it('should stringify single var line', () => {
|
||||
const input = {
|
||||
variables: [
|
||||
{
|
||||
@@ -33,7 +33,7 @@ describe('env parser', () => {
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should parse multiple var lines', () => {
|
||||
it('should stringify multiple var lines', () => {
|
||||
const input = {
|
||||
variables: [
|
||||
{
|
||||
@@ -58,7 +58,7 @@ describe('env parser', () => {
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should parse secret vars', () => {
|
||||
it('should stringify secret vars', () => {
|
||||
const input = {
|
||||
variables: [
|
||||
{
|
||||
@@ -86,7 +86,7 @@ vars:secret [
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should parse multiple secret vars', () => {
|
||||
it('should stringify multiple secret vars', () => {
|
||||
const input = {
|
||||
variables: [
|
||||
{
|
||||
@@ -121,7 +121,7 @@ vars:secret [
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should parse even if the only secret vars are present', () => {
|
||||
it('should stringify even if the only secret vars are present', () => {
|
||||
const input = {
|
||||
variables: [
|
||||
{
|
||||
@@ -137,6 +137,109 @@ vars:secret [
|
||||
const expected = `vars:secret [
|
||||
token
|
||||
]
|
||||
`;
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should stringify multiline variables', () => {
|
||||
const input = {
|
||||
variables: [
|
||||
{
|
||||
name: 'json_data',
|
||||
value: '{\n "name": "test",\n "value": 123\n}',
|
||||
enabled: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const output = parser(input);
|
||||
const expected = `vars {
|
||||
json_data: '''
|
||||
{
|
||||
"name": "test",
|
||||
"value": 123
|
||||
}
|
||||
'''
|
||||
}
|
||||
`;
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should stringify multiline variables containing indentation', () => {
|
||||
const input = {
|
||||
variables: [
|
||||
{
|
||||
name: 'script',
|
||||
value: 'function test() {\n console.log("hello");\n return true;\n}',
|
||||
enabled: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const output = parser(input);
|
||||
const expected = `vars {
|
||||
script: '''
|
||||
function test() {
|
||||
console.log("hello");
|
||||
return true;
|
||||
}
|
||||
'''
|
||||
}
|
||||
`;
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should stringify disabled multiline variable', () => {
|
||||
const input = {
|
||||
variables: [
|
||||
{
|
||||
name: 'disabled_multiline',
|
||||
value: 'line 1\nline 2\nline 3',
|
||||
enabled: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const output = parser(input);
|
||||
const expected = `vars {
|
||||
~disabled_multiline: '''
|
||||
line 1
|
||||
line 2
|
||||
line 3
|
||||
'''
|
||||
}
|
||||
`;
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should stringify multiple multiline variables', () => {
|
||||
const input = {
|
||||
variables: [
|
||||
{
|
||||
name: 'config',
|
||||
value: 'debug=true\nport=3000',
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
name: 'template',
|
||||
value: '<html>\n <body>Hello World</body>\n</html>',
|
||||
enabled: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const output = parser(input);
|
||||
const expected = `vars {
|
||||
config: '''
|
||||
debug=true
|
||||
port=3000
|
||||
'''
|
||||
template: '''
|
||||
<html>
|
||||
<body>Hello World</body>
|
||||
</html>
|
||||
'''
|
||||
}
|
||||
`;
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
21
packages/bruno-lang/v2/tests/utils.spec.js
Normal file
21
packages/bruno-lang/v2/tests/utils.spec.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const { getValueString } = require('../src/utils');
|
||||
|
||||
describe('getValueString', () => {
|
||||
it('returns single line value as-is', () => {
|
||||
expect(getValueString('hello world')).toBe('hello world');
|
||||
});
|
||||
|
||||
it('wraps multiline value in triple quotes with indentation', () => {
|
||||
expect(getValueString('line1\nline2\nline3')).toBe("'''\n line1\n line2\n line3\n'''");
|
||||
});
|
||||
|
||||
it('normalizes different newline types', () => {
|
||||
expect(getValueString('line1\r\nline2\rline3\nline4')).toBe("'''\n line1\n line2\n line3\n line4\n'''");
|
||||
});
|
||||
|
||||
it('returns empty string for empty/null/undefined', () => {
|
||||
expect(getValueString('')).toBe('');
|
||||
expect(getValueString(null)).toBe('');
|
||||
expect(getValueString(undefined)).toBe('');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user