mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-15 03:41:28 +00:00
ca certs fixes and tests (#5429)
Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
This commit is contained in:
26
.github/actions/common/setup-node-deps/action.yml
vendored
Normal file
26
.github/actions/common/setup-node-deps/action.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: 'Setup Node Dependencies'
|
||||
description: 'Install Node.js and npm dependencies'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: v22.17.0
|
||||
cache: 'npm'
|
||||
cache-dependency-path: './package-lock.json'
|
||||
|
||||
- name: Install node dependencies
|
||||
shell: bash
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Build libraries
|
||||
shell: bash
|
||||
run: |
|
||||
npm run build:graphql-docs
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
npm run build:bruno-converters
|
||||
npm run build:bruno-requests
|
||||
npm run build:bruno-filestore
|
||||
36
.github/actions/ssl/linux/run-basic-ssl-cli-tests/action.yml
vendored
Normal file
36
.github/actions/ssl/linux/run-basic-ssl-cli-tests/action.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: 'Run Basic SSL CLI Tests - Linux'
|
||||
description: 'Run basic SSL CLI tests on Linux'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Run CLI tests
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# navigate to basic SSL test collection directory
|
||||
cd tests/ssl/basic-ssl/collections/badssl
|
||||
|
||||
echo "basic ssl success"
|
||||
# should pass
|
||||
node ../../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit1.xml --insecure --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit1.xml | grep -q "^1$" || exit 1
|
||||
|
||||
echo "with default/system ca certs"
|
||||
# should pass
|
||||
node ../../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit2.xml --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit2.xml | grep -q "^1$" || exit 1
|
||||
|
||||
# navigate to self-signed SSL test collection directory
|
||||
cd ../self-signed-badssl
|
||||
|
||||
echo "self-signed ssl with validation disabled"
|
||||
# should pass
|
||||
node ../../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit3.xml --insecure --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit3.xml | grep -q "^1$" || exit 1
|
||||
|
||||
echo "self-signed ssl with default/system ca certs"
|
||||
echo "request will error"
|
||||
# should fail
|
||||
node ../../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit4.xml --format junit 2>/dev/null || true
|
||||
xmllint --xpath 'count(//testsuite[@errors="1"])' junit4.xml | grep -q "^1$" || exit 1
|
||||
33
.github/actions/ssl/linux/run-custom-ca-certs-cli-tests/action.yml
vendored
Normal file
33
.github/actions/ssl/linux/run-custom-ca-certs-cli-tests/action.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: 'Run Custom CA Certs CLI Tests - Linux'
|
||||
description: 'Run custom CA certs CLI tests on Linux'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Run CLI tests
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# navigate to CA certificates test collection directory
|
||||
cd tests/ssl/custom-ca-certs/collection
|
||||
|
||||
echo "custom valid ca cert"
|
||||
# should pass
|
||||
node ../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit1.xml --cacert ../server/certs/ca-cert.pem --ignore-truststore --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit1.xml | grep -q "^1$" || exit 1
|
||||
|
||||
echo "custom valid ca cert with defaults"
|
||||
# should pass
|
||||
node ../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit2.xml --cacert ../server/certs/ca-cert.pem --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit2.xml | grep -q "^1$" || exit 1
|
||||
|
||||
echo "custom invalid ca cert"
|
||||
echo "request will error"
|
||||
# should fail
|
||||
node ../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit3.xml --cacert ../server/certs/ca-key.pem --ignore-truststore --format junit 2>/dev/null || true
|
||||
xmllint --xpath 'count(//testsuite[@errors="1"])' junit3.xml | grep -q "^1$" || exit 1
|
||||
|
||||
echo "custom invalid ca cert with defaults"
|
||||
# should pass
|
||||
node ../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit4.xml --cacert ../server/certs/ca-key.pem --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit4.xml | grep -q "^1$" || exit 1
|
||||
19
.github/actions/ssl/linux/run-ssl-e2e-tests/action.yml
vendored
Normal file
19
.github/actions/ssl/linux/run-ssl-e2e-tests/action.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: 'Run SSL E2E Tests - Linux'
|
||||
description: 'Run SSL E2E tests on Linux'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Run E2E tests
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
xvfb-run npm run test:e2e:ssl
|
||||
|
||||
- name: Upload Playwright Report
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report-linux
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
26
.github/actions/ssl/linux/setup-ca-certs/action.yml
vendored
Normal file
26
.github/actions/ssl/linux/setup-ca-certs/action.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: 'Setup CA Certificates - Linux'
|
||||
description: 'Setup CA certificates and start test server for custom CA certs tests on Linux'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup CA certificates
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
cd tests/ssl/custom-ca-certs/server
|
||||
|
||||
echo "running certificate setup"
|
||||
node scripts/generate-certs.js
|
||||
|
||||
- name: Start test server
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
cd tests/ssl/custom-ca-certs/server
|
||||
|
||||
echo "starting server in background"
|
||||
node index.js &
|
||||
|
||||
echo "server started with PID: $!"
|
||||
14
.github/actions/ssl/linux/setup-feature-specific-deps/action.yml
vendored
Normal file
14
.github/actions/ssl/linux/setup-feature-specific-deps/action.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: 'Setup Custom CA Certs Feature Dependencies - Linux'
|
||||
description: 'Setup feature-specific dependencies for custom CA certs tests on Linux'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Install additional OS dependencies for custom CA certs
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get --no-install-recommends install -y \
|
||||
libglib2.0-0 libnss3 libdbus-1-3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libgtk-3-0 libasound2t64 \
|
||||
xvfb libxml2-utils
|
||||
|
||||
sudo chown root /home/runner/work/bruno/bruno/node_modules/electron/dist/chrome-sandbox
|
||||
sudo chmod 4755 /home/runner/work/bruno/bruno/node_modules/electron/dist/chrome-sandbox
|
||||
36
.github/actions/ssl/macos/run-basic-ssl-cli-tests/action.yml
vendored
Normal file
36
.github/actions/ssl/macos/run-basic-ssl-cli-tests/action.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: 'Run Basic SSL CLI Tests - macOS'
|
||||
description: 'Run basic SSL CLI tests on macOS'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Run CLI tests
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# navigate to basic SSL test collection directory
|
||||
cd tests/ssl/basic-ssl/collections/badssl
|
||||
|
||||
echo "basic ssl success"
|
||||
# should pass
|
||||
node ../../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit1.xml --insecure --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit1.xml | grep -q "^1$" || exit 1
|
||||
|
||||
echo "with default/system ca certs"
|
||||
# should pass
|
||||
node ../../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit2.xml --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit2.xml | grep -q "^1$" || exit 1
|
||||
|
||||
# navigate to self-signed SSL test collection directory
|
||||
cd ../self-signed-badssl
|
||||
|
||||
echo "self-signed ssl with validation disabled"
|
||||
# should pass
|
||||
node ../../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit3.xml --insecure --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit3.xml | grep -q "^1$" || exit 1
|
||||
|
||||
echo "self-signed ssl with default/system ca certs"
|
||||
echo "request will error"
|
||||
# should fail
|
||||
node ../../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit4.xml --format junit 2>/dev/null || true
|
||||
xmllint --xpath 'count(//testsuite[@errors="1"])' junit4.xml | grep -q "^1$" || exit 1
|
||||
33
.github/actions/ssl/macos/run-custom-ca-certs-cli-tests/action.yml
vendored
Normal file
33
.github/actions/ssl/macos/run-custom-ca-certs-cli-tests/action.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: 'Run Custom CA Certs CLI Tests - macOS'
|
||||
description: 'Run custom CA certs CLI tests on macOS'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Run CLI tests
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# navigate to CA certificates test collection directory
|
||||
cd tests/ssl/custom-ca-certs/collection
|
||||
|
||||
echo "custom valid ca cert"
|
||||
# should pass
|
||||
node ../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit1.xml --cacert ../server/certs/ca-cert.pem --ignore-truststore --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit1.xml | grep -q "^1$" || exit 1
|
||||
|
||||
echo "custom valid ca cert with defaults"
|
||||
# should pass
|
||||
node ../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit2.xml --cacert ../server/certs/ca-cert.pem --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit2.xml | grep -q "^1$" || exit 1
|
||||
|
||||
echo "custom invalid ca cert"
|
||||
echo "request will error"
|
||||
# should fail
|
||||
node ../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit3.xml --cacert ../server/certs/ca-key.pem --ignore-truststore --format junit 2>/dev/null || true
|
||||
xmllint --xpath 'count(//testsuite[@errors="1"])' junit3.xml | grep -q "^1$" || exit 1
|
||||
|
||||
echo "custom invalid ca cert with defaults"
|
||||
# should pass
|
||||
node ../../../../packages/bruno-cli/bin/bru.js run ./request.bru --output junit4.xml --cacert ../server/certs/ca-key.pem --format junit
|
||||
xmllint --xpath 'count(//testsuite[@errors="0"])' junit4.xml | grep -q "^1$" || exit 1
|
||||
17
.github/actions/ssl/macos/run-ssl-e2e-tests/action.yml
vendored
Normal file
17
.github/actions/ssl/macos/run-ssl-e2e-tests/action.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: 'Run SSL E2E Tests - macOS'
|
||||
description: 'Run SSL E2E tests on macOS'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Run E2E tests
|
||||
shell: bash
|
||||
run: |
|
||||
npm run test:e2e:ssl
|
||||
|
||||
- name: Upload Playwright Report
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report-macos
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
26
.github/actions/ssl/macos/setup-ca-certs/action.yml
vendored
Normal file
26
.github/actions/ssl/macos/setup-ca-certs/action.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: 'Setup CA Certificates - macOS'
|
||||
description: 'Setup CA certificates and start test server for custom CA certs tests on macOS'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup CA certificates
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
cd tests/ssl/custom-ca-certs/server
|
||||
|
||||
echo "running certificate setup"
|
||||
node scripts/generate-certs.js
|
||||
|
||||
- name: Start test server
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
cd tests/ssl/custom-ca-certs/server
|
||||
|
||||
echo "starting server in background"
|
||||
node index.js &
|
||||
|
||||
echo "server started with PID: $!"
|
||||
9
.github/actions/ssl/macos/setup-feature-specific-deps/action.yml
vendored
Normal file
9
.github/actions/ssl/macos/setup-feature-specific-deps/action.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
name: 'Setup Custom CA Certs Feature Dependencies - macOS'
|
||||
description: 'Setup feature-specific dependencies for custom CA certs tests on macOS'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Install additional OS dependencies for custom CA certs
|
||||
shell: bash
|
||||
run: |
|
||||
brew install libxml2
|
||||
50
.github/actions/ssl/windows/run-basic-ssl-cli-tests/action.yml
vendored
Normal file
50
.github/actions/ssl/windows/run-basic-ssl-cli-tests/action.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: 'Run Basic SSL CLI Tests - Windows'
|
||||
description: 'Run basic SSL CLI tests on Windows'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Run CLI tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# navigate to basic SSL test collection directory
|
||||
Set-Location tests\ssl\basic-ssl\collections\badssl
|
||||
|
||||
Write-Host "basic ssl success"
|
||||
# should pass
|
||||
$process = Start-Process -FilePath "node" -ArgumentList "..\..\..\..\..\packages\bruno-cli\bin\bru.js run .\request.bru --output junit1.xml --insecure --format junit" -NoNewWindow -Wait -PassThru -RedirectStandardError "nul"
|
||||
[xml]$xml1 = Get-Content junit1.xml
|
||||
$testsuites1 = if ($xml1.testsuites) { $xml1.testsuites.testsuite } else { $xml1.testsuite }
|
||||
$errorCount1 = ($testsuites1 | Where-Object { $_.errors -eq "0" } | Measure-Object).Count
|
||||
if ($errorCount1 -ne 1) { exit 1 }
|
||||
|
||||
Write-Host "with default/system ca certs"
|
||||
# should pass
|
||||
$process = Start-Process -FilePath "node" -ArgumentList "..\..\..\..\..\packages\bruno-cli\bin\bru.js run .\request.bru --output junit2.xml --format junit" -NoNewWindow -Wait -PassThru -RedirectStandardError "nul"
|
||||
[xml]$xml2 = Get-Content junit2.xml
|
||||
$testsuites2 = if ($xml2.testsuites) { $xml2.testsuites.testsuite } else { $xml2.testsuite }
|
||||
$errorCount2 = ($testsuites2 | Where-Object { $_.errors -eq "0" } | Measure-Object).Count
|
||||
if ($errorCount2 -ne 1) { exit 1 }
|
||||
|
||||
# navigate to self-signed SSL test collection directory
|
||||
Set-Location ..\self-signed-badssl
|
||||
|
||||
Write-Host "self-signed ssl with validation disabled"
|
||||
# should pass
|
||||
$process = Start-Process -FilePath "node" -ArgumentList "..\..\..\..\..\packages\bruno-cli\bin\bru.js run .\request.bru --output junit3.xml --insecure --format junit" -NoNewWindow -Wait -PassThru -RedirectStandardError "nul"
|
||||
[xml]$xml3 = Get-Content junit3.xml
|
||||
$testsuites3 = if ($xml3.testsuites) { $xml3.testsuites.testsuite } else { $xml3.testsuite }
|
||||
$errorCount3 = ($testsuites3 | Where-Object { $_.errors -eq "0" } | Measure-Object).Count
|
||||
if ($errorCount3 -ne 1) { exit 1 }
|
||||
|
||||
Write-Host "self-signed ssl with default/system ca certs"
|
||||
Write-Host "request will error"
|
||||
# should fail
|
||||
$process = Start-Process -FilePath "node" -ArgumentList "..\..\..\..\..\packages\bruno-cli\bin\bru.js run .\request.bru --output junit4.xml --format junit" -NoNewWindow -Wait -PassThru -RedirectStandardError "nul"
|
||||
# Ignore the exit code - we expect this to fail
|
||||
[xml]$xml4 = Get-Content junit4.xml
|
||||
$testsuites4 = if ($xml4.testsuites) { $xml4.testsuites.testsuite } else { $xml4.testsuite }
|
||||
$errorCount4 = ($testsuites4 | Where-Object { $_.errors -eq "1" } | Measure-Object).Count
|
||||
if ($errorCount4 -ne 1) { exit 1 }
|
||||
47
.github/actions/ssl/windows/run-custom-ca-certs-cli-tests/action.yml
vendored
Normal file
47
.github/actions/ssl/windows/run-custom-ca-certs-cli-tests/action.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: 'Run Custom CA Certs CLI Tests - Windows'
|
||||
description: 'Run custom CA certs CLI tests on Windows'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Run CLI tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# navigate to CA certificates test collection directory
|
||||
Set-Location tests\ssl\custom-ca-certs\collection
|
||||
|
||||
Write-Host "custom valid ca cert"
|
||||
# should pass
|
||||
$process = Start-Process -FilePath "node" -ArgumentList "..\..\..\..\packages\bruno-cli\bin\bru.js run .\request.bru --output junit1.xml --cacert ..\server\certs\ca-cert.pem --ignore-truststore --format junit" -NoNewWindow -Wait -PassThru -RedirectStandardError "nul"
|
||||
[xml]$xml1 = Get-Content junit1.xml
|
||||
$testsuites1 = if ($xml1.testsuites) { $xml1.testsuites.testsuite } else { $xml1.testsuite }
|
||||
$errorCount1 = ($testsuites1 | Where-Object { $_.errors -eq "0" } | Measure-Object).Count
|
||||
if ($errorCount1 -ne 1) { exit 1 }
|
||||
|
||||
Write-Host "custom valid ca cert with defaults"
|
||||
# should pass
|
||||
$process = Start-Process -FilePath "node" -ArgumentList "..\..\..\..\packages\bruno-cli\bin\bru.js run .\request.bru --output junit2.xml --cacert ..\server\certs\ca-cert.pem --format junit" -NoNewWindow -Wait -PassThru -RedirectStandardError "nul"
|
||||
[xml]$xml2 = Get-Content junit2.xml
|
||||
$testsuites2 = if ($xml2.testsuites) { $xml2.testsuites.testsuite } else { $xml2.testsuite }
|
||||
$errorCount2 = ($testsuites2 | Where-Object { $_.errors -eq "0" } | Measure-Object).Count
|
||||
if ($errorCount2 -ne 1) { exit 1 }
|
||||
|
||||
Write-Host "custom invalid ca cert"
|
||||
Write-Host "request will error"
|
||||
# should fail
|
||||
$process = Start-Process -FilePath "node" -ArgumentList "..\..\..\..\packages\bruno-cli\bin\bru.js run .\request.bru --output junit3.xml --cacert ..\server\certs\ca-key.pem --ignore-truststore --format junit" -NoNewWindow -Wait -PassThru -RedirectStandardError "nul"
|
||||
# Ignore the exit code - we expect this to fail
|
||||
[xml]$xml3 = Get-Content junit3.xml
|
||||
$testsuites3 = if ($xml3.testsuites) { $xml3.testsuites.testsuite } else { $xml3.testsuite }
|
||||
$errorCount3 = ($testsuites3 | Where-Object { $_.errors -eq "1" } | Measure-Object).Count
|
||||
if ($errorCount3 -ne 1) { exit 1 }
|
||||
|
||||
Write-Host "custom invalid ca cert with defaults"
|
||||
# should pass
|
||||
$process = Start-Process -FilePath "node" -ArgumentList "..\..\..\..\packages\bruno-cli\bin\bru.js run .\request.bru --output junit4.xml --cacert ..\server\certs\ca-key.pem --format junit" -NoNewWindow -Wait -PassThru -RedirectStandardError "nul"
|
||||
[xml]$xml4 = Get-Content junit4.xml
|
||||
$testsuites4 = if ($xml4.testsuites) { $xml4.testsuites.testsuite } else { $xml4.testsuite }
|
||||
$errorCount4 = ($testsuites4 | Where-Object { $_.errors -eq "0" } | Measure-Object).Count
|
||||
if ($errorCount4 -ne 1) { exit 1 }
|
||||
17
.github/actions/ssl/windows/run-ssl-e2e-tests/action.yml
vendored
Normal file
17
.github/actions/ssl/windows/run-ssl-e2e-tests/action.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: 'Run SSL E2E Tests - Windows'
|
||||
description: 'Run SSL E2E tests on Windows'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Run E2E tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
npm run test:e2e:ssl
|
||||
|
||||
- name: Upload Playwright Report
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report-windows
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
25
.github/actions/ssl/windows/setup-ca-certs/action.yml
vendored
Normal file
25
.github/actions/ssl/windows/setup-ca-certs/action.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: 'Setup CA Certificates - Windows'
|
||||
description: 'Setup CA certificates and start test server for custom CA certs tests on Windows'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup CA certificates
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Set-Location tests\ssl\custom-ca-certs\server
|
||||
|
||||
Write-Host "running certificate setup"
|
||||
node scripts/generate-certs.js
|
||||
|
||||
- name: Start test server
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
Set-Location tests\ssl\custom-ca-certs\server
|
||||
|
||||
Write-Host "starting server in background"
|
||||
Start-Process -FilePath "node" -ArgumentList "index.js" -PassThru -WindowStyle Hidden
|
||||
91
.github/workflows/ssl-tests.yml
vendored
Normal file
91
.github/workflows/ssl-tests.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
name: SSL Tests
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
tests-for-linux:
|
||||
name: SSL Tests - Linux
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
checks: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node Dependencies
|
||||
uses: ./.github/actions/common/setup-node-deps
|
||||
|
||||
- name: Setup Feature Dependencies
|
||||
uses: ./.github/actions/ssl/linux/setup-feature-specific-deps
|
||||
|
||||
- name: Setup CA Certificates
|
||||
uses: ./.github/actions/ssl/linux/setup-ca-certs
|
||||
|
||||
- name: Run Basic SSL CLI Tests
|
||||
uses: ./.github/actions/ssl/linux/run-basic-ssl-cli-tests
|
||||
|
||||
- name: Run Custom CA Certs CLI Tests
|
||||
uses: ./.github/actions/ssl/linux/run-custom-ca-certs-cli-tests
|
||||
|
||||
- name: Run Custom CA Certs E2E Tests
|
||||
uses: ./.github/actions/ssl/linux/run-ssl-e2e-tests
|
||||
|
||||
tests-for-macos:
|
||||
name: SSL Tests - macOS
|
||||
timeout-minutes: 60
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
checks: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node Dependencies
|
||||
uses: ./.github/actions/common/setup-node-deps
|
||||
|
||||
- name: Setup Feature Dependencies
|
||||
uses: ./.github/actions/ssl/macos/setup-feature-specific-deps
|
||||
|
||||
- name: Setup CA Certificates
|
||||
uses: ./.github/actions/ssl/macos/setup-ca-certs
|
||||
|
||||
- name: Run Basic SSL CLI Tests
|
||||
uses: ./.github/actions/ssl/macos/run-basic-ssl-cli-tests
|
||||
|
||||
- name: Run Custom CA Certs CLI Tests
|
||||
uses: ./.github/actions/ssl/macos/run-custom-ca-certs-cli-tests
|
||||
|
||||
- name: Run Custom CA Certs E2E Tests
|
||||
uses: ./.github/actions/ssl/macos/run-ssl-e2e-tests
|
||||
|
||||
tests-for-windows:
|
||||
name: SSL Tests - Windows
|
||||
timeout-minutes: 60
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
checks: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node Dependencies
|
||||
uses: ./.github/actions/common/setup-node-deps
|
||||
|
||||
- name: Setup CA Certificates
|
||||
uses: ./.github/actions/ssl/windows/setup-ca-certs
|
||||
|
||||
- name: Run Basic SSL CLI Tests
|
||||
uses: ./.github/actions/ssl/windows/run-basic-ssl-cli-tests
|
||||
|
||||
- name: Run Custom CA Certs CLI Tests
|
||||
uses: ./.github/actions/ssl/windows/run-custom-ca-certs-cli-tests
|
||||
|
||||
- name: Run Custom CA Certs E2E Tests
|
||||
uses: ./.github/actions/ssl/windows/run-ssl-e2e-tests
|
||||
@@ -65,7 +65,8 @@
|
||||
"build:electron:snap": "./scripts/build-electron.sh snap",
|
||||
"watch:common": "npm run watch --workspace=packages/bruno-common",
|
||||
"test:codegen": "node playwright/codegen.ts",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e": "playwright test --project=default",
|
||||
"test:e2e:ssl": "playwright test --project=ssl",
|
||||
"test:prettier:web": "npm run test:prettier --workspace=packages/bruno-app",
|
||||
"lint": "node --max_old_space_size=4096 $(npx which eslint)"
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ const { createFormData } = require('../utils/form-data');
|
||||
const { getOAuth2Token } = require('./oauth2');
|
||||
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
||||
const { NtlmClient } = require('axios-ntlm');
|
||||
const { addDigestInterceptor } = require('@usebruno/requests');
|
||||
const { addDigestInterceptor, getCACertificates } = require('@usebruno/requests');
|
||||
const { encodeUrl } = require('@usebruno/common').utils;
|
||||
|
||||
const onConsoleLog = (type, args) => {
|
||||
@@ -151,21 +151,16 @@ const runSingleRequest = async function (
|
||||
const insecure = get(options, 'insecure', false);
|
||||
const noproxy = get(options, 'noproxy', false);
|
||||
const httpsAgentRequestFields = {};
|
||||
|
||||
if (insecure) {
|
||||
httpsAgentRequestFields['rejectUnauthorized'] = false;
|
||||
} else {
|
||||
const caCertArray = [options['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS];
|
||||
const caCert = caCertArray.find((el) => el);
|
||||
if (caCert && caCert.length > 1) {
|
||||
try {
|
||||
let caCertBuffer = fs.readFileSync(caCert);
|
||||
if (!options['ignoreTruststore']) {
|
||||
caCertBuffer += '\n' + tls.rootCertificates.join('\n'); // Augment default truststore with custom CA certificates
|
||||
}
|
||||
httpsAgentRequestFields['ca'] = caCertBuffer;
|
||||
} catch (err) {
|
||||
console.log('Error reading CA cert file:' + caCert, err);
|
||||
}
|
||||
const caCertArray = [options['cacert'], process.env.SSL_CERT_FILE];
|
||||
const caCertFilePath = caCertArray.find((el) => el);
|
||||
let caCertificatesWithCertType = getCACertificates({ caCertFilePath, shouldKeepDefaultCerts: !options['ignoreTruststore'] });
|
||||
let caCertificates = caCertificatesWithCertType.map(certData => certData.certificate);
|
||||
if (caCertificates?.length > 0) {
|
||||
httpsAgentRequestFields['ca'] = caCertificates;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const tls = require('tls');
|
||||
const fs = require('node:fs');
|
||||
const path = require('path');
|
||||
const { get } = require('lodash');
|
||||
const { getCACertificates } = require('@usebruno/requests');
|
||||
const { preferencesUtil } = require('../../store/preferences');
|
||||
const { getBrunoConfig } = require('../../store/bruno-config');
|
||||
const { interpolateString } = require('./interpolate-string');
|
||||
@@ -26,15 +26,28 @@ const getCertsAndProxyConfig = async ({
|
||||
httpsAgentRequestFields['rejectUnauthorized'] = false;
|
||||
}
|
||||
|
||||
if (preferencesUtil.shouldUseCustomCaCertificate()) {
|
||||
const caCertFilePath = preferencesUtil.getCustomCaCertificateFilePath();
|
||||
if (caCertFilePath) {
|
||||
let caCertBuffer = fs.readFileSync(caCertFilePath);
|
||||
if (preferencesUtil.shouldKeepDefaultCaCertificates()) {
|
||||
caCertBuffer += '\n' + tls.rootCertificates.join('\n'); // Augment default truststore with custom CA certificates
|
||||
}
|
||||
httpsAgentRequestFields['ca'] = caCertBuffer;
|
||||
}
|
||||
let caCertFilePath = preferencesUtil.shouldUseCustomCaCertificate() && preferencesUtil.getCustomCaCertificateFilePath();
|
||||
let caCertificatesWithCertType = getCACertificates({
|
||||
caCertFilePath,
|
||||
shouldKeepDefaultCerts: preferencesUtil.shouldKeepDefaultCaCertificates()
|
||||
});
|
||||
|
||||
let caCertificates = caCertificatesWithCertType.map(certData => certData.certificate);
|
||||
let caCertificateDetails = caCertificatesWithCertType.reduce((details, certificateData) => {
|
||||
// get the count for each certificate type
|
||||
details[certificateData.type] += 1;
|
||||
return details;
|
||||
}, {
|
||||
custom: 0,
|
||||
bundled: 0,
|
||||
system: 0,
|
||||
extra: 0
|
||||
});
|
||||
|
||||
// configure HTTPS agent with aggregated CA certificates
|
||||
if (caCertificates?.length > 0) {
|
||||
httpsAgentRequestFields['caCertificateDetails'] = caCertificateDetails;
|
||||
httpsAgentRequestFields['ca'] = caCertificates;
|
||||
}
|
||||
|
||||
const brunoConfig = getBrunoConfig(collectionUid);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const path = require('node:path');
|
||||
const _ = require('lodash');
|
||||
const Store = require('electron-store');
|
||||
const { isDirectory } = require('../utils/filesystem');
|
||||
@@ -12,7 +13,9 @@ class LastOpenedCollections {
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.store.get('lastOpenedCollections') || [];
|
||||
let collections = this.store.get('lastOpenedCollections') || [];
|
||||
collections = collections.map(collection => path.resolve(collection));
|
||||
return collections;
|
||||
}
|
||||
|
||||
add(collectionPath) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const parseUrl = require('url').parse;
|
||||
const https = require('https');
|
||||
const https = require('node:https');
|
||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||
const { interpolateString } = require('../ipc/network/interpolate-string');
|
||||
const { SocksProxyAgent } = require('socks-proxy-agent');
|
||||
@@ -87,6 +87,10 @@ class PatchedHttpsProxyAgent extends HttpsProxyAgent {
|
||||
function createTimelineAgentClass(BaseAgentClass) {
|
||||
return class extends BaseAgentClass {
|
||||
constructor(options, timeline) {
|
||||
|
||||
let caCertificateDetails = options.caCertificateDetails || {};
|
||||
delete options.caCertificateDetails;
|
||||
|
||||
// For proxy agents, the first argument is the proxy URI and the second is options
|
||||
if (options?.proxy) {
|
||||
const { proxy: proxyUri, ...agentOptions } = options;
|
||||
@@ -118,7 +122,7 @@ function createTimelineAgentClass(BaseAgentClass) {
|
||||
const tlsOptions = {
|
||||
...options,
|
||||
rejectUnauthorized: options.rejectUnauthorized ?? true,
|
||||
};
|
||||
};
|
||||
super(tlsOptions);
|
||||
this.timeline = Array.isArray(timeline) ? timeline : [];
|
||||
this.alpnProtocols = options.ALPNProtocols || ['h2', 'http/1.1'];
|
||||
@@ -131,6 +135,8 @@ function createTimelineAgentClass(BaseAgentClass) {
|
||||
message: `SSL validation: ${tlsOptions.rejectUnauthorized ? 'enabled' : 'disabled'}`,
|
||||
});
|
||||
}
|
||||
|
||||
this.caCertificateDetails = caCertificateDetails;
|
||||
}
|
||||
|
||||
|
||||
@@ -146,20 +152,16 @@ function createTimelineAgentClass(BaseAgentClass) {
|
||||
});
|
||||
}
|
||||
|
||||
// Log CAfile and CApath (if possible)
|
||||
if (this.caProvided) {
|
||||
this.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'tls',
|
||||
message: `CA certificates provided`,
|
||||
});
|
||||
} else {
|
||||
this.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'tls',
|
||||
message: `Using system default CA certificates`,
|
||||
});
|
||||
}
|
||||
const bundledCerts = this.caCertificateDetails.bundled || 0;
|
||||
const systemCerts = this.caCertificateDetails.system || 0;
|
||||
const extraCerts = this.caCertificateDetails.extra || 0;
|
||||
const customCerts = this.caCertificateDetails.custom || 0;
|
||||
|
||||
this.timeline.push({
|
||||
timestamp: new Date(),
|
||||
type: 'tls',
|
||||
message: `CA Certificates: ${bundledCerts} bundled, ${systemCerts} system, ${extraCerts} extra, ${customCerts} custom`,
|
||||
});
|
||||
|
||||
// Log "Trying host:port..."
|
||||
this.timeline.push({
|
||||
|
||||
@@ -2,6 +2,6 @@ export { addDigestInterceptor, getOAuth2Token } from './auth';
|
||||
export { GrpcClient, generateGrpcSampleMessage } from './grpc';
|
||||
export { default as cookies } from './cookies';
|
||||
|
||||
export * as network from './network';
|
||||
export { getCACertificates } from './network';
|
||||
|
||||
export * as scripting from './scripting';
|
||||
165
packages/bruno-requests/src/network/ca-cert.ts
Normal file
165
packages/bruno-requests/src/network/ca-cert.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import * as fs from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
type T_CACertSource = 'bundled' | 'system' | 'extra'
|
||||
|
||||
type T_CACertificateData = {
|
||||
type: T_CACertSource | 'custom';
|
||||
certificate: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely executes tls.getCACertificates in a separate Node.js process
|
||||
* Returns empty array if the process fails or exits
|
||||
*/
|
||||
const safeTlsGetCACertificates = (certType: T_CACertSource): string[] => {
|
||||
try {
|
||||
|
||||
// adding seperate script for each cert type
|
||||
// to make sure no unexpected code can be included in the script
|
||||
|
||||
const getBundledCACertificatesScript = `
|
||||
const tls = require('node:tls');
|
||||
try {
|
||||
const result = tls.getCACertificates('bundled');
|
||||
console.log(JSON.stringify(result || []));
|
||||
} catch (error) {
|
||||
console.log('[]');
|
||||
}
|
||||
`;
|
||||
|
||||
const getSystemCACertificatesScript = `
|
||||
const tls = require('node:tls');
|
||||
try {
|
||||
const result = tls.getCACertificates('system');
|
||||
console.log(JSON.stringify(result || []));
|
||||
} catch (error) {
|
||||
console.log('[]');
|
||||
}
|
||||
`;
|
||||
|
||||
const getExtraCACertificatesScript = `
|
||||
const tls = require('node:tls');
|
||||
try {
|
||||
const result = tls.getCACertificates('extra');
|
||||
console.log(JSON.stringify(result || []));
|
||||
} catch (error) {
|
||||
console.log('[]');
|
||||
}
|
||||
`;
|
||||
|
||||
// bundled
|
||||
let script = getBundledCACertificatesScript;
|
||||
|
||||
// system
|
||||
if (certType === 'system') script = getSystemCACertificatesScript;
|
||||
|
||||
// extra
|
||||
if (certType === 'extra') script = getExtraCACertificatesScript;
|
||||
|
||||
const result = spawnSync('node', ['-e', script], {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000, // 5 second timeout
|
||||
stdio: 'pipe',
|
||||
maxBuffer: 1024 * 1024 * 50
|
||||
});
|
||||
|
||||
if (result.error || result.status !== 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const output = result.stdout.trim();
|
||||
|
||||
return JSON.parse(output);
|
||||
} catch (error) {
|
||||
// Return empty array if child process fails
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* retrieves default CA certificates from multiple sources using Node.js TLS API
|
||||
*
|
||||
* this function aggregates CA certificates from three sources:
|
||||
* - 'bundled': mozilla CA certificates bundled with Node.js (same as tls.rootCertificates)
|
||||
* - 'system': CA certificates from the system's trusted certificate store
|
||||
* - 'extra': additional CA certificates loaded from `NODE_EXTRA_CA_CERTS` environment variable
|
||||
*
|
||||
* @returns {string[]} Array of PEM-encoded CA certificate strings
|
||||
* @see https://nodejs.org/docs/latest-v22.x/api/tls.html#tlsgetcacertificatestype
|
||||
*/
|
||||
const getCerts = (sources: T_CACertSource[] = ['bundled', 'system', 'extra']): T_CACertificateData[] => {
|
||||
let certificates: T_CACertificateData[] = [];
|
||||
|
||||
// iterate through different certificate store types to build comprehensive CA list
|
||||
(sources).forEach(certType => {
|
||||
try {
|
||||
// get certificates from specific store type
|
||||
const certList = safeTlsGetCACertificates(certType);
|
||||
|
||||
if (certList && Array.isArray(certList)) {
|
||||
// filter out empty/invalid certificates to ensure we only include valid data
|
||||
const validCertificates = certList.filter(cert => cert && cert.trim());
|
||||
const validCertificatesWithCertType = validCertificates.map(certificate => ({
|
||||
type: certType,
|
||||
certificate
|
||||
}));
|
||||
certificates.push(...validCertificatesWithCertType);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Failed to load ${certType} CA certificates:`, (err as Error).message);
|
||||
}
|
||||
});
|
||||
|
||||
return certificates;
|
||||
};
|
||||
|
||||
const getCACertificates = ({ caCertFilePath, shouldKeepDefaultCerts = true }: { caCertFilePath: string, shouldKeepDefaultCerts: boolean }) : T_CACertificateData[] => {
|
||||
// CA certificate configuration
|
||||
try {
|
||||
let caCertificates: T_CACertificateData[] = [];
|
||||
|
||||
// handle user-provided custom CA certificate file with optional default certificates
|
||||
if (caCertFilePath) {
|
||||
|
||||
// validate custom CA certificate file
|
||||
if (fs.existsSync(caCertFilePath)) {
|
||||
try {
|
||||
const customCert = fs.readFileSync(caCertFilePath, 'utf8');
|
||||
if (customCert && customCert.trim()) {
|
||||
caCertificates.push({
|
||||
type: 'custom',
|
||||
certificate: customCert.trim()
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to read custom CA certificate from ${caCertFilePath}:`, (err as Error).message);
|
||||
throw new Error(`Unable to load custom CA certificate: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// optionally augment custom CA with default certificates
|
||||
if (shouldKeepDefaultCerts) {
|
||||
const defaultCertificates = getCerts(['bundled', 'system', 'extra']);
|
||||
if (defaultCertificates?.length > 0) {
|
||||
caCertificates.push(...defaultCertificates);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// use default CA certificates when no custom configuration is specified
|
||||
const defaultCertificates = getCerts(['bundled', 'system', 'extra']);
|
||||
if (defaultCertificates?.length > 0) {
|
||||
caCertificates.push(...defaultCertificates);
|
||||
}
|
||||
}
|
||||
|
||||
return caCertificates;
|
||||
} catch (err) {
|
||||
console.error('Error configuring CA certificates:', (err as Error).message);
|
||||
throw err; // Re-throw certificate loading errors as they're critical
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getCACertificates
|
||||
};
|
||||
@@ -1 +1,3 @@
|
||||
export { makeAxiosInstance } from './axios-instance';
|
||||
export { makeAxiosInstance } from './axios-instance';
|
||||
|
||||
export { getCACertificates } from './ca-cert';
|
||||
@@ -1 +1 @@
|
||||
v20
|
||||
v22.17.0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
||||
const reporter: any[] = [['list'], ['html']];
|
||||
|
||||
@@ -7,7 +7,6 @@ if (process.env.CI) {
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
fullyParallel: false,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 1 : 0,
|
||||
@@ -20,7 +19,15 @@ export default defineConfig({
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: 'Bruno Electron App'
|
||||
name: 'default',
|
||||
testDir: './tests',
|
||||
testIgnore: [
|
||||
'ssl/**' // custom CA certificate tests require separate server setup and certificate generation
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'ssl',
|
||||
testDir: './tests/ssl'
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ const { _electron: electron } = require('playwright');
|
||||
const electronAppPath = path.join(__dirname, '../packages/bruno-electron');
|
||||
|
||||
exports.startApp = async () => {
|
||||
const app = await electron.launch({ args: [electronAppPath] });
|
||||
const app = await electron.launch({
|
||||
args: [electronAppPath]
|
||||
});
|
||||
const context = await app.context();
|
||||
|
||||
app.process().stdout.on('data', (data) => {
|
||||
|
||||
@@ -48,7 +48,7 @@ export const test = baseTest.extend<
|
||||
|
||||
if (initUserDataPath) {
|
||||
const replacements = {
|
||||
projectRoot: path.join(__dirname, '..')
|
||||
projectRoot: path.posix.join(__dirname, '..')
|
||||
};
|
||||
|
||||
for (const file of await fs.promises.readdir(initUserDataPath)) {
|
||||
|
||||
6
tests/ssl/basic-ssl/collections/badssl/bruno.json
Normal file
6
tests/ssl/basic-ssl/collections/badssl/bruno.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "badssl",
|
||||
"type": "collection",
|
||||
"ignore": ["node_modules", ".git"]
|
||||
}
|
||||
5
tests/ssl/basic-ssl/collections/badssl/package.json
Normal file
5
tests/ssl/basic-ssl/collections/badssl/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "badssl",
|
||||
"version": "1.0.0",
|
||||
"description": "Bruno test collection for basic ssl testing"
|
||||
}
|
||||
15
tests/ssl/basic-ssl/collections/badssl/request.bru
Normal file
15
tests/ssl/basic-ssl/collections/badssl/request.bru
Normal file
@@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: request
|
||||
type: http
|
||||
seq: 6
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://www.badssl.com
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
assert {
|
||||
res.status: eq 200
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "self-signed-badssl",
|
||||
"type": "collection",
|
||||
"ignore": ["node_modules", ".git"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "self-signed-badssl",
|
||||
"version": "1.0.0",
|
||||
"description": "Bruno test collection for basic ssl testing"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: request
|
||||
type: http
|
||||
seq: 6
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://self-signed.badssl.com
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
assert {
|
||||
res.status: eq 200
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { test, expect } from '../../../../../playwright';
|
||||
|
||||
test.describe.serial('basic ssl success', () => {
|
||||
test('developer mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init dev mode
|
||||
await page.getByText('badssl').click();
|
||||
await page.getByLabel('Developer Mode(use only if').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(1);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
|
||||
test('safe mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init safe mode
|
||||
await page.getByText('Developer Mode').click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(1);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/basic-ssl/collections/badssl"],
|
||||
"preferences": {
|
||||
"request": {
|
||||
"sslVerification": true,
|
||||
"customCaCertificate": {
|
||||
"enabled": false,
|
||||
"filePath": ""
|
||||
},
|
||||
"keepDefaultCaCertificates": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/basic-ssl/collections/self-signed-badssl"],
|
||||
"preferences": {
|
||||
"request": {
|
||||
"sslVerification": true,
|
||||
"customCaCertificate": {
|
||||
"enabled": false,
|
||||
"filePath": ""
|
||||
},
|
||||
"keepDefaultCaCertificates": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { test, expect } from '../../../../../playwright';
|
||||
|
||||
test.describe.serial('self signed rejected', () => {
|
||||
test('developer mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init dev mode
|
||||
await page.getByText('self-signed-badssl').click();
|
||||
await page.getByLabel('Developer Mode(use only if').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(0);
|
||||
await expect(parseInt(failed)).toBe(1);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
|
||||
test('safe mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init safe mode
|
||||
await page.getByText('Developer Mode').click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(0);
|
||||
await expect(parseInt(failed)).toBe(1);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/basic-ssl/collections/self-signed-badssl"],
|
||||
"preferences": {
|
||||
"request": {
|
||||
"sslVerification": false,
|
||||
"customCaCertificate": {
|
||||
"enabled": false,
|
||||
"filePath": ""
|
||||
},
|
||||
"keepDefaultCaCertificates": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { test, expect } from '../../../../../playwright';
|
||||
|
||||
test.describe.serial('self signed success with validation disabled', () => {
|
||||
test('developer mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init dev mode
|
||||
await page.getByText('self-signed-badssl').click();
|
||||
await page.getByLabel('Developer Mode(use only if').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(1);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
|
||||
test('safe mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init safe mode
|
||||
await page.getByText('Developer Mode').click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(1);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
});
|
||||
6
tests/ssl/custom-ca-certs/collection/bruno.json
Normal file
6
tests/ssl/custom-ca-certs/collection/bruno.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "custom-ca-certs",
|
||||
"type": "collection",
|
||||
"ignore": ["node_modules", ".git"]
|
||||
}
|
||||
5
tests/ssl/custom-ca-certs/collection/package.json
Normal file
5
tests/ssl/custom-ca-certs/collection/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "custom-ca-certs",
|
||||
"version": "1.0.0",
|
||||
"description": "Bruno test collection for CA certificates and HTTPS server testing"
|
||||
}
|
||||
16
tests/ssl/custom-ca-certs/collection/request.bru
Normal file
16
tests/ssl/custom-ca-certs/collection/request.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: request
|
||||
type: http
|
||||
seq: 6
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://localhost:8090
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
assert {
|
||||
res.status: eq 200
|
||||
res.body: eq helloworld
|
||||
}
|
||||
1
tests/ssl/custom-ca-certs/server/.gitignore
vendored
Normal file
1
tests/ssl/custom-ca-certs/server/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
certs
|
||||
225
tests/ssl/custom-ca-certs/server/helpers/certs.js
Normal file
225
tests/ssl/custom-ca-certs/server/helpers/certs.js
Normal file
@@ -0,0 +1,225 @@
|
||||
const { execCommand, execCommandSilent, detectPlatform } = require('./platform');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
function createCertsDir(certsDir) {
|
||||
if (fs.existsSync(certsDir)) {
|
||||
fs.rmSync(certsDir, { recursive: true, force: true });
|
||||
}
|
||||
fs.mkdirSync(certsDir, { recursive: true });
|
||||
}
|
||||
|
||||
function generateCertificates(certsDir) {
|
||||
execCommand('openssl version');
|
||||
|
||||
// Generate CA private key
|
||||
execCommand('openssl genrsa -out ca-key.pem 4096', certsDir);
|
||||
|
||||
// Create CA configuration file with proper CA extensions and subject (LibreSSL/OpenSSL compatible)
|
||||
const caConfigContent = `[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
x509_extensions = v3_ca
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
C = US
|
||||
ST = Dev
|
||||
L = Local
|
||||
O = Local Dev CA
|
||||
CN = Local Dev CA
|
||||
|
||||
[v3_ca]
|
||||
basicConstraints = critical, CA:TRUE
|
||||
keyUsage = critical, keyCertSign, cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always,issuer:always`;
|
||||
|
||||
fs.writeFileSync(path.join(certsDir, 'ca.conf'), caConfigContent);
|
||||
|
||||
// Generate CA certificate with proper CA extensions using config file (no -subj needed)
|
||||
execCommand('openssl req -new -x509 -key ca-key.pem -out ca-cert.pem -days 3650 -config ca.conf', certsDir);
|
||||
|
||||
// Generate server private key and CSR
|
||||
execCommand('openssl genrsa -out localhost-key.pem 4096', certsDir);
|
||||
|
||||
// Create server CSR configuration file
|
||||
const serverCsrConfigContent = `[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
C = US
|
||||
ST = Dev
|
||||
L = Local
|
||||
O = Local Dev
|
||||
CN = localhost`;
|
||||
|
||||
fs.writeFileSync(path.join(certsDir, 'localhost-csr.conf'), serverCsrConfigContent);
|
||||
execCommand('openssl req -new -key localhost-key.pem -out localhost.csr -config localhost-csr.conf', certsDir);
|
||||
|
||||
// Create server certificate configuration file (LibreSSL/OpenSSL compatible)
|
||||
const serverConfigContent = `[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
C = Country Name
|
||||
ST = State or Province Name
|
||||
L = Locality Name
|
||||
O = Organization Name
|
||||
CN = Common Name
|
||||
|
||||
[v3_req]
|
||||
keyUsage = critical, keyEncipherment, dataEncipherment, digitalSignature
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
basicConstraints = critical, CA:FALSE
|
||||
authorityKeyIdentifier = keyid:always,issuer:always
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = localhost
|
||||
DNS.2 = localhost.localdomain
|
||||
IP.1 = 127.0.0.1
|
||||
IP.2 = ::1
|
||||
IP.3 = ::ffff:127.0.0.1`;
|
||||
|
||||
fs.writeFileSync(path.join(certsDir, 'localhost.conf'), serverConfigContent);
|
||||
execCommand('openssl x509 -req -in localhost.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out localhost-cert.pem -days 730 -extensions v3_req -extfile localhost.conf', certsDir);
|
||||
|
||||
const platform = detectPlatform();
|
||||
if (platform === 'windows') {
|
||||
execCommand('openssl x509 -in ca-cert.pem -outform DER -out ca-cert.der', certsDir);
|
||||
execCommand('openssl pkcs12 -export -out localhost.p12 -inkey localhost-key.pem -in localhost-cert.pem -certfile ca-cert.pem -password pass:', certsDir);
|
||||
execCommand('openssl x509 -in localhost-cert.pem -outform DER -out localhost-cert.der', certsDir);
|
||||
}
|
||||
|
||||
if (platform !== 'windows') {
|
||||
execCommand('chmod 600 ca-key.pem localhost-key.pem', certsDir);
|
||||
execCommand('chmod 644 ca-cert.pem localhost-cert.pem', certsDir);
|
||||
}
|
||||
|
||||
['localhost.csr', 'localhost.conf', 'localhost-csr.conf', 'ca.conf', 'ca-cert.srl'].forEach(file => {
|
||||
const filePath = path.join(certsDir, file);
|
||||
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
||||
});
|
||||
|
||||
// Validate certificate chain
|
||||
validateCertificateChain(certsDir);
|
||||
}
|
||||
|
||||
function validateCertificateChain(certsDir) {
|
||||
try {
|
||||
// Verify CA certificate is valid and has proper CA extensions
|
||||
const caVerifyOutput = execCommandSilent('openssl x509 -in ca-cert.pem -text -noout', certsDir).toString();
|
||||
|
||||
if (!caVerifyOutput.includes('CA:TRUE')) {
|
||||
throw new Error('CA certificate missing basicConstraints=CA:TRUE');
|
||||
}
|
||||
|
||||
if (!caVerifyOutput.includes('Certificate Sign')) {
|
||||
throw new Error('CA certificate missing keyCertSign in keyUsage');
|
||||
}
|
||||
|
||||
// Verify server certificate is valid and signed by CA
|
||||
const serverVerifyOutput = execCommandSilent('openssl x509 -in localhost-cert.pem -text -noout', certsDir).toString();
|
||||
|
||||
if (!serverVerifyOutput.includes('CA:FALSE')) {
|
||||
throw new Error('Server certificate should have basicConstraints=CA:FALSE');
|
||||
}
|
||||
|
||||
if (!serverVerifyOutput.includes('TLS Web Server Authentication')) {
|
||||
throw new Error('Server certificate missing serverAuth in extendedKeyUsage');
|
||||
}
|
||||
|
||||
// Verify certificate chain
|
||||
execCommandSilent('openssl verify -CAfile ca-cert.pem localhost-cert.pem', certsDir);
|
||||
|
||||
console.log('✅ Certificate chain validation passed');
|
||||
} catch (error) {
|
||||
console.error('❌ Certificate validation failed:', error.message);
|
||||
throw new Error(`Certificate validation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function addCAToTruststore(certsDir) {
|
||||
const platform = detectPlatform();
|
||||
|
||||
switch (platform) {
|
||||
case 'macos': {
|
||||
const macCertPath = path.join(certsDir, 'ca-cert.pem');
|
||||
execCommand(`sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${macCertPath}"`);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'linux': {
|
||||
const linuxCertPath = path.join(certsDir, 'ca-cert.pem');
|
||||
execCommand(`sudo cp "${linuxCertPath}" /usr/local/share/ca-certificates/bruno-ca.crt`);
|
||||
execCommand('sudo update-ca-certificates');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'windows': {
|
||||
const winCertPath = path.join(certsDir, 'ca-cert.der');
|
||||
|
||||
// Escape backslashes for PowerShell
|
||||
const psPath = winCertPath.replace(/\\/g, '\\\\');
|
||||
|
||||
// PowerShell .NET method (works reliably in CI)
|
||||
const psCommand = [
|
||||
`$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('${psPath}');`,
|
||||
`$store = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root','LocalMachine');`,
|
||||
`$store.Open('ReadWrite');`,
|
||||
`$store.Add($cert);`,
|
||||
`$store.Close();`,
|
||||
// Verify cert was added by checking if it exists in LocalMachine\Root
|
||||
`$verifyStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root','LocalMachine');`,
|
||||
`$verifyStore.Open('ReadOnly');`,
|
||||
`$found = $verifyStore.Certificates | Where-Object { $_.Thumbprint -eq $cert.Thumbprint };`,
|
||||
`$verifyStore.Close();`,
|
||||
`if (-not $found) { throw 'Certificate was not added to LocalMachine\Root' };`
|
||||
].join(' ');
|
||||
|
||||
execCommand(`powershell -Command "${psCommand}"`);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyCertificates(certsDir) {
|
||||
const platform = detectPlatform();
|
||||
// Core PEM files required for all platforms
|
||||
const requiredFiles = ['ca-cert.pem', 'ca-key.pem', 'localhost-cert.pem', 'localhost-key.pem'];
|
||||
|
||||
// Verify required PEM files exist
|
||||
for (const file of requiredFiles) {
|
||||
const filePath = path.join(certsDir, file);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(`missing certificate file: ${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check Windows-specific files but don't require them (they're optional fallbacks)
|
||||
if (platform === 'windows') {
|
||||
const windowsFiles = ['ca-cert.der', 'localhost.p12', 'localhost-cert.der'];
|
||||
for (const file of windowsFiles) {
|
||||
const filePath = path.join(certsDir, file);
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.log(`✅ Windows certificate file available: ${file}`);
|
||||
} else {
|
||||
console.log(`⚠️ Windows certificate file missing (but not required): ${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createCertsDir,
|
||||
generateCertificates,
|
||||
addCAToTruststore,
|
||||
verifyCertificates
|
||||
};
|
||||
|
||||
60
tests/ssl/custom-ca-certs/server/helpers/platform.js
Normal file
60
tests/ssl/custom-ca-certs/server/helpers/platform.js
Normal file
@@ -0,0 +1,60 @@
|
||||
const { execSync } = require('node:child_process');
|
||||
const os = require('node:os');
|
||||
|
||||
function execCommand(command, cwd = process.cwd()) {
|
||||
return execSync(command, {
|
||||
cwd,
|
||||
stdio: 'inherit',
|
||||
timeout: 30000
|
||||
});
|
||||
}
|
||||
|
||||
function execCommandSilent(command, cwd = process.cwd()) {
|
||||
return execSync(command, {
|
||||
cwd,
|
||||
stdio: 'pipe',
|
||||
timeout: 30000
|
||||
});
|
||||
}
|
||||
|
||||
function detectPlatform() {
|
||||
const platform = os.platform();
|
||||
switch (platform) {
|
||||
case 'darwin': return 'macos';
|
||||
case 'linux': return 'linux';
|
||||
case 'win32': return 'windows';
|
||||
default: throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
function killProcessOnPort(port) {
|
||||
const platform = detectPlatform();
|
||||
|
||||
try {
|
||||
switch (platform) {
|
||||
case 'macos':
|
||||
execCommand(`lsof -ti :${port} | xargs kill -9`);
|
||||
break;
|
||||
case 'linux':
|
||||
execCommand(`lsof -ti :${port} | xargs kill -9`);
|
||||
break;
|
||||
case 'windows':
|
||||
const result = execCommandSilent(`netstat -ano | findstr :${port}`);
|
||||
const lines = result.toString().split('\n');
|
||||
for (const line of lines) {
|
||||
const match = line.trim().match(/\s+(\d+)$/);
|
||||
if (match) {
|
||||
execCommandSilent(`taskkill /F /PID ${match[1]}`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
execCommand,
|
||||
execCommandSilent,
|
||||
detectPlatform,
|
||||
killProcessOnPort
|
||||
};
|
||||
74
tests/ssl/custom-ca-certs/server/index.js
Normal file
74
tests/ssl/custom-ca-certs/server/index.js
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
const https = require('node:https');
|
||||
const { killProcessOnPort } = require('./helpers/platform');
|
||||
|
||||
function createServer(certsDir, port = 8090) {
|
||||
const serverOptions = {
|
||||
key: fs.readFileSync(path.join(certsDir, 'localhost-key.pem')),
|
||||
cert: fs.readFileSync(path.join(certsDir, 'localhost-cert.pem')),
|
||||
ca: fs.readFileSync(path.join(certsDir, 'ca-cert.pem'))
|
||||
}
|
||||
|
||||
const server = https.createServer(serverOptions, (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
res.end('helloworld');
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.listen(port, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(server);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function shutdownServer(server, cleanup) {
|
||||
const shutdown = (signal) => {
|
||||
console.log(`🛑 Received ${signal}, shutting down`);
|
||||
|
||||
if (cleanup) cleanup();
|
||||
|
||||
if (server) {
|
||||
server.close(() => process.exit(0));
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
}
|
||||
|
||||
async function startServer() {
|
||||
const certsDir = path.join(__dirname, 'certs');
|
||||
const port = 8090;
|
||||
|
||||
console.log('🚀 Starting HTTPS test server');
|
||||
|
||||
try {
|
||||
killProcessOnPort(port);
|
||||
|
||||
console.log(`🌐 Creating server on port ${port}`);
|
||||
const server = await createServer(certsDir, port);
|
||||
|
||||
shutdownServer(server, () => {
|
||||
console.log('✨ Server cleanup completed');
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Server startup failed:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
startServer();
|
||||
}
|
||||
|
||||
module.exports = { startServer };
|
||||
106
tests/ssl/custom-ca-certs/server/readme.md
Normal file
106
tests/ssl/custom-ca-certs/server/readme.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# CA Certificates Test Server
|
||||
|
||||
A Node.js HTTPS test server with self-signed certificate generation for testing SSL/TLS connections in Bruno.
|
||||
|
||||
## Overview
|
||||
|
||||
This server provides two main functionalities:
|
||||
1. **Certificate Generation** - Creates a complete CA certificate chain for testing
|
||||
2. **HTTPS Server** - Runs a secure server using the generated certificates
|
||||
|
||||
## Usage
|
||||
|
||||
### 1. Generate Certificates
|
||||
|
||||
Generate the required CA certificates and add them to your system's truststore:
|
||||
|
||||
```bash
|
||||
node scripts/generate-certs.js
|
||||
```
|
||||
|
||||
This will:
|
||||
- Create a `certs/` directory
|
||||
- Generate CA certificate, server certificate, and private keys
|
||||
- Verify the certificate chain
|
||||
- Add the CA certificate to your system's truststore (macOS/Linux/Windows)
|
||||
|
||||
**Generated Files:**
|
||||
- `certs/ca-cert.pem` - Certificate Authority certificate
|
||||
- `certs/ca-key.pem` - CA private key
|
||||
- `certs/localhost-cert.pem` - Server certificate for localhost
|
||||
- `certs/localhost-key.pem` - Server private key
|
||||
|
||||
**Windows-Specific Files (automatically generated on Windows):**
|
||||
- `certs/ca-cert.der` - CA certificate in DER format (for Windows certificate store)
|
||||
- `certs/localhost.p12` - PKCS#12 bundle containing server certificate and key
|
||||
- `certs/localhost-cert.der` - Server certificate in DER format
|
||||
|
||||
### Certificate Installation Details
|
||||
|
||||
The certificate generation script automatically adds the CA certificate to your system's truststore:
|
||||
|
||||
**macOS:** Uses `security add-trusted-cert` to add the CA to the System keychain
|
||||
**Linux:** Copies the CA certificate to `/usr/local/share/ca-certificates/` and runs `update-ca-certificates`
|
||||
**Windows:** Uses PowerShell to add the CA certificate to the LocalMachine\Root certificate store
|
||||
|
||||
> **Note:** On Windows, the script requires Administrator privileges to install certificates to the machine-wide certificate store. If you encounter permission issues, run your terminal as Administrator.
|
||||
|
||||
### 2. Run HTTPS Server
|
||||
|
||||
Start the HTTPS server on port 8090:
|
||||
|
||||
```bash
|
||||
node index.js
|
||||
```
|
||||
|
||||
The server will:
|
||||
- Load certificates from the `certs/` directory
|
||||
- Start an HTTPS server on `https://localhost:8090`
|
||||
- Serve a simple "helloworld" response
|
||||
- Handle graceful shutdown on SIGINT/SIGTERM
|
||||
|
||||
## Testing
|
||||
|
||||
Once the server is running, you can test SSL connections:
|
||||
|
||||
### Unix/Linux/macOS
|
||||
```bash
|
||||
# Test with curl
|
||||
curl https://localhost:8090
|
||||
|
||||
# Test certificate verification
|
||||
openssl s_client -connect localhost:8090 -CAfile certs/ca-cert.pem
|
||||
```
|
||||
|
||||
### Windows
|
||||
```powershell
|
||||
# Test with curl (if available)
|
||||
curl https://localhost:8090
|
||||
|
||||
# Test with PowerShell Invoke-WebRequest
|
||||
Invoke-WebRequest -Uri https://localhost:8090
|
||||
|
||||
# Test certificate verification with OpenSSL
|
||||
openssl s_client -connect localhost:8090 -CAfile certs/ca-cert.pem
|
||||
|
||||
# Verify certificate is installed in Windows certificate store
|
||||
Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object { $_.Subject -like "*Local Dev CA*" }
|
||||
|
||||
# Test with .NET WebClient (alternative method)
|
||||
$client = New-Object System.Net.WebClient
|
||||
$client.DownloadString("https://localhost:8090")
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
server/
|
||||
├── index.js # Main HTTPS server
|
||||
├── scripts/
|
||||
│ └── generate-certs.js # Certificate generation script
|
||||
├── helpers/
|
||||
│ ├── certs.js # Certificate management utilities
|
||||
│ └── platform.js # Platform-specific utilities
|
||||
├── certs/ # Generated certificates (created by script)
|
||||
└── readme.md # This file
|
||||
```
|
||||
46
tests/ssl/custom-ca-certs/server/scripts/generate-certs.js
Normal file
46
tests/ssl/custom-ca-certs/server/scripts/generate-certs.js
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const path = require('node:path');
|
||||
const {
|
||||
createCertsDir,
|
||||
generateCertificates,
|
||||
addCAToTruststore,
|
||||
verifyCertificates
|
||||
} = require('../helpers/certs');
|
||||
|
||||
/**
|
||||
* Setup CA certificates for testing server
|
||||
*/
|
||||
async function setup() {
|
||||
console.log('🔧 Setting up CA certificates for test server');
|
||||
|
||||
const certsDir = path.join(__dirname, '..', 'certs');
|
||||
|
||||
try {
|
||||
console.log('📁 Creating certificates directory');
|
||||
createCertsDir(certsDir);
|
||||
|
||||
console.log('🔐 Generating certificates');
|
||||
generateCertificates(certsDir);
|
||||
|
||||
console.log('✅ Verifying certificates');
|
||||
verifyCertificates(certsDir);
|
||||
|
||||
console.log('🛡️ Adding CA to truststore');
|
||||
addCAToTruststore(certsDir);
|
||||
|
||||
console.log('🎉 CA certificate setup completed successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Generate certs failed:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
setup()
|
||||
.then(() => process.exit(0))
|
||||
.catch(() => process.exit(1));
|
||||
}
|
||||
|
||||
module.exports = { setup };
|
||||
@@ -0,0 +1,57 @@
|
||||
import { test, expect } from '../../../../../playwright';
|
||||
|
||||
test.describe.serial('custom invalid ca cert added to the config and keep default ca certs', () => {
|
||||
test('developer mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init dev mode
|
||||
await page.getByText('custom-ca-certs').click();
|
||||
await page.getByLabel('Developer Mode(use only if').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(1);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
|
||||
test('safe mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init safe mode
|
||||
await page.getByText('Developer Mode').click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(1);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/custom-ca-certs/collection"],
|
||||
"preferences": {
|
||||
"request": {
|
||||
"sslVerification": true,
|
||||
"customCaCertificate": {
|
||||
"enabled": true,
|
||||
"filePath": "{{projectRoot}}/tests/ssl/custom-ca-certs/server/certs/ca-key.pem"
|
||||
},
|
||||
"keepDefaultCaCertificates": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { test, expect } from '../../../../../playwright';
|
||||
|
||||
test.describe.serial('custom invalid ca cert added to the config and NO default ca certs', () => {
|
||||
test('developer mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init dev mode
|
||||
await page.getByText('custom-ca-certs').click();
|
||||
await page.getByLabel('Developer Mode(use only if').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(0);
|
||||
await expect(parseInt(failed)).toBe(1);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
|
||||
test('safe mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init safe mode
|
||||
await page.getByText('Developer Mode').click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(0);
|
||||
await expect(parseInt(failed)).toBe(1);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/custom-ca-certs/collection"],
|
||||
"preferences": {
|
||||
"request": {
|
||||
"sslVerification": true,
|
||||
"customCaCertificate": {
|
||||
"enabled": true,
|
||||
"filePath": "{{projectRoot}}/tests/ssl/custom-ca-certs/server/certs/ca-key.pem"
|
||||
},
|
||||
"keepDefaultCaCertificates": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { test, expect } from '../../../../../playwright';
|
||||
|
||||
test.describe.serial('custom valid ca cert added to the config and keep default ca certs', () => {
|
||||
test('developer mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init dev mode
|
||||
await page.getByText('custom-ca-certs').click();
|
||||
await page.getByLabel('Developer Mode(use only if').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(1);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
|
||||
test('safe mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init safe mode
|
||||
await page.getByText('Developer Mode').click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(1);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/custom-ca-certs/collection"],
|
||||
"preferences": {
|
||||
"request": {
|
||||
"sslVerification": true,
|
||||
"customCaCertificate": {
|
||||
"enabled": true,
|
||||
"filePath": "{{projectRoot}}/tests/ssl/custom-ca-certs/server/certs/ca-cert.pem"
|
||||
},
|
||||
"keepDefaultCaCertificates": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { test, expect } from '../../../../../playwright';
|
||||
|
||||
test.describe.serial('custom valid ca cert added to the config and NO default ca certs', () => {
|
||||
test('developer mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init dev mode
|
||||
await page.getByText('custom-ca-certs').click();
|
||||
await page.getByLabel('Developer Mode(use only if').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(1);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
|
||||
test('safe mode', async ({ pageWithUserData: page }) => {
|
||||
|
||||
// init safe mode
|
||||
await page.getByText('Developer Mode').click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
await expect(parseInt(totalRequests)).toBe(1);
|
||||
await expect(parseInt(passed)).toBe(1);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": ["{{projectRoot}}/tests/ssl/custom-ca-certs/collection"],
|
||||
"preferences": {
|
||||
"request": {
|
||||
"sslVerification": true,
|
||||
"customCaCertificate": {
|
||||
"enabled": true,
|
||||
"filePath": "{{projectRoot}}/tests/ssl/custom-ca-certs/server/certs/ca-cert.pem"
|
||||
},
|
||||
"keepDefaultCaCertificates": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user