-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix(app): make multi-file environment import resilient and idempotent #8374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
4df0cba
ef9bccb
3d81bff
79ce252
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,17 +2,18 @@ import React, { useState } from 'react'; | |
| import Portal from 'components/Portal'; | ||
| import Modal from 'components/Modal'; | ||
| import toast from 'react-hot-toast'; | ||
| import { useDispatch } from 'react-redux'; | ||
| import { useDispatch, useSelector } from 'react-redux'; | ||
| import importPostmanEnvironment from 'utils/importers/postman-environment'; | ||
| import importBrunoEnvironment from 'utils/importers/bruno-environment'; | ||
| import { readMultipleFiles } from 'utils/importers/file-reader'; | ||
| import { importEnvironment } from 'providers/ReduxStore/slices/collections/actions'; | ||
| import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments'; | ||
| import { toastError } from 'utils/common/error'; | ||
| import { sanitizeName } from 'utils/common/regex'; | ||
| import { IconFileImport } from '@tabler/icons'; | ||
|
|
||
| const ImportEnvironmentModal = ({ type = 'collection', collection, onClose, onEnvironmentCreated }) => { | ||
| const dispatch = useDispatch(); | ||
| const globalEnvironments = useSelector((state) => state.globalEnvironments.globalEnvironments) || []; | ||
| const [isDragOver, setIsDragOver] = useState(false); | ||
|
|
||
| const isGlobal = type === 'global'; | ||
|
|
@@ -26,39 +27,68 @@ const ImportEnvironmentModal = ({ type = 'collection', collection, onClose, onEn | |
| const modalTestId = isGlobal ? 'import-global-environment-modal' : 'import-environment-modal'; | ||
| const importTestId = isGlobal ? 'import-global-environment' : 'import-environment'; | ||
|
|
||
| const processEnvironments = async (environments, successMessage) => { | ||
| const validEnvironments = environments.filter((env) => { | ||
| if (env.name && env.name !== 'undefined') { | ||
| return true; | ||
| } else { | ||
| toast.error('Failed to import environment: env has no name'); | ||
| return false; | ||
| } | ||
| }); | ||
| const processEnvironments = async (environments, parseFailures = []) => { | ||
| const failures = [...parseFailures]; | ||
|
|
||
| if (validEnvironments.length === 0) { | ||
| toast.error('No valid environments found to import'); | ||
| return; | ||
| const named = []; | ||
| let unnamed = 0; | ||
| for (const env of environments) { | ||
| if (env.name && env.name !== 'undefined') named.push(env); | ||
| else unnamed++; | ||
| } | ||
|
|
||
| try { | ||
| // Process environments sequentially to ensure unique name checking considers previously imported environments | ||
| let importedCount = 0; | ||
| for (const environment of validEnvironments) { | ||
| const existing = isGlobal ? globalEnvironments : collection?.environments || []; | ||
| const seen = new Set(existing.map((e) => sanitizeName(e.name || ''))); | ||
|
|
||
| const toImport = []; | ||
| let skipped = 0; | ||
| for (const env of named) { | ||
| const name = sanitizeName(env.name); | ||
| if (seen.has(name)) { | ||
| skipped++; | ||
| continue; | ||
| } | ||
| seen.add(name); | ||
| toImport.push({ ...env, name }); | ||
| } | ||
|
|
||
| let imported = 0; | ||
| for (const environment of toImport) { | ||
| try { | ||
| const action = isGlobal | ||
| ? addGlobalEnvironment({ name: environment.name, variables: environment.variables, color: environment.color }) | ||
| : importEnvironment({ name: environment.name, variables: environment.variables, color: environment.color, collectionUid: collection?.uid }); | ||
|
|
||
| await dispatch(action); | ||
| importedCount++; | ||
| imported++; | ||
| } catch (error) { | ||
| console.error(`Failed to import environment "${environment.name}":`, error); | ||
| failures.push({ name: environment.name, message: error?.message || String(error) }); | ||
| } | ||
| } | ||
|
|
||
| if (imported > 0) { | ||
| toast.success(`Imported ${imported} environment${imported > 1 ? 's' : ''}.`); | ||
| } | ||
|
|
||
| toast.success(`${importedCount > 1 ? `${importedCount} environments` : 'Environment'} imported successfully`); | ||
| } catch (error) { | ||
| toast.error('An error occurred while importing the environment(s)'); | ||
| console.error(error); | ||
| throw error; | ||
| const notes = []; | ||
| if (skipped > 0) notes.push(`${skipped} already existed and ${skipped > 1 ? 'were' : 'was'} skipped`); | ||
| if (unnamed > 0) notes.push(`${unnamed} had no name`); | ||
| if (failures.length > 0) { | ||
| const names = failures.map((f) => f.name).slice(0, 3).join(', '); | ||
| const more = failures.length > 3 ? ` and ${failures.length - 3} more` : ''; | ||
| notes.push(`${failures.length} failed (${names}${more})`); | ||
| } | ||
|
|
||
| if (notes.length > 0) { | ||
| const message = notes.join('; ') + '.'; | ||
| if (failures.length > 0) toast.error(message); | ||
| else toast(message); | ||
| } else if (imported === 0) { | ||
| toast.error('No valid environments found to import.'); | ||
| } | ||
|
|
||
| return { imported, skipped, unnamed, failures }; | ||
|
Comment on lines
+30
to
+92
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Look for tests covering the Import Environment modal and new batch outcome messages.
# Expected: specs or tests asserting mixed-format import, duplicate skips, malformed-file tolerance, and summary feedback.
rg -n --glob '*.{js,jsx,ts,tsx}' \
'ImportEnvironmentModal|import-environment|import-global-environment|already existed|No valid environments|failed \(|had no name|mixed.*Postman|mixed.*Bruno' \
packages/bruno-app testsRepository: usebruno/bruno Length of output: 7650 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the modal implementation.
sed -n '1,240p' packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js
printf '\n--- TEST FILES ---\n'
# List relevant import-environment tests.
find tests/environments/import-environment -type f \( -name '*.spec.ts' -o -name '*.spec.js' -o -name '*.spec.jsx' -o -name '*.spec.tsx' \) | sort
printf '\n--- COLLECTION BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts
printf '\n--- GLOBAL BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts
printf '\n--- COLLECTION POSTMAN/ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/collection-env-import.spec.ts
printf '\n--- GLOBAL ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/global-env-import.spec.ts
printf '\n--- COLOR IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/env-color-import/env-color-import.spec.tsRepository: usebruno/bruno Length of output: 34003 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the modal implementation.
sed -n '1,240p' packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js
printf '\n--- TEST FILES ---\n'
# List relevant import-environment tests.
find tests/environments/import-environment -type f \( -name '*.spec.ts' -o -name '*.spec.js' -o -name '*.spec.jsx' -o -name '*.spec.tsx' \) | sort
printf '\n--- COLLECTION BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts
printf '\n--- GLOBAL BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts
printf '\n--- COLLECTION POSTMAN/ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/collection-env-import.spec.ts
printf '\n--- GLOBAL ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/global-env-import.spec.ts
printf '\n--- COLOR IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/env-color-import/env-color-import.spec.tsRepository: usebruno/bruno Length of output: 34003 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the modal implementation.
sed -n '1,240p' packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js
printf '\n--- TEST FILES ---\n'
# List relevant import-environment tests.
find tests/environments/import-environment -type f \( -name '*.spec.ts' -o -name '*.spec.js' -o -name '*.spec.jsx' -o -name '*.spec.tsx' \) | sort
printf '\n--- COLLECTION BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts
printf '\n--- GLOBAL BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts
printf '\n--- COLLECTION POSTMAN/ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/collection-env-import.spec.ts
printf '\n--- GLOBAL ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/global-env-import.spec.ts
printf '\n--- COLOR IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/env-color-import/env-color-import.spec.tsRepository: usebruno/bruno Length of output: 34003 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1,240p' packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js
printf '\n--- TEST FILES ---\n'
find tests/environments/import-environment -type f \( -name '*.spec.ts' -o -name '*.spec.js' -o -name '*.spec.jsx' -o -name '*.spec.tsx' \) | sort
printf '\n--- COLLECTION BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts
printf '\n--- GLOBAL BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts
printf '\n--- COLLECTION POSTMAN/ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/collection-env-import.spec.ts
printf '\n--- GLOBAL ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/global-env-import.spec.ts
printf '\n--- COLOR IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/env-color-import/env-color-import.spec.tsRepository: usebruno/bruno Length of output: 34003 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1,240p' packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.js
printf '\n--- TEST FILES ---\n'
find tests/environments/import-environment -type f \( -name '*.spec.ts' -o -name '*.spec.js' -o -name '*.spec.jsx' -o -name '*.spec.tsx' \) | sort
printf '\n--- COLLECTION BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/collection-env-import/collection-env-import.spec.ts
printf '\n--- GLOBAL BRUNO IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/bruno-env-import/global-env-import/global-env-import.spec.ts
printf '\n--- COLLECTION POSTMAN/ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/collection-env-import.spec.ts
printf '\n--- GLOBAL ENV IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/global-env-import.spec.ts
printf '\n--- COLOR IMPORT ---\n'
sed -n '1,220p' tests/environments/import-environment/env-color-import/env-color-import.spec.tsRepository: usebruno/bruno Length of output: 34003 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Read only the relevant test files and search for the new batch outcome assertions.
rg -n -A 4 -B 4 \
'already existed|had no name|No valid environments found to import|failed \(|toast\.success|toast\.error|ImportEnvironmentModal|import-environment' \
tests/environments/import-environment packages/bruno-app/src/components/Environments/Common/ImportEnvironmentModal/index.jsRepository: usebruno/bruno Length of output: 18189 Add batch outcome coverage for environment imports Add tests for the new summary states in 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| }; | ||
|
|
||
| const detectEnvironmentFormat = (data) => { | ||
|
|
@@ -76,27 +106,40 @@ const ImportEnvironmentModal = ({ type = 'collection', collection, onClose, onEn | |
| }; | ||
|
|
||
| const handleImportEnvironment = async (files) => { | ||
| try { | ||
| // Read and parse all files | ||
| const parsedFiles = await readMultipleFiles(Array.from(files)); | ||
|
|
||
| // Detect format from first file's content | ||
| const format = detectEnvironmentFormat(parsedFiles[0].content); | ||
| let environments; | ||
|
|
||
| if (format === 'postman') { | ||
| environments = await importPostmanEnvironment(parsedFiles); | ||
| } else { | ||
| environments = await importBrunoEnvironment(parsedFiles); | ||
| const environments = []; | ||
| const parseFailures = []; | ||
| for (const file of Array.from(files)) { | ||
| let parsedFile; | ||
| try { | ||
| [parsedFile] = await readMultipleFiles([file]); | ||
| } catch (err) { | ||
| console.error(`Failed to read ${file.name}:`, err); | ||
| parseFailures.push({ name: file.name, message: err?.message || String(err) }); | ||
| continue; | ||
| } | ||
|
|
||
| await processEnvironments(environments); | ||
| onClose(); | ||
| if (onEnvironmentCreated) { | ||
| onEnvironmentCreated(); | ||
| try { | ||
| const format = detectEnvironmentFormat(parsedFile.content); | ||
| const result | ||
| = format === 'postman' | ||
| ? await importPostmanEnvironment([parsedFile]) | ||
| : await importBrunoEnvironment([parsedFile]); | ||
| environments.push(...result); | ||
| } catch (err) { | ||
| console.error(`Failed to parse ${parsedFile.fileName}:`, err); | ||
| parseFailures.push({ name: parsedFile.fileName, message: err?.message || String(err) }); | ||
| } | ||
| } catch (err) { | ||
| toastError(err, 'Import environment failed'); | ||
| } | ||
|
|
||
| const summary = await processEnvironments(environments, parseFailures); | ||
|
|
||
| if (summary.imported === 0 && summary.skipped === 0) { | ||
| return; | ||
| } | ||
|
|
||
| onClose(); | ||
| if (summary.imported > 0 && onEnvironmentCreated) { | ||
| onEnvironmentCreated(); | ||
| } | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| import '@testing-library/jest-dom'; | ||
| import React from 'react'; | ||
| import { render, screen, waitFor, fireEvent, createEvent } from '@testing-library/react'; | ||
|
|
||
| jest.mock('components/Portal', () => ({ __esModule: true, default: ({ children }) => children })); | ||
| jest.mock('components/Modal', () => ({ __esModule: true, default: ({ children }) => children })); | ||
| jest.mock('@tabler/icons', () => ({ __esModule: true, IconFileImport: () => null })); | ||
|
|
||
| jest.mock('react-hot-toast', () => { | ||
| const fn = jest.fn(); | ||
| fn.success = jest.fn(); | ||
| fn.error = jest.fn(); | ||
| return { __esModule: true, default: fn }; | ||
| }); | ||
|
|
||
| let mockDispatch; | ||
| let mockSelectorState; | ||
| jest.mock('react-redux', () => ({ | ||
| __esModule: true, | ||
| useDispatch: () => mockDispatch, | ||
| useSelector: (selector) => selector(mockSelectorState) | ||
| })); | ||
|
|
||
| jest.mock('providers/ReduxStore/slices/collections/actions', () => ({ | ||
| __esModule: true, | ||
| importEnvironment: jest.fn((payload) => ({ thunk: 'importEnvironment', ...payload })) | ||
| })); | ||
| jest.mock('providers/ReduxStore/slices/global-environments', () => ({ | ||
| __esModule: true, | ||
| addGlobalEnvironment: jest.fn((payload) => ({ thunk: 'addGlobalEnvironment', ...payload })) | ||
| })); | ||
|
|
||
| jest.mock('utils/importers/postman-environment', () => ({ __esModule: true, default: jest.fn() })); | ||
| jest.mock('utils/importers/bruno-environment', () => ({ __esModule: true, default: jest.fn() })); | ||
| jest.mock('utils/importers/file-reader', () => ({ __esModule: true, readMultipleFiles: jest.fn() })); | ||
|
|
||
| import toast from 'react-hot-toast'; | ||
| import { importEnvironment } from 'providers/ReduxStore/slices/collections/actions'; | ||
| import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments'; | ||
| import importPostmanEnvironment from 'utils/importers/postman-environment'; | ||
| import importBrunoEnvironment from 'utils/importers/bruno-environment'; | ||
| import { readMultipleFiles } from 'utils/importers/file-reader'; | ||
| import ImportEnvironmentModal from './index'; | ||
|
|
||
| const brunoContent = (envs) => ({ info: { type: 'bruno-environment' }, envs }); | ||
| const postmanContent = (envs) => ({ id: 'pm-id', values: [], envs }); | ||
|
|
||
| const makeFile = (name) => new File(['{}'], name, { type: 'application/json' }); | ||
| const collectionWith = (environments) => ({ uid: 'col-1', name: 'Col', environments }); | ||
|
|
||
| // map: { 'file.json': { content } | { error } } | ||
| const setupFiles = (map) => { | ||
| readMultipleFiles.mockImplementation(async (files) => { | ||
| const file = files[0]; | ||
| const entry = map[file.name]; | ||
| if (!entry || entry.error) { | ||
| throw new Error(entry?.error || `Unable to parse JSON file: ${file.name}`); | ||
| } | ||
| return [{ fileName: file.name, content: entry.content }]; | ||
| }); | ||
| }; | ||
|
|
||
| const dropFiles = (testId, files) => { | ||
| const node = screen.getByTestId(testId); | ||
| const event = createEvent.drop(node); | ||
| Object.defineProperty(event, 'dataTransfer', { value: { files } }); | ||
| fireEvent(node, event); | ||
| }; | ||
|
|
||
| const renderModal = (props = {}) => | ||
| render(<ImportEnvironmentModal type="collection" onClose={() => {}} onEnvironmentCreated={() => {}} {...props} />); | ||
|
|
||
| describe('ImportEnvironmentModal — batch import summary', () => { | ||
| beforeEach(() => { | ||
| mockDispatch = jest.fn(() => Promise.resolve()); | ||
| mockSelectorState = { globalEnvironments: { globalEnvironments: [] } }; | ||
|
|
||
| const passthrough = async ([parsedFile]) => { | ||
| if (parsedFile.content.importError) throw new Error(parsedFile.content.importError); | ||
| return parsedFile.content.envs || []; | ||
| }; | ||
| importBrunoEnvironment.mockImplementation(passthrough); | ||
| importPostmanEnvironment.mockImplementation(passthrough); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| }); | ||
|
|
||
| it('imports a mix of Bruno and Postman files and reports the combined count', async () => { | ||
| setupFiles({ | ||
| 'bruno.json': { content: brunoContent([{ name: 'B1', variables: [] }]) }, | ||
| 'postman.json': { content: postmanContent([{ name: 'P1', variables: [] }]) } | ||
| }); | ||
| const onClose = jest.fn(); | ||
| const onEnvironmentCreated = jest.fn(); | ||
| renderModal({ collection: collectionWith([]), onClose, onEnvironmentCreated }); | ||
|
|
||
| dropFiles('import-environment', [makeFile('bruno.json'), makeFile('postman.json')]); | ||
|
|
||
| await waitFor(() => expect(toast.success).toHaveBeenCalledWith('Imported 2 environments.')); | ||
| expect(importBrunoEnvironment).toHaveBeenCalledTimes(1); | ||
| expect(importPostmanEnvironment).toHaveBeenCalledTimes(1); | ||
| expect(mockDispatch).toHaveBeenCalledTimes(2); | ||
| await waitFor(() => expect(onClose).toHaveBeenCalled()); | ||
| expect(onEnvironmentCreated).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('imports valid files even when one file is malformed, and reports the failure', async () => { | ||
| setupFiles({ | ||
| 'good1.json': { content: brunoContent([{ name: 'Good1', variables: [] }]) }, | ||
| 'bad.json': { error: 'Unable to parse JSON file: bad.json' }, | ||
| 'good2.json': { content: postmanContent([{ name: 'Good2', variables: [] }]) } | ||
| }); | ||
| renderModal({ collection: collectionWith([]) }); | ||
|
|
||
| dropFiles('import-environment', [makeFile('good1.json'), makeFile('bad.json'), makeFile('good2.json')]); | ||
|
|
||
| await waitFor(() => expect(toast.success).toHaveBeenCalledWith('Imported 2 environments.')); | ||
| expect(mockDispatch).toHaveBeenCalledTimes(2); | ||
| await waitFor(() => expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('1 failed (bad.json)'))); | ||
| }); | ||
|
|
||
| it('skips environments whose name already exists', async () => { | ||
| setupFiles({ 'dup.json': { content: brunoContent([{ name: 'Existing', variables: [] }]) } }); | ||
| const onClose = jest.fn(); | ||
| const onEnvironmentCreated = jest.fn(); | ||
| renderModal({ collection: collectionWith([{ name: 'Existing' }]), onClose, onEnvironmentCreated }); | ||
|
|
||
| dropFiles('import-environment', [makeFile('dup.json')]); | ||
|
|
||
| await waitFor(() => expect(toast).toHaveBeenCalledWith('1 already existed and was skipped.')); | ||
| expect(mockDispatch).not.toHaveBeenCalled(); | ||
| expect(toast.success).not.toHaveBeenCalled(); | ||
| await waitFor(() => expect(onClose).toHaveBeenCalled()); | ||
| expect(onEnvironmentCreated).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('counts unnamed environments while still importing the named ones', async () => { | ||
| setupFiles({ | ||
| 'mixed.json': { content: brunoContent([{ name: 'Named', variables: [] }, { variables: [] }]) } | ||
| }); | ||
| renderModal({ collection: collectionWith([]) }); | ||
|
|
||
| dropFiles('import-environment', [makeFile('mixed.json')]); | ||
|
|
||
| await waitFor(() => expect(toast.success).toHaveBeenCalledWith('Imported 1 environment.')); | ||
| await waitFor(() => expect(toast).toHaveBeenCalledWith('1 had no name.')); | ||
| expect(mockDispatch).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('reports a failure when an environment fails to dispatch but imports the rest', async () => { | ||
| setupFiles({ | ||
| 'two.json': { content: brunoContent([{ name: 'Ok', variables: [] }, { name: 'Will-Fail', variables: [] }]) } | ||
| }); | ||
| mockDispatch.mockImplementation((action) => { | ||
| if (action.name === 'Will-Fail') return Promise.reject(new Error('disk full')); | ||
| return Promise.resolve(); | ||
| }); | ||
| renderModal({ collection: collectionWith([]) }); | ||
|
|
||
| dropFiles('import-environment', [makeFile('two.json')]); | ||
|
|
||
| await waitFor(() => expect(toast.success).toHaveBeenCalledWith('Imported 1 environment.')); | ||
| await waitFor(() => expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('1 failed (Will-Fail)'))); | ||
| expect(mockDispatch).toHaveBeenCalledTimes(2); | ||
| }); | ||
|
|
||
| it('reports when nothing valid was found to import', async () => { | ||
| setupFiles({ 'empty.json': { content: brunoContent([]) } }); | ||
| const onClose = jest.fn(); | ||
| renderModal({ collection: collectionWith([]), onClose }); | ||
|
|
||
| dropFiles('import-environment', [makeFile('empty.json')]); | ||
|
|
||
| await waitFor(() => expect(toast.error).toHaveBeenCalledWith('No valid environments found to import.')); | ||
| expect(mockDispatch).not.toHaveBeenCalled(); | ||
| expect(onClose).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| describe('name normalization', () => { | ||
| it('passes a sanitized name to importEnvironment for collection imports', async () => { | ||
| setupFiles({ | ||
| 'env.json': { content: brunoContent([{ name: 'Prod/Env', variables: [{ name: 'k' }], color: 'red' }]) } | ||
| }); | ||
| renderModal({ type: 'collection', collection: collectionWith([]) }); | ||
|
|
||
| dropFiles('import-environment', [makeFile('env.json')]); | ||
|
|
||
| await waitFor(() => expect(importEnvironment).toHaveBeenCalledTimes(1)); | ||
| expect(importEnvironment).toHaveBeenCalledWith( | ||
| expect.objectContaining({ name: 'Prod-Env', collectionUid: 'col-1' }) | ||
| ); | ||
| }); | ||
|
|
||
| it('passes a sanitized name to addGlobalEnvironment for global imports', async () => { | ||
| setupFiles({ | ||
| 'env.json': { content: brunoContent([{ name: 'Prod/Env', variables: [{ name: 'k' }], color: 'red' }]) } | ||
| }); | ||
| mockSelectorState = { globalEnvironments: { globalEnvironments: [] } }; | ||
| renderModal({ type: 'global', collection: undefined }); | ||
|
|
||
| dropFiles('import-global-environment', [makeFile('env.json')]); | ||
|
|
||
| await waitFor(() => expect(addGlobalEnvironment).toHaveBeenCalledTimes(1)); | ||
| expect(addGlobalEnvironment).toHaveBeenCalledWith(expect.objectContaining({ name: 'Prod-Env' })); | ||
| }); | ||
| }); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.