From 1b4bd3ee1ba3da275c0b2eb185f9dbfd82519da1 Mon Sep 17 00:00:00 2001
From: Mohammed Al Sahaf <msaa1990@gmail.com>
Date: Thu, 18 Apr 2024 23:47:57 +0300
Subject: [PATCH] testing: use Hurl in CI to test Caddy against spec

---
 .github/workflows/ci.yml                      |  29 +++++
 caddytest/spec/http/headers/spec.hurl         |  22 ++++
 caddytest/spec/http/rewrite/spec.hurl         |  66 +++++++++++
 caddytest/spec/http/static_response/spec.hurl | 105 ++++++++++++++++++
 4 files changed, 222 insertions(+)
 create mode 100644 caddytest/spec/http/headers/spec.hurl
 create mode 100644 caddytest/spec/http/rewrite/spec.hurl
 create mode 100644 caddytest/spec/http/static_response/spec.hurl

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 32da779ba..d60b08b2f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,6 +7,7 @@ on:
     branches:
       - master
       - 2.*
+      - hurl-tests
   pull_request:
     branches:
       - master
@@ -14,6 +15,9 @@ on:
 
 jobs:
   test:
+    permissions:
+      checks: write
+      pull-requests: write
     strategy:
       # Default is true, cancels jobs for other platforms in the matrix if one fails
       fail-fast: false
@@ -125,6 +129,31 @@ jobs:
         go test -tags nobadger -v -coverprofile="cover-profile.out" -short -race ./...
         # echo "status=$?" >> $GITHUB_OUTPUT
 
+    - name: Install Hurl
+      if: matrix.os == 'linux' && matrix.go == '1.22'
+      run: |
+        curl --location --remote-name https://github.com/Orange-OpenSource/hurl/releases/download/4.2.0/hurl_4.2.0_amd64.deb
+        sudo dpkg -i hurl_4.2.0_amd64.deb
+
+    - name: Run Caddy
+      if: matrix.os == 'linux' && matrix.go == '1.22'
+      working-directory: ./cmd/caddy
+      run: |
+        ./caddy start
+
+    - name: Run tests with Hurl
+      if: matrix.os == 'linux' && matrix.go == '1.22'
+      run: |
+        mkdir hurl-report
+        find . -name *.hurl -exec hurl --very-verbose --verbose --test --report-junit hurl-report/junit.xml --color {} \;
+    
+    - name: Publish Test Results
+      if: matrix.os == 'linux' && matrix.go == '1.22'
+      uses: EnricoMi/publish-unit-test-result-action@v2
+      with:
+        files: |
+          hurl-report/junit.xml
+
     # Relevant step if we reinvestigate publishing test/coverage reports
     # - name: Prepare coverage reports
     #   run: |
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/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/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!"
+}
+```