diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50501a0f1..3e568d38f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,6 +63,7 @@ jobs: contents: read pull-requests: read actions: write # to allow uploading artifacts and cache + checks: write steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 @@ -152,6 +153,111 @@ jobs: # echo "step_test ${{ steps.step_test.outputs.status }}\n" # exit 1 + spec-test: + permissions: + checks: write + pull-requests: write + strategy: + matrix: + os: + - linux + go: + - '1.25' + + include: + # Set the minimum Go patch version for the given Go minor + # Usable via ${{ matrix.GO_SEMVER }} + - go: '1.25' + GO_SEMVER: '~1.25.0' + + # Set some variables per OS, usable via ${{ matrix.VAR }} + # OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories) + # CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing + # SUCCESS: the typical value for $? per OS (Windows/pwsh returns 'True') + - os: linux + OS_LABEL: ubuntu-latest + CADDY_BIN_PATH: ./cmd/caddy/caddy + SUCCESS: 0 + + runs-on: ${{ matrix.OS_LABEL }} + + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install Go + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version: ${{ matrix.GO_SEMVER }} + check-latest: true + + - name: Print Go version and environment + id: vars + shell: bash + run: | + printf "curl version: $(curl --version)\n" + printf "Using go at: $(which go)\n" + printf "Go version: $(go version)\n" + printf "\n\nGo environment:\n\n" + go env + printf "\n\nSystem environment:\n\n" + env + printf "Git version: $(git version)\n\n" + # Calculate the short SHA1 hash of the git commit + echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Get dependencies + run: | + go get -v -t ./... + # mkdir test-results + - name: Build Caddy + working-directory: ./cmd/caddy + env: + CGO_ENABLED: 0 + run: | + go build -cover -tags nobadger,nopgx,nomysql -trimpath -ldflags="-w -s" -v + + - name: Install Hurl + env: + HURL_VERSION: "7.0.0" + run: | + curl --location --remote-name https://github.com/Orange-OpenSource/hurl/releases/download/${HURL_VERSION}/hurl_${HURL_VERSION}_amd64.deb + sudo dpkg -i hurl_${HURL_VERSION}_amd64.deb + hurl --version + + - name: Run Caddy + run: | + ./cmd/caddy/caddy environ + mkdir coverdir + export GOCOVERDIR=./coverdir + ./cmd/caddy/caddy start + sleep 5 + + - name: Run tests with Hurl + run: | + mkdir hurl-report + find . -name *.hurl -exec hurl --jobs 1 --variables-file caddytest/spec/hurl_vars.properties --very-verbose --verbose --test --report-junit hurl-report/junit.xml --color {} \; + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@3a74b2957438d0b6e2e61d67b05318aa25c9e6c6 # v2.20.0 + with: + files: | + hurl-report/junit.xml + + - name: Generate Coverage Data + run: | + export GOCOVERDIR=./coverdir + ./cmd/caddy/caddy stop + go tool covdata textfmt -i=coverdir -o hurl-report/caddy_cover_${{ steps.vars.outputs.short_sha }}.txt + go tool cover -html hurl-report/caddy_cover_${{ steps.vars.outputs.short_sha }}.txt -o hurl-report/caddy_cover_${{ steps.vars.outputs.short_sha }}.html + + + - name: Publish Coverage Profile + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + path: hurl-report/caddy_cover_${{ steps.vars.outputs.short_sha }}.html + compression-level: 0 + s390x-test: name: test (s390x on IBM Z) permissions: diff --git a/caddytest/spec/http/basicauth/spec.hurl b/caddytest/spec/http/basicauth/spec.hurl new file mode 100644 index 000000000..0362d3dbd --- /dev/null +++ b/caddytest/spec/http/basicauth/spec.hurl @@ -0,0 +1,38 @@ +# Configure Caddy +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs + debug +} +localhost { + log + basic_auth { + john $2a$14$x4HlYwA9Zeer4RkMEYbUzug9XxWmncneR.dcMs.UjalR95URnHg5. + } + respond "Hello, World!" +} +``` + +# requests without `Authorization` header are rejected with 401 +GET https://localhost:9443 +[Options] +insecure: true +HTTP 401 +[Asserts] +header "WWW-Authenticate" == "Basic realm=\"restricted\"" + + +# requests with `Authorization` header are accepted with 200 +GET https://localhost:9443 +[BasicAuth] +john:password +[Options] +insecure: true +HTTP 200 +[Asserts] +`Hello, World!` diff --git a/caddytest/spec/http/error/spec.hurl b/caddytest/spec/http/error/spec.hurl new file mode 100644 index 000000000..8e2798da6 --- /dev/null +++ b/caddytest/spec/http/error/spec.hurl @@ -0,0 +1,150 @@ +# Configure Caddy with error directive +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + error /forbidden* "Access denied" 403 + respond "OK" +} +``` + +# error directive triggers 403 for matching paths +GET https://localhost:9443/forbidden/resource +[Options] +insecure: true +HTTP 403 + + +# error directive does not trigger for non-matching paths +GET https://localhost:9443/allowed +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "OK" + + +# Configure Caddy with error and handle_errors +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + error /admin* "Forbidden" 403 + handle_errors { + respond "Custom error: {err.status_code} - {err.status_text}" + } +} +``` + +# error with handle_errors shows custom error page +GET https://localhost:9443/admin/panel +[Options] +insecure: true +HTTP 403 +[Asserts] +body == "Custom error: 403 - Forbidden" + + +# Configure Caddy with conditional error +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + @admin path /admin* + error @admin 404 + respond "Public content" +} +``` + +# error with named matcher triggers on match +GET https://localhost:9443/admin/users +[Options] +insecure: true +HTTP 404 + + +# error with named matcher doesn't trigger on non-match +GET https://localhost:9443/public +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "Public content" + + +# Configure Caddy with error for specific methods +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + @post method POST + error @post "Method not allowed" 405 + respond "GET OK" +} +``` + +# error blocks POST requests +POST https://localhost:9443 +[Options] +insecure: true +HTTP 405 + + +# error allows GET requests +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "GET OK" + + +# Configure Caddy with dynamic error message +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + error /error* "Path {path} not found" 404 + handle_errors { + respond "{err.message}" + } +} +``` + +# error message can use placeholders +GET https://localhost:9443/error/test +[Options] +insecure: true +HTTP 404 +[Asserts] +body == "Path /error/test not found" diff --git a/caddytest/spec/http/file_server/assets/indexed/index.html b/caddytest/spec/http/file_server/assets/indexed/index.html new file mode 100644 index 000000000..da5de44a0 --- /dev/null +++ b/caddytest/spec/http/file_server/assets/indexed/index.html @@ -0,0 +1,9 @@ + + + + Index.html Title + + + Index.html + + \ No newline at end of file diff --git a/caddytest/spec/http/file_server/assets/indexed/index.txt b/caddytest/spec/http/file_server/assets/indexed/index.txt new file mode 100644 index 000000000..21fc0c67e --- /dev/null +++ b/caddytest/spec/http/file_server/assets/indexed/index.txt @@ -0,0 +1 @@ +index.txt \ No newline at end of file diff --git a/caddytest/spec/http/file_server/assets/unindexed/.gitkeep b/caddytest/spec/http/file_server/assets/unindexed/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/caddytest/spec/http/file_server/spec.hurl b/caddytest/spec/http/file_server/spec.hurl new file mode 100644 index 000000000..be9504e15 --- /dev/null +++ b/caddytest/spec/http/file_server/spec.hurl @@ -0,0 +1,119 @@ +# Configure Caddy with default configuration +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs + debug +} +localhost { + root {{indexed_root}} + file_server +} +``` + +# requests without specific file receive index file per +# the default index list: index.html, index.txt +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +``` + + + + Index.html Title + + + Index.html + +``` + + +# if index.txt is specifically requested, we expect index.txt +GET https://localhost:9443/index.txt +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "index.txt" + +# requests for sub-folder followed by .. result in sanitized path +GET https://localhost:9443/non-existent/../index.txt +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "index.txt" + +# results out of root folder are sanitized, +# and conform to default index list sequence. +GET https://localhost:9443/../ +[Options] +insecure: true +HTTP 200 +[Asserts] +``` + + + + Index.html Title + + + Index.html + +``` + + +# Configure Caddy with custsom index "index.txt" +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs + debug +} +localhost { + root {{indexed_root}} + file_server { + index index.txt + } +} +``` + +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "index.txt" + + +# Configure with a root not containing index files +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs + debug +} +localhost { + root {{unindexed_root}} + file_server +} +``` + +GET https://localhost:9443 +[Options] +insecure: true +HTTP 404 \ No newline at end of file diff --git a/caddytest/spec/http/forward_auth/spec.hurl b/caddytest/spec/http/forward_auth/spec.hurl new file mode 100644 index 000000000..e863c9e9a --- /dev/null +++ b/caddytest/spec/http/forward_auth/spec.hurl @@ -0,0 +1,132 @@ +# Configure Caddy with forward_auth directive +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + forward_auth localhost:9080 { + uri /auth + } + respond "Protected content" +} +http://localhost:9080 { + handle /auth { + respond 200 + } +} +``` + +# forward_auth allows request when auth endpoint returns 2xx +GET https://localhost:9443 +[Options] +delay: 500ms +insecure: true +HTTP 200 +[Asserts] +body == "Protected content" + + +# Configure Caddy with forward_auth rejecting +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + forward_auth localhost:9080 { + uri /auth + } + respond "Protected content" +} +http://localhost:9080 { + handle /auth { + respond 401 + } +} +``` + +# forward_auth blocks request when auth endpoint returns 4xx +GET https://localhost:9443 +[Options] +delay: 500ms +insecure: true +HTTP 401 + + +# Configure Caddy with forward_auth copying headers +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + forward_auth localhost:9080 { + uri /auth + copy_headers X-User-ID X-User-Email + } + respond "User: {header.X-User-ID}, Email: {header.X-User-Email}" +} +http://localhost:9080 { + handle /auth { + header X-User-ID "user123" + header X-User-Email "user@example.com" + respond 200 + } +} +``` + +# forward_auth copies specified headers from auth response +GET https://localhost:9443 +[Options] +delay: 500ms +insecure: true +HTTP 200 +[Asserts] +body == "User: user123, Email: user@example.com" + + +# Configure Caddy with forward_auth and custom headers +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + forward_auth localhost:9080 { + uri /auth + header_up X-Original-URL {uri} + } + respond "OK" +} +http://localhost:9080 { + handle /auth { + respond "{header.X-Original-URL}" + } +} +``` + +# forward_auth can send custom headers to auth endpoint +GET https://localhost:9443/test/path +[Options] +delay: 500ms +insecure: true +HTTP 200 +[Asserts] +body == "OK" diff --git a/caddytest/spec/http/headers/spec.hurl b/caddytest/spec/http/headers/spec.hurl new file mode 100644 index 000000000..909402b6f --- /dev/null +++ b/caddytest/spec/http/headers/spec.hurl @@ -0,0 +1,22 @@ +# Configure Caddy +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs + debug +} +localhost { + header "X-Custom-Header" "Custom-Value" +} +``` + +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +header "X-Custom-Header" == "Custom-Value" diff --git a/caddytest/spec/http/request_header/spec.hurl b/caddytest/spec/http/request_header/spec.hurl new file mode 100644 index 000000000..44e8c3d06 --- /dev/null +++ b/caddytest/spec/http/request_header/spec.hurl @@ -0,0 +1,190 @@ +# Configure Caddy with request_header directive +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + request_header X-Custom-Header "CustomValue" + respond "{header.X-Custom-Header}" +} +``` + +# request_header adds headers to request +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "CustomValue" + + +# Configure Caddy with request_header removing headers +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + request_header -User-Agent + respond "UA: {header.User-Agent}" +} +``` + +# request_header can remove headers +GET https://localhost:9443 +User-Agent: TestAgent/1.0 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "UA: " + + +# Configure Caddy with request_header replacing headers +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + request_header Host "example.com" + respond "Host: {host}" +} +``` + +# request_header can replace Host header +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "Host: example.com" + + +# Configure Caddy with request_header using placeholders +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + request_header X-Original-Path {path} + respond "Path: {header.X-Original-Path}" +} +``` + +# request_header can use placeholders +GET https://localhost:9443/test/path +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "Path: /test/path" + + +# Configure Caddy with conditional request_header +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + @api path /api/* + request_header @api X-API "true" + respond "API: {header.X-API}" +} +``` + +# request_header applies conditionally based on matcher +GET https://localhost:9443/api/test +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "API: true" + + +# request_header doesn't apply when matcher doesn't match +GET https://localhost:9443/other +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "API: " + + +# Configure Caddy with multiple request_header operations +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + request_header X-First "1" + request_header X-Second "2" + request_header X-Third "3" + respond "{header.X-First},{header.X-Second},{header.X-Third}" +} +``` + +# multiple request_header directives are applied +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "1,2,3" + + +# Configure Caddy with request_header and reverse_proxy +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs + debug +} +localhost { + request_header X-Custom-Header "Value" + reverse_proxy localhost:9450 +} +http://localhost:9450 { + respond "{header.X-Custom-Header}" +} +``` + +# request_header adds header before reverse_proxy +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "Value" diff --git a/caddytest/spec/http/requestbody/spec.hurl b/caddytest/spec/http/requestbody/spec.hurl new file mode 100644 index 000000000..3abf56e96 --- /dev/null +++ b/caddytest/spec/http/requestbody/spec.hurl @@ -0,0 +1,36 @@ +# Configure Caddy +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + log + request_body { + max_size 2B + } + reverse_proxy localhost:8000 # to fake body reading + handle_errors 4xx { + respond "OK" + } +} +http://localhost:8000 { + respond "Failed" +} +``` + +GET https://localhost:9443 +[Options] +delay: 1s +insecure: true +``` +Hello +``` +HTTP 413 +`OK` + +# TODO: how to test{read,write}_timeout? \ No newline at end of file diff --git a/caddytest/spec/http/rewrite/spec.hurl b/caddytest/spec/http/rewrite/spec.hurl new file mode 100644 index 000000000..dd1de7bdc --- /dev/null +++ b/caddytest/spec/http/rewrite/spec.hurl @@ -0,0 +1,66 @@ +# Configure Caddy +POST http://localhost:2019/load +User-Agent: hurl/ci +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + rewrite /from /to + respond {uri} +} +``` + +# simple scenario: rewriting /from to /to produces expected result of seeing /to +GET https://localhost:9443/from +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "/to" + +# unmatched path is passed through unchanged +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "/" + +# having a query parameter does not trip the rewrite and retains the query +GET https://localhost:9443/from?query_param=value +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "/to?query_param=value" + + +# Configure Caddy +POST http://localhost:2019/load +User-Agent: hurl/ci +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + rewrite /from /to?a=b + respond {uri} +} +``` + +# a rewrite with query parameters affects the parameters +GET https://localhost:9443/from?query_param=value +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "/to?a=b" diff --git a/caddytest/spec/http/route/spec.hurl b/caddytest/spec/http/route/spec.hurl new file mode 100644 index 000000000..7d6ba8706 --- /dev/null +++ b/caddytest/spec/http/route/spec.hurl @@ -0,0 +1,171 @@ +# Configure Caddy with route directive +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + route /api/* { + uri strip_prefix /api + respond "API: {uri}" + } + respond "Not API" +} +``` + +# route groups handlers and maintains order +GET https://localhost:9443/api/users +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "API: /users" + + +# route doesn't match non-matching paths +GET https://localhost:9443/other +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "Not API" + + +# Configure Caddy with nested routes +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + route /api/* { + uri strip_prefix /api + route /v1/* { + uri strip_prefix /v1 + respond "API v1: {uri}" + } + respond "API: {uri}" + } +} +``` + +# nested routes process sequentially +GET https://localhost:9443/api/v1/users +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "API v1: /users" + + +# outer route processes when inner doesn't match +GET https://localhost:9443/api/users +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "API: /users" + + +# Configure Caddy with route and terminal handlers +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + route { + header X-First "1" + respond "Response" + header X-Second "2" + } +} +``` + +# route stops at terminal handler (respond) +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +header "X-First" == "1" +header "X-Second" not exists + + +# Configure Caddy with route preserving handler order +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + route { + vars step1 "done" + vars step2 "done" + vars step3 "done" + respond "{vars.step1},{vars.step2},{vars.step3}" + } +} +``` + +# route preserves exact handler order +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "done,done,done" + + +# Configure Caddy with route and matchers +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + route { + @api path /api/* + vars @api type "api" + vars type "other" + respond "{vars.type}" + } +} +``` + +# route applies matchers in sequence +GET https://localhost:9443/api/test +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "other" + + +# route continues when matcher doesn't match +GET https://localhost:9443/test +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "other" diff --git a/caddytest/spec/http/static_response/spec.hurl b/caddytest/spec/http/static_response/spec.hurl new file mode 100644 index 000000000..e3d0c0697 --- /dev/null +++ b/caddytest/spec/http/static_response/spec.hurl @@ -0,0 +1,105 @@ +# Configure Caddy +POST http://localhost:2019/load +User-Agent: hurl/ci +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + log + respond "Hello, World!" +} +``` + +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +`Hello, World!` + + +GET https://localhost:9443/foo +[Options] +insecure: true +HTTP 200 +[Asserts] +`Hello, World!` + +# Configure Caddy +POST http://localhost:2019/load +User-Agent: hurl/ci +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + respond "New text!" +} +``` + +GET https://localhost:9443 +[Options] +insecure: true +HTTP/2 200 +[Asserts] +`New text!` + + +GET https://localhost:9443/foo +[Options] +insecure: true +HTTP/2 200 +[Asserts] +`New text!` + +GET https://localhost:9443/foo +[Options] +insecure: true +HTTP/2 200 +[Asserts] +body != "Hello, World!" + +# Configure Caddy +# The body is a placeholder +POST http://localhost:2019/load +User-Agent: hurl/ci +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + log + respond {http.request.body} +} +``` + +# handler responds with the "application/json" if the response body is valid JSON +POST https://localhost:9443 +[Options] +insecure: true +```json +{ + "greeting": "Hello, world!" +} +``` +HTTP/2 200 +[Asserts] +header "Content-Type" == "application/json" +```json +{ + "greeting": "Hello, world!" +} +``` diff --git a/caddytest/spec/http/uri/spec.hurl b/caddytest/spec/http/uri/spec.hurl new file mode 100644 index 000000000..b3402531c --- /dev/null +++ b/caddytest/spec/http/uri/spec.hurl @@ -0,0 +1,191 @@ +# Configure Caddy with uri strip_prefix +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + uri strip_prefix /api + respond {uri} +} +``` + +# strip_prefix removes the prefix from the URI +GET https://localhost:9443/api/users +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "/users" + + +# URI without prefix is unchanged +GET https://localhost:9443/users +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "/users" + + +# Configure Caddy with uri strip_suffix +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + uri strip_suffix .php + respond {uri} +} +``` + +# strip_suffix removes the suffix from the URI +GET https://localhost:9443/index.php +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "/index" + + +# URI without suffix is unchanged +GET https://localhost:9443/index.html +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "/index.html" + + +# Configure Caddy with uri replace +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + uri replace old new + respond {uri} +} +``` + +# replace substitutes all occurrences +GET https://localhost:9443/old/path/old +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "/new/path/new" + + +# Configure Caddy with uri path_regexp +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + uri path_regexp /([0-9]+) /$1/id + respond {uri} +} +``` + +# path_regexp replaces using regular expressions +GET https://localhost:9443/123 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "/123/id" + + +# Configure Caddy with uri query operations +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + uri query +foo bar + respond {query} +} +``` + +# query operations add parameters +GET https://localhost:9443/?existing=value +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "existing=value&foo=bar" + + +# Configure Caddy with uri query delete +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + uri query -sensitive + respond {query} +} +``` + +# query operations delete parameters +GET https://localhost:9443/?keep=this&sensitive=secret +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "keep=this" + + +# Configure Caddy with uri query rename +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + uri query old>new + respond {query} +} +``` + +# query operations rename parameters +GET https://localhost:9443/?old=value +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "new=value" diff --git a/caddytest/spec/http/vars/spec.hurl b/caddytest/spec/http/vars/spec.hurl new file mode 100644 index 000000000..effa1d4cb --- /dev/null +++ b/caddytest/spec/http/vars/spec.hurl @@ -0,0 +1,125 @@ +# Configure Caddy with vars directive +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + vars my_var "custom_value" + vars another_var "another_value" + respond "{vars.my_var} {vars.another_var}" +} +``` + +# Variables are accessible in placeholders +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "custom_value another_value" + + +# Configure Caddy with vars using placeholders +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + vars request_path {path} + vars request_method {method} + respond "Path: {vars.request_path}, Method: {vars.request_method}" +} +``` + +# Variables can be set from request placeholders +GET https://localhost:9443/test/path +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "Path: /test/path, Method: GET" + + +# POST method is captured correctly +POST https://localhost:9443/another +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "Path: /another, Method: POST" + + +# Configure Caddy with vars in route +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + route /api/* { + vars api_version "v1" + respond "API {vars.api_version}" + } + respond "Not API" +} +``` + +# Variables are scoped to their route +GET https://localhost:9443/api/users +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "API v1" + + +# Outside the route, variables are not set +GET https://localhost:9443/other +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "Not API" + + +# Configure Caddy with vars overwriting +POST http://localhost:2019/load +Content-Type: text/caddyfile +``` +{ + skip_install_trust + http_port 9080 + https_port 9443 + local_certs +} +localhost { + # without `route`, middlewares are sorted an unstable sort + route { + vars my_var "2" + vars my_var "1" + } + respond "{vars.my_var}" +} +``` + +# Later vars directives overwrite earlier ones +GET https://localhost:9443 +[Options] +insecure: true +HTTP 200 +[Asserts] +body == "1" diff --git a/caddytest/spec/hurl_vars.properties b/caddytest/spec/hurl_vars.properties new file mode 100644 index 000000000..a3832eaea --- /dev/null +++ b/caddytest/spec/hurl_vars.properties @@ -0,0 +1,2 @@ +indexed_root=caddytest/spec/http/file_server/assets/indexed +unindexed_root=caddytest/spec/http/file_server/assets/unindexed \ No newline at end of file