diff --git a/package-lock.json b/package-lock.json
index d3ed0bc10..385fc649b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"packages/bruno-query",
"packages/bruno-js",
"packages/bruno-lang",
+ "packages/bruno-tests",
"packages/bruno-testbench",
"packages/bruno-toml",
"packages/bruno-graphql-docs"
@@ -5421,6 +5422,10 @@
"resolved": "packages/bruno-testbench",
"link": true
},
+ "node_modules/@usebruno/tests": {
+ "resolved": "packages/bruno-tests",
+ "link": true
+ },
"node_modules/@usebruno/toml": {
"resolved": "packages/bruno-toml",
"link": true
@@ -6167,6 +6172,16 @@
"version": "1.12.0",
"license": "MIT"
},
+ "node_modules/axios": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
+ "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+ "dependencies": {
+ "follow-redirects": "^1.15.4",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/babel-jest": {
"version": "29.3.1",
"dev": true,
@@ -6347,6 +6362,22 @@
],
"license": "MIT"
},
+ "node_modules/basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "dependencies": {
+ "safe-buffer": "5.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/basic-auth/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"license": "BSD-3-Clause",
@@ -7460,6 +7491,26 @@
"node": ">= 0.6"
}
},
+ "node_modules/cookie-parser": {
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
+ "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
+ "dependencies": {
+ "cookie": "0.4.1",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/node_modules/cookie": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+ "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/cookie-signature": {
"version": "1.0.6",
"license": "MIT"
@@ -8745,7 +8796,6 @@
},
"node_modules/eventemitter3": {
"version": "4.0.7",
- "dev": true,
"license": "MIT"
},
"node_modules/events": {
@@ -8843,6 +8893,14 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/express-basic-auth": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.1.tgz",
+ "integrity": "sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==",
+ "dependencies": {
+ "basic-auth": "^2.0.1"
+ }
+ },
"node_modules/express-xml-bodyparser": {
"version": "0.3.0",
"license": "MIT",
@@ -9214,14 +9272,15 @@
}
},
"node_modules/follow-redirects": {
- "version": "1.15.2",
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
+ "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
- "license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -10231,6 +10290,19 @@
"node": ">= 0.8"
}
},
+ "node_modules/http-proxy": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+ "dependencies": {
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/http-proxy-agent": {
"version": "5.0.0",
"dev": true,
@@ -18557,6 +18629,39 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "packages/bruno-tests": {
+ "version": "0.0.1",
+ "license": "MIT",
+ "dependencies": {
+ "axios": "^1.5.1",
+ "body-parser": "^1.20.0",
+ "cookie-parser": "^1.4.6",
+ "cors": "^2.8.5",
+ "express": "^4.18.1",
+ "express-basic-auth": "^1.2.1",
+ "express-xml-bodyparser": "^0.3.0",
+ "http-proxy": "^1.18.1",
+ "js-yaml": "^4.1.0",
+ "lodash": "^4.17.21",
+ "multer": "^1.4.5-lts.1"
+ }
+ },
+ "packages/bruno-tests/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
+ "packages/bruno-tests/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
"packages/bruno-toml": {
"name": "@usebruno/toml",
"version": "0.1.0",
@@ -22804,6 +22909,37 @@
}
}
},
+ "@usebruno/tests": {
+ "version": "file:packages/bruno-tests",
+ "requires": {
+ "axios": "^1.5.1",
+ "body-parser": "^1.20.0",
+ "cookie-parser": "^1.4.6",
+ "cors": "^2.8.5",
+ "express": "^4.18.1",
+ "express-basic-auth": "^1.2.1",
+ "express-xml-bodyparser": "^0.3.0",
+ "http-proxy": "^1.18.1",
+ "js-yaml": "^4.1.0",
+ "lodash": "^4.17.21",
+ "multer": "^1.4.5-lts.1"
+ },
+ "dependencies": {
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ }
+ }
+ },
"@usebruno/toml": {
"version": "file:packages/bruno-toml",
"requires": {
@@ -23328,6 +23464,16 @@
"aws4": {
"version": "1.12.0"
},
+ "axios": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
+ "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+ "requires": {
+ "follow-redirects": "^1.15.4",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"babel-jest": {
"version": "29.3.1",
"dev": true,
@@ -23441,6 +23587,21 @@
"base64-js": {
"version": "1.5.1"
},
+ "basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "requires": {
+ "safe-buffer": "5.1.2"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ }
+ }
+ },
"bcrypt-pbkdf": {
"version": "1.0.2",
"requires": {
@@ -24287,6 +24448,22 @@
"cookie": {
"version": "0.5.0"
},
+ "cookie-parser": {
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
+ "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
+ "requires": {
+ "cookie": "0.4.1",
+ "cookie-signature": "1.0.6"
+ },
+ "dependencies": {
+ "cookie": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+ "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
+ }
+ }
+ },
"cookie-signature": {
"version": "1.0.6"
},
@@ -25116,8 +25293,7 @@
}
},
"eventemitter3": {
- "version": "4.0.7",
- "dev": true
+ "version": "4.0.7"
},
"events": {
"version": "3.3.0",
@@ -25203,6 +25379,14 @@
}
}
},
+ "express-basic-auth": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.1.tgz",
+ "integrity": "sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==",
+ "requires": {
+ "basic-auth": "^2.0.1"
+ }
+ },
"express-xml-bodyparser": {
"version": "0.3.0",
"requires": {
@@ -25449,7 +25633,9 @@
}
},
"follow-redirects": {
- "version": "1.15.2"
+ "version": "1.15.5",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
+ "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw=="
},
"forever-agent": {
"version": "0.6.1"
@@ -26094,6 +26280,16 @@
"toidentifier": "1.0.1"
}
},
+ "http-proxy": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+ "requires": {
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ }
+ },
"http-proxy-agent": {
"version": "5.0.0",
"dev": true,
diff --git a/package.json b/package.json
index a623adc66..5c4ced347 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"packages/bruno-query",
"packages/bruno-js",
"packages/bruno-lang",
+ "packages/bruno-tests",
"packages/bruno-testbench",
"packages/bruno-toml",
"packages/bruno-graphql-docs"
diff --git a/packages/bruno-tests/.gitignore b/packages/bruno-tests/.gitignore
new file mode 100644
index 000000000..253e9824f
--- /dev/null
+++ b/packages/bruno-tests/.gitignore
@@ -0,0 +1,107 @@
+# JUnit
+collection/junit.xml
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and *not* Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
diff --git a/packages/bruno-tests/.nvmrc b/packages/bruno-tests/.nvmrc
new file mode 100644
index 000000000..9a2a0e219
--- /dev/null
+++ b/packages/bruno-tests/.nvmrc
@@ -0,0 +1 @@
+v20
diff --git a/packages/bruno-tests/collection/auth/basic/via auth/Basic Auth 200.bru b/packages/bruno-tests/collection/auth/basic/via auth/Basic Auth 200.bru
new file mode 100644
index 000000000..de5612b1f
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/basic/via auth/Basic Auth 200.bru
@@ -0,0 +1,21 @@
+meta {
+ name: Basic Auth 200
+ type: http
+ seq: 1
+}
+
+post {
+ url: {{host}}/api/auth/basic/protected
+ body: json
+ auth: basic
+}
+
+auth:basic {
+ username: bruno
+ password: {{basic_auth_password}}
+}
+
+assert {
+ res.status: 200
+ res.body.message: Authentication successful
+}
diff --git a/packages/bruno-tests/collection/auth/basic/via auth/Basic Auth 401.bru b/packages/bruno-tests/collection/auth/basic/via auth/Basic Auth 401.bru
new file mode 100644
index 000000000..0c8388c97
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/basic/via auth/Basic Auth 401.bru
@@ -0,0 +1,21 @@
+meta {
+ name: Basic Auth 400
+ type: http
+ seq: 2
+}
+
+post {
+ url: {{host}}/api/auth/basic/protected
+ body: json
+ auth: none
+}
+
+auth:basic {
+ username: bruno
+ password: invalid
+}
+
+assert {
+ res.status: 401
+ res.body: Unauthorized
+}
diff --git a/packages/bruno-tests/collection/auth/basic/via script/Basic Auth 200.bru b/packages/bruno-tests/collection/auth/basic/via script/Basic Auth 200.bru
new file mode 100644
index 000000000..04d12f226
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/basic/via script/Basic Auth 200.bru
@@ -0,0 +1,26 @@
+meta {
+ name: Basic Auth 200
+ type: http
+ seq: 1
+}
+
+post {
+ url: {{host}}/api/auth/basic/protected
+ body: json
+ auth: none
+}
+
+assert {
+ res.status: eq 200
+ res.body.message: Authentication successful
+}
+
+script:pre-request {
+ const username = "bruno";
+ const password = "della";
+
+ const authString = `${username}:${password}`;
+ const encodedAuthString = require('btoa')(authString);
+
+ req.setHeader("Authorization", `Basic ${encodedAuthString}`);
+}
diff --git a/packages/bruno-tests/collection/auth/basic/via script/Basic Auth 401.bru b/packages/bruno-tests/collection/auth/basic/via script/Basic Auth 401.bru
new file mode 100644
index 000000000..e560115d2
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/basic/via script/Basic Auth 401.bru
@@ -0,0 +1,26 @@
+meta {
+ name: Basic Auth 401
+ type: http
+ seq: 2
+}
+
+post {
+ url: {{host}}/api/auth/basic/protected
+ body: json
+ auth: none
+}
+
+assert {
+ res.status: 401
+ res.body: Unauthorized
+}
+
+script:pre-request {
+ const username = "bruno";
+ const password = "invalid";
+
+ const authString = `${username}:${password}`;
+ const encodedAuthString = require('btoa')(authString);
+
+ req.setHeader("Authorization", `Basic ${encodedAuthString}`);
+}
diff --git a/packages/bruno-tests/collection/auth/bearer/via auth/Bearer Auth 200.bru b/packages/bruno-tests/collection/auth/bearer/via auth/Bearer Auth 200.bru
new file mode 100644
index 000000000..1ca671163
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/bearer/via auth/Bearer Auth 200.bru
@@ -0,0 +1,24 @@
+meta {
+ name: Bearer Auth 200
+ type: http
+ seq: 1
+}
+
+get {
+ url: {{host}}/api/auth/bearer/protected
+ body: none
+ auth: bearer
+}
+
+auth:bearer {
+ token: {{bearer_auth_token}}
+}
+
+assert {
+ res.status: 200
+ res.body.message: Authentication successful
+}
+
+script:post-response {
+ bru.setEnvVar("foo", "bar");
+}
diff --git a/packages/bruno-tests/collection/auth/bearer/via auth/Bearer Auth 401.bru b/packages/bruno-tests/collection/auth/bearer/via auth/Bearer Auth 401.bru
new file mode 100644
index 000000000..4b2a98657
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/bearer/via auth/Bearer Auth 401.bru
@@ -0,0 +1,24 @@
+meta {
+ name: Bearer Auth 401
+ type: http
+ seq: 2
+}
+
+get {
+ url: {{host}}/api/auth/bearer/protected
+ body: none
+ auth: none
+}
+
+auth:bearer {
+ token: {{bearerAuthToken}}zz
+}
+
+assert {
+ res.status: 401
+ res.body.message: Unauthorized
+}
+
+script:post-response {
+ bru.setEnvVar("foo", "bar");
+}
diff --git a/packages/bruno-tests/collection/auth/bearer/via headers/Bearer Auth 200.bru b/packages/bruno-tests/collection/auth/bearer/via headers/Bearer Auth 200.bru
new file mode 100644
index 000000000..a837bdd7e
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/bearer/via headers/Bearer Auth 200.bru
@@ -0,0 +1,28 @@
+meta {
+ name: Bearer Auth 200
+ type: http
+ seq: 1
+}
+
+get {
+ url: {{host}}/api/auth/bearer/protected
+ body: json
+ auth: none
+}
+
+headers {
+ Authorization: Bearer your_secret_token
+}
+
+vars:pre-request {
+ a-c: foo
+}
+
+assert {
+ res.status: 200
+ res.body.message: Authentication successful
+}
+
+script:post-response {
+ bru.setEnvVar("foo", "bar");
+}
diff --git a/packages/bruno-tests/collection/auth/bearer/via headers/Bearer Auth 401.bru b/packages/bruno-tests/collection/auth/bearer/via headers/Bearer Auth 401.bru
new file mode 100644
index 000000000..5bfc64038
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/bearer/via headers/Bearer Auth 401.bru
@@ -0,0 +1,20 @@
+meta {
+ name: Bearer Auth 401
+ type: http
+ seq: 2
+}
+
+get {
+ url: {{host}}/api/auth/bearer/protected
+ body: json
+ auth: none
+}
+
+assert {
+ res.status: 401
+ res.body.message: Unauthorized
+}
+
+script:post-response {
+ bru.setEnvVar("foo", "bar");
+}
diff --git a/packages/bruno-tests/collection/auth/cookie/Check.bru b/packages/bruno-tests/collection/auth/cookie/Check.bru
new file mode 100644
index 000000000..eb7f16f3f
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/cookie/Check.bru
@@ -0,0 +1,11 @@
+meta {
+ name: Check
+ type: http
+ seq: 2
+}
+
+get {
+ url: {{host}}/api/auth/cookie/protected
+ body: none
+ auth: none
+}
diff --git a/packages/bruno-tests/collection/auth/cookie/Login.bru b/packages/bruno-tests/collection/auth/cookie/Login.bru
new file mode 100644
index 000000000..230486495
--- /dev/null
+++ b/packages/bruno-tests/collection/auth/cookie/Login.bru
@@ -0,0 +1,11 @@
+meta {
+ name: Login
+ type: http
+ seq: 1
+}
+
+post {
+ url: {{host}}/api/auth/cookie/login
+ body: none
+ auth: none
+}
diff --git a/packages/bruno-tests/collection/bruno.json b/packages/bruno-tests/collection/bruno.json
new file mode 100644
index 000000000..79602ccd3
--- /dev/null
+++ b/packages/bruno-tests/collection/bruno.json
@@ -0,0 +1,31 @@
+{
+ "version": "1",
+ "name": "bruno-testbench",
+ "type": "collection",
+ "proxy": {
+ "enabled": false,
+ "protocol": "http",
+ "hostname": "{{proxyHostname}}",
+ "port": 4000,
+ "auth": {
+ "enabled": false,
+ "username": "anoop",
+ "password": "password"
+ },
+ "bypassProxy": ""
+ },
+ "scripts": {
+ "moduleWhitelist": ["crypto"],
+ "filesystemAccess": {
+ "allow": true
+ }
+ },
+ "clientCertificates": {
+ "enabled": true,
+ "certs": []
+ },
+ "presets": {
+ "requestType": "http",
+ "requestUrl": "http://localhost:6000"
+ }
+}
diff --git a/packages/bruno-tests/collection/collection.bru b/packages/bruno-tests/collection/collection.bru
new file mode 100644
index 000000000..e31b64995
--- /dev/null
+++ b/packages/bruno-tests/collection/collection.bru
@@ -0,0 +1,22 @@
+headers {
+ check: again
+}
+
+auth {
+ mode: none
+}
+
+auth:basic {
+ username: bruno
+ password: {{basicAuthPassword}}
+}
+
+auth:bearer {
+ token: {{bearerAuthToken}}
+}
+
+docs {
+ # bruno-testbench 🐶
+
+ This is a test collection that I am using to test various functionalities around bruno
+}
diff --git a/packages/bruno-tests/collection/echo/echo json.bru b/packages/bruno-tests/collection/echo/echo json.bru
new file mode 100644
index 000000000..09a8ed90c
--- /dev/null
+++ b/packages/bruno-tests/collection/echo/echo json.bru
@@ -0,0 +1,48 @@
+meta {
+ name: echo json
+ type: http
+ seq: 1
+}
+
+post {
+ url: {{host}}/api/echo/json
+ body: json
+ auth: none
+}
+
+headers {
+ foo: bar
+}
+
+auth:basic {
+ username: asd
+ password: j
+}
+
+auth:bearer {
+ token:
+}
+
+body:json {
+ {
+ "hello": "bruno"
+ }
+}
+
+assert {
+ res.status: eq 200
+}
+
+script:pre-request {
+ bru.setVar("foo", "foo-world-2");
+}
+
+tests {
+ test("should return json", function() {
+ const data = res.getBody();
+ expect(res.getBody()).to.eql({
+ "hello": "bruno"
+ });
+ });
+
+}
diff --git a/packages/bruno-tests/collection/echo/echo plaintext.bru b/packages/bruno-tests/collection/echo/echo plaintext.bru
new file mode 100644
index 000000000..56a23d345
--- /dev/null
+++ b/packages/bruno-tests/collection/echo/echo plaintext.bru
@@ -0,0 +1,27 @@
+meta {
+ name: echo plaintext
+ type: http
+ seq: 3
+}
+
+post {
+ url: {{host}}/api/echo/text
+ body: text
+ auth: none
+}
+
+body:text {
+ hello
+}
+
+assert {
+ res.status: eq 200
+}
+
+tests {
+ test("should return plain text", function() {
+ const data = res.getBody();
+ expect(res.getBody()).to.eql("hello");
+ });
+
+}
diff --git a/packages/bruno-tests/collection/echo/echo xml parsed.bru b/packages/bruno-tests/collection/echo/echo xml parsed.bru
new file mode 100644
index 000000000..586541664
--- /dev/null
+++ b/packages/bruno-tests/collection/echo/echo xml parsed.bru
@@ -0,0 +1,35 @@
+meta {
+ name: echo xml parsed
+ type: http
+ seq: 4
+}
+
+post {
+ url: {{host}}/api/echo/xml-parsed
+ body: xml
+ auth: none
+}
+
+body:xml {
+
+ bruno
+
+}
+
+assert {
+ res.status: eq 200
+}
+
+tests {
+ test("should return parsed xml", function() {
+ const data = res.getBody();
+ expect(res.getBody()).to.eql({
+ "hello": {
+ "world": [
+ "bruno"
+ ]
+ }
+ });
+ });
+
+}
diff --git a/packages/bruno-tests/collection/echo/echo xml raw.bru b/packages/bruno-tests/collection/echo/echo xml raw.bru
new file mode 100644
index 000000000..6a02ac238
--- /dev/null
+++ b/packages/bruno-tests/collection/echo/echo xml raw.bru
@@ -0,0 +1,15 @@
+meta {
+ name: echo xml raw
+ type: http
+ seq: 5
+}
+
+post {
+ url: {{host}}/api/echo/xml-raw
+ body: xml
+ auth: none
+}
+
+body:xml {
+ bruno
+}
diff --git a/packages/bruno-tests/collection/environments/Local.bru b/packages/bruno-tests/collection/environments/Local.bru
new file mode 100644
index 000000000..113502138
--- /dev/null
+++ b/packages/bruno-tests/collection/environments/Local.bru
@@ -0,0 +1,5 @@
+vars {
+ host: http://localhost:80
+ bearer_auth_token: your_secret_token
+ basic_auth_password: della
+}
diff --git a/packages/bruno-tests/collection/environments/Prod.bru b/packages/bruno-tests/collection/environments/Prod.bru
new file mode 100644
index 000000000..557e3f0a6
--- /dev/null
+++ b/packages/bruno-tests/collection/environments/Prod.bru
@@ -0,0 +1,6 @@
+vars {
+ host: https://testbench-sanity.usebruno.com
+ bearer_auth_token: your_secret_token
+ basic_auth_password: della
+ foo: bar
+}
diff --git a/packages/bruno-tests/collection/file.json b/packages/bruno-tests/collection/file.json
new file mode 100644
index 000000000..a967fac5b
--- /dev/null
+++ b/packages/bruno-tests/collection/file.json
@@ -0,0 +1,3 @@
+{
+ "hello": "bruno"
+}
diff --git a/packages/bruno-tests/collection/ping.bru b/packages/bruno-tests/collection/ping.bru
new file mode 100644
index 000000000..96a6eb89f
--- /dev/null
+++ b/packages/bruno-tests/collection/ping.bru
@@ -0,0 +1,63 @@
+meta {
+ name: ping
+ type: http
+ seq: 1
+}
+
+get {
+ url: {{host}}/ping
+ body: none
+ auth: none
+}
+
+auth:awsv4 {
+ accessKeyId: a
+ secretAccessKey: b
+ sessionToken: c
+ service: d
+ region: e
+ profileName: f
+}
+
+vars:pre-request {
+ m4: true
+ pong: pong
+}
+
+assert {
+ res.status: eq 200
+ ~res.body: eq {{pong}}
+}
+
+script:pre-request {
+ console.log(bru.getEnvName());
+}
+
+tests {
+ test("should ping pong", function() {
+ const data = res.getBody();
+ expect(data).to.equal(bru.getVar("pong"));
+ });
+}
+
+docs {
+ # API Documentation
+
+ ## Introduction
+
+ Welcome to the API documentation for [Your API Name]. This document provides instructions on how to make requests to the API and covers available authentication methods.
+
+ ## Authentication
+
+ Before making requests to the API, you need to authenticate your application. [Your API Name] supports the following authentication methods:
+
+ ### API Key
+
+ To use API key authentication, include your API key in the request headers as follows:
+
+ ```http
+ GET /api/endpoint
+ Host: api.example.com
+ Authorization: Bearer YOUR_API_KEY
+
+}
diff --git a/packages/bruno-tests/collection/preview/html/bruno.bru b/packages/bruno-tests/collection/preview/html/bruno.bru
new file mode 100644
index 000000000..a259f231a
--- /dev/null
+++ b/packages/bruno-tests/collection/preview/html/bruno.bru
@@ -0,0 +1,23 @@
+meta {
+ name: bruno
+ type: http
+ seq: 1
+}
+
+get {
+ url: https://www.usebruno.com
+ body: none
+ auth: none
+}
+
+assert {
+ res.status: eq 200
+}
+
+tests {
+ test("should return parsed xml", function() {
+ const headers = res.getHeaders();
+ expect(headers['content-type']).to.eql("text/html; charset=utf-8");
+ });
+
+}
diff --git a/packages/bruno-tests/collection/preview/image/bruno.bru b/packages/bruno-tests/collection/preview/image/bruno.bru
new file mode 100644
index 000000000..bb773d91c
--- /dev/null
+++ b/packages/bruno-tests/collection/preview/image/bruno.bru
@@ -0,0 +1,19 @@
+meta {
+ name: bruno
+ type: http
+ seq: 1
+}
+
+get {
+ url: https://www.usebruno.com/images/landing-2.png
+ body: none
+ auth: none
+}
+
+tests {
+ test("should return parsed xml", function() {
+ const headers = res.getHeaders();
+ expect(headers['content-type']).to.eql("image/png");
+ });
+
+}
diff --git a/packages/bruno-tests/collection/readme.md b/packages/bruno-tests/collection/readme.md
new file mode 100644
index 000000000..1ad4a6e88
--- /dev/null
+++ b/packages/bruno-tests/collection/readme.md
@@ -0,0 +1,15 @@
+# bruno-sanity collection
+
+API Collection to run sanity tests on Bruno.
+
+### Test
+
+```bash
+npm i @usebruno/cli -g
+
+# Test locally
+bru run --env Local
+
+# Test on production
+bru run --env Prod --output junit.xml --format junit
+```
diff --git a/packages/bruno-tests/collection/redirects/Disable Redirect.bru b/packages/bruno-tests/collection/redirects/Disable Redirect.bru
new file mode 100644
index 000000000..31aedd2f2
--- /dev/null
+++ b/packages/bruno-tests/collection/redirects/Disable Redirect.bru
@@ -0,0 +1,26 @@
+meta {
+ name: Disable Redirect
+ type: http
+ seq: 1
+}
+
+get {
+ url: {{host}}/redirect-to-ping
+ body: none
+ auth: none
+}
+
+assert {
+ res.status: 302
+}
+
+script:pre-request {
+ req.setMaxRedirects(0);
+}
+
+tests {
+ test("should disable redirect to ping", function() {
+ const data = res.getBody();
+ expect(data).to.equal('Found. Redirecting to /ping');
+ });
+}
diff --git a/packages/bruno-tests/collection/redirects/Test Redirect.bru b/packages/bruno-tests/collection/redirects/Test Redirect.bru
new file mode 100644
index 000000000..a2b5bd9be
--- /dev/null
+++ b/packages/bruno-tests/collection/redirects/Test Redirect.bru
@@ -0,0 +1,23 @@
+meta {
+ name: Test Redirect
+ type: http
+ seq: 2
+}
+
+get {
+ url: {{host}}/redirect-to-ping
+ body: none
+ auth: none
+}
+
+assert {
+ res.status: 200
+ res.body: pong
+}
+
+tests {
+ test("should redirect to ping", function() {
+ const data = res.getBody();
+ expect(data).to.equal('pong');
+ });
+}
diff --git a/packages/bruno-tests/package.json b/packages/bruno-tests/package.json
new file mode 100644
index 000000000..0135eeb61
--- /dev/null
+++ b/packages/bruno-tests/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "@usebruno/tests",
+ "version": "0.0.1",
+ "description": "",
+ "main": "src/index.js",
+ "scripts": {
+ "start": "node ."
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/usebruno/bruno-testbench.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/usebruno/bruno-testbench/issues"
+ },
+ "homepage": "https://github.com/usebruno/bruno-testbench#readme",
+ "dependencies": {
+ "axios": "^1.5.1",
+ "body-parser": "^1.20.0",
+ "cookie-parser": "^1.4.6",
+ "cors": "^2.8.5",
+ "express": "^4.18.1",
+ "express-basic-auth": "^1.2.1",
+ "express-xml-bodyparser": "^0.3.0",
+ "http-proxy": "^1.18.1",
+ "js-yaml": "^4.1.0",
+ "lodash": "^4.17.21",
+ "multer": "^1.4.5-lts.1"
+ }
+}
diff --git a/packages/bruno-tests/readme.md b/packages/bruno-tests/readme.md
new file mode 100644
index 000000000..0b09a96b2
--- /dev/null
+++ b/packages/bruno-tests/readme.md
@@ -0,0 +1,28 @@
+# bruno-tests
+
+This package is used to test the Bruno CLI.
+We have a collection that sits in the `collection` directory.
+
+### Test Server
+
+This will start the server on port 80 which exposes endpoints that the collection will hit.
+
+```bash
+# install node dependencies
+npm install
+
+# start server
+npm start
+```
+
+### Run Bru CLI on Collection
+
+```bash
+cd collection
+
+node ../../bruno-cli/bin/bru.js run --env Local --output junit.xml --format junit
+```
+
+### License
+
+[MIT](LICENSE)
diff --git a/packages/bruno-tests/src/auth/basic.js b/packages/bruno-tests/src/auth/basic.js
new file mode 100644
index 000000000..1b0a3928f
--- /dev/null
+++ b/packages/bruno-tests/src/auth/basic.js
@@ -0,0 +1,19 @@
+const express = require('express');
+const router = express.Router();
+const basicAuth = require('express-basic-auth');
+
+const users = {
+ bruno: 'della'
+};
+
+const basicAuthMiddleware = basicAuth({
+ users,
+ challenge: true, // Sends a 401 Unauthorized response when authentication fails
+ unauthorizedResponse: 'Unauthorized'
+});
+
+router.post('/protected', basicAuthMiddleware, (req, res) => {
+ res.status(200).json({ message: 'Authentication successful' });
+});
+
+module.exports = router;
diff --git a/packages/bruno-tests/src/auth/bearer.js b/packages/bruno-tests/src/auth/bearer.js
new file mode 100644
index 000000000..d3715aa54
--- /dev/null
+++ b/packages/bruno-tests/src/auth/bearer.js
@@ -0,0 +1,18 @@
+const express = require('express');
+const router = express.Router();
+
+const authenticateToken = (req, res, next) => {
+ const token = req.header('Authorization');
+
+ if (!token || token !== `Bearer your_secret_token`) {
+ return res.status(401).json({ message: 'Unauthorized' });
+ }
+
+ next();
+};
+
+router.get('/protected', authenticateToken, (req, res) => {
+ res.status(200).json({ message: 'Authentication successful' });
+});
+
+module.exports = router;
diff --git a/packages/bruno-tests/src/auth/cookie.js b/packages/bruno-tests/src/auth/cookie.js
new file mode 100644
index 000000000..c73943171
--- /dev/null
+++ b/packages/bruno-tests/src/auth/cookie.js
@@ -0,0 +1,38 @@
+const express = require('express');
+const cookieParser = require('cookie-parser');
+const router = express.Router();
+
+// Initialize the cookie-parser middleware
+router.use(cookieParser());
+
+// Middleware to check if the user is authenticated
+function requireAuth(req, res, next) {
+ const isAuthenticated = req.cookies.isAuthenticated === 'true';
+
+ if (isAuthenticated) {
+ next(); // User is authenticated, continue to the next middleware or route handler
+ } else {
+ res.status(401).json({ message: 'Unauthorized' }); // User is not authenticated, send a 401 Unauthorized response
+ }
+}
+
+// Route to set a cookie when a user logs in
+router.post('/login', (req, res) => {
+ // You should perform authentication here, and if successful, set the cookie.
+ // For demonstration purposes, let's assume the user is authenticated.
+ res.cookie('isAuthenticated', 'true');
+ res.status(200).json({ message: 'Logged in successfully' });
+});
+
+// Route to log out and clear the cookie
+router.post('/logout', (req, res) => {
+ res.clearCookie('isAuthenticated');
+ res.status(200).json({ message: 'Logged out successfully' });
+});
+
+// Protected route that requires authentication
+router.get('/protected', requireAuth, (req, res) => {
+ res.status(200).json({ message: 'Authentication successful' });
+});
+
+module.exports = router;
diff --git a/packages/bruno-tests/src/auth/index.js b/packages/bruno-tests/src/auth/index.js
new file mode 100644
index 000000000..84d93798e
--- /dev/null
+++ b/packages/bruno-tests/src/auth/index.js
@@ -0,0 +1,12 @@
+const express = require('express');
+const router = express.Router();
+
+const authBearer = require('./bearer');
+const authBasic = require('./basic');
+const authCookie = require('./cookie');
+
+router.use('/bearer', authBearer);
+router.use('/basic', authBasic);
+router.use('/cookie', authCookie);
+
+module.exports = router;
diff --git a/packages/bruno-tests/src/echo/index.js b/packages/bruno-tests/src/echo/index.js
new file mode 100644
index 000000000..36a37102e
--- /dev/null
+++ b/packages/bruno-tests/src/echo/index.js
@@ -0,0 +1,22 @@
+const express = require('express');
+const router = express.Router();
+
+router.post('/json', (req, res) => {
+ return res.json(req.body);
+});
+
+router.post('/text', (req, res) => {
+ res.setHeader('Content-Type', 'text/plain');
+ return res.send(req.body);
+});
+
+router.post('/xml-parsed', (req, res) => {
+ return res.send(req.body);
+});
+
+router.post('/xml-raw', (req, res) => {
+ res.setHeader('Content-Type', 'application/xml');
+ return res.send(req.rawBody);
+});
+
+module.exports = router;
diff --git a/packages/bruno-tests/src/index.js b/packages/bruno-tests/src/index.js
new file mode 100644
index 000000000..286cfdad6
--- /dev/null
+++ b/packages/bruno-tests/src/index.js
@@ -0,0 +1,45 @@
+const express = require('express');
+const bodyParser = require('body-parser');
+const xmlparser = require('express-xml-bodyparser');
+const cors = require('cors');
+const multer = require('multer');
+
+const app = new express();
+const port = process.env.PORT || 80;
+const upload = multer();
+
+app.use(cors());
+app.use(xmlparser());
+app.use(bodyParser.text());
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: true }));
+
+const authRouter = require('./auth');
+const echoRouter = require('./echo');
+
+app.use('/api/auth', authRouter);
+app.use('/api/echo', echoRouter);
+
+app.get('/ping', function (req, res) {
+ return res.send('pong');
+});
+
+app.get('/headers', function (req, res) {
+ return res.json(req.headers);
+});
+
+app.get('/query', function (req, res) {
+ return res.json(req.query);
+});
+
+app.post('/echo/multipartForm', upload.none(), function (req, res) {
+ return res.json(req.body);
+});
+
+app.get('/redirect-to-ping', function (req, res) {
+ return res.redirect('/ping');
+});
+
+app.listen(port, function () {
+ console.log(`Testbench started on port: ${port}`);
+});