Compare commits

...

4 Commits

Author SHA1 Message Date
ramki-bruno
d7cde7d621 Fix failing test case when the selected env is Local 2025-05-27 14:23:47 +05:30
ramki-bruno
427025fc2b Fix: XML body-parser in testbench server is giving error 2025-05-27 14:23:47 +05:30
ramki-bruno
dee6acbf09 Modified CLI-Test workflow to use its own testbench server 2025-05-27 14:23:47 +05:30
ramki-bruno
8cf8321087 Added test requests for cookies and related changes in testbench server
In bruno-testbench(packages/bruno-tests) server
- Added support for customising response status-code in
`POST /api/echo/custom` API using `statusCode` field
- Added `/api/echo/trace` API which returns all the request details as
  an JSON response
- Added SSL support for `localhost` in dev env to test HTTPS requests
2025-05-27 14:23:47 +05:30
24 changed files with 391 additions and 31 deletions

View File

@@ -36,11 +36,26 @@ jobs:
- name: Display Bru CLI Version
run: bru --version
- name: Start Testbench Server
run: |
cd packages/bruno-tests
npm run dev 1>/tmp/tesbench-server-logs.txt 2>&1 &
- name: Run tests
run: |
cd packages/bruno-tests/collection
npm install
bru run --env Prod --output junit.xml --format junit
bru run \
--env Prod \
--cacert ../ssl/localhost.crt \
--output junit.xml --format junit
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: tesbench-server-logs
path: /tmp/tesbench-server-logs.txt
retention-days: 7
- name: Publish Test Report
uses: dorny/test-reporter@v2

View File

@@ -81,11 +81,26 @@ jobs:
npm run build --workspace=packages/bruno-converters
npm run build --workspace=packages/bruno-requests
- name: Start Testbench Server
run: |
cd packages/bruno-tests
npm run dev 1>/tmp/tesbench-server-logs.txt 2>&1 &
- name: Run tests
run: |
cd packages/bruno-tests/collection
npm install
node ../../bruno-cli/bin/bru.js run --env Prod --output junit.xml --format junit
node ../../bruno-cli/bin/bru.js run \
--env Prod \
--cacert ../ssl/localhost.crt \
--output junit.xml --format junit
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: tesbench-server-logs
path: /tmp/tesbench-server-logs.txt
retention-days: 7
- name: Publish Test Report
uses: EnricoMi/publish-unit-test-result-action@v2

View File

@@ -0,0 +1,4 @@
meta {
name: cookies
seq: 13
}

View File

@@ -0,0 +1,4 @@
meta {
name: test secure+https,http
seq: 3
}

View File

@@ -0,0 +1,31 @@
meta {
name: set and verify
type: http
seq: 2
}
post {
url: {{remote_host}}/api/echo/custom
body: json
auth: inherit
}
body:json {
{
"type": "text/plain",
"headers": {
"set-cookie": [
"secure-https=val; Secure",
"nosecure-https-key2=val"
],
"location": "/api/echo/trace"
},
"statusCode": 302,
"content": "hello"
}
}
assert {
res.body.headers.cookie: contains secure-https=val
res.body.headers.cookie: contains nosecure-https-key2=val
}

View File

@@ -0,0 +1,16 @@
meta {
name: verify the cookie with http
type: http
seq: 3
}
post {
url: {{remote_host_http}}/api/echo/trace
body: none
auth: inherit
}
assert {
res.body.headers.cookie: notContains secure-https=val
res.body.headers.cookie: contains nosecure-https-key2=val
}

View File

@@ -0,0 +1,29 @@
meta {
name: test secure+local-ipv4+http
type: http
seq: 2
}
post {
url: http://127.0.0.1:{{local_http_port}}/api/echo/custom
body: json
auth: inherit
}
body:json {
{
"type": "text/plain",
"headers": {
"set-cookie": [
"secure-local-ipv4-http=val; Secure"
],
"location": "/api/echo/trace"
},
"statusCode": 302,
"content": "hello"
}
}
assert {
res.body.headers.cookie: contains secure-local-ipv4-http=val
}

View File

@@ -0,0 +1,29 @@
meta {
name: test secure+local-ipv4+https
type: http
seq: 2
}
post {
url: https://127.0.0.1:{{local_https_port}}/api/echo/custom
body: json
auth: inherit
}
body:json {
{
"type": "text/plain",
"headers": {
"set-cookie": [
"secure-local-ipv4-https=val; Secure"
],
"location": "/api/echo/trace"
},
"statusCode": 302,
"content": "hello"
}
}
assert {
res.body.headers.cookie: contains secure-local-ipv4-https=val
}

View File

@@ -0,0 +1,29 @@
meta {
name: test secure+localhost+http
type: http
seq: 2
}
post {
url: http://localhost:{{local_http_port}}/api/echo/custom
body: json
auth: inherit
}
body:json {
{
"type": "text/plain",
"headers": {
"set-cookie": [
"secure-localhost-http=val; Secure"
],
"location": "/api/echo/trace"
},
"statusCode": 302,
"content": "hello"
}
}
assert {
res.body.headers.cookie: contains secure-localhost-http=val
}

View File

@@ -0,0 +1,29 @@
meta {
name: test secure+localhost+https
type: http
seq: 2
}
post {
url: https://localhost:{{local_https_port}}/api/echo/custom
body: json
auth: inherit
}
body:json {
{
"type": "text/plain",
"headers": {
"set-cookie": [
"secure-localhost-https=val; Secure"
],
"location": "/api/echo/trace"
},
"statusCode": 302,
"content": "hello"
}
}
assert {
res.body.headers.cookie: contains secure-localhost-https=val
}

View File

@@ -0,0 +1,31 @@
meta {
name: test secure+non-local+http
type: http
seq: 2
}
post {
url: http://internal:{{local_http_port}}/api/echo/custom
body: json
auth: inherit
}
body:json {
{
"type": "text/plain",
"headers": {
"set-cookie": [
"secure-non-local-http=val; Secure",
"secure-non-local-http-key2=val"
],
"location": "/api/echo/trace"
},
"statusCode": 302,
"content": "hello"
}
}
assert {
res.body.headers.cookie: notContains secure-non-local-http=val
res.body.headers.cookie: contains secure-non-local-http-key2=val
}

View File

@@ -0,0 +1,27 @@
meta {
name: test set-cookie simple
type: http
seq: 1
}
post {
url: {{host}}/api/echo/custom
body: json
auth: inherit
}
body:json {
{
"type": "text/plain",
"headers": {
"set-cookie": "simple-cookie=val",
"location": "/api/echo/trace"
},
"statusCode": 302,
"content": "hello"
}
}
assert {
res.body.headers.cookie: contains simple-cookie=val
}

View File

@@ -1,6 +1,13 @@
vars {
host: http://localhost:8080
httpfaker: https://www.httpfaker.org
host: {{local_host}}
host_http: {{local_host_http}}
httpfaker: {{host}}
local_http_port: 8081
local_https_port: 8082
local_host: https://localhost:{{local_https_port}}
local_host_http: http://localhost:{{local_http_port}}
remote_host: https://testbench-sanity.usebruno.com
remote_host_http: http://testbench-sanity.usebruno.com
bearer_auth_token: your_secret_token
basic_auth_password: della
env.var1: envVar1
@@ -9,6 +16,8 @@ vars {
foo: bar
testSetEnvVar: bruno-29653
echo-host: https://echo.usebruno.com
env_name: Local
test_get_env_var_key1: foo
client_id: client_id_1
client_secret: client_secret_1
auth_url: http://localhost:8080/api/auth/oauth2/authorization_code/authorize

View File

@@ -1,6 +1,13 @@
vars {
host: https://testbench-sanity.usebruno.com
host: {{remote_host}}
host_http: {{remote_host}}
httpfaker: https://www.httpfaker.org
local_http_port: 8081
local_https_port: 8082
local_host: https://localhost:{{local_https_port}}
local_host_http: http://localhost:{{local_http_port}}
remote_host: https://testbench-sanity.usebruno.com
remote_host_http: http://testbench-sanity.usebruno.com
bearer_auth_token: your_secret_token
basic_auth_password: della
env.var1: envVar1
@@ -9,4 +16,6 @@ vars {
foo: bar
testSetEnvVar: bruno-29653
echo-host: https://echo.usebruno.com
env_name: Prod
test_get_env_var_key1: foo
}

View File

@@ -18,6 +18,6 @@ script:pre-request {
tests {
test("should get env name in scripts", function() {
const testEnvName = bru.getVar("testEnvName");
expect(testEnvName).to.equal("Prod");
expect(testEnvName).to.equal(bru.getEnvVar("env_name"));
});
}

View File

@@ -10,10 +10,9 @@ get {
auth: none
}
tests {
test("should get env var in scripts", function() {
const host = bru.getEnvVar("host")
expect(host).to.equal("https://testbench-sanity.usebruno.com");
const host = bru.getEnvVar("test_get_env_var_key1")
expect(host).to.equal("foo");
});
}
}

View File

@@ -18,6 +18,6 @@ assert {
tests {
test("req.getUrl()", function() {
const url = req.getUrl();
expect(url).to.equal("https://testbench-sanity.usebruno.com/ping");
expect(url).to.equal(bru.getEnvVar("host") + "/ping");
});
}

View File

@@ -33,7 +33,8 @@ tests {
test("res.getResponseTime()", function() {
const responseTime = res.getResponseTime();
expect(typeof responseTime).to.eql("number");
expect(responseTime > 0).to.be.true;
expect(responseTime >= 0).to.be.true;
// 0ms is possible in with local server and CLI run
});
}

View File

@@ -4,6 +4,7 @@
"description": "",
"main": "src/index.js",
"scripts": {
"dev": "NODE_ENV=development node .",
"start": "node ."
},
"repository": {

View File

@@ -49,10 +49,14 @@ router.get('/iso-enc', (req, res) => {
});
router.post("/custom", (req, res) => {
const { headers, content, contentBase64, contentJSON, type } = req.body || {};
const { headers, content, contentBase64, contentJSON, type, statusCode } = req.body || {};
res._headers = {};
if (statusCode) {
res.status(statusCode);
}
if (type) {
res.setHeader('Content-Type', type);
}
@@ -74,4 +78,16 @@ router.post("/custom", (req, res) => {
return res.end();
});
router.all('/trace', (req, res) => {
const requestDetails = {
url: req.url,
method: req.method,
query: req.query,
headers: req.headers,
body: req.body,
rawBody: req.rawBody
};
res.json(requestDetails);
});
module.exports = router;

View File

@@ -6,9 +6,18 @@ const authRouter = require('./auth');
const echoRouter = require('./echo');
const xmlParser = require('./utils/xmlParser');
const multipartRouter = require('./multipart');
const app = new express();
const port = process.env.PORT || 8081;
const httpPort = process.env.HTTP_PORT || 8081;
let requestCounter = 0;
app.use((req, res, next) => {
const requestId = ++requestCounter;
console.log(`Request [${requestId}]: ${req.method} ${req.originalUrl}`);
res.on('finish', () => {
console.log(`Response [${requestId}]: ${res.statusCode}`);
});
next();
});
app.use(cors());
@@ -20,8 +29,8 @@ const saveRawBody = (req, res, buf) => {
app.use(bodyParser.json({ verify: saveRawBody }));
app.use(bodyParser.urlencoded({ extended: true, verify: saveRawBody }));
app.use(bodyParser.text({ verify: saveRawBody }));
app.use(xmlParser());
app.use(express.raw({ type: '*/*', limit: '100mb', verify: saveRawBody }));
app.use(xmlParser());
formDataParser.init(app, express);
@@ -45,6 +54,20 @@ app.get('/redirect-to-ping', function (req, res) {
return res.redirect('/ping');
});
app.listen(port, function () {
console.log(`Testbench started on port: ${port}`);
app.listen(httpPort, function () {
console.log(`Testbench started on port: ${httpPort}(HTTP)`);
});
if (process.env.NODE_ENV === 'development') {
const https = require('https');
const fs = require('fs');
const path = require('path');
const httpsPort = process.env.HTTPS_PORT || 8082;
const sslOptions = {
key: fs.readFileSync(path.join(__dirname, '../ssl/localhost.key')),
cert: fs.readFileSync(path.join(__dirname, '../ssl/localhost.crt'))
};
https.createServer(sslOptions, app).listen(httpsPort, function () {
console.log(`Testbench started on port: ${httpsPort}(HTTPS)`);
});
}

View File

@@ -4,23 +4,18 @@ const xmlParser = () => {
const parser = new XMLParser({
ignoreAttributes: false,
allowBooleanAttributes: true,
attributeNamePrefix: '',
isArray: (name, jpath, isLeafNode) => isLeafNode,
});
return (req, res, next) => {
if (req.is('application/xml') || req.is('text/xml')) {
let data = '';
req.setEncoding('utf8');
req.on('data', (chunk) => {
data += chunk;
});
req.on('end', () => {
try {
req.body = parser.parse(data);
next();
} catch (err) {
res.status(400).send('Invalid XML');
}
});
try {
req.body = parser.parse(req.rawBody);
next();
} catch (err) {
res.status(400).send('Invalid XML');
}
} else {
next();
}

View File

@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIUTnHvaGqAMWT2r5kb1A7gJiY3ZUowDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDUxOTA5MzAwMVoXDTM1MDUx
NzA5MzAwMVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAm4Gt9rW0Ir4PgpLGGWzbhbSC/swPql6tb3R2jirWddjl
o/vq/22+P9Xu47SAlj9N3TcETANfrazATVWzjjm5GtSfWFSgVqW1dCRQ1J2FqESP
279MM5tfqfhBx3Mtsg+K9F4nLTooG9zDhbPx8X0zua4b8HObH3fW0iOQRyiVIUQj
vzCaPjulOrjPxrUuY9hNm+sdmI12UEYY0IGcxhLDs8AFGUQDp/u56BALh8T/91gh
TJQwOQZIFwfUTK3iIIKg4sRvr71yqE485R4F4JTZfIizcKHOhmp3LxBrfQ/OX1Xe
D4dcG2I8Q7BnjxTJIEIsWx0XyKqPy4Wm5HEe+UnaGwIDAQABo4GdMIGaMB0GA1Ud
DgQWBBRXh0x2qA9vCfXozDPG4e2zhXtN+jAfBgNVHSMEGDAWgBRXh0x2qA9vCfXo
zDPG4e2zhXtN+jAPBgNVHRMBAf8EBTADAQH/MEcGA1UdEQRAMD6CCWxvY2FsaG9z
dIIQKi5zdWIxLmxvY2FsaG9zdIIIaW50ZXJuYWyCDyouc3ViMS5pbnRlcm5hbIcE
fwAAATANBgkqhkiG9w0BAQsFAAOCAQEAHdGnVwSttfBhbhKTekI3+o1hYIu7EsUu
NtX6+ZLXWpb2NInYEFMgfKOCpOzRLoh8IfPVolN/3x0N/Zr6PY7WAGp2aPXBumw9
OwPyz9rBHHpXVh2l5OHyhXWTSmyc18xRZdqFfm/uskvH6LIAGOy4b7buPAxBXpeU
LjJfvXLg9vjhoMWyaBDvMGjwopDvrslDD7ZK5R4SjmbsTFvJ304zBTdx3TMCMXpz
rWP2iR+qrRab4prXHCrRnAEc+SYMyCIld3sm6mqdTuohXbEpWDTpulhuvYs3Csmz
m+RTSQ+1NjsEqtkAz82PRl6HPjGaHCivxkQEAdE/x3Lh5LmkfgZCVQ==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbga32tbQivg+C
ksYZbNuFtIL+zA+qXq1vdHaOKtZ12OWj++r/bb4/1e7jtICWP03dNwRMA1+trMBN
VbOOObka1J9YVKBWpbV0JFDUnYWoRI/bv0wzm1+p+EHHcy2yD4r0XictOigb3MOF
s/HxfTO5rhvwc5sfd9bSI5BHKJUhRCO/MJo+O6U6uM/GtS5j2E2b6x2YjXZQRhjQ
gZzGEsOzwAUZRAOn+7noEAuHxP/3WCFMlDA5BkgXB9RMreIggqDixG+vvXKoTjzl
HgXglNl8iLNwoc6GancvEGt9D85fVd4Ph1wbYjxDsGePFMkgQixbHRfIqo/Lhabk
cR75SdobAgMBAAECggEAFUfpOgqiZ4wXAfQlbrP6VBE19A88/ZW4qmNdGMDoBHdK
8JFjFtLlPEXXTAtsUCNtpnOxqi1er0fQMUUkg2GG4WhuPgpRaZ+Cdi3ExLP3PJCA
F2nV0HeCNKJ9bgFnJCets8TGxmZfuCBoFCi3WChIJzBFMTdrkneLSyAEI0N8f8a6
yodjLYvApM+nXj8vibFONdyqEPno41rbQQQXtYHwGm1l+q29sAstEqCPsoiQnEBI
HFsEuTRkQ27xSvhdOr6t5RnFZ/EK1PZ3Q6eeO/+G035HrMbP31Q0Ts4l8BVJ9vEk
dznxGp3NP7NryOuQpo5y7uK0Xd4o1gSQciri+1nyoQKBgQDMgsEhdaEQvuFkVn2C
m/1aBOZWzfdtGSMUwovEPVENpu8CCA3W+ImZ8R0GaMyo0hQfrZ70QiHUDTkPpqe0
15Lx17UOLzHIX5wwKS1vTitFNwqhfCGIJXJLzJpKa69usISEde5FtovFhz6+P3iZ
jTS53LzfHibzSAymVfkeABwiUwKBgQDCqHpsBsPF2TWXEz5zmbRTMonZkH2WW2UM
gdQudmdQf/A6MVJnSE1FfvFHcdDXV1M4KNSWXagIHA9yXmORCt2WdrfGYlo5ZwxR
XnsuXCD/TPo4r/tk+UbsN4S9zg3cmmMJGIydKXDVJ3u0VpMGqHxwr5Oj/leAJ6yA
QnwyCvuAGQKBgCqJO6A49hIkkX6sGpjS1wQlJr+BQWg4pTpJKIbddgFP6kBS6oX+
1afmJXJS09Z9M2BPXVNSefS/91FRCWqst8yDYA4eNM2HTVYbCm8vJALWauihh9vo
ZfhsCt9VvHxaTIW8fZ2UVUf1VFB/pRbS7teFmOcTP2i0YKUsFo9t2GwvAoGARBtS
ig1r3gN7fppbFXNH5nweQyMM7diYaGHcbU08JKw+zv2GyWBAuPoLTWYDHfUNxu5y
QxINwpiexvNDfvIASa7L6ftref4WDzoxeyz81paGTeM04EVfjTJ3nTlFHFRgJkSS
nkJrmgiwfY507rVwpLSpwY0x7EL1VgLtFU1GZbECgYEAuoCOmJb1j9gPGCyoH4Jx
Pn2cHMrmp7DDVD08pq3WBee0MZeXxzilQbagcb6DFda//ohaVhNxMn9suZwSp+5g
itbFwmA0P6tqPKTZa3HZTm3kjm3fShGyubaPiWC6sju0KMkBv4/k5zZzhuvypW5u
qUjOG7crIctZ3QaSye6xwHc=
-----END PRIVATE KEY-----