mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
fix: resolve process.env variables in global environment level (#7600)
* feat: enhance environment variable resolution in EnvironmentVariablesTable
- Added logic to populate process environment variables from the active workspace when no collection is selected, allowing for proper resolution of {{process.env.X}}.
- Updated workspace actions to map scratch collections to their respective workspaces, ensuring that environment variables can be resolved correctly.
This improves the user experience by providing access to workspace-specific environment variables in the environment variables table.
* refactor: improve state selection and add test IDs for better testing
- Refactored the state selection logic in EnvironmentVariablesTable for clarity.
- Added data-testid attributes to various components including CollapsibleSection, DotEnvFileDetails, DotEnvRawView, and EnvironmentList to enhance testability.
- This change aims to streamline component interactions and facilitate easier testing.
* feat: add test IDs to EnvironmentList for improved testability
- Introduced data-testid attributes to the EnvironmentList component, enhancing the ability to target elements in tests.
- This update includes test IDs for the CollapsibleSection, create .env file button, and the input field for the .env name, facilitating better integration with testing frameworks.
* refactor: simplify global environment test setup
- Removed unnecessary timeout setting and afterEach cleanup logic from the global environment process.env resolution test.
- This change streamlines the test structure, focusing on the core functionality being tested.
This commit is contained in:
@@ -45,6 +45,10 @@ const EnvironmentVariablesTable = ({
|
||||
}) => {
|
||||
const { storedTheme } = useTheme();
|
||||
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
|
||||
const activeWorkspace = useSelector((state) => {
|
||||
const uid = state.workspaces?.activeWorkspaceUid;
|
||||
return state.workspaces?.workspaces?.find((w) => w.uid === uid);
|
||||
});
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
@@ -138,6 +142,12 @@ const EnvironmentVariablesTable = ({
|
||||
_collection.globalEnvironmentVariables = globalEnvironmentVariables;
|
||||
}
|
||||
|
||||
// When collection is null (global/workspace environments), populate process env
|
||||
// variables from the active workspace so that {{process.env.X}} can resolve
|
||||
if (!collection && activeWorkspace?.processEnvVariables) {
|
||||
_collection.workspaceProcessEnvVariables = activeWorkspace.processEnvVariables;
|
||||
}
|
||||
|
||||
const initialValues = useMemo(() => {
|
||||
const vars = environment.variables || [];
|
||||
return [
|
||||
|
||||
@@ -8,11 +8,12 @@ const CollapsibleSection = ({
|
||||
onToggle,
|
||||
badge,
|
||||
actions,
|
||||
children
|
||||
children,
|
||||
testId
|
||||
}) => {
|
||||
return (
|
||||
<StyledWrapper className={expanded ? 'expanded' : 'collapsed'}>
|
||||
<div className="section-header" onClick={onToggle}>
|
||||
<div className="section-header" onClick={onToggle} data-testid={testId}>
|
||||
<div className="section-title-wrapper">
|
||||
<IconChevronRight
|
||||
size={14}
|
||||
|
||||
@@ -44,6 +44,7 @@ const DotEnvFileDetails = ({
|
||||
className={`toggle-btn ${viewMode === 'raw' ? 'active' : ''}`}
|
||||
onClick={() => onViewModeChange?.('raw')}
|
||||
aria-pressed={viewMode === 'raw'}
|
||||
data-testid="dotenv-view-raw"
|
||||
>
|
||||
Raw
|
||||
</button>
|
||||
|
||||
@@ -13,7 +13,7 @@ const DotEnvRawView = ({
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="raw-editor-container">
|
||||
<div className="raw-editor-container" data-testid="dotenv-raw-editor">
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
item={item}
|
||||
|
||||
@@ -46,7 +46,7 @@ const EnvironmentListContent = ({
|
||||
</div>
|
||||
</ToolHint>
|
||||
<div className="dropdown-item configure-button">
|
||||
<button onClick={onSettingsClick} id="configure-env">
|
||||
<button onClick={onSettingsClick} id="configure-env" data-testid="configure-env">
|
||||
<IconSettings size={16} strokeWidth={1.5} />
|
||||
<span>Configure</span>
|
||||
</button>
|
||||
|
||||
@@ -736,6 +736,7 @@ const EnvironmentList = ({
|
||||
|
||||
<CollapsibleSection
|
||||
title=".env Files"
|
||||
testId="dotenv-files-section"
|
||||
expanded={dotEnvExpanded}
|
||||
onToggle={() => setDotEnvExpanded(!dotEnvExpanded)}
|
||||
badge={dotEnvFiles.length}
|
||||
@@ -744,6 +745,7 @@ const EnvironmentList = ({
|
||||
className="btn-action"
|
||||
onClick={handleCreateDotEnvInlineClick}
|
||||
title="Create .env file"
|
||||
data-testid="create-dotenv-file"
|
||||
>
|
||||
<IconPlus size={14} strokeWidth={1.5} />
|
||||
</button>
|
||||
@@ -768,6 +770,7 @@ const EnvironmentList = ({
|
||||
ref={dotEnvInputRef}
|
||||
type="text"
|
||||
className="environment-name-input"
|
||||
data-testid="dotenv-name-input"
|
||||
value={newDotEnvName}
|
||||
onChange={handleDotEnvNameChange}
|
||||
onKeyDown={handleDotEnvNameKeyDown}
|
||||
|
||||
@@ -731,6 +731,7 @@ const EnvironmentList = ({
|
||||
|
||||
<CollapsibleSection
|
||||
title=".env Files"
|
||||
testId="dotenv-files-section"
|
||||
expanded={dotEnvExpanded}
|
||||
onToggle={() => setDotEnvExpanded(!dotEnvExpanded)}
|
||||
badge={dotEnvFiles.length}
|
||||
@@ -739,6 +740,7 @@ const EnvironmentList = ({
|
||||
className="btn-action"
|
||||
onClick={handleCreateDotEnvInlineClick}
|
||||
title="Create .env file"
|
||||
data-testid="create-dotenv-file"
|
||||
>
|
||||
<IconPlus size={14} strokeWidth={1.5} />
|
||||
</button>
|
||||
@@ -763,6 +765,7 @@ const EnvironmentList = ({
|
||||
ref={dotEnvInputRef}
|
||||
type="text"
|
||||
className="environment-name-input"
|
||||
data-testid="dotenv-name-input"
|
||||
value={newDotEnvName}
|
||||
onChange={handleDotEnvNameChange}
|
||||
onKeyDown={handleDotEnvNameKeyDown}
|
||||
|
||||
@@ -1001,6 +1001,11 @@ export const mountScratchCollection = (workspaceUid) => {
|
||||
brunoConfig
|
||||
});
|
||||
|
||||
// Map scratch collection to workspace so getProcessEnvVars can resolve workspace .env values
|
||||
if (workspace.pathname) {
|
||||
await ipcRenderer.invoke('renderer:set-collection-workspace', scratchCollectionUid, workspace.pathname);
|
||||
}
|
||||
|
||||
await dispatch(openScratchCollectionEvent(scratchCollectionUid, tempDirectoryPath, brunoConfig));
|
||||
|
||||
dispatch(setWorkspaceScratchCollection({
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "process-env-global-test",
|
||||
"type": "collection"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
meta {
|
||||
name: echo-post
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://echo.usebruno.com
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"value": "{{testVar}}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import {
|
||||
openCollection,
|
||||
openEnvironmentSelector,
|
||||
openRequest,
|
||||
sendRequest,
|
||||
expectResponseContains
|
||||
} from '../../utils/page';
|
||||
|
||||
test.describe('Global Environment process.env Resolution', () => {
|
||||
test('should resolve process.env variables referenced in global environment', async ({
|
||||
pageWithUserData: page
|
||||
}) => {
|
||||
await test.step('Open collection', async () => {
|
||||
await openCollection(page, 'process-env-global-test');
|
||||
});
|
||||
|
||||
await test.step('Create .env file with variable via UI', async () => {
|
||||
// Open global environment configuration
|
||||
await openEnvironmentSelector(page, 'global');
|
||||
await page.getByTestId('configure-env').click();
|
||||
|
||||
// Expand the .env Files section
|
||||
const dotEnvSection = page.getByTestId('dotenv-files-section');
|
||||
await dotEnvSection.waitFor({ state: 'visible' });
|
||||
await dotEnvSection.click();
|
||||
|
||||
// Click + to create a new .env file
|
||||
await page.getByTestId('create-dotenv-file').click();
|
||||
|
||||
// Accept the default name (.env) and press Enter
|
||||
await page.getByTestId('dotenv-name-input').press('Enter');
|
||||
await expect(page.getByText('.env file created!')).toBeVisible();
|
||||
|
||||
// Switch to Raw mode to type the variable
|
||||
await page.getByTestId('dotenv-view-raw').click();
|
||||
|
||||
// Type the variable into the raw editor
|
||||
const rawEditor = page.getByTestId('dotenv-raw-editor').locator('.CodeMirror');
|
||||
await rawEditor.click();
|
||||
await page.keyboard.type('MY_SECRET=hello-from-dotenv');
|
||||
|
||||
// Save the .env file
|
||||
await page.getByTestId('save-dotenv-raw').click();
|
||||
});
|
||||
|
||||
await test.step('Verify global environment is active', async () => {
|
||||
await expect(page.locator('.current-environment')).toContainText('ProcessEnv Test');
|
||||
});
|
||||
|
||||
await test.step('Open request', async () => {
|
||||
await openRequest(page, 'process-env-global-test', 'echo-post');
|
||||
});
|
||||
|
||||
await test.step('Send request and verify process.env resolved', async () => {
|
||||
await sendRequest(page, 200);
|
||||
});
|
||||
|
||||
await test.step('Verify response contains resolved value from .env file', async () => {
|
||||
await expectResponseContains(page, ['hello-from-dotenv']);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"collections": [
|
||||
{
|
||||
"path": "{{collectionPath}}",
|
||||
"securityConfig": {
|
||||
"jsSandboxMode": "safe"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"environments": [
|
||||
{
|
||||
"uid": "pEnvTestGlobalEnvUi01",
|
||||
"name": "ProcessEnv Test",
|
||||
"variables": [
|
||||
{
|
||||
"uid": "pEnvTestVarUid0000001",
|
||||
"name": "testVar",
|
||||
"value": "{{process.env.MY_SECRET}}",
|
||||
"type": "text",
|
||||
"secret": false,
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"activeGlobalEnvironmentUid": "pEnvTestGlobalEnvUi01"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"maximized": false,
|
||||
"lastOpenedCollections": [
|
||||
"{{collectionPath}}"
|
||||
],
|
||||
"preferences": {
|
||||
"onboarding": {
|
||||
"hasLaunchedBefore": true,
|
||||
"hasSeenWelcomeModal": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user