mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
Import WSDL to collection (#5015)
* Import WSDL to bruno collection * feat(wsdl-import): remove unused code and minor refactor --------- Co-authored-by: Bijin Bruno <bijin@usebruno.com>
This commit is contained in:
@@ -7,14 +7,14 @@ const eslintPluginDiff = require('eslint-plugin-diff');
|
||||
let stylistic;
|
||||
|
||||
const runESMImports = async () => {
|
||||
stylistic = await import('@stylistic/eslint-plugin').then(d => d.default);
|
||||
stylistic = await import('@stylistic/eslint-plugin').then((d) => d.default);
|
||||
};
|
||||
|
||||
module.exports = runESMImports().then(() => defineConfig([
|
||||
{
|
||||
plugins: {
|
||||
'diff': fixupPluginRules(eslintPluginDiff),
|
||||
'@stylistic': stylistic,
|
||||
'@stylistic': stylistic
|
||||
},
|
||||
languageOptions: {
|
||||
parser: require('@typescript-eslint/parser'),
|
||||
@@ -45,7 +45,7 @@ module.exports = runESMImports().then(() => defineConfig([
|
||||
indent: 2,
|
||||
quotes: 'single',
|
||||
semi: true,
|
||||
jsx: true,
|
||||
jsx: true
|
||||
}).rules,
|
||||
'@stylistic/comma-dangle': ['error', 'never'],
|
||||
'@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }],
|
||||
@@ -53,7 +53,7 @@ module.exports = runESMImports().then(() => defineConfig([
|
||||
'@stylistic/curly-newline': ['error', {
|
||||
multiline: true,
|
||||
minElements: 2,
|
||||
consistent: true,
|
||||
consistent: true
|
||||
}],
|
||||
'@stylistic/function-paren-newline': ['error', 'never'],
|
||||
'@stylistic/array-bracket-spacing': ['error', 'never'],
|
||||
@@ -64,7 +64,7 @@ module.exports = runESMImports().then(() => defineConfig([
|
||||
'@stylistic/semi-style': ['error', 'last'],
|
||||
'@stylistic/max-len': ['off'],
|
||||
'@stylistic/jsx-one-expression-per-line': ['off']
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["packages/bruno-app/**/*.{js,jsx,ts}"],
|
||||
|
||||
3
package-lock.json
generated
3
package-lock.json
generated
@@ -30130,7 +30130,8 @@
|
||||
"js-yaml": "^4.1.0",
|
||||
"jscodeshift": "^17.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nanoid": "3.3.8"
|
||||
"nanoid": "3.3.8",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
||||
@@ -45,7 +45,7 @@ const CollectionSettings = ({ collection }) => {
|
||||
const authMode = get(collection, 'root.request.auth', {}).mode || 'none';
|
||||
|
||||
const presets = get(collection, 'brunoConfig.presets', []);
|
||||
const hasPresets = presets && presets.requestUrl !== "";
|
||||
const hasPresets = presets && presets.requestUrl !== '';
|
||||
|
||||
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
|
||||
const proxyEnabled = proxyConfig.hostname ? true : false;
|
||||
@@ -167,7 +167,7 @@ const CollectionSettings = ({ collection }) => {
|
||||
</div>
|
||||
<div className={getTabClassname('presets')} role="tab" onClick={() => setTab('presets')}>
|
||||
Presets
|
||||
{hasPresets && <StatusDot />}
|
||||
{hasPresets && <StatusDot />}
|
||||
</div>
|
||||
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
|
||||
Proxy
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
IconChevronDown,
|
||||
IconTerminal2,
|
||||
IconNetwork,
|
||||
IconDashboard,
|
||||
IconDashboard
|
||||
} from '@tabler/icons';
|
||||
import {
|
||||
closeConsole,
|
||||
|
||||
@@ -5,7 +5,7 @@ const StyledWrapper = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: ${props => props.theme.console.bg};
|
||||
background: ${(props) => props.theme.console.bg};
|
||||
}
|
||||
|
||||
.tab-content-area {
|
||||
@@ -30,19 +30,19 @@ const StyledWrapper = styled.div`
|
||||
.section-header {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid ${props => props.theme.console.border};
|
||||
border-bottom: 1px solid ${(props) => props.theme.console.border};
|
||||
|
||||
h3 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: ${props => props.theme.console.titleColor};
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: ${props => props.theme.console.textMuted};
|
||||
color: ${(props) => props.theme.console.textMuted};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ const StyledWrapper = styled.div`
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: ${props => props.theme.console.titleColor};
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
|
||||
.resource-card {
|
||||
background: ${props => props.theme.console.headerBg};
|
||||
border: 1px solid ${props => props.theme.console.border};
|
||||
background: ${(props) => props.theme.console.headerBg};
|
||||
border: 1px solid ${(props) => props.theme.console.border};
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
@@ -76,7 +76,7 @@ const StyledWrapper = styled.div`
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
color: ${props => props.theme.console.titleColor};
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
}
|
||||
|
||||
.resource-title {
|
||||
@@ -87,13 +87,13 @@ const StyledWrapper = styled.div`
|
||||
.resource-value {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: ${props => props.theme.console.titleColor};
|
||||
color: ${(props) => props.theme.console.titleColor};
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.resource-subtitle {
|
||||
font-size: 11px;
|
||||
color: ${props => props.theme.console.buttonColor};
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
}
|
||||
|
||||
.resource-trend {
|
||||
@@ -112,7 +112,7 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
|
||||
&.stable {
|
||||
color: ${props => props.theme.console.buttonColor};
|
||||
color: ${(props) => props.theme.console.buttonColor};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
IconDatabase,
|
||||
IconClock,
|
||||
IconServer,
|
||||
IconChartLine,
|
||||
IconChartLine
|
||||
} from '@tabler/icons';
|
||||
|
||||
const Performance = () => {
|
||||
const { systemResources } = useSelector(state => state.performance);
|
||||
const { systemResources } = useSelector((state) => state.performance);
|
||||
|
||||
const formatBytes = bytes => {
|
||||
const formatBytes = (bytes) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
@@ -20,7 +20,7 @@ const Performance = () => {
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
const formatUptime = seconds => {
|
||||
const formatUptime = (seconds) => {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
|
||||
@@ -17,7 +17,7 @@ import NTLMAuth from 'components/RequestPane/Auth/NTLMAuth';
|
||||
import WsseAuth from 'components/RequestPane/Auth/WsseAuth';
|
||||
import ApiKeyAuth from 'components/RequestPane/Auth/ApiKeyAuth';
|
||||
import AwsV4Auth from 'components/RequestPane/Auth/AwsV4Auth';
|
||||
import { humanizeRequestAuthMode, getTreePathFromCollectionToItem } from 'utils/collections/index';
|
||||
import { humanizeRequestAuthMode, getTreePathFromCollectionToItem } from 'utils/collections/index';
|
||||
|
||||
const GrantTypeComponentMap = ({ collection, folder }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -48,8 +48,6 @@ const Auth = ({ collection, folder }) => {
|
||||
let request = get(folder, 'root.request', {});
|
||||
const authMode = get(folder, 'root.request.auth.mode');
|
||||
|
||||
|
||||
|
||||
const getEffectiveAuthSource = () => {
|
||||
if (authMode !== 'inherit') return null;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const addButtonRef = useRef(null);
|
||||
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector(state => state.globalEnvironments);
|
||||
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
|
||||
|
||||
let _collection = cloneDeep(collection);
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ const GlobalSearchModal = ({ isOpen, onClose }) => {
|
||||
|
||||
if (isItemARequest(item)) {
|
||||
// add an optional check for the item name to prevent a crash if it doesn’t exist.
|
||||
const nameMatch = searchTerms.every(term => (item.name || '').toLowerCase().includes(term));
|
||||
const nameMatch = searchTerms.every((term) => (item.name || '').toLowerCase().includes(term));
|
||||
const urlMatch = searchTerms.every(term => (item.request?.url || '').toLowerCase().includes(term));
|
||||
const pathMatch = enablePathMatch && searchTerms.every(term => itemPathLower.includes(term));
|
||||
|
||||
|
||||
@@ -251,7 +251,6 @@ function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, col
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
|
||||
function handleRevertChanges(event) {
|
||||
event.stopPropagation();
|
||||
dropdownTippyRef.current.hide();
|
||||
@@ -263,12 +262,10 @@ function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, col
|
||||
try {
|
||||
const item = findItemInCollection(collection, currentTabUid);
|
||||
if (item.draft) {
|
||||
dispatch(
|
||||
deleteRequestDraft({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
dispatch(deleteRequestDraft({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
@@ -340,7 +337,7 @@ function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, col
|
||||
>
|
||||
Clone Request
|
||||
</button>
|
||||
<button
|
||||
<button
|
||||
className="dropdown-item w-full"
|
||||
onClick={handleRevertChanges}
|
||||
disabled={!currentTabItem?.draft}
|
||||
|
||||
@@ -5,14 +5,21 @@ import Modal from 'components/Modal';
|
||||
import jsyaml from 'js-yaml';
|
||||
import { postmanToBruno, isPostmanCollection } from 'utils/importers/postman-collection';
|
||||
import { convertInsomniaToBruno, isInsomniaCollection } from 'utils/importers/insomnia-collection';
|
||||
import { isOpenApiSpec, convertOpenapiToBruno } from 'utils/importers/openapi-collection';
|
||||
import { convertOpenapiToBruno, isOpenApiSpec } from 'utils/importers/openapi-collection';
|
||||
import { isWSDLCollection } from 'utils/importers/wsdl-collection';
|
||||
import { processBrunoCollection } from 'utils/importers/bruno-collection';
|
||||
import { wsdlToBruno } from '@usebruno/converters';
|
||||
import ImportSettings from 'components/Sidebar/ImportSettings';
|
||||
import FullscreenLoader from './FullscreenLoader/index';
|
||||
|
||||
const convertFileToObject = async (file) => {
|
||||
const text = await file.text();
|
||||
|
||||
// Handle WSDL files - return as plain text
|
||||
if (file.name.endsWith('.wsdl') || file.type === 'text/xml' || file.type === 'application/xml') {
|
||||
return text;
|
||||
}
|
||||
|
||||
try {
|
||||
if (file.type === 'application/json' || file.name.endsWith('.json')) {
|
||||
return JSON.parse(text);
|
||||
@@ -79,8 +86,9 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
|
||||
}
|
||||
|
||||
let collection;
|
||||
|
||||
if (isPostmanCollection(data)) {
|
||||
if (isWSDLCollection(data)) {
|
||||
collection = await wsdlToBruno(data);
|
||||
} else if (isPostmanCollection(data)) {
|
||||
collection = await postmanToBruno(data);
|
||||
} else if (isInsomniaCollection(data)) {
|
||||
collection = convertInsomniaToBruno(data);
|
||||
@@ -120,7 +128,17 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
|
||||
return <FullscreenLoader isLoading={isLoading} />;
|
||||
}
|
||||
|
||||
const acceptedFileTypes = ['.json', '.yaml', '.yml', 'application/json', 'application/yaml', 'application/x-yaml'];
|
||||
const acceptedFileTypes = [
|
||||
'.json',
|
||||
'.yaml',
|
||||
'.yml',
|
||||
'.wsdl',
|
||||
'application/json',
|
||||
'application/yaml',
|
||||
'application/x-yaml',
|
||||
'text/xml',
|
||||
'application/xml'
|
||||
];
|
||||
|
||||
if (showImportSettings) {
|
||||
return (
|
||||
@@ -170,7 +188,7 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
|
||||
</button>
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Supports Bruno, Postman, Insomnia, and OpenAPI v3 formats
|
||||
Supports Bruno, Postman, Insomnia, OpenAPI v3, and WSDL formats
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -146,7 +146,7 @@ const useIpcEvents = () => {
|
||||
}));
|
||||
});
|
||||
|
||||
const removeSystemResourcesListener = ipcRenderer.on('main:filesync-system-resources', resourceData => {
|
||||
const removeSystemResourcesListener = ipcRenderer.on('main:filesync-system-resources', (resourceData) => {
|
||||
dispatch(updateSystemResources(resourceData));
|
||||
});
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export const store = configureStore({
|
||||
notifications: notificationsReducer,
|
||||
globalEnvironments: globalEnvironmentsReducer,
|
||||
logs: logsReducer,
|
||||
performance: performanceReducer,
|
||||
performance: performanceReducer
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware)
|
||||
});
|
||||
|
||||
@@ -6,8 +6,8 @@ const initialState = {
|
||||
memory: 0,
|
||||
pid: null,
|
||||
uptime: 0,
|
||||
lastUpdated: null,
|
||||
},
|
||||
lastUpdated: null
|
||||
}
|
||||
};
|
||||
|
||||
export const performanceSlice = createSlice({
|
||||
@@ -18,10 +18,10 @@ export const performanceSlice = createSlice({
|
||||
state.systemResources = {
|
||||
...state.systemResources,
|
||||
...action.payload,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
lastUpdated: new Date().toISOString()
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const { updateSystemResources } = performanceSlice.actions;
|
||||
|
||||
@@ -52,10 +52,10 @@ const createQuery = (queryParams = [], request) => {
|
||||
value: param.value
|
||||
}));
|
||||
|
||||
if (request?.auth?.mode === 'apikey' &&
|
||||
request?.auth?.apikey?.placement === 'queryparams' &&
|
||||
request?.auth?.apikey?.key &&
|
||||
request?.auth?.apikey?.value) {
|
||||
if (request?.auth?.mode === 'apikey'
|
||||
&& request?.auth?.apikey?.placement === 'queryparams'
|
||||
&& request?.auth?.apikey?.key
|
||||
&& request?.auth?.apikey?.value) {
|
||||
params.push({
|
||||
name: request.auth.apikey.key,
|
||||
value: request.auth.apikey.value
|
||||
|
||||
@@ -29,7 +29,7 @@ const COPY_SUCCESS_COLOR = '#22c55e';
|
||||
|
||||
export const COPY_SUCCESS_TIMEOUT = 1000;
|
||||
|
||||
const getCopyButton = variableValue => {
|
||||
const getCopyButton = (variableValue) => {
|
||||
const copyButton = document.createElement('button');
|
||||
|
||||
copyButton.className = 'copy-button';
|
||||
@@ -64,7 +64,7 @@ const getCopyButton = variableValue => {
|
||||
copyButton.style.opacity = '0.7';
|
||||
});
|
||||
|
||||
copyButton.addEventListener('click', e => {
|
||||
copyButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Prevent clicking if showing success checkmark
|
||||
@@ -91,7 +91,7 @@ const getCopyButton = variableValue => {
|
||||
copyButton.classList.remove('copy-success');
|
||||
}, COPY_SUCCESS_TIMEOUT);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
console.error('Failed to copy to clipboard:', err.message);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -237,7 +237,7 @@ describe('renderVarInfo', () => {
|
||||
clipboardText = '';
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
value: {
|
||||
writeText: jest.fn(text => {
|
||||
writeText: jest.fn((text) => {
|
||||
if (text === 'cause-clipboard-error') {
|
||||
return Promise.reject(new Error('Clipboard error'));
|
||||
}
|
||||
@@ -245,9 +245,9 @@ describe('renderVarInfo', () => {
|
||||
clipboardText = text;
|
||||
|
||||
return Promise.resolve();
|
||||
}),
|
||||
})
|
||||
},
|
||||
configurable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
// mock console.error
|
||||
@@ -283,7 +283,7 @@ describe('renderVarInfo', () => {
|
||||
it('should correctly mask the variable value in the popup', () => {
|
||||
const { descriptionDiv } = setupRender({
|
||||
apiKey: 'test-value',
|
||||
maskedEnvVariables: ['apiKey'],
|
||||
maskedEnvVariables: ['apiKey']
|
||||
});
|
||||
|
||||
expect(descriptionDiv.textContent).toBe('*****');
|
||||
|
||||
@@ -54,7 +54,7 @@ export const safeStringifyJSON = (obj, indent = false) => {
|
||||
|
||||
export const prettifyJSON = (obj, spaces = 2) => {
|
||||
try {
|
||||
const text = obj.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
||||
const text = obj.replace(/\\"/g, '"').replace(/\\'/g, '\'');
|
||||
|
||||
const placeholders = [];
|
||||
const modifiedJson = text.replace(/"[^"]*?"|{{[^{}]+}}/g, (match) => {
|
||||
|
||||
@@ -102,7 +102,6 @@ export class MaskedEditor {
|
||||
|
||||
// Restore cursor state
|
||||
this.restoreCursorState();
|
||||
|
||||
} finally {
|
||||
this.isProcessing = false;
|
||||
}
|
||||
@@ -135,7 +134,6 @@ export class MaskedEditor {
|
||||
|
||||
// Restore cursor state
|
||||
this.restoreCursorState();
|
||||
|
||||
} finally {
|
||||
this.isProcessing = false;
|
||||
}
|
||||
@@ -331,15 +329,13 @@ export class MaskedEditor {
|
||||
const maskedNode = document.createTextNode(this.maskChar.repeat(lineLength));
|
||||
|
||||
// Create mark with proper bounds checking
|
||||
const mark = this.editor.markText(
|
||||
{ line, ch: 0 },
|
||||
const mark = this.editor.markText({ line, ch: 0 },
|
||||
{ line, ch: lineLength },
|
||||
{
|
||||
replacedWith: maskedNode,
|
||||
handleMouseEvents: false,
|
||||
className: 'masked-line'
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Store mark for cleanup
|
||||
this.marks.add(mark);
|
||||
@@ -355,7 +351,7 @@ export class MaskedEditor {
|
||||
* Clear all marks with proper cleanup
|
||||
*/
|
||||
clearAllMarks() {
|
||||
this.marks.forEach(mark => {
|
||||
this.marks.forEach((mark) => {
|
||||
try {
|
||||
mark.clear();
|
||||
} catch (e) {
|
||||
@@ -365,7 +361,7 @@ export class MaskedEditor {
|
||||
this.marks.clear();
|
||||
|
||||
// Also clear any marks that might have been created outside our control
|
||||
this.editor.getAllMarks().forEach(mark => {
|
||||
this.editor.getAllMarks().forEach((mark) => {
|
||||
try {
|
||||
mark.clear();
|
||||
} catch (e) {
|
||||
@@ -437,8 +433,8 @@ export function createMaskedEditor(editor, maskChar = '*') {
|
||||
* Utility function to check if an editor supports masking
|
||||
*/
|
||||
export function supportsMasking(editor) {
|
||||
return editor &&
|
||||
typeof editor.getValue === 'function' &&
|
||||
typeof editor.markText === 'function' &&
|
||||
typeof editor.operation === 'function';
|
||||
}
|
||||
return editor
|
||||
&& typeof editor.getValue === 'function'
|
||||
&& typeof editor.markText === 'function'
|
||||
&& typeof editor.operation === 'function';
|
||||
}
|
||||
|
||||
28
packages/bruno-app/src/utils/importers/wsdl-collection.js
Normal file
28
packages/bruno-app/src/utils/importers/wsdl-collection.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const isWSDLCollection = (data) => {
|
||||
// Check if data is a string (WSDL content)
|
||||
if (typeof data !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for WSDL-specific XML elements
|
||||
const wsdlIndicators = [
|
||||
'wsdl:definitions',
|
||||
'definitions',
|
||||
'wsdl:types',
|
||||
'wsdl:message',
|
||||
'wsdl:portType',
|
||||
'wsdl:binding',
|
||||
'wsdl:service'
|
||||
];
|
||||
|
||||
// Check if the content contains WSDL namespace or elements
|
||||
const hasWSDLNamespace = data.includes('xmlns:wsdl=')
|
||||
|| data.includes('xmlns="http://schemas.xmlsoap.org/wsdl/"')
|
||||
|| data.includes('xmlns="http://www.w3.org/2001/XMLSchema"');
|
||||
|
||||
const hasWSDLElements = wsdlIndicators.some((indicator) => data.includes(indicator));
|
||||
|
||||
return hasWSDLNamespace || hasWSDLElements;
|
||||
};
|
||||
|
||||
export { isWSDLCollection };
|
||||
@@ -34,7 +34,7 @@ export const parsePathParams = (url) => {
|
||||
|
||||
// Enhanced: also match :param inside parentheses and/or quotes
|
||||
const foundParams = new Set();
|
||||
paths.forEach(segment => {
|
||||
paths.forEach((segment) => {
|
||||
// traditional path parameters
|
||||
if (segment.startsWith(':')) {
|
||||
const name = segment.slice(1);
|
||||
@@ -65,7 +65,7 @@ export const parsePathParams = (url) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
return Array.from(foundParams).map(name => ({ name, value: '' }));
|
||||
return Array.from(foundParams).map((name) => ({ name, value: '' }));
|
||||
};
|
||||
|
||||
export const splitOnFirst = (str, char) => {
|
||||
|
||||
@@ -166,7 +166,6 @@ describe('Url Utils - parsePathParams', () => {
|
||||
const params = parsePathParams('https://example.com/start/1:2:AHLS-HASD/form');
|
||||
expect(params).toEqual([]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Url Utils - URN parsing', () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
const jsyaml = require('js-yaml');
|
||||
const axios = require('axios');
|
||||
const { openApiToBruno } = require('@usebruno/converters');
|
||||
const { openApiToBruno, wsdlToBruno } = require('@usebruno/converters');
|
||||
const { exists, isDirectory, sanitizeName } = require('../utils/filesystem');
|
||||
const { createCollectionFromBrunoObject } = require('../utils/collection');
|
||||
|
||||
@@ -15,7 +15,7 @@ const builder = (yargs) => {
|
||||
.positional('type', {
|
||||
describe: 'Type of collection to import',
|
||||
type: 'string',
|
||||
choices: ['openapi']
|
||||
choices: ['openapi', 'wsdl']
|
||||
})
|
||||
.option('source', {
|
||||
alias: 's',
|
||||
@@ -59,7 +59,9 @@ const builder = (yargs) => {
|
||||
.example('$0 import openapi --source api.yml --output-file ~/Desktop/my-collection.json --collection-name "My API"')
|
||||
.example('$0 import openapi -s api.yml -f ~/Desktop/my-collection.json -n "My API"')
|
||||
.example('$0 import openapi --source api.yml --output ~/Desktop/my-collection --group-by path')
|
||||
.example('$0 import openapi -s api.yml -o ~/Desktop/my-collection -g tags');
|
||||
.example('$0 import openapi -s api.yml -o ~/Desktop/my-collection -g tags')
|
||||
.example('$0 import wsdl --source service.wsdl --output ~/Desktop/soap-collection --collection-name "SOAP Service"')
|
||||
.example('$0 import wsdl -s https://example.com/service.wsdl -o ~/Desktop -n "Remote SOAP Service"');
|
||||
};
|
||||
|
||||
const isUrl = (str) => {
|
||||
@@ -139,12 +141,68 @@ const readOpenApiFile = async (source, options = {}) => {
|
||||
}
|
||||
};
|
||||
|
||||
const readWSDLFile = async (source, options = {}) => {
|
||||
try {
|
||||
let content;
|
||||
|
||||
if (isUrl(source)) {
|
||||
// Handle URL input
|
||||
console.log(chalk.yellow(`Fetching WSDL from URL: ${source}`));
|
||||
try {
|
||||
const axiosOptions = {
|
||||
timeout: 30000, // 30 second timeout
|
||||
maxContentLength: 10 * 1024 * 1024,
|
||||
validateStatus: (status) => status >= 200 && status < 300
|
||||
};
|
||||
|
||||
// Skip SSL certificate validation if insecure flag is set
|
||||
if (options.insecure) {
|
||||
console.log(chalk.yellow('Warning: SSL certificate verification is disabled. Use with caution.'));
|
||||
axiosOptions.httpsAgent = new (require('https')).Agent({ rejectUnauthorized: false });
|
||||
}
|
||||
|
||||
const response = await axios.get(source, axiosOptions);
|
||||
content = response.data;
|
||||
} catch (error) {
|
||||
if (error.code === 'ECONNABORTED') {
|
||||
throw new Error('Request timed out. The server took too long to respond.');
|
||||
} else if (error.code === 'CERT_HAS_EXPIRED' || error.code === 'DEPTH_ZERO_SELF_SIGNED_CERT'
|
||||
|| error.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
|
||||
throw new Error(`SSL Certificate error: ${error.code}. Try using --insecure if you trust this source.`);
|
||||
} else if (error.response) {
|
||||
throw new Error(`Failed to fetch from URL: ${error.response.status} ${error.response.statusText}`);
|
||||
} else if (error.request) {
|
||||
throw new Error(`No response received from server. Check the URL and your network connection.`);
|
||||
} else {
|
||||
throw new Error(`Error fetching URL: ${error.message}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle file input
|
||||
if (!await exists(source)) {
|
||||
throw new Error(`File does not exist: ${source}`);
|
||||
}
|
||||
content = fs.readFileSync(source, 'utf8');
|
||||
}
|
||||
|
||||
// WSDL files are XML, so we return the content as a string
|
||||
if (typeof content === 'string') {
|
||||
return content;
|
||||
}
|
||||
|
||||
throw new Error('WSDL content must be a string');
|
||||
} catch (error) {
|
||||
// Let the specific error handling from above propagate
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const handler = async (argv) => {
|
||||
try {
|
||||
const { type, source, output, outputFile, collectionName, insecure, groupBy } = argv;
|
||||
|
||||
if (!type || type !== 'openapi') {
|
||||
console.error(chalk.red('Only OpenAPI import is supported currently'));
|
||||
if (!type || !['openapi', 'wsdl'].includes(type)) {
|
||||
console.error(chalk.red('Only OpenAPI and WSDL imports are supported currently'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -158,19 +216,37 @@ const handler = async (argv) => {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(chalk.yellow(`Reading OpenAPI specification from ${source}...`));
|
||||
|
||||
const openApiSpec = await readOpenApiFile(source, { insecure });
|
||||
|
||||
if (!openApiSpec) {
|
||||
console.error(chalk.red('Failed to parse OpenAPI specification'));
|
||||
process.exit(1);
|
||||
}
|
||||
let brunoCollection;
|
||||
|
||||
console.log(chalk.yellow('Converting OpenAPI specification to Bruno format...'));
|
||||
|
||||
// Convert OpenAPI to Bruno format
|
||||
let brunoCollection = openApiToBruno(openApiSpec, { groupBy });
|
||||
if (type === 'openapi') {
|
||||
console.log(chalk.yellow(`Reading OpenAPI specification from ${source}...`));
|
||||
|
||||
const openApiSpec = await readOpenApiFile(source, { insecure });
|
||||
|
||||
if (!openApiSpec) {
|
||||
console.error(chalk.red('Failed to parse OpenAPI specification'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(chalk.yellow('Converting OpenAPI specification to Bruno format...'));
|
||||
|
||||
// Convert OpenAPI to Bruno format
|
||||
brunoCollection = openApiToBruno(openApiSpec, { groupBy });
|
||||
} else if (type === 'wsdl') {
|
||||
console.log(chalk.yellow(`Reading WSDL from ${source}...`));
|
||||
|
||||
const wsdlContent = await readWSDLFile(source, { insecure });
|
||||
|
||||
if (!wsdlContent) {
|
||||
console.error(chalk.red('Failed to read WSDL file'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(chalk.yellow('Converting WSDL to Bruno format...'));
|
||||
|
||||
// Convert WSDL to Bruno format
|
||||
brunoCollection = await wsdlToBruno(wsdlContent);
|
||||
}
|
||||
|
||||
// Override collection name if provided
|
||||
if (collectionName) {
|
||||
@@ -235,5 +311,6 @@ module.exports = {
|
||||
builder,
|
||||
handler,
|
||||
isUrl,
|
||||
readOpenApiFile
|
||||
readOpenApiFile,
|
||||
readWSDLFile
|
||||
};
|
||||
@@ -123,7 +123,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
// traditional path parameters
|
||||
if (path.startsWith(':')) {
|
||||
const paramName = path.slice(1);
|
||||
const existingPathParam = request.pathParams.find(param => param.name === paramName);
|
||||
const existingPathParam = request.pathParams.find((param) => param.name === paramName);
|
||||
if (!existingPathParam) {
|
||||
return '/' + path;
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ const prepareRequest = async (item = {}, collection = {}) => {
|
||||
axiosRequest.headers['content-type'] = 'application/octet-stream'; // Default headers for binary file uploads
|
||||
}
|
||||
|
||||
const bodyFile = find(request.body.file, param => param.selected);
|
||||
const bodyFile = find(request.body.file, (param) => param.selected);
|
||||
if (bodyFile) {
|
||||
let { filePath, contentType } = bodyFile;
|
||||
|
||||
|
||||
@@ -559,7 +559,7 @@ describe('prepare-request: prepareRequest', () => {
|
||||
selected: true
|
||||
}]
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const result = await prepareRequest(item);
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"js-yaml": "^4.1.0",
|
||||
"jscodeshift": "^17.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nanoid": "3.3.8"
|
||||
"nanoid": "3.3.8",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
||||
@@ -44,6 +44,14 @@ const { openApiToBruno } = require('@usebruno/converters');
|
||||
const brunoCollection = openApiToBruno(openApiSpecification);
|
||||
```
|
||||
|
||||
### Convert WSDL file to Bruno collection
|
||||
|
||||
```javascript
|
||||
import { wsdlToBruno } from '@usebruno/converters';
|
||||
|
||||
const brunoCollection = await wsdlToBruno(wsdlContent);
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```bash copy
|
||||
@@ -76,3 +84,70 @@ const outputFilePath = path.resolve(__dirname, 'bruno-collection.json');
|
||||
convertPostmanToBruno(inputFilePath, outputFilePath);
|
||||
|
||||
```
|
||||
|
||||
## WSDL Import Features
|
||||
|
||||
The WSDL importer supports the following features:
|
||||
|
||||
- **Service Discovery**: Automatically extracts service endpoints from WSDL definitions
|
||||
- **Operation Mapping**: Converts WSDL operations to Bruno HTTP requests
|
||||
- **SOAP Envelope Generation**: Creates proper SOAP envelopes for each operation
|
||||
- **Header Configuration**: Sets up appropriate Content-Type and SOAPAction headers
|
||||
- **Environment Variables**: Creates environment variables for service base URLs
|
||||
- **Folder Organization**: Groups operations by port type for better organization
|
||||
|
||||
### WSDL Import Example
|
||||
|
||||
```javascript
|
||||
import { wsdlToBruno } from '@usebruno/converters';
|
||||
import fs from 'fs/promises';
|
||||
|
||||
async function importWSDL() {
|
||||
try {
|
||||
// Read WSDL file
|
||||
const wsdlContent = await fs.readFile('service.wsdl', 'utf8');
|
||||
|
||||
// Convert to Bruno collection
|
||||
const brunoCollection = await wsdlToBruno(wsdlContent);
|
||||
|
||||
// Save Bruno collection
|
||||
await fs.writeFile('soap-collection.json', JSON.stringify(brunoCollection, null, 2));
|
||||
|
||||
console.log('WSDL import successful!');
|
||||
} catch (error) {
|
||||
console.error('Error during WSDL import:', error);
|
||||
}
|
||||
}
|
||||
|
||||
importWSDL();
|
||||
```
|
||||
|
||||
### CLI Usage
|
||||
|
||||
You can also use the Bruno CLI to import WSDL files:
|
||||
|
||||
```bash
|
||||
# Import WSDL file to a directory
|
||||
bruno import wsdl --source service.wsdl --output ~/Desktop/soap-collection --collection-name "SOAP Service"
|
||||
|
||||
# Import WSDL from URL
|
||||
bruno import wsdl --source https://example.com/service.wsdl --output ~/Desktop --collection-name "Remote SOAP Service"
|
||||
|
||||
# Import WSDL and save as JSON file
|
||||
bruno import wsdl --source service.wsdl --output-file ~/Desktop/soap-collection.json --collection-name "SOAP Service"
|
||||
```
|
||||
|
||||
## Supported Formats
|
||||
|
||||
- **Postman Collections** (v2.1)
|
||||
- **Insomnia Collections** (v4 and v5)
|
||||
- **OpenAPI Specifications** (v3.0)
|
||||
- **WSDL Files** (Web Services Description Language)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `lodash` - Utility functions
|
||||
- `nanoid` - UUID generation
|
||||
- `js-yaml` - YAML parsing
|
||||
- `xml2js` - XML parsing for WSDL
|
||||
- `@usebruno/schema` - Schema validation
|
||||
|
||||
@@ -3,4 +3,5 @@ export { default as postmanToBrunoEnvironment } from './postman/postman-env-to-b
|
||||
export { default as brunoToPostman } from './postman/bruno-to-postman.js';
|
||||
export { default as openApiToBruno } from './openapi/openapi-to-bruno.js';
|
||||
export { default as insomniaToBruno } from './insomnia/insomnia-to-bruno.js';
|
||||
export { default as wsdlToBruno } from './wsdl/wsdl-to-bruno.js';
|
||||
export { default as postmanTranslation } from './postman/postman-translations.js';
|
||||
1133
packages/bruno-converters/src/wsdl/wsdl-to-bruno.js
Normal file
1133
packages/bruno-converters/src/wsdl/wsdl-to-bruno.js
Normal file
File diff suppressed because it is too large
Load Diff
16
packages/bruno-converters/tests/wsdl/wsdl-to-bruno.spec.js
Normal file
16
packages/bruno-converters/tests/wsdl/wsdl-to-bruno.spec.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { describe, it, expect } from '@jest/globals';
|
||||
import wsdlToBruno from '../../src/wsdl/wsdl-to-bruno.js';
|
||||
|
||||
describe('wsdl-to-bruno', () => {
|
||||
it('should throw error for non-string input', async () => {
|
||||
await expect(wsdlToBruno({})).rejects.toThrow('WSDL content must be a string');
|
||||
});
|
||||
|
||||
it('should throw error for empty string input', async () => {
|
||||
await expect(wsdlToBruno('')).rejects.toThrow('Import WSDL collection failed');
|
||||
});
|
||||
|
||||
it('should throw error for invalid XML', async () => {
|
||||
await expect(wsdlToBruno('<invalid>xml</invalid>')).rejects.toThrow('Import WSDL collection failed');
|
||||
});
|
||||
});
|
||||
@@ -43,7 +43,7 @@ class SystemMonitor {
|
||||
memory: stats.memory || 0,
|
||||
pid: pid,
|
||||
uptime: uptime,
|
||||
timestamp: new Date().toISOString(),
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
win.webContents.send('main:filesync-system-resources', systemResources);
|
||||
@@ -56,7 +56,7 @@ class SystemMonitor {
|
||||
memory: process.memoryUsage().rss,
|
||||
pid: process.pid,
|
||||
uptime: (Date.now() - this.startTime) / 1000,
|
||||
timestamp: new Date().toISOString(),
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
win.webContents.send('main:filesync-system-resources', fallbackStats);
|
||||
|
||||
@@ -91,14 +91,14 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
if (typeof request.data === 'string') {
|
||||
if (request.data.length) {
|
||||
request.data = _interpolate(request.data, {
|
||||
escapeJSONStrings: true,
|
||||
escapeJSONStrings: true
|
||||
});
|
||||
}
|
||||
} else if (typeof request.data === 'object') {
|
||||
try {
|
||||
const jsonDoc = JSON.stringify(request.data);
|
||||
const parsed = _interpolate(jsonDoc, {
|
||||
escapeJSONStrings: true,
|
||||
escapeJSONStrings: true
|
||||
});
|
||||
request.data = JSON.parse(parsed);
|
||||
} catch (err) {}
|
||||
@@ -148,7 +148,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
// traditional path parameters
|
||||
if (path.startsWith(':')) {
|
||||
const paramName = path.slice(1);
|
||||
const existingPathParam = request.pathParams.find(param => param.name === paramName);
|
||||
const existingPathParam = request.pathParams.find((param) => param.name === paramName);
|
||||
if (!existingPathParam) {
|
||||
return '/' + path;
|
||||
}
|
||||
|
||||
@@ -163,19 +163,19 @@ describe('interpolate-vars: interpolateVars', () => {
|
||||
{
|
||||
type: 'path',
|
||||
name: 'CategoryID',
|
||||
value: 'foobar',
|
||||
value: 'foobar'
|
||||
},
|
||||
{
|
||||
type: 'path',
|
||||
name: 'ItemId',
|
||||
value: 1,
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
type: 'path',
|
||||
name: 'xpath',
|
||||
value: 'foobar',
|
||||
},
|
||||
],
|
||||
value: 'foobar'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const result = interpolateVars(request, null, null, null);
|
||||
|
||||
@@ -90,5 +90,4 @@ describe('prepareGqlIntrospectionRequest', () => {
|
||||
|
||||
expect(result.headers['X-API-Key']).toBe('{{process.env.MISSING_VAR}}');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -90,7 +90,7 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
||||
let getBody = vm.newFunction('getBody', function (options) {
|
||||
return marshallToVm(req.getBody(vm.dump(options)), vm);
|
||||
});
|
||||
|
||||
|
||||
vm.setProp(reqObject, 'getBody', getBody);
|
||||
getBody.dispose();
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ const safeParseJSON = (jsonString, context = 'JSON string') => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class WsClient {
|
||||
messageQueues = {};
|
||||
activeConnections = new Map();
|
||||
|
||||
@@ -54,4 +54,4 @@ server.on('upgrade', wsRouter);
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log(`Testbench started on port: ${port}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,12 +8,12 @@ const wss = new ws.Server({
|
||||
noServer: true,
|
||||
handleProtocols: (protocols, request) => {
|
||||
if (request.url == '/ws/sub-proto') {
|
||||
if (protocols.has("soap")) {
|
||||
return 'soap'
|
||||
if (protocols.has('soap')) {
|
||||
return 'soap';
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -55,20 +55,18 @@ const wsRouter = (request, socket, head) => {
|
||||
}
|
||||
|
||||
if (request.url == '/ws/sub-proto') {
|
||||
const subproto = request.headers["sec-websocket-protocol"] || request.headers["Sec-WebSocket-Protocol"]
|
||||
if (subproto != "soap") {
|
||||
const message = "Unsupported WebSocket subprotocol"
|
||||
socket.write(
|
||||
'HTTP/1.1 400 Bad Request\r\n' +
|
||||
'Content-Type: text/plain\r\n' +
|
||||
`Content-Length: ${Buffer.byteLength(message)}\r\n` +
|
||||
'Connection: close\r\n' +
|
||||
'\r\n' +
|
||||
message
|
||||
);
|
||||
const subproto = request.headers['sec-websocket-protocol'] || request.headers['Sec-WebSocket-Protocol'];
|
||||
if (subproto != 'soap') {
|
||||
const message = 'Unsupported WebSocket subprotocol';
|
||||
socket.write('HTTP/1.1 400 Bad Request\r\n'
|
||||
+ 'Content-Type: text/plain\r\n'
|
||||
+ `Content-Length: ${Buffer.byteLength(message)}\r\n`
|
||||
+ 'Connection: close\r\n'
|
||||
+ '\r\n'
|
||||
+ message);
|
||||
socket.destroy();
|
||||
socket.removeListener('error', onSocketError);
|
||||
return
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
|
||||
test.describe('File Input Acceptance', () => {
|
||||
test('File input accepts expected file types', async ({ page }) => {
|
||||
test('File input accepts expected file types', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Import Collection' }).click();
|
||||
|
||||
|
||||
// Check that file input exists (even if hidden)
|
||||
const fileInput = page.locator('input[type="file"]');
|
||||
await expect(fileInput).toBeAttached();
|
||||
|
||||
|
||||
// Verify it accepts the expected file types
|
||||
const acceptValue = await fileInput.getAttribute('accept');
|
||||
expect(acceptValue).toContain('.json');
|
||||
expect(acceptValue).toContain('.yaml');
|
||||
expect(acceptValue).toContain('.yml');
|
||||
|
||||
|
||||
// Cleanup: close any open modals
|
||||
await page.locator('[data-test-id="modal-close-button"]').click();
|
||||
});
|
||||
|
||||
102
tests/import/wsdl/fixtures/wsdl-bruno.json
Normal file
102
tests/import/wsdl/fixtures/wsdl-bruno.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"uid": "TestServiceCollection",
|
||||
"version": "1",
|
||||
"name": "TestWSDLServiceJSON",
|
||||
"items": [
|
||||
{
|
||||
"uid": "UserServiceFolder",
|
||||
"name": "UserService",
|
||||
"type": "folder",
|
||||
"items": [
|
||||
{
|
||||
"uid": "GetUserRequest",
|
||||
"name": "GetUser",
|
||||
"type": "http-request",
|
||||
"seq": 1,
|
||||
"request": {
|
||||
"url": "http://example.com/soap/userservice",
|
||||
"method": "POST",
|
||||
"auth": {
|
||||
"mode": "none",
|
||||
"basic": null,
|
||||
"bearer": null,
|
||||
"digest": null
|
||||
},
|
||||
"headers": [
|
||||
{
|
||||
"uid": "ContentTypeHeader",
|
||||
"name": "Content-Type",
|
||||
"value": "text/xml; charset=utf-8",
|
||||
"description": "",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"uid": "SOAPActionHeader",
|
||||
"name": "SOAPAction",
|
||||
"value": "http://example.com/testservice/GetUser",
|
||||
"description": "",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"params": [],
|
||||
"body": {
|
||||
"mode": "xml",
|
||||
"json": null,
|
||||
"text": null,
|
||||
"xml": "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><GetUserRequest xmlns=\"http://example.com/testservice\"><userId>string</userId><includeDetails>true</includeDetails></GetUserRequest></soap:Body></soap:Envelope>",
|
||||
"formUrlEncoded": [],
|
||||
"multipartForm": []
|
||||
},
|
||||
"script": {
|
||||
"res": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "CreateUserRequest",
|
||||
"name": "CreateUser",
|
||||
"type": "http-request",
|
||||
"seq": 2,
|
||||
"request": {
|
||||
"url": "http://example.com/soap/userservice",
|
||||
"method": "POST",
|
||||
"auth": {
|
||||
"mode": "none",
|
||||
"basic": null,
|
||||
"bearer": null,
|
||||
"digest": null
|
||||
},
|
||||
"headers": [
|
||||
{
|
||||
"uid": "ContentTypeHeader2",
|
||||
"name": "Content-Type",
|
||||
"value": "text/xml; charset=utf-8",
|
||||
"description": "",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"uid": "SOAPActionHeader2",
|
||||
"name": "SOAPAction",
|
||||
"value": "http://example.com/testservice/CreateUser",
|
||||
"description": "",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"params": [],
|
||||
"body": {
|
||||
"mode": "xml",
|
||||
"json": null,
|
||||
"text": null,
|
||||
"xml": "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><CreateUserRequest xmlns=\"http://example.com/testservice\"><name>string</name><email>string</email><password>string</password></CreateUserRequest></soap:Body></soap:Envelope>",
|
||||
"formUrlEncoded": [],
|
||||
"multipartForm": []
|
||||
},
|
||||
"script": {
|
||||
"res": null
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
126
tests/import/wsdl/fixtures/wsdl.xml
Normal file
126
tests/import/wsdl/fixtures/wsdl.xml
Normal file
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<wsdl:definitions
|
||||
name="TestWSDLServiceXML"
|
||||
targetNamespace="http://example.com/testservice"
|
||||
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
|
||||
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
|
||||
xmlns:tns="http://example.com/testservice"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<wsdl:documentation>Test WSDL for Bruno import testing</wsdl:documentation>
|
||||
|
||||
<wsdl:types>
|
||||
<xsd:schema targetNamespace="http://example.com/testservice" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<xsd:element name="GetUserRequest">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="userId" type="xsd:string"/>
|
||||
<xsd:element name="includeDetails" type="xsd:boolean" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="GetUserResponse">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="user" type="tns:User"/>
|
||||
<xsd:element name="status" type="xsd:string"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:complexType name="User">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="id" type="xsd:string"/>
|
||||
<xsd:element name="name" type="xsd:string"/>
|
||||
<xsd:element name="email" type="xsd:string"/>
|
||||
<xsd:element name="active" type="xsd:boolean"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:element name="CreateUserRequest">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="name" type="xsd:string"/>
|
||||
<xsd:element name="email" type="xsd:string"/>
|
||||
<xsd:element name="password" type="xsd:string"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="CreateUserResponse">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="userId" type="xsd:string"/>
|
||||
<xsd:element name="status" type="xsd:string"/>
|
||||
<xsd:element name="message" type="xsd:string"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
</wsdl:types>
|
||||
|
||||
<wsdl:message name="GetUserRequestMessage">
|
||||
<wsdl:part name="parameters" element="tns:GetUserRequest"/>
|
||||
</wsdl:message>
|
||||
|
||||
<wsdl:message name="GetUserResponseMessage">
|
||||
<wsdl:part name="parameters" element="tns:GetUserResponse"/>
|
||||
</wsdl:message>
|
||||
|
||||
<wsdl:message name="CreateUserRequestMessage">
|
||||
<wsdl:part name="parameters" element="tns:CreateUserRequest"/>
|
||||
</wsdl:message>
|
||||
|
||||
<wsdl:message name="CreateUserResponseMessage">
|
||||
<wsdl:part name="parameters" element="tns:CreateUserResponse"/>
|
||||
</wsdl:message>
|
||||
|
||||
<wsdl:portType name="UserServicePortType">
|
||||
<wsdl:documentation>User management service port type</wsdl:documentation>
|
||||
|
||||
<wsdl:operation name="GetUser">
|
||||
<wsdl:documentation>Retrieve user information by ID</wsdl:documentation>
|
||||
<wsdl:input message="tns:GetUserRequestMessage"/>
|
||||
<wsdl:output message="tns:GetUserResponseMessage"/>
|
||||
</wsdl:operation>
|
||||
|
||||
<wsdl:operation name="CreateUser">
|
||||
<wsdl:documentation>Create a new user</wsdl:documentation>
|
||||
<wsdl:input message="tns:CreateUserRequestMessage"/>
|
||||
<wsdl:output message="tns:CreateUserResponseMessage"/>
|
||||
</wsdl:operation>
|
||||
</wsdl:portType>
|
||||
|
||||
<wsdl:binding name="UserServiceBinding" type="tns:UserServicePortType">
|
||||
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
|
||||
|
||||
<wsdl:operation name="GetUser">
|
||||
<soap:operation soapAction="http://example.com/testservice/GetUser"/>
|
||||
<wsdl:input>
|
||||
<soap:body use="literal"/>
|
||||
</wsdl:input>
|
||||
<wsdl:output>
|
||||
<soap:body use="literal"/>
|
||||
</wsdl:output>
|
||||
</wsdl:operation>
|
||||
|
||||
<wsdl:operation name="CreateUser">
|
||||
<soap:operation soapAction="http://example.com/testservice/CreateUser"/>
|
||||
<wsdl:input>
|
||||
<soap:body use="literal"/>
|
||||
</wsdl:input>
|
||||
<wsdl:output>
|
||||
<soap:body use="literal"/>
|
||||
</wsdl:output>
|
||||
</wsdl:operation>
|
||||
</wsdl:binding>
|
||||
|
||||
<wsdl:service name="UserService">
|
||||
<wsdl:documentation>User management web service</wsdl:documentation>
|
||||
<wsdl:port name="UserServicePort" binding="tns:UserServiceBinding">
|
||||
<soap:address location="http://example.com/soap/userservice"/>
|
||||
</wsdl:port>
|
||||
</wsdl:service>
|
||||
|
||||
</wsdl:definitions>
|
||||
127
tests/import/wsdl/import-wsdl.spec.ts
Normal file
127
tests/import/wsdl/import-wsdl.spec.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import * as path from 'path';
|
||||
import { closeAllCollections, openCollectionAndAcceptSandbox } from '../../utils/page/actions';
|
||||
|
||||
test.describe('Import WSDL Collection', () => {
|
||||
const testDataDir = path.join(__dirname, 'fixtures');
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
await closeAllCollections(page);
|
||||
});
|
||||
|
||||
test('Import WSDL XML file as Bruno collection', async ({ page, createTmpDir }) => {
|
||||
const wsdlFile = path.join(testDataDir, 'wsdl.xml');
|
||||
|
||||
await test.step('Open import collection modal', async () => {
|
||||
await page.getByRole('button', { name: 'Import Collection' }).click();
|
||||
|
||||
// Wait for import collection modal to be ready
|
||||
const importModal = page.getByRole('dialog');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
});
|
||||
|
||||
await test.step('Choose WSDL XML file', async () => {
|
||||
await page.setInputFiles('input[type="file"]', wsdlFile);
|
||||
|
||||
// Wait for the loader to disappear
|
||||
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
|
||||
});
|
||||
|
||||
await test.step('Select the location for the collection and submit to import', async () => {
|
||||
// Verify that the location selection modal is displayed to import the collection
|
||||
const locationModal = page.getByRole('dialog');
|
||||
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
|
||||
|
||||
// Wait for collection to appear in the location modal
|
||||
await expect(locationModal.getByText('TestWSDLServiceXML')).toBeVisible();
|
||||
|
||||
// select a location
|
||||
await page.locator('#collection-location').fill(await createTmpDir('wsdl-xml-test'));
|
||||
await page.getByRole('button', { name: 'Import', exact: true }).click();
|
||||
});
|
||||
|
||||
await test.step('Verify that the collection was imported successfully', async () => {
|
||||
// verify the collection was imported successfully
|
||||
await expect(page.locator('#sidebar-collection-name').getByText('TestWSDLServiceXML')).toBeVisible();
|
||||
|
||||
// open the collection and accept the sandbox modal
|
||||
await openCollectionAndAcceptSandbox(page, 'TestWSDLServiceXML', 'safe');
|
||||
|
||||
// verify that all requests were imported correctly
|
||||
await expect(page.locator('#collection-testwsdlservicexml .collection-item-name')).toHaveCount(1);
|
||||
});
|
||||
|
||||
await test.step('Verify that folders and requests were imported correctly', async () => {
|
||||
await expect(page.locator('#collection-testwsdlservicexml .collection-item-name').getByText('UserService')).toBeVisible();
|
||||
// open the user service folder
|
||||
await page.locator('#collection-testwsdlservicexml .collection-item-name').getByText('UserService').click();
|
||||
|
||||
await expect(page.locator('#collection-testwsdlservicexml .collection-item-name').getByText('GetUser')).toBeVisible();
|
||||
await expect(page.locator('#collection-testwsdlservicexml .collection-item-name').getByText('CreateUser')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Verify the GetUser request is imported correctly', async () => {
|
||||
await page.locator('#collection-testwsdlservicexml .collection-item-name').getByText('GetUser').click();
|
||||
await expect(page.locator('.request-tab.active').getByText('GetUser')).toBeVisible();
|
||||
await expect(page.locator('#request-url').getByText('http://example.com/soap/userservice')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('Import WSDL JSON file as Bruno collection', async ({ page, createTmpDir }) => {
|
||||
const wsdlFile = path.join(testDataDir, 'wsdl-bruno.json');
|
||||
|
||||
await test.step('Open import collection modal', async () => {
|
||||
await page.getByRole('button', { name: 'Import Collection' }).click();
|
||||
|
||||
// Wait for import collection modal to be ready
|
||||
const importModal = page.getByRole('dialog');
|
||||
await importModal.waitFor({ state: 'visible' });
|
||||
});
|
||||
|
||||
await test.step('Choose WSDL JSON file', async () => {
|
||||
await page.setInputFiles('input[type="file"]', wsdlFile);
|
||||
|
||||
// Wait for the loader to disappear
|
||||
await page.locator('#import-collection-loader').waitFor({ state: 'hidden' });
|
||||
});
|
||||
|
||||
await test.step('Select the location for the collection and submit to import', async () => {
|
||||
// Verify that the location selection modal is displayed to import the collection
|
||||
const locationModal = page.getByRole('dialog');
|
||||
await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection');
|
||||
|
||||
// Wait for collection to appear in the location modal
|
||||
await expect(locationModal.getByText('TestWSDLServiceJSON')).toBeVisible();
|
||||
|
||||
// select a location
|
||||
await page.locator('#collection-location').fill(await createTmpDir('wsdl-json-test'));
|
||||
await page.getByRole('button', { name: 'Import', exact: true }).click();
|
||||
});
|
||||
|
||||
await test.step('Verify that the collection was imported successfully', async () => {
|
||||
// verify the collection was imported successfully
|
||||
await expect(page.locator('#sidebar-collection-name').getByText('TestWSDLServiceJSON')).toBeVisible();
|
||||
|
||||
// open the collection and accept the sandbox modal
|
||||
await openCollectionAndAcceptSandbox(page, 'TestWSDLServiceJSON', 'safe');
|
||||
|
||||
// verify that all requests were imported correctly
|
||||
await expect(page.locator('#collection-testwsdlservicejson .collection-item-name')).toHaveCount(1);
|
||||
});
|
||||
|
||||
await test.step('Verify that folders and requests were imported correctly', async () => {
|
||||
await expect(page.locator('#collection-testwsdlservicejson .collection-item-name').getByText('UserService')).toBeVisible();
|
||||
// open the user service folder
|
||||
await page.locator('#collection-testwsdlservicejson .collection-item-name').getByText('UserService').click();
|
||||
|
||||
await expect(page.locator('#collection-testwsdlservicejson .collection-item-name').getByText('GetUser')).toBeVisible();
|
||||
await expect(page.locator('#collection-testwsdlservicejson .collection-item-name').getByText('CreateUser')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Verify the CreateUser request is imported correctly', async () => {
|
||||
await page.locator('#collection-testwsdlservicejson .collection-item-name').getByText('CreateUser').click();
|
||||
await expect(page.locator('.request-tab.active').getByText('CreateUser')).toBeVisible();
|
||||
await expect(page.locator('#request-url').getByText('http://example.com/soap/userservice')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,7 +17,7 @@ function normalizeJunitReport(xmlContent: string): string {
|
||||
|
||||
test.describe('Collection Run Report Tests', () => {
|
||||
const collectionPath = path.join(__dirname, 'collection');
|
||||
|
||||
|
||||
test('CLI: Run collection and generate JUnit report', async ({ createTmpDir }) => {
|
||||
const outputDir = await createTmpDir('junit-report');
|
||||
const junitOutputPath = path.join(outputDir, 'cli-report.xml');
|
||||
|
||||
Reference in New Issue
Block a user