mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
Feat: support bin header in gRPC (#5869)
* Fix -bin header handling in grpc * fix: bin-header, tests rm: tests rm: unused fix: bin header fix: test fix: test rm: un-necessarycode --------- Co-authored-by: Juan Pablo Orsay <jporsay@gmail.com>
This commit is contained in:
@@ -408,6 +408,7 @@ const GrpcQueryUrl = ({ item, collection, handleRun }) => {
|
||||
{(!isConnectionActive || !isStreamingMethod) && (
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
data-testid="grpc-send-request-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRun(e);
|
||||
|
||||
@@ -18,8 +18,8 @@ const GrpcStatusCode = ({ status, text }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper className={getTabClassname(status)}>
|
||||
{Number.isInteger(status) ? <div className="mr-1">{status}</div> : null}
|
||||
{statusText && <div>{statusText}</div>}
|
||||
{Number.isInteger(status) ? <div className="mr-1" data-testid="grpc-response-status-code">{status}</div> : null}
|
||||
{statusText && <div data-testid="grpc-response-status-text">{statusText}</div>}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -83,7 +83,7 @@ const GrpcTimelineItem = ({ timestamp, request, response, eventType, eventData,
|
||||
{Object.entries(effectiveRequest.headers).map(([key, value], idx) => (
|
||||
<div key={idx} className="contents">
|
||||
<div className="text-xs font-medium overflow-hidden text-ellipsis">{key}:</div>
|
||||
<div className="text-xs overflow-hidden text-ellipsis">{value}</div>
|
||||
<div className="text-xs overflow-hidden text-ellipsis">{typeof value === 'string' ? value : '[Buffer Buffer]'}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,137 +2,12 @@
|
||||
const { ipcMain, app } = require('electron');
|
||||
const { GrpcClient } = require("@usebruno/requests")
|
||||
const { safeParseJSON, safeStringifyJSON } = require('../../utils/common');
|
||||
const { cloneDeep, each, get } = require('lodash');
|
||||
const interpolateVars = require('./interpolate-vars');
|
||||
const { cloneDeep, get } = require('lodash');
|
||||
const { preferencesUtil } = require('../../store/preferences');
|
||||
const { getCertsAndProxyConfig } = require('./cert-utils');
|
||||
const { getEnvVars, getTreePathFromCollectionToItem, mergeHeaders, mergeScripts, mergeVars, mergeAuth, getFormattedCollectionOauth2Credentials } = require('../../utils/collection');
|
||||
const { getProcessEnvVars } = require('../../store/process-env');
|
||||
const { getOAuth2TokenUsingPasswordCredentials, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingAuthorizationCode } = require('../../utils/oauth2');
|
||||
const { interpolateString } = require('./interpolate-string');
|
||||
const path = require('node:path');
|
||||
const { setAuthHeaders } = require('./prepare-request');
|
||||
|
||||
const prepareRequest = async (item, collection, environment, runtimeVariables, certsAndProxyConfig = {}) => {
|
||||
const request = item.draft ? item.draft.request : item.request;
|
||||
const collectionRoot = collection?.draft ? get(collection, 'draft', {}) : get(collection, 'root', {});
|
||||
const headers = {};
|
||||
const url = request.url;
|
||||
let contentTypeDefined = false;
|
||||
|
||||
each(get(collectionRoot, 'request.headers', []), (h) => {
|
||||
if (h.enabled && h.name?.toLowerCase() === 'content-type') {
|
||||
contentTypeDefined = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const scriptFlow = collection?.brunoConfig?.scripts?.flow ?? 'sandwich';
|
||||
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
||||
if (requestTreePath && requestTreePath.length > 0) {
|
||||
mergeAuth(collection, request, requestTreePath);
|
||||
mergeHeaders(collection, request, requestTreePath);
|
||||
mergeScripts(collection, request, requestTreePath, scriptFlow);
|
||||
mergeVars(collection, request, requestTreePath);
|
||||
request.globalEnvironmentVariables = collection?.globalEnvironmentVariables;
|
||||
request.oauth2CredentialVariables = getFormattedCollectionOauth2Credentials({ oauth2Credentials: collection?.oauth2Credentials });
|
||||
}
|
||||
|
||||
each(get(request, 'headers', []), (h) => {
|
||||
if (h.enabled && h.name.length > 0) {
|
||||
headers[h.name] = h.value;
|
||||
if (h.name.toLowerCase() === 'content-type') {
|
||||
contentTypeDefined = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const processEnvVars = getProcessEnvVars(collection.uid);
|
||||
const envVars = getEnvVars(environment);
|
||||
|
||||
let grpcRequest = {
|
||||
uid: item.uid,
|
||||
mode: request.body.mode,
|
||||
method: request.method,
|
||||
methodType: request.methodType,
|
||||
url,
|
||||
headers,
|
||||
processEnvVars,
|
||||
envVars,
|
||||
runtimeVariables,
|
||||
body: request.body,
|
||||
protoPath: request.protoPath,
|
||||
// Add variable properties for interpolation
|
||||
vars: request.vars,
|
||||
collectionVariables: request.collectionVariables,
|
||||
folderVariables: request.folderVariables,
|
||||
requestVariables: request.requestVariables,
|
||||
globalEnvironmentVariables: request.globalEnvironmentVariables,
|
||||
oauth2CredentialVariables: request.oauth2CredentialVariables,
|
||||
}
|
||||
|
||||
grpcRequest = setAuthHeaders(grpcRequest, request, collectionRoot);
|
||||
|
||||
if (grpcRequest.oauth2) {
|
||||
let requestCopy = cloneDeep(grpcRequest);
|
||||
const { oauth2: { grantType, tokenPlacement, tokenHeaderPrefix, tokenQueryKey } = {} } = requestCopy || {};
|
||||
let credentials, credentialsId, oauth2Url, debugInfo;
|
||||
switch (grantType) {
|
||||
case 'authorization_code':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingAuthorizationCode({ request: requestCopy, collectionUid: collection.uid, certsAndProxyConfig }));
|
||||
grpcRequest.oauth2Credentials = { credentials, url: oauth2Url, collectionUid: collection.uid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
|
||||
if (tokenPlacement == 'header') {
|
||||
grpcRequest.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
|
||||
request.url = url?.toString();
|
||||
}
|
||||
catch(error) {}
|
||||
}
|
||||
break;
|
||||
case 'client_credentials':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingClientCredentials({ request: requestCopy, collectionUid: collection.uid, certsAndProxyConfig }));
|
||||
grpcRequest.oauth2Credentials = { credentials, url: oauth2Url, collectionUid: collection.uid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
|
||||
if (tokenPlacement == 'header') {
|
||||
grpcRequest.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
|
||||
request.url = url?.toString();
|
||||
}
|
||||
catch(error) {}
|
||||
}
|
||||
break;
|
||||
case 'password':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingPasswordCredentials({ request: requestCopy, collectionUid: collection.uid, certsAndProxyConfig }));
|
||||
grpcRequest.oauth2Credentials = { credentials, url: oauth2Url, collectionUid: collection.uid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
|
||||
if (tokenPlacement == 'header') {
|
||||
grpcRequest.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
|
||||
request.url = url?.toString();
|
||||
}
|
||||
catch(error) {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
interpolateVars(grpcRequest, envVars, runtimeVariables, processEnvVars);
|
||||
|
||||
return grpcRequest;
|
||||
}
|
||||
const prepareGrpcRequest = require('./prepare-grpc-request');
|
||||
|
||||
// Creating grpcClient at module level so it can be accessed from window-all-closed event
|
||||
let grpcClient;
|
||||
@@ -162,7 +37,7 @@ const registerGrpcEventHandlers = (window) => {
|
||||
const requestCopy = cloneDeep(request);
|
||||
|
||||
|
||||
const preparedRequest = await prepareRequest(requestCopy, collection, environment, runtimeVariables, {});
|
||||
const preparedRequest = await prepareGrpcRequest(requestCopy, collection, environment, runtimeVariables, {});
|
||||
|
||||
// Get certificates and proxy configuration
|
||||
const certsAndProxyConfig = await getCertsAndProxyConfig({
|
||||
@@ -294,7 +169,7 @@ const registerGrpcEventHandlers = (window) => {
|
||||
ipcMain.handle('grpc:load-methods-reflection', async (event, { request, collection, environment, runtimeVariables }) => {
|
||||
try {
|
||||
const requestCopy = cloneDeep(request);
|
||||
const preparedRequest = await prepareRequest(requestCopy, collection, environment, runtimeVariables);
|
||||
const preparedRequest = await prepareGrpcRequest(requestCopy, collection, environment, runtimeVariables);
|
||||
|
||||
// Get certificates and proxy configuration
|
||||
const certsAndProxyConfig = await getCertsAndProxyConfig({
|
||||
@@ -401,7 +276,7 @@ const registerGrpcEventHandlers = (window) => {
|
||||
ipcMain.handle('grpc:generate-grpcurl', async (event, { request, collection, environment, runtimeVariables }) => {
|
||||
try {
|
||||
const requestCopy = cloneDeep(request);
|
||||
const preparedRequest = await prepareRequest(requestCopy, collection, environment, runtimeVariables, {});
|
||||
const preparedRequest = await prepareGrpcRequest(requestCopy, collection, environment, runtimeVariables, {});
|
||||
const interpolationOptions = {
|
||||
envVars: preparedRequest.envVars,
|
||||
runtimeVariables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { interpolate } = require('@usebruno/common');
|
||||
const { each, forOwn, cloneDeep, find } = require('lodash');
|
||||
const { each, forOwn, cloneDeep } = require('lodash');
|
||||
const FormData = require('form-data');
|
||||
|
||||
const getContentType = (headers = {}) => {
|
||||
@@ -65,6 +65,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
};
|
||||
|
||||
request.url = _interpolate(request.url);
|
||||
const isGrpcRequest = request.mode === 'grpc';
|
||||
|
||||
forOwn(request.headers, (value, key) => {
|
||||
delete request.headers[key];
|
||||
@@ -72,9 +73,8 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
|
||||
});
|
||||
|
||||
const contentType = getContentType(request.headers);
|
||||
const isGrpcBody = request.mode === 'grpc';
|
||||
|
||||
if (isGrpcBody) {
|
||||
if (isGrpcRequest) {
|
||||
const jsonDoc = JSON.stringify(request.body);
|
||||
const parsed = _interpolate(jsonDoc, {
|
||||
escapeJSONStrings: true
|
||||
|
||||
121
packages/bruno-electron/src/ipc/network/prepare-grpc-request.js
Normal file
121
packages/bruno-electron/src/ipc/network/prepare-grpc-request.js
Normal file
@@ -0,0 +1,121 @@
|
||||
const { cloneDeep, each, get } = require('lodash');
|
||||
const interpolateVars = require('./interpolate-vars');
|
||||
const { getEnvVars, getTreePathFromCollectionToItem, mergeHeaders, mergeScripts, mergeVars, mergeAuth, getFormattedCollectionOauth2Credentials } = require('../../utils/collection');
|
||||
const { getProcessEnvVars } = require('../../store/process-env');
|
||||
const { getOAuth2TokenUsingPasswordCredentials, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingAuthorizationCode } = require('../../utils/oauth2');
|
||||
const { setAuthHeaders } = require('./prepare-request');
|
||||
|
||||
const processHeaders = (headers) => {
|
||||
Object.entries(headers).forEach(([key, value]) => {
|
||||
if (key?.toLowerCase().endsWith('-bin')) {
|
||||
headers[key] = Buffer.from(value, 'base64');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const prepareGrpcRequest = async (item, collection, environment, runtimeVariables, certsAndProxyConfig = {}) => {
|
||||
const request = item.draft ? item.draft.request : item.request;
|
||||
const collectionRoot = collection?.draft ? get(collection, 'draft', {}) : get(collection, 'root', {});
|
||||
const headers = {};
|
||||
const url = request.url;
|
||||
|
||||
const scriptFlow = collection?.brunoConfig?.scripts?.flow ?? 'sandwich';
|
||||
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
|
||||
if (requestTreePath && requestTreePath.length > 0) {
|
||||
mergeAuth(collection, request, requestTreePath);
|
||||
mergeHeaders(collection, request, requestTreePath);
|
||||
mergeScripts(collection, request, requestTreePath, scriptFlow);
|
||||
mergeVars(collection, request, requestTreePath);
|
||||
request.globalEnvironmentVariables = collection?.globalEnvironmentVariables;
|
||||
request.oauth2CredentialVariables = getFormattedCollectionOauth2Credentials({ oauth2Credentials: collection?.oauth2Credentials });
|
||||
}
|
||||
|
||||
each(get(request, 'headers', []), (h) => {
|
||||
if (h.enabled && h.name.length > 0) {
|
||||
headers[h.name] = h.value;
|
||||
}
|
||||
});
|
||||
|
||||
const processEnvVars = getProcessEnvVars(collection.uid);
|
||||
const envVars = getEnvVars(environment);
|
||||
|
||||
let grpcRequest = {
|
||||
uid: item.uid,
|
||||
mode: request.body.mode,
|
||||
method: request.method,
|
||||
methodType: request.methodType,
|
||||
url,
|
||||
headers,
|
||||
processEnvVars,
|
||||
envVars,
|
||||
runtimeVariables,
|
||||
body: request.body,
|
||||
protoPath: request.protoPath,
|
||||
// Add variable properties for interpolation
|
||||
vars: request.vars,
|
||||
collectionVariables: request.collectionVariables,
|
||||
folderVariables: request.folderVariables,
|
||||
requestVariables: request.requestVariables,
|
||||
globalEnvironmentVariables: request.globalEnvironmentVariables,
|
||||
oauth2CredentialVariables: request.oauth2CredentialVariables
|
||||
};
|
||||
|
||||
grpcRequest = setAuthHeaders(grpcRequest, request, collectionRoot);
|
||||
|
||||
if (grpcRequest.oauth2) {
|
||||
let requestCopy = cloneDeep(grpcRequest);
|
||||
const { oauth2: { grantType, tokenPlacement, tokenHeaderPrefix, tokenQueryKey } = {} } = requestCopy || {};
|
||||
let credentials, credentialsId, oauth2Url, debugInfo;
|
||||
switch (grantType) {
|
||||
case 'authorization_code':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingAuthorizationCode({ request: requestCopy, collectionUid: collection.uid, certsAndProxyConfig }));
|
||||
grpcRequest.oauth2Credentials = { credentials, url: oauth2Url, collectionUid: collection.uid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
|
||||
if (tokenPlacement == 'header') {
|
||||
grpcRequest.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
|
||||
} else {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
|
||||
request.url = url?.toString();
|
||||
} catch (error) { }
|
||||
}
|
||||
break;
|
||||
case 'client_credentials':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingClientCredentials({ request: requestCopy, collectionUid: collection.uid, certsAndProxyConfig }));
|
||||
grpcRequest.oauth2Credentials = { credentials, url: oauth2Url, collectionUid: collection.uid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
|
||||
if (tokenPlacement == 'header') {
|
||||
grpcRequest.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
|
||||
} else {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
|
||||
request.url = url?.toString();
|
||||
} catch (error) { }
|
||||
}
|
||||
break;
|
||||
case 'password':
|
||||
interpolateVars(requestCopy, envVars, runtimeVariables, processEnvVars);
|
||||
({ credentials, url: oauth2Url, credentialsId, debugInfo } = await getOAuth2TokenUsingPasswordCredentials({ request: requestCopy, collectionUid: collection.uid, certsAndProxyConfig }));
|
||||
grpcRequest.oauth2Credentials = { credentials, url: oauth2Url, collectionUid: collection.uid, credentialsId, debugInfo, folderUid: request.oauth2Credentials?.folderUid };
|
||||
if (tokenPlacement == 'header') {
|
||||
grpcRequest.headers['Authorization'] = `${tokenHeaderPrefix} ${credentials?.access_token}`;
|
||||
} else {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
url?.searchParams?.set(tokenQueryKey, credentials?.access_token);
|
||||
request.url = url?.toString();
|
||||
} catch (error) { }
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
interpolateVars(grpcRequest, envVars, runtimeVariables, processEnvVars);
|
||||
processHeaders(grpcRequest.headers);
|
||||
|
||||
return grpcRequest;
|
||||
};
|
||||
|
||||
module.exports = prepareGrpcRequest;
|
||||
@@ -198,8 +198,8 @@ describe('interpolate-vars: interpolateVars', () => {
|
||||
|
||||
describe('With gRPC requests and all variable types', () => {
|
||||
it('Should interpolate collection variables, global environment variables, etc. in gRPC requests', async () => {
|
||||
const request = {
|
||||
method: '/random.Service/randomMethod',
|
||||
const request = {
|
||||
method: '/random.Service/randomMethod',
|
||||
url: '{{baseUrl}}/{{service}}/{{method}}',
|
||||
mode: 'grpc',
|
||||
body: {
|
||||
@@ -225,8 +225,8 @@ describe('interpolate-vars: interpolateVars', () => {
|
||||
});
|
||||
|
||||
it('Should handle gRPC requests with global environment variables', async () => {
|
||||
const request = {
|
||||
method: '/random.Service/randomMethod',
|
||||
const request = {
|
||||
method: '/random.Service/randomMethod',
|
||||
url: '{{globalBaseUrl}}/{{service}}',
|
||||
mode: 'grpc',
|
||||
body: {
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
const { describe, it, expect, beforeEach } = require('@jest/globals');
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('../../src/ipc/network/interpolate-vars');
|
||||
jest.mock('../../src/utils/collection');
|
||||
jest.mock('../../src/store/process-env');
|
||||
jest.mock('../../src/utils/oauth2');
|
||||
jest.mock('../../src/ipc/network/prepare-request');
|
||||
|
||||
const prepareGrpcRequest = require('../../src/ipc/network/prepare-grpc-request');
|
||||
const interpolateVars = require('../../src/ipc/network/interpolate-vars');
|
||||
const { getEnvVars, getTreePathFromCollectionToItem } = require('../../src/utils/collection');
|
||||
const { getProcessEnvVars } = require('../../src/store/process-env');
|
||||
const { setAuthHeaders } = require('../../src/ipc/network/prepare-request');
|
||||
|
||||
describe('prepare-grpc-request: prepareGrpcRequest', () => {
|
||||
let mockItem;
|
||||
let mockCollection;
|
||||
let mockEnvironment;
|
||||
let mockRuntimeVariables;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
getEnvVars.mockReturnValue({});
|
||||
getTreePathFromCollectionToItem.mockReturnValue([]);
|
||||
getProcessEnvVars.mockReturnValue({});
|
||||
setAuthHeaders.mockImplementation((request) => request);
|
||||
interpolateVars.mockImplementation((request) => request);
|
||||
|
||||
mockItem = {
|
||||
uid: 'test-item-uid',
|
||||
request: {
|
||||
method: 'POST',
|
||||
methodType: 'unary',
|
||||
url: 'grpc://localhost:50051',
|
||||
headers: [],
|
||||
body: {
|
||||
mode: 'json',
|
||||
json: '{"test": "data"}'
|
||||
},
|
||||
protoPath: '/path/to/proto.proto',
|
||||
auth: { mode: 'none' }
|
||||
}
|
||||
};
|
||||
|
||||
mockCollection = {
|
||||
uid: 'test-collection-uid',
|
||||
root: {
|
||||
request: {
|
||||
headers: []
|
||||
}
|
||||
},
|
||||
brunoConfig: {
|
||||
scripts: {
|
||||
flow: 'sandwich'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mockEnvironment = {};
|
||||
mockRuntimeVariables = {};
|
||||
});
|
||||
|
||||
describe('Header processing', () => {
|
||||
it('should keep regular headers as strings', async () => {
|
||||
mockItem.request.headers = [
|
||||
{ name: 'content-type', value: 'application/grpc', enabled: true },
|
||||
{ name: 'authorization', value: 'Bearer token123', enabled: true },
|
||||
{ name: 'user-agent', value: 'bruno-client', enabled: true }
|
||||
];
|
||||
|
||||
const result = await prepareGrpcRequest(mockItem, mockCollection, mockEnvironment, mockRuntimeVariables);
|
||||
|
||||
expect(result.headers['content-type']).toBe('application/grpc');
|
||||
expect(result.headers['authorization']).toBe('Bearer token123');
|
||||
expect(result.headers['user-agent']).toBe('bruno-client');
|
||||
expect(typeof result.headers['content-type']).toBe('string');
|
||||
expect(typeof result.headers['authorization']).toBe('string');
|
||||
expect(typeof result.headers['user-agent']).toBe('string');
|
||||
});
|
||||
|
||||
it('should skip disabled headers', async () => {
|
||||
mockItem.request.headers = [
|
||||
{ name: 'content-type', value: 'application/grpc', enabled: false },
|
||||
{ name: 'authorization', value: 'Bearer token123', enabled: false }
|
||||
];
|
||||
|
||||
const result = await prepareGrpcRequest(mockItem, mockCollection, mockEnvironment, mockRuntimeVariables);
|
||||
|
||||
expect(result.headers).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
9
tests/grpc/metadata/fixtures/collection/bruno.json
Normal file
9
tests/grpc/metadata/fixtures/collection/bruno.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "Grpcbin",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
]
|
||||
}
|
||||
27
tests/grpc/metadata/fixtures/collection/sayHello.bru
Normal file
27
tests/grpc/metadata/fixtures/collection/sayHello.bru
Normal file
@@ -0,0 +1,27 @@
|
||||
meta {
|
||||
name: SayHello
|
||||
type: grpc
|
||||
seq: 1
|
||||
}
|
||||
|
||||
grpc {
|
||||
url: grpc://grpcb.in:9000
|
||||
method: /hello.HelloService/SayHello
|
||||
body: grpc
|
||||
auth: inherit
|
||||
methodType: unary
|
||||
}
|
||||
|
||||
metadata {
|
||||
test-bin: hello
|
||||
test: hello
|
||||
}
|
||||
|
||||
body:grpc {
|
||||
name: message 1
|
||||
content: '''
|
||||
{
|
||||
"greeting": "amoveo"
|
||||
}
|
||||
'''
|
||||
}
|
||||
10
tests/grpc/metadata/init-user-data/collection-security.json
Normal file
10
tests/grpc/metadata/init-user-data/collection-security.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"collections": [
|
||||
{
|
||||
"path": "{{projectRoot}}/tests/grpc/metadata/fixtures/collection",
|
||||
"securityConfig": {
|
||||
"jsSandboxMode": "safe"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
11
tests/grpc/metadata/init-user-data/preferences.json
Normal file
11
tests/grpc/metadata/init-user-data/preferences.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": [
|
||||
"{{projectRoot}}/tests/grpc/metadata/fixtures/collection"
|
||||
],
|
||||
"preferences": {
|
||||
"beta": {
|
||||
"nodevm": false
|
||||
}
|
||||
}
|
||||
}
|
||||
32
tests/grpc/metadata/with-bin-metadata.spec.ts
Normal file
32
tests/grpc/metadata/with-bin-metadata.spec.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import { closeAllCollections } from '../../utils/page';
|
||||
|
||||
test.describe('grpc metadata', () => {
|
||||
test.afterAll(async ({ pageWithUserData: page }) => {
|
||||
await closeAllCollections(page);
|
||||
});
|
||||
|
||||
test('should handle binary metadata', async ({ pageWithUserData: page }) => {
|
||||
await test.step('Open the request', async () => {
|
||||
const collection = page.locator('#sidebar-collection-name').filter({ hasText: 'Grpcbin' });
|
||||
await collection.click();
|
||||
const request = page.locator('.collection-item-name').filter({ hasText: 'SayHello' });
|
||||
await request.click();
|
||||
});
|
||||
|
||||
await test.step('Verify request sent successfully', async () => {
|
||||
await page.getByTestId('grpc-send-request-button').click();
|
||||
const statusCode = page.getByTestId('grpc-response-status-code');
|
||||
const statusText = page.getByTestId('grpc-response-status-text');
|
||||
await expect(statusCode).toBeVisible({ timeout: 30000 });
|
||||
await expect(statusText).toBeVisible({ timeout: 30000 });
|
||||
await expect(statusCode).toHaveText(/0/);
|
||||
await expect(statusText).toHaveText(/OK/);
|
||||
});
|
||||
|
||||
/* TODO: Reflection fetching incorrectly marks requests as modified, causing save indicators to appear. This save step prevents test timeouts by clearing the modified state. This is a temporary workaround until the reflection fetching issue is resolved. */
|
||||
await test.step('save request via shortcut', async () => {
|
||||
await page.keyboard.press('Meta+s');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user