diff --git a/.dockerignore b/.dockerignore
index 1898f916e..30b12bd2e 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -13,3 +13,5 @@ CHANGELOG.md
LICENSE
.golangci*
.rr-sample-*.yaml
+go.work
+go.work.sum
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
new file mode 100644
index 000000000..c82138bed
--- /dev/null
+++ b/.github/workflows/e2e.yml
@@ -0,0 +1,57 @@
+name: e2e_tests
+on:
+ push:
+ branches:
+ - master
+ - stable
+ pull_request:
+ branches:
+ - master
+ - stable
+jobs:
+ e2e_test:
+ name: E2E tests (Go ${{ matrix.go }}, PHP ${{ matrix.php }}, OS ${{ matrix.os }})
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 30
+ strategy:
+ matrix:
+ php: ["8.5"]
+ go: [stable]
+ os: ["ubuntu-latest"]
+ steps:
+ - name: Set up Go ${{ matrix.go }}
+ uses: actions/setup-go@v6
+ with:
+ go-version: ${{ matrix.go }}
+ - name: Set up PHP ${{ matrix.php }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: sockets
+ - name: Check out code
+ uses: actions/checkout@v6
+ - name: Get Composer Cache Directory
+ id: composer-cache
+ run: |
+ cd tests/php_test_files
+ echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+ - name: Init Composer Cache
+ uses: actions/cache@v5
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-composer-
+ - name: Install Composer dependencies
+ run: cd tests/php_test_files && composer update --prefer-dist --no-progress --ansi
+ - name: Init Go modules Cache
+ uses: actions/cache@v5
+ with:
+ path: ~/go/pkg/mod
+ key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+ restore-keys: ${{ runner.os }}-go-
+ - name: Install Go dependencies
+ run: cd tests && go mod download
+ - name: Run e2e tests
+ run: |
+ cd tests
+ go test -timeout 15m -v -race -failfast ./...
diff --git a/.gitignore b/.gitignore
index e27fd1192..ecebd2780 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,5 @@ rr.exe
.DS_Store
**/.DS_Store
**/node_modules
+**/vendor/
+**/composer.lock
diff --git a/go.mod b/go.mod
index c37743267..fdd20b0ce 100644
--- a/go.mod
+++ b/go.mod
@@ -152,7 +152,7 @@ require (
github.com/redis/go-redis/extra/redisotel/v9 v9.18.0 // indirect
github.com/redis/go-redis/extra/redisprometheus/v9 v9.18.0 // indirect
github.com/redis/go-redis/v9 v9.18.0 // indirect
- github.com/roadrunner-server/context v1.2.0 // indirect
+ github.com/roadrunner-server/context v1.3.0 // indirect
github.com/roadrunner-server/events v1.0.1 // indirect
github.com/roadrunner-server/priority_queue v1.0.6 // indirect
github.com/roadrunner-server/tcplisten v1.5.2 // indirect
@@ -216,9 +216,9 @@ require (
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.43.0 // indirect
google.golang.org/api v0.271.0 // indirect
- google.golang.org/genproto v0.0.0-20260311181403-84a4fc48630c // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect
+ google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20260316172706-e463d84ca32d // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
google.golang.org/grpc v1.79.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
diff --git a/go.sum b/go.sum
index 544fec16d..46bdcf605 100644
--- a/go.sum
+++ b/go.sum
@@ -354,8 +354,8 @@ github.com/roadrunner-server/centrifuge/v5 v5.1.9 h1:rkXRR6JFAUTHMQhqS0RiLciQDhF
github.com/roadrunner-server/centrifuge/v5 v5.1.9/go.mod h1:N015rsGdTD7dDIkNeYidwBXQoseizHltv3q94tRzIic=
github.com/roadrunner-server/config/v5 v5.1.9 h1:ReWwts/prEvuC4yVJ0BRDmY5sxw/1c+hGTSdJ71hIQU=
github.com/roadrunner-server/config/v5 v5.1.9/go.mod h1:R6YyTWahW61tWHOI2BfdkQU/0Zc/2d6/JbJ/KEvq8F8=
-github.com/roadrunner-server/context v1.2.0 h1:QKSd6ZypPbtHlxaSUULsRYHPO1IlIm0H6hm6Fdu5Ge4=
-github.com/roadrunner-server/context v1.2.0/go.mod h1:BSjiuIBLbA5qxlLIIDS/tFDruikZ/AElS5wKtqZiltw=
+github.com/roadrunner-server/context v1.3.0 h1:iyTXVORhPU2/26z7kdzEaggwG5P8yhIKUDLiePjylFQ=
+github.com/roadrunner-server/context v1.3.0/go.mod h1:KPAzAlnErXekQazW9t4h55U1S42Q2bk0WCaPQrezJw4=
github.com/roadrunner-server/endure/v2 v2.6.2 h1:sIB4kTyE7gtT3fDhuYWUYn6Vt/dcPtiA6FoNS1eS+84=
github.com/roadrunner-server/endure/v2 v2.6.2/go.mod h1:t/2+xpNYgGBwhzn83y2MDhvhZ19UVq1REcvqn7j7RB8=
github.com/roadrunner-server/errors v1.4.1 h1:LKNeaCGiwd3t8IaL840ZNF3UA9yDQlpvHnKddnh0YRQ=
@@ -707,12 +707,12 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20260311181403-84a4fc48630c h1:ZhFDeBMmFc/4g8/GwxnJ4rzB3O4GwQVNr+8Mh7Y5z4g=
-google.golang.org/genproto v0.0.0-20260311181403-84a4fc48630c/go.mod h1:hf4r/rBuzaTkLUWRO03771Xvcs6P5hwdQK3UUEJjqo0=
-google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c h1:OyQPd6I3pN/9gDxz6L13kYGJgqkpdrAohJRBeXyxlgI=
-google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c/go.mod h1:X2gu9Qwng7Nn009s/r3RUxqkzQNqOrAy79bluY7ojIg=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c h1:xgCzyF2LFIO/0X2UAoVRiXKU5Xg6VjToG4i2/ecSswk=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
+google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
+google.golang.org/genproto/googleapis/api v0.0.0-20260316172706-e463d84ca32d h1:RdWlPmVySdTF0IBIZzvZJvSD0ZocPBNUsnE+uGBxj+4=
+google.golang.org/genproto/googleapis/api v0.0.0-20260316172706-e463d84ca32d/go.mod h1:X2gu9Qwng7Nn009s/r3RUxqkzQNqOrAy79bluY7ojIg=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
diff --git a/go.work b/go.work
new file mode 100644
index 000000000..52edf0aed
--- /dev/null
+++ b/go.work
@@ -0,0 +1,6 @@
+go 1.26.1
+
+use (
+ .
+ ./tests
+)
diff --git a/go.work.sum b/go.work.sum
new file mode 100644
index 000000000..8d78b80af
--- /dev/null
+++ b/go.work.sum
@@ -0,0 +1,364 @@
+buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U=
+buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0=
+cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
+cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
+cloud.google.com/go/accessapproval v1.8.8/go.mod h1:RFwPY9JDKseP4gJrX1BlAVsP5O6kI8NdGlTmaeDefmk=
+cloud.google.com/go/accesscontextmanager v1.9.7/go.mod h1:i6e0nd5CPcrh7+YwGq4bKvju5YB9sgoAip+mXU73aMM=
+cloud.google.com/go/aiplatform v1.119.0/go.mod h1:27DcZJbaxFntewF6O0HojDE1B8JQOGKYopNjwoICFdI=
+cloud.google.com/go/aiplatform v1.120.0/go.mod h1:6mDthfmy0oS1EQhVFdijoxkVdI2+HIZkpuGTBpedeCg=
+cloud.google.com/go/analytics v0.30.1/go.mod h1:V/FnINU5kMOsttZnKPnXfKi6clJUHTEXUKQjHxcNK8A=
+cloud.google.com/go/apigateway v1.7.7/go.mod h1:j1bCmrUK1BzVHpiIyTApxB7cRyhivKzltqLmp6j6i7U=
+cloud.google.com/go/apigeeconnect v1.7.7/go.mod h1:ftGK3nca0JePiVLl0A6alaMjKdOc5C+sAkFMyH2RH8U=
+cloud.google.com/go/apigeeregistry v0.10.0/go.mod h1:SAlF5OhKvyLDuwWAaFAIVJjrEqKRrGTPkJs+TWNnSqg=
+cloud.google.com/go/appengine v1.9.7/go.mod h1:y1XpGVeAhbsNzHida79cHbr3pFRsym0ob8xnC8yphbo=
+cloud.google.com/go/area120 v0.9.7/go.mod h1:5nJ0yksmjOMfc4Zpk+okWfJ3A1004FvB82rfia+ZLaY=
+cloud.google.com/go/area120 v0.10.0/go.mod h1:Xg3fKl4xU3UVai9wsI1FXwNU8wSCDYT7dFZfwJKViAM=
+cloud.google.com/go/artifactregistry v1.20.0/go.mod h1:0G9wdbGyDFkvrYH+2AlQs9MuTJdbY8Vg45M8VjlI8rc=
+cloud.google.com/go/asset v1.22.1/go.mod h1:NlvWwmca7CX6BIBEdRNxOocH6DowmBghAAHucOHuHng=
+cloud.google.com/go/assuredworkloads v1.13.0/go.mod h1:o/oHEOnUlribR+uJWTKQo8A5RhSl9K9FNeMOew4TJ3M=
+cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=
+cloud.google.com/go/automl v1.15.0/go.mod h1:U9zOtQb8zVrFNGTuW3BfxeqmLyeleLgT9B12EaXfODg=
+cloud.google.com/go/baremetalsolution v1.4.0/go.mod h1:K6C6g4aS8LW95I0fEHZiBsBlh0UxwDLGf+S/vyfXbvg=
+cloud.google.com/go/batch v1.14.0/go.mod h1:oeQveyG6NDS/ks2ilOP4LzKRmuIaI7GLe0CkR7WF6pk=
+cloud.google.com/go/beyondcorp v1.2.0/go.mod h1:sszcgxpPPBEfLzbI0aYCTg6tT1tyt3CmKav3NZIUcvI=
+cloud.google.com/go/bigquery v1.74.0/go.mod h1:iViO7Cx3A/cRKcHNRsHB3yqGAMInFBswrE9Pxazsc90=
+cloud.google.com/go/bigtable v1.42.0/go.mod h1:oZ30nofVB6/UYGg7lBwGLWSea7NZUvw/WvBBgLY07xU=
+cloud.google.com/go/billing v1.21.0/go.mod h1:ZGairB3EVnb3i09E2SxFxo50p5unPaMTuo1jh6jW9js=
+cloud.google.com/go/binaryauthorization v1.10.0/go.mod h1:WOuiaQkI4PU/okwrcREjSAr2AUtjQgVe+PlrXKOmKKw=
+cloud.google.com/go/certificatemanager v1.9.6/go.mod h1:vWogV874jKZkSRDFCMM3r7wqybv8WXs3XhyNff6o/Zo=
+cloud.google.com/go/channel v1.21.0/go.mod h1:8v3TwHtgLmFxTpL2U+e10CLFOQN8u/Vr9RhYcJUS3y8=
+cloud.google.com/go/cloudbuild v1.25.0/go.mod h1:lCu+T6IPkobPo2Nw+vCE7wuaAl9HbXLzdPx/tcF+oWo=
+cloud.google.com/go/clouddms v1.8.8/go.mod h1:QtCyw+a73dlkDb2q20aTAPvfaTZCepDDi6Gb1AKq0a4=
+cloud.google.com/go/cloudtasks v1.13.7/go.mod h1:H0TThOUG+Ml34e2+ZtW6k6nt4i9KuH3nYAJ5mxh7OM4=
+cloud.google.com/go/compute v1.54.0 h1:4CKmnpO+40z44bKG5bdcKxQ7ocNpRtOc9SCLLUzze1w=
+cloud.google.com/go/compute v1.54.0/go.mod h1:RfBj0L1x/pIM84BrzNX2V21oEv16EKRPBiTcBRRH1Ww=
+cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
+cloud.google.com/go/contactcenterinsights v1.17.4/go.mod h1:kZe6yOnKDfpPz2GphDHynxk/Spx+53UX/pGf+SmWAKM=
+cloud.google.com/go/container v1.46.0/go.mod h1:A7gMqdQduTk46+zssWDTKbGS2z46UsJNXfKqvMI1ZO4=
+cloud.google.com/go/containeranalysis v0.14.2/go.mod h1:FjppROiUtP9cyMegdWdY/TsBSGc6kqh1GjA2NOJXXL8=
+cloud.google.com/go/datacatalog v1.26.1/go.mod h1:2Qcq8vsHNxMDgjgadRFmFG47Y+uuIVsyEGUrlrKEdrg=
+cloud.google.com/go/dataflow v0.11.1/go.mod h1:3s6y/h5Qz7uuxTmKJKBifkYZ3zs63jS+6VGtSu8Cf7Y=
+cloud.google.com/go/dataform v0.13.0/go.mod h1:U3fqrPY5jAcFh1a8rQb4a+PQ7zKlc5qfgotFZ+luKPo=
+cloud.google.com/go/datafusion v1.8.7/go.mod h1:4dkFb1la41qCEXh1AzYtFwl842bu2ikTUXyKhjvFCb0=
+cloud.google.com/go/datalabeling v0.9.7/go.mod h1:EEUVn+wNn3jl19P2S13FqE1s9LsKzRsPuuMRq2CMsOk=
+cloud.google.com/go/dataplex v1.28.0/go.mod h1:VB+xlYJiJ5kreonXsa2cHPj0A3CfPh/mgiHG4JFhbUA=
+cloud.google.com/go/dataproc/v2 v2.16.0/go.mod h1:HlzFg8k1SK+bJN3Zsy2z5g6OZS1D4DYiDUgJtF0gJnE=
+cloud.google.com/go/dataqna v0.9.8/go.mod h1:2lHKmGPOqzzuqCc5NI0+Xrd5om4ulxGwPpLB4AnFgpA=
+cloud.google.com/go/datastore v1.22.0/go.mod h1:aopSX+Whx0lHspWWBj+AjWt68/zjYsPfDe3LjWtqZg8=
+cloud.google.com/go/datastream v1.15.1/go.mod h1:aV1Grr9LFon0YvqryE5/gF1XAhcau2uxN2OvQJPpqRw=
+cloud.google.com/go/deploy v1.27.3/go.mod h1:7LFIYYTSSdljYRqY3n+JSmIFdD4lv6aMD5xg0crB5iw=
+cloud.google.com/go/dialogflow v1.76.0/go.mod h1:mdLkMmSCghfcP85X9dFBlirC1OssS65KE5hrrSz2GXY=
+cloud.google.com/go/dlp v1.28.0/go.mod h1:C3od1fIK8lf7Kr62aU1Uh0z4OL5Z8s3do3znAiEupAw=
+cloud.google.com/go/documentai v1.42.0/go.mod h1:CABOUzRNOuvb/QwJS2LS80Hpqbu3UW2afyRKTYuW7bo=
+cloud.google.com/go/domains v0.10.7/go.mod h1:T3WG/QUAO/52z4tUPooKS8AY7yXaFxPYn1V3F0/JbNQ=
+cloud.google.com/go/edgecontainer v1.4.4/go.mod h1:yyNVHsCKtsX/0mqFdbljQw0Uo660q2dlMPaiqYiC2Tg=
+cloud.google.com/go/errorreporting v0.4.0/go.mod h1:dZGEhqzdHZSRxxWLVjC3Ue5CVaROzvP58D9rU6zbBfw=
+cloud.google.com/go/essentialcontacts v1.7.7/go.mod h1:ytycWAEn/aKUMRKQPMVgMrAtphEMgjbzL8vFwM3tqXs=
+cloud.google.com/go/eventarc v1.18.0/go.mod h1:/6SDoqh5+9QNUqCX4/oQcJVK16fG/snHBSXu7lrJtO8=
+cloud.google.com/go/filestore v1.10.3/go.mod h1:94ZGyLTx9j+aWKozPQ6Wbq1DuImie/L/HIdGMshtwac=
+cloud.google.com/go/firestore v1.21.0/go.mod h1:1xH6HNcnkf/gGyR8udd6pFO4Z7GWJSwLKQMx/u6UrP4=
+cloud.google.com/go/functions v1.19.7/go.mod h1:xbcKfS7GoIcaXr2FSwmtn9NXal1JR4TV6iYZlgXffwA=
+cloud.google.com/go/gkebackup v1.8.1/go.mod h1:GAaAl+O5D9uISH5MnClUop2esQW4pDa2qe/95A4l7YQ=
+cloud.google.com/go/gkeconnect v0.12.5/go.mod h1:wMD2RXcsAWlkREZWJDVeDV70PYka1iEb9stFmgpw+5o=
+cloud.google.com/go/gkehub v0.16.0/go.mod h1:ADp27Ucor8v81wY+x/5pOxTorxkPj/xswH3AUpN62GU=
+cloud.google.com/go/gkemulticloud v1.6.0/go.mod h1:bGpd4o/Z5Z/XFlaojkgdVisHRwb+fLJvUPzsmV0I9ok=
+cloud.google.com/go/gsuiteaddons v1.7.8/go.mod h1:DBKNHH4YXAdd/rd6zVvtOGAJNGo0ekOh+nIjTUDEJ5U=
+cloud.google.com/go/iap v1.11.3/go.mod h1:+gXO0ClH62k2LVlfhHzrpiHQNyINlEVmGAE3+DB4ShU=
+cloud.google.com/go/ids v1.5.7/go.mod h1:N3ZQOIgIBwwOu2tzyhmh3JDT+kt8PcoKkn2BRT9Qe4A=
+cloud.google.com/go/iot v1.8.7/go.mod h1:HvVcypV8LPv1yTXSLCNK+YCtqGHhq+p0F3BXETfpN+U=
+cloud.google.com/go/kms v1.26.0/go.mod h1:pHKOdFJm63hxBsiPkYtowZPltu9dW0MWvBa6IA4HM58=
+cloud.google.com/go/language v1.14.6/go.mod h1:7y3J9OexQsfkWNGCxhT+7lb64pa60e12ZCoWDOHxJ1M=
+cloud.google.com/go/lifesciences v0.10.7/go.mod h1:v3AbTki9iWttEls/Wf4ag3EqeLRHofploOcpsLnu7iY=
+cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak=
+cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
+cloud.google.com/go/managedidentities v1.7.7/go.mod h1:nwNlMxtBo2YJMvsKXRtAD1bL41qiCI9npS7cbqrsJUs=
+cloud.google.com/go/maps v1.28.0/go.mod h1:6EWjz3AFh52w3qe2reWShQDmGRtryhP7NAfGolnr9+g=
+cloud.google.com/go/maps v1.29.0/go.mod h1:FNATcM5ziB2TDE2IVWH4f/yeXc+SbUk1X+bmKjR8HEA=
+cloud.google.com/go/mediatranslation v0.9.7/go.mod h1:mz3v6PR7+Fd/1bYrRxNFGnd+p4wqdc/fyutqC5QHctw=
+cloud.google.com/go/memcache v1.11.7/go.mod h1:AU1jYlUqCihxapcJ1GGMtlMWDVhzjbfUWBXqsXa4rBg=
+cloud.google.com/go/metastore v1.14.8/go.mod h1:h1XI2LpD4ohJhQYn9TwXqKb5sVt6KSo47ft96SiFF1s=
+cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
+cloud.google.com/go/networkconnectivity v1.20.0/go.mod h1:9MzGwD4ljiq+Z2Pg3ue27OEewCuHz7IUfw1fITrIdSw=
+cloud.google.com/go/networkconnectivity v1.21.0/go.mod h1:XC1UJ+tqBsLWz73dqrMc7kUvdTv0FIxtDGv6YntTBO0=
+cloud.google.com/go/networkmanagement v1.23.0/go.mod h1:QTYCWp5UxUnU280SqF7AX/mf6NhsqKblmLeCALQmx5c=
+cloud.google.com/go/networksecurity v0.11.0/go.mod h1:JLgDsg4tOyJ3eMO8lypjqMftbfd60SJ+P7T+DUmWBsM=
+cloud.google.com/go/notebooks v1.12.7/go.mod h1:uR9pxAkKmlNloibMr9Q1t8WhIu4P2JeqJs7c064/0Mo=
+cloud.google.com/go/optimization v1.7.7/go.mod h1:OY2IAlX23o52qwMAZ0w65wibKuV12a4x6IHDTCq6kcU=
+cloud.google.com/go/orchestration v1.11.10/go.mod h1:tz7m1s4wNEvhNNIM3JOMH0lYxBssu9+7si5MCPw/4/0=
+cloud.google.com/go/orgpolicy v1.15.1/go.mod h1:bpvi9YIyU7wCW9WiXL/ZKT7pd2Ovegyr2xENIeRX5q0=
+cloud.google.com/go/osconfig v1.16.0/go.mod h1:PRmLgZ1loD1hGaqnTBww1nETbqcqAvmTQOLYiIZ7Nvk=
+cloud.google.com/go/oslogin v1.14.7/go.mod h1:NB6NqBHfDMwznePdBVX+ILllc1oPCdNSGp5u/WIyndY=
+cloud.google.com/go/phishingprotection v0.9.7/go.mod h1:JTI4HNGyAbWolBoNOoCyCF0e3cqPNrYnlievHU49EwE=
+cloud.google.com/go/policytroubleshooter v1.11.7/go.mod h1:JP/aQ+bUkt4Gz6lQXBi/+A/6nyNRZ0Pvxui5Xl9ieyk=
+cloud.google.com/go/privatecatalog v0.10.8/go.mod h1:BkLHi+rtAGYBt5DocXLytHhF0n6F03Tegxgty40Y7aA=
+cloud.google.com/go/pubsub v1.50.1 h1:fzbXpPyJnSGvWXF1jabhQeXyxdbCIkXTpjXHy7xviBM=
+cloud.google.com/go/pubsub v1.50.1/go.mod h1:6YVJv3MzWJUVdvQXG081sFvS0dWQOdnV+oTo++q/xFk=
+cloud.google.com/go/pubsub/v2 v2.0.0/go.mod h1:0aztFxNzVQIRSZ8vUr79uH2bS3jwLebwK6q1sgEub+E=
+cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI=
+cloud.google.com/go/recaptchaenterprise/v2 v2.21.0/go.mod h1:HxQYqZC2/zl2CvKN7jJEv71vEdDi1GMGNUiZxnpiuVI=
+cloud.google.com/go/recommendationengine v0.9.7/go.mod h1:snZ/FL147u86Jqpv1j95R+CyU5NvL/UzYiyDo6UByTM=
+cloud.google.com/go/recommender v1.13.6/go.mod h1:y5/5womtdOaIM3xx+76vbsiA+8EBTIVfWnxHDFHBGJM=
+cloud.google.com/go/redis v1.18.3/go.mod h1:x8HtXZbvMBDNT6hMHaQ022Pos5d7SP7YsUH8fCJ2Wm4=
+cloud.google.com/go/resourcemanager v1.10.7/go.mod h1:rScGkr6j2eFwxAjctvOP/8sqnEpDbQ9r5CKwKfomqjs=
+cloud.google.com/go/resourcesettings v1.8.3/go.mod h1:BzgfXFHIWOOmHe6ZV9+r3OWfpHJgnqXy8jqwx4zTMLw=
+cloud.google.com/go/retail v1.26.0/go.mod h1:gMfh6s174Mvy1rK4g50J9TH5sRim8px+Krml25kdrqo=
+cloud.google.com/go/run v1.15.0/go.mod h1:rgFHMdAopLl++57vzeqA+a1o2x0/ILZnEacRD6nC0EA=
+cloud.google.com/go/scheduler v1.11.8/go.mod h1:bNKU7/f04eoM6iKQpwVLvFNBgGyJNS87RiFN73mIPik=
+cloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q=
+cloud.google.com/go/security v1.19.2/go.mod h1:KXmf64mnOsLVKe8mk/bZpU1Rsvxqc0Ej0A6tgCeN93w=
+cloud.google.com/go/securitycenter v1.38.1/go.mod h1:Ge2D/SlG2lP1FrQD7wXHy8qyeloRenvKXeB4e7zO6z0=
+cloud.google.com/go/servicedirectory v1.12.7/go.mod h1:gOtN+qbuCMH6tj2dqlDY3qQL7w3V0+nkWaZElnJK8Ps=
+cloud.google.com/go/shell v1.8.7/go.mod h1:OTke7qc3laNEW5Jr5OV9VR3IwU5x5VqGOE6705zFex4=
+cloud.google.com/go/spanner v1.88.0/go.mod h1:MzulBwuuYwQUVdkZXBBFapmXee3N+sQrj2T/yup6uEE=
+cloud.google.com/go/speech v1.30.0/go.mod h1:F2+NJujR8uzDLd6bwy5kgtVycxvEq06nzvzz5eQ/gMo=
+cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU=
+cloud.google.com/go/storagetransfer v1.13.1/go.mod h1:S858w5l383ffkdqAqrAA+BC7KlhCqeNieK3sFf5Bj4Y=
+cloud.google.com/go/talent v1.8.4/go.mod h1:3yukBXUTVFNyKcJpUExW/k5gqEy8qW6OCNj7WdN0MWo=
+cloud.google.com/go/texttospeech v1.16.0/go.mod h1:AeSkoH3ziPvapsuyI07TWY4oGxluAjntX+pF4PJ2jy0=
+cloud.google.com/go/tpu v1.8.4/go.mod h1:ul0cyWSHr6jHGZYElZe6HvQn35VY93RAlwpDiSBRnPA=
+cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
+cloud.google.com/go/translate v1.12.7/go.mod h1:wwJp14NZyWvcrFANhIXutXj0pOBkYciBHwSlUOykcjI=
+cloud.google.com/go/video v1.27.1/go.mod h1:xzfAC77B4vtnbi/TT3UUxEjCa/+Ehy5EA8w470ytOig=
+cloud.google.com/go/videointelligence v1.12.7/go.mod h1:XAk5hCMY+GihxJ55jNoMdwdXSNZnCl3wGs2+94gK7MA=
+cloud.google.com/go/vision/v2 v2.9.6/go.mod h1:lJC+vP15D5znJvHQYjEoTKnpToX1L93BUlvBmzM0gyg=
+cloud.google.com/go/vmmigration v1.10.0/go.mod h1:LDztCWEb+RwS1bPg4Xzt0fcJS9kVrFxa3ejhH7OW9vg=
+cloud.google.com/go/vmwareengine v1.3.6/go.mod h1:ps0rb+Skgpt9ppHYC0o5DqtJ5ld2FyS8sAqtbHH8t9s=
+cloud.google.com/go/vpcaccess v1.8.7/go.mod h1:9RYw5bVvk4Z51Rc8vwXT63yjEiMD/l7XyEaDyrNHgmk=
+cloud.google.com/go/webrisk v1.11.2/go.mod h1:yH44GeXz5iz4HFsIlGeoVvnjwnmfbni7Lwj1SelV4f0=
+cloud.google.com/go/websecurityscanner v1.7.7/go.mod h1:ng/PzARaus3Bj4Os4LpUnyYHsbtJky1HbBDmz148v1o=
+cloud.google.com/go/workflows v1.14.3/go.mod h1:CC9+YdVI2Kvp0L58WajHpEfKJxhrtRh3uQ0SYWcmAk4=
+codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU=
+codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw=
+codeberg.org/go-pdf/fpdf v0.10.0/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU=
+dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=
+github.com/IBM/sarama v1.43.1/go.mod h1:GG5q1RURtDNPz8xxJs3mgX6Ytak8Z9eLhAkJPObe2xE=
+github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
+github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
+github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
+github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
+github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
+github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw=
+github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
+github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
+github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
+github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/eapache/go-resiliency v1.6.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
+github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
+github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
+github.com/go-faker/faker/v4 v4.6.0/go.mod h1:ZmrHuVtTTm2Em9e0Du6CJ9CADaLEzGXW62z1YqFH0m0=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/goccmack/gocc v0.0.0-20230228185258-2292f9e40198/go.mod h1:DTh/Y2+NbnOVVoypCCQrovMPDKUGp4yZpSbWg5D0XIM=
+github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4=
+github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY=
+github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
+github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8=
+github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
+github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
+github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
+github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
+github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
+github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
+github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
+github.com/jordanlewis/gcassert v0.0.0-20250430164644-389ef753e22e/go.mod h1:ZybsQk6DWyN5t7An1MuPm1gtSZ1xDaTXS9ZjIOxvQrk=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jstemmer/go-junit-report/v2 v2.1.0/go.mod h1:mgHVr7VUo5Tn8OLVr1cKnLuEy0M92wdRntM99h7RkgQ=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk=
+github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
+github.com/maruel/panicparse/v2 v2.4.0/go.mod h1:nOY2OKe8csO3F3SA5+hsxot05JLgukrF54B9x88fVp4=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
+github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k=
+github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
+github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
+github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
+github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/roadrunner-server/otel/v5 v5.5.0 h1:IYMw04K110sMANyZpauvQ3rRMnq0zZhwQbISHLisfQE=
+github.com/roadrunner-server/otel/v5 v5.5.0/go.mod h1:B1EaW4hC2VGUKCKLw9CZYKxuQid+2Zb7ojzXpRUb6oo=
+github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
+github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
+github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/temporalio/ringpop-go v0.0.0-20250130211428-b97329e994f7/go.mod h1:RE+CHmY+kOZQk47AQaVzwrGmxpflnLgTd6EOK0853j4=
+github.com/temporalio/sqlparser v0.0.0-20231115171017-f4060bcfa6cb/go.mod h1:143qKdh3G45IgV9p+gbAwp3ikRDI8mxsijFiXDfuxsw=
+github.com/temporalio/tchannel-go v1.22.1-0.20240528171429-1db37fdea938/go.mod h1:ezRQRwu9KQXy8Wuuv1aaFFxoCNz5CeNbVOOkh3xctbY=
+github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
+github.com/uber-common/bark v1.3.0/go.mod h1:5fDe/YcIVP55XhFF9hUihX2lDsDcpFrTZEAwAVwtPDw=
+github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po=
+github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
+github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
+github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
+go.etcd.io/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o=
+go.opentelemetry.io/collector/pdata v1.34.0/go.mod h1:StPHMFkhLBellRWrULq0DNjv4znCDJZP6La4UuC+JHI=
+go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
+go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
+go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA=
+go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk=
+go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
+go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
+go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
+go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
+go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw=
+go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
+go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
+go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
+go.temporal.io/api v1.62.1/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
+go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
+go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
+golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
+golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
+golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
+golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
+golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
+golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
+golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
+golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
+golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
+golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
+golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
+golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
+golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
+golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
+golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
+golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw=
+golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
+golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
+golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
+golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
+golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
+golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
+golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
+golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
+gonum.org/v1/plot v0.15.2/go.mod h1:DX+x+DWso3LTha+AdkJEv5Txvi+Tql3KAGkehP0/Ubg=
+google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4=
+google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
+google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
+google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=
+google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:0oz9d7g9QLSdv9/lgbIjowW1JoxMbxmBVNe8i6tORJI=
+google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
+google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
+google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
+google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
+google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
+google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ=
+google.golang.org/genproto/googleapis/bytestream v0.0.0-20260226221140-a57be14db171/go.mod h1:9amqk/8LQWEC4RjyUxMx1DebyQ7hZB9gvl67bHmgZ2E=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
+google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
+google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
+google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
+google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
+google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6/go.mod h1:6ytKWczdvnpnO+m+JiG9NjEDzR1FJfsnmJdG7B8QVZ8=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8=
+modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
+modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
+modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
+modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/tests/configs/.rr-grpc-otel.yaml b/tests/configs/.rr-grpc-otel.yaml
new file mode 100644
index 000000000..806d4372a
--- /dev/null
+++ b/tests/configs/.rr-grpc-otel.yaml
@@ -0,0 +1,31 @@
+version: '3'
+
+rpc:
+ listen: tcp://127.0.0.1:6206
+
+server:
+ command: "php php_test_files/grpc/worker-grpc.php"
+ relay: "pipes"
+ relay_timeout: "20s"
+
+grpc:
+ listen: "tcp://127.0.0.1:9192"
+ proto:
+ - "proto/service/service.proto"
+ max_send_msg_size: 50
+ max_recv_msg_size: 50
+ max_concurrent_streams: 10
+ ping_time: 1s
+ timeout: 200s
+ pool:
+ num_workers: 2
+ max_jobs: 0
+ allocate_timeout: 60s
+ destroy_timeout: 60s
+
+otel:
+ resource:
+ service_name: "rr-e2e-grpc-otel"
+ service_version: "1.0.0"
+ insecure: true
+ exporter: stdout
diff --git a/tests/configs/.rr-grpc.yaml b/tests/configs/.rr-grpc.yaml
new file mode 100644
index 000000000..c264aa33c
--- /dev/null
+++ b/tests/configs/.rr-grpc.yaml
@@ -0,0 +1,24 @@
+version: '3'
+
+rpc:
+ listen: tcp://127.0.0.1:6204
+
+server:
+ command: "php php_test_files/grpc/worker-grpc.php"
+ relay: "pipes"
+ relay_timeout: "20s"
+
+grpc:
+ listen: "tcp://127.0.0.1:9191"
+ proto:
+ - "proto/service/service.proto"
+ max_send_msg_size: 50
+ max_recv_msg_size: 50
+ max_concurrent_streams: 10
+ ping_time: 1s
+ timeout: 200s
+ pool:
+ num_workers: 2
+ max_jobs: 0
+ allocate_timeout: 60s
+ destroy_timeout: 60s
diff --git a/tests/configs/.rr-http-middleware.yaml b/tests/configs/.rr-http-middleware.yaml
new file mode 100644
index 000000000..3641f50f1
--- /dev/null
+++ b/tests/configs/.rr-http-middleware.yaml
@@ -0,0 +1,30 @@
+version: '3'
+
+rpc:
+ listen: tcp://127.0.0.1:6202
+
+server:
+ command: "php php_test_files/http/client.php echo pipes"
+ relay: "pipes"
+ relay_timeout: "20s"
+
+http:
+ address: 127.0.0.1:18950
+ max_request_size: 1024
+ middleware: ["headers", "gzip", "http_metrics", "proxy_ip_parser", "sendfile"]
+ headers:
+ response:
+ X-Test: "e2e-roadrunner"
+ trusted_subnets:
+ - "10.0.0.0/8"
+ - "127.0.0.0/8"
+ - "172.16.0.0/12"
+ - "192.168.0.0/16"
+ - "::1/128"
+ - "fc00::/7"
+ - "fe80::/10"
+ pool:
+ num_workers: 2
+ max_jobs: 0
+ allocate_timeout: 60s
+ destroy_timeout: 1s
diff --git a/tests/configs/.rr-http-otel.yaml b/tests/configs/.rr-http-otel.yaml
new file mode 100644
index 000000000..3eaa0ccb4
--- /dev/null
+++ b/tests/configs/.rr-http-otel.yaml
@@ -0,0 +1,26 @@
+version: '3'
+
+rpc:
+ listen: tcp://127.0.0.1:6205
+
+server:
+ command: "php php_test_files/http/client.php echo pipes"
+ relay: "pipes"
+ relay_timeout: "20s"
+
+http:
+ address: 127.0.0.1:18952
+ max_request_size: 1024
+ middleware: ["otel", "gzip"]
+ pool:
+ num_workers: 2
+ max_jobs: 0
+ allocate_timeout: 60s
+ destroy_timeout: 1s
+
+otel:
+ resource:
+ service_name: "rr-e2e-test"
+ service_version: "1.0.0"
+ insecure: true
+ exporter: stdout
diff --git a/tests/configs/.rr-http-static.yaml b/tests/configs/.rr-http-static.yaml
new file mode 100644
index 000000000..034d60875
--- /dev/null
+++ b/tests/configs/.rr-http-static.yaml
@@ -0,0 +1,23 @@
+version: '3'
+
+rpc:
+ listen: tcp://127.0.0.1:6203
+
+server:
+ command: "php php_test_files/http/client.php echo pipes"
+ relay: "pipes"
+ relay_timeout: "20s"
+
+http:
+ address: 127.0.0.1:18951
+ max_request_size: 1024
+ middleware: ["static", "gzip"]
+ static:
+ dir: "testdata"
+ forbid: []
+ allow: [".txt"]
+ pool:
+ num_workers: 2
+ max_jobs: 0
+ allocate_timeout: 60s
+ destroy_timeout: 1s
diff --git a/tests/configs/.rr-jobs-memory-otel.yaml b/tests/configs/.rr-jobs-memory-otel.yaml
new file mode 100644
index 000000000..19c3d4c95
--- /dev/null
+++ b/tests/configs/.rr-jobs-memory-otel.yaml
@@ -0,0 +1,39 @@
+version: '3'
+
+rpc:
+ listen: tcp://127.0.0.1:6207
+
+server:
+ command: "php php_test_files/jobs/jobs_ok.php"
+ relay: "pipes"
+ relay_timeout: "20s"
+
+jobs:
+ num_pollers: 10
+ pipeline_size: 100000
+ pool:
+ num_workers: 2
+ max_jobs: 0
+ allocate_timeout: 60s
+ destroy_timeout: 60s
+
+ pipelines:
+ test-1:
+ driver: memory
+ config:
+ priority: 10
+ prefetch: 10000
+ test-2:
+ driver: memory
+ config:
+ priority: 10
+ prefetch: 10000
+
+ consume: ["test-1", "test-2"]
+
+otel:
+ resource:
+ service_name: "rr-e2e-jobs-otel"
+ service_version: "1.0.0"
+ insecure: true
+ exporter: stdout
diff --git a/tests/configs/.rr-jobs-memory.yaml b/tests/configs/.rr-jobs-memory.yaml
new file mode 100644
index 000000000..4cc6b1621
--- /dev/null
+++ b/tests/configs/.rr-jobs-memory.yaml
@@ -0,0 +1,32 @@
+version: '3'
+
+rpc:
+ listen: tcp://127.0.0.1:6201
+
+server:
+ command: "php php_test_files/jobs/jobs_ok.php"
+ relay: "pipes"
+ relay_timeout: "20s"
+
+jobs:
+ num_pollers: 10
+ pipeline_size: 100000
+ pool:
+ num_workers: 2
+ max_jobs: 0
+ allocate_timeout: 60s
+ destroy_timeout: 60s
+
+ pipelines:
+ test-1:
+ driver: memory
+ config:
+ priority: 10
+ prefetch: 10000
+ test-2:
+ driver: memory
+ config:
+ priority: 10
+ prefetch: 10000
+
+ consume: ["test-1", "test-2"]
diff --git a/tests/doc.go b/tests/doc.go
new file mode 100644
index 000000000..e1ca21691
--- /dev/null
+++ b/tests/doc.go
@@ -0,0 +1,5 @@
+// Package tests contains end-to-end integration tests for RoadRunner.
+// Tests exercise the full plugin stack via the Endure dependency injection
+// container, spawning real PHP worker processes and verifying behavior
+// through RPC calls, HTTP requests, and gRPC clients.
+package tests
diff --git a/tests/e2e_grpc_test.go b/tests/e2e_grpc_test.go
new file mode 100644
index 000000000..d2e1f6e4a
--- /dev/null
+++ b/tests/e2e_grpc_test.go
@@ -0,0 +1,202 @@
+package tests
+
+import (
+ "context"
+ "log/slog"
+ "os"
+ "os/signal"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ mocklogger "tests/mock"
+ "tests/proto/service"
+
+ "github.com/roadrunner-server/config/v5"
+ "github.com/roadrunner-server/endure/v2"
+ grpcPlugin "github.com/roadrunner-server/grpc/v5"
+ rrOtel "github.com/roadrunner-server/otel/v5"
+ rpcPlugin "github.com/roadrunner-server/rpc/v5"
+ "github.com/roadrunner-server/server/v5"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "go.uber.org/zap"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+)
+
+// TestGrpcPing verifies the full gRPC lifecycle: container startup, PHP worker
+// registration of the Echo service, sending a Ping RPC, and validating the
+// response (PHP uppercases the message).
+func TestGrpcPing(t *testing.T) {
+ cont := endure.New(slog.LevelDebug)
+
+ cfg := &config.Plugin{
+ Version: "2024.1.0",
+ Path: "configs/.rr-grpc.yaml",
+ }
+
+ l, _ := mocklogger.ZapTestLogger(zap.DebugLevel)
+
+ err := cont.RegisterAll(
+ cfg,
+ &grpcPlugin.Plugin{},
+ &rpcPlugin.Plugin{},
+ &server.Plugin{},
+ l,
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ stopCh := make(chan struct{}, 1)
+
+ wg := &sync.WaitGroup{}
+ wg.Go(func() {
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ case <-sig:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ case <-stopCh:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ }
+ }
+ })
+
+ time.Sleep(time.Second)
+
+ t.Run("PingEcho", func(t *testing.T) {
+ conn, errDial := grpc.NewClient(
+ "127.0.0.1:9191",
+ grpc.WithTransportCredentials(insecure.NewCredentials()),
+ )
+ require.NoError(t, errDial)
+ require.NotNil(t, conn)
+ defer func() { _ = conn.Close() }()
+
+ ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
+ defer cancel()
+
+ client := service.NewEchoClient(conn)
+ resp, errPing := client.Ping(ctx, &service.Message{Msg: "hello"})
+ require.NoError(t, errPing)
+ require.Equal(t, "HELLO", resp.GetMsg())
+ })
+
+ stopCh <- struct{}{}
+ wg.Wait()
+}
+
+// TestGrpcPingWithOtel verifies gRPC + OTEL plugin integration.
+// The OTEL plugin is registered alongside gRPC and instruments
+// the gRPC server with tracing (stdout exporter, no external collector).
+func TestGrpcPingWithOtel(t *testing.T) {
+ cont := endure.New(slog.LevelDebug)
+
+ cfg := &config.Plugin{
+ Version: "2024.1.0",
+ Path: "configs/.rr-grpc-otel.yaml",
+ }
+
+ l, _ := mocklogger.ZapTestLogger(zap.DebugLevel)
+
+ err := cont.RegisterAll(
+ cfg,
+ &grpcPlugin.Plugin{},
+ &rpcPlugin.Plugin{},
+ &server.Plugin{},
+ &rrOtel.Plugin{},
+ l,
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ stopCh := make(chan struct{}, 1)
+
+ wg := &sync.WaitGroup{}
+ wg.Go(func() {
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ case <-sig:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ case <-stopCh:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ }
+ }
+ })
+
+ time.Sleep(time.Second)
+
+ t.Run("PingEchoWithOtel", func(t *testing.T) {
+ conn, errDial := grpc.NewClient(
+ "127.0.0.1:9192",
+ grpc.WithTransportCredentials(insecure.NewCredentials()),
+ )
+ require.NoError(t, errDial)
+ require.NotNil(t, conn)
+ defer func() { _ = conn.Close() }()
+
+ ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
+ defer cancel()
+
+ client := service.NewEchoClient(conn)
+ resp, errPing := client.Ping(ctx, &service.Message{Msg: "hello"})
+ require.NoError(t, errPing)
+ require.Equal(t, "HELLO", resp.GetMsg())
+ })
+
+ stopCh <- struct{}{}
+ wg.Wait()
+}
diff --git a/tests/e2e_http_test.go b/tests/e2e_http_test.go
new file mode 100644
index 000000000..97e9bebda
--- /dev/null
+++ b/tests/e2e_http_test.go
@@ -0,0 +1,321 @@
+package tests
+
+import (
+ compressGzip "compress/gzip"
+ "io"
+ "log/slog"
+ "net/http"
+ "os"
+ "os/signal"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ mocklogger "tests/mock"
+
+ "github.com/roadrunner-server/config/v5"
+ "github.com/roadrunner-server/endure/v2"
+ gzipPlugin "github.com/roadrunner-server/gzip/v5"
+ "github.com/roadrunner-server/headers/v5"
+ httpPlugin "github.com/roadrunner-server/http/v5"
+ rrOtel "github.com/roadrunner-server/otel/v5"
+ "github.com/roadrunner-server/prometheus/v5"
+ proxyIP "github.com/roadrunner-server/proxy_ip_parser/v5"
+ rpcPlugin "github.com/roadrunner-server/rpc/v5"
+ "github.com/roadrunner-server/send/v5"
+ "github.com/roadrunner-server/server/v5"
+ "github.com/roadrunner-server/static/v5"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "go.uber.org/zap"
+)
+
+// newHTTPClient returns an HTTP client with a reasonable request timeout for e2e tests.
+func newHTTPClient() *http.Client {
+ return &http.Client{Timeout: 5 * time.Second}
+}
+
+// TestHTTPWithMiddleware verifies that the HTTP plugin works end-to-end with
+// headers, gzip, prometheus metrics, proxy_ip_parser, and sendfile middleware
+// all wired together via the Endure DI container.
+func TestHTTPWithMiddleware(t *testing.T) {
+ cont := endure.New(slog.LevelDebug)
+
+ cfg := &config.Plugin{
+ Version: "2024.1.0",
+ Path: "configs/.rr-http-middleware.yaml",
+ }
+
+ l, _ := mocklogger.ZapTestLogger(zap.DebugLevel)
+
+ err := cont.RegisterAll(
+ cfg,
+ &server.Plugin{},
+ &rpcPlugin.Plugin{},
+ &httpPlugin.Plugin{},
+ &headers.Plugin{},
+ &gzipPlugin.Plugin{},
+ &prometheus.Plugin{},
+ &proxyIP.Plugin{},
+ &send.Plugin{},
+ l,
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ stopCh := make(chan struct{}, 1)
+
+ wg := &sync.WaitGroup{}
+ wg.Go(func() {
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ case <-sig:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ case <-stopCh:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ }
+ }
+ })
+
+ time.Sleep(time.Second)
+
+ t.Run("EchoWithMiddleware", func(t *testing.T) {
+ req, errReq := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://127.0.0.1:18950/?hello=world", nil)
+ require.NoError(t, errReq)
+ req.Header.Set("Accept-Encoding", "gzip")
+
+ resp, errDo := newHTTPClient().Do(req)
+ require.NoError(t, errDo)
+ defer func() { _ = resp.Body.Close() }()
+
+ assert.Equal(t, http.StatusCreated, resp.StatusCode)
+
+ // Verify the headers middleware added our custom response header.
+ assert.Equal(t, "e2e-roadrunner", resp.Header.Get("X-Test"))
+
+ // Verify gzip encoding is applied.
+ assert.Equal(t, "gzip", resp.Header.Get("Content-Encoding"))
+
+ // Decompress and verify the response body.
+ gr, errGz := compressGzip.NewReader(resp.Body)
+ require.NoError(t, errGz)
+ defer func() { _ = gr.Close() }()
+
+ body, errRead := io.ReadAll(gr)
+ require.NoError(t, errRead)
+ assert.Equal(t, "WORLD", string(body))
+ })
+
+ stopCh <- struct{}{}
+ wg.Wait()
+}
+
+// TestHTTPStaticFile verifies that the static middleware serves files from disk,
+// and that non-static requests fall through to the PHP worker.
+func TestHTTPStaticFile(t *testing.T) {
+ cont := endure.New(slog.LevelDebug)
+
+ cfg := &config.Plugin{
+ Version: "2024.1.0",
+ Path: "configs/.rr-http-static.yaml",
+ }
+
+ l, _ := mocklogger.ZapTestLogger(zap.DebugLevel)
+
+ err := cont.RegisterAll(
+ cfg,
+ &server.Plugin{},
+ &rpcPlugin.Plugin{},
+ &httpPlugin.Plugin{},
+ &static.Plugin{},
+ &gzipPlugin.Plugin{},
+ l,
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ stopCh := make(chan struct{}, 1)
+
+ wg := &sync.WaitGroup{}
+ wg.Go(func() {
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ case <-sig:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ case <-stopCh:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ }
+ }
+ })
+
+ time.Sleep(time.Second)
+
+ t.Run("ServeStaticFile", func(t *testing.T) {
+ req, errReq := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://127.0.0.1:18951/sample.txt", nil)
+ require.NoError(t, errReq)
+
+ resp, errDo := newHTTPClient().Do(req)
+ require.NoError(t, errDo)
+ defer func() { _ = resp.Body.Close() }()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ body, errRead := io.ReadAll(resp.Body)
+ require.NoError(t, errRead)
+ assert.Contains(t, string(body), "Hello from RoadRunner e2e static file test!")
+ })
+
+ t.Run("FallThroughToPHP", func(t *testing.T) {
+ req, errReq := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://127.0.0.1:18951/?hello=world", nil)
+ require.NoError(t, errReq)
+
+ resp, errDo := newHTTPClient().Do(req)
+ require.NoError(t, errDo)
+ defer func() { _ = resp.Body.Close() }()
+
+ assert.Equal(t, http.StatusCreated, resp.StatusCode)
+
+ body, errRead := io.ReadAll(resp.Body)
+ require.NoError(t, errRead)
+ assert.Equal(t, "WORLD", string(body))
+ })
+
+ stopCh <- struct{}{}
+ wg.Wait()
+}
+
+// TestHTTPWithOtel verifies HTTP + real OTEL plugin integration.
+// The OTEL plugin is registered as an HTTP middleware alongside gzip.
+// This test exercises the full OTEL plugin lifecycle (Init/Serve/Stop)
+// with stdout exporter (no external collector needed).
+func TestHTTPWithOtel(t *testing.T) {
+ cont := endure.New(slog.LevelDebug)
+
+ cfg := &config.Plugin{
+ Version: "2024.1.0",
+ Path: "configs/.rr-http-otel.yaml",
+ }
+
+ l, _ := mocklogger.ZapTestLogger(zap.DebugLevel)
+
+ err := cont.RegisterAll(
+ cfg,
+ &server.Plugin{},
+ &rpcPlugin.Plugin{},
+ &httpPlugin.Plugin{},
+ &gzipPlugin.Plugin{},
+ &rrOtel.Plugin{},
+ l,
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ require.NoError(t, err)
+
+ ch, err := cont.Serve()
+ require.NoError(t, err)
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ stopCh := make(chan struct{}, 1)
+
+ wg := &sync.WaitGroup{}
+ wg.Go(func() {
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ case <-sig:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ case <-stopCh:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ }
+ }
+ })
+
+ time.Sleep(time.Second)
+
+ t.Run("EchoWithOtel", func(t *testing.T) {
+ req, errReq := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://127.0.0.1:18952/?hello=world", nil)
+ require.NoError(t, errReq)
+
+ resp, errDo := newHTTPClient().Do(req)
+ require.NoError(t, errDo)
+ defer func() { _ = resp.Body.Close() }()
+
+ assert.Equal(t, http.StatusCreated, resp.StatusCode)
+
+ body, errRead := io.ReadAll(resp.Body)
+ require.NoError(t, errRead)
+ assert.Equal(t, "WORLD", string(body))
+ })
+
+ stopCh <- struct{}{}
+ wg.Wait()
+}
diff --git a/tests/e2e_jobs_test.go b/tests/e2e_jobs_test.go
new file mode 100644
index 000000000..62c5b4c64
--- /dev/null
+++ b/tests/e2e_jobs_test.go
@@ -0,0 +1,194 @@
+package tests
+
+import (
+ "log/slog"
+ "os"
+ "os/signal"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ mocklogger "tests/mock"
+
+ "tests/helpers"
+
+ "github.com/roadrunner-server/config/v5"
+ "github.com/roadrunner-server/endure/v2"
+ "github.com/roadrunner-server/informer/v5"
+ "github.com/roadrunner-server/jobs/v5"
+ "github.com/roadrunner-server/memory/v5"
+ rrOtel "github.com/roadrunner-server/otel/v5"
+ "github.com/roadrunner-server/resetter/v5"
+ rpcPlugin "github.com/roadrunner-server/rpc/v5"
+ "github.com/roadrunner-server/server/v5"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "go.uber.org/zap"
+)
+
+// TestJobsInMemory verifies the full jobs lifecycle using the in-memory driver:
+// container startup, pipeline consumption, job push, processing, and teardown.
+func TestJobsInMemory(t *testing.T) {
+ cont := endure.New(slog.LevelDebug)
+
+ cfg := &config.Plugin{
+ Version: "2024.1.0",
+ Path: "configs/.rr-jobs-memory.yaml",
+ }
+
+ l, oLogger := mocklogger.ZapTestLogger(zap.DebugLevel)
+
+ err := cont.RegisterAll(
+ cfg,
+ &server.Plugin{},
+ &rpcPlugin.Plugin{},
+ &jobs.Plugin{},
+ &memory.Plugin{},
+ &resetter.Plugin{},
+ &informer.Plugin{},
+ l,
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ stopCh := make(chan struct{}, 1)
+
+ wg := &sync.WaitGroup{}
+ wg.Go(func() {
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ case <-sig:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ case <-stopCh:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ }
+ }
+ })
+
+ time.Sleep(time.Second * 3)
+
+ t.Run("PushToTest1", helpers.PushToPipe("test-1", false, "127.0.0.1:6201"))
+ t.Run("PushToTest2", helpers.PushToPipe("test-2", false, "127.0.0.1:6201"))
+
+ time.Sleep(time.Second * 2)
+
+ t.Run("DestroyPipelines", helpers.DestroyPipelines("127.0.0.1:6201", "test-1", "test-2"))
+
+ stopCh <- struct{}{}
+ wg.Wait()
+
+ require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("pipeline was started").Len(), 2)
+ require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("pipeline was stopped").Len(), 2)
+ require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("job was pushed successfully").Len(), 2)
+}
+
+// TestJobsInMemoryWithOtel verifies the jobs lifecycle with OTEL tracing enabled.
+// The OTEL plugin instruments job push/consume operations
+// (stdout exporter, no external collector).
+func TestJobsInMemoryWithOtel(t *testing.T) {
+ cont := endure.New(slog.LevelDebug)
+
+ cfg := &config.Plugin{
+ Version: "2024.1.0",
+ Path: "configs/.rr-jobs-memory-otel.yaml",
+ }
+
+ l, oLogger := mocklogger.ZapTestLogger(zap.DebugLevel)
+
+ err := cont.RegisterAll(
+ cfg,
+ &server.Plugin{},
+ &rpcPlugin.Plugin{},
+ &jobs.Plugin{},
+ &memory.Plugin{},
+ &resetter.Plugin{},
+ &informer.Plugin{},
+ &rrOtel.Plugin{},
+ l,
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ stopCh := make(chan struct{}, 1)
+
+ wg := &sync.WaitGroup{}
+ wg.Go(func() {
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ case <-sig:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ case <-stopCh:
+ stopErr := cont.Stop()
+ if stopErr != nil {
+ assert.FailNow(t, "error", stopErr.Error())
+ }
+ return
+ }
+ }
+ })
+
+ time.Sleep(time.Second * 3)
+
+ t.Run("PushToTest1WithOtel", helpers.PushToPipe("test-1", false, "127.0.0.1:6207"))
+ t.Run("PushToTest2WithOtel", helpers.PushToPipe("test-2", false, "127.0.0.1:6207"))
+
+ time.Sleep(time.Second * 2)
+
+ t.Run("DestroyPipelinesWithOtel", helpers.DestroyPipelines("127.0.0.1:6207", "test-1", "test-2"))
+
+ stopCh <- struct{}{}
+ wg.Wait()
+
+ require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("pipeline was started").Len(), 2)
+ require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("pipeline was stopped").Len(), 2)
+ require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("job was pushed successfully").Len(), 2)
+}
diff --git a/tests/go.mod b/tests/go.mod
new file mode 100644
index 000000000..bf11df63e
--- /dev/null
+++ b/tests/go.mod
@@ -0,0 +1,138 @@
+module tests
+
+go 1.26.1
+
+require (
+ github.com/google/uuid v1.6.0
+ github.com/roadrunner-server/api/v4 v4.23.0
+ github.com/roadrunner-server/config/v5 v5.1.9
+ github.com/roadrunner-server/endure/v2 v2.6.2
+ github.com/roadrunner-server/goridge/v3 v3.8.3
+ github.com/roadrunner-server/grpc/v5 v5.3.0
+ github.com/roadrunner-server/gzip/v5 v5.3.0
+ github.com/roadrunner-server/headers/v5 v5.2.0
+ github.com/roadrunner-server/http/v5 v5.3.0
+ github.com/roadrunner-server/informer/v5 v5.1.9
+ github.com/roadrunner-server/jobs/v5 v5.1.9
+ github.com/roadrunner-server/memory/v5 v5.2.9
+ github.com/roadrunner-server/otel/v5 v5.5.0
+ github.com/roadrunner-server/prometheus/v5 v5.2.0
+ github.com/roadrunner-server/proxy_ip_parser/v5 v5.1.9
+ github.com/roadrunner-server/resetter/v5 v5.1.9
+ github.com/roadrunner-server/rpc/v5 v5.1.9
+ github.com/roadrunner-server/send/v5 v5.2.0
+ github.com/roadrunner-server/server/v5 v5.2.10
+ github.com/roadrunner-server/static/v5 v5.2.0
+ github.com/stretchr/testify v1.11.1
+ go.uber.org/zap v1.27.1
+ google.golang.org/grpc v1.79.2
+ google.golang.org/protobuf v1.36.11
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
+ github.com/caddyserver/certmagic v0.25.2 // indirect
+ github.com/caddyserver/zerossl v0.1.5 // indirect
+ github.com/cenkalti/backoff/v5 v5.0.3 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+ github.com/emicklei/proto v1.14.3 // indirect
+ github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
+ github.com/fatih/color v1.18.0 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/fsnotify/fsnotify v1.9.0 // indirect
+ github.com/go-logr/logr v1.4.3 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
+ github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
+ github.com/goccy/go-json v0.10.6 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/mock v1.7.0-rc.1 // indirect
+ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
+ github.com/joho/godotenv v1.5.1 // indirect
+ github.com/klauspost/compress v1.18.4 // indirect
+ github.com/klauspost/cpuid/v2 v2.3.0 // indirect
+ github.com/libdns/libdns v1.1.1 // indirect
+ github.com/mattn/go-colorable v0.1.14 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mholt/acmez v1.2.0 // indirect
+ github.com/mholt/acmez/v3 v3.1.6 // indirect
+ github.com/miekg/dns v1.1.72 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/nexus-rpc/sdk-go v0.6.0 // indirect
+ github.com/openzipkin/zipkin-go v0.4.3 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.4 // indirect
+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+ github.com/prometheus/client_golang v1.23.2 // indirect
+ github.com/prometheus/client_model v0.6.2 // indirect
+ github.com/prometheus/common v0.67.5 // indirect
+ github.com/prometheus/procfs v0.20.1 // indirect
+ github.com/quic-go/qpack v0.6.0 // indirect
+ github.com/quic-go/quic-go v0.59.0 // indirect
+ github.com/roadrunner-server/context v1.3.0 // indirect
+ github.com/roadrunner-server/errors v1.4.1 // indirect
+ github.com/roadrunner-server/events v1.0.1 // indirect
+ github.com/roadrunner-server/pool v1.1.3 // indirect
+ github.com/roadrunner-server/priority_queue v1.0.6 // indirect
+ github.com/roadrunner-server/tcplisten v1.5.2 // indirect
+ github.com/robfig/cron v1.2.0 // indirect
+ github.com/rs/cors v1.11.1 // indirect
+ github.com/sagikazarmark/locafero v0.12.0 // indirect
+ github.com/shirou/gopsutil v3.21.11+incompatible // indirect
+ github.com/spf13/afero v1.15.0 // indirect
+ github.com/spf13/cast v1.10.0 // indirect
+ github.com/spf13/pflag v1.0.10 // indirect
+ github.com/spf13/viper v1.21.0 // indirect
+ github.com/stretchr/objx v0.5.3 // indirect
+ github.com/subosito/gotenv v1.6.0 // indirect
+ github.com/tklauser/go-sysconf v0.3.16 // indirect
+ github.com/tklauser/numcpus v0.11.0 // indirect
+ github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
+ github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
+ github.com/zeebo/assert v1.3.1 // indirect
+ github.com/zeebo/blake3 v0.2.4 // indirect
+ go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
+ go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 // indirect
+ go.opentelemetry.io/otel v1.42.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
+ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect
+ go.opentelemetry.io/otel/exporters/zipkin v1.42.0 // indirect
+ go.opentelemetry.io/otel/metric v1.42.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.42.0 // indirect
+ go.opentelemetry.io/otel/trace v1.42.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.10.0 // indirect
+ go.temporal.io/api v1.62.3 // indirect
+ go.temporal.io/sdk v1.41.0 // indirect
+ go.temporal.io/sdk/contrib/opentelemetry v0.7.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ go.uber.org/zap/exp v0.3.0 // indirect
+ go.yaml.in/yaml/v2 v2.4.4 // indirect
+ go.yaml.in/yaml/v3 v3.0.4 // indirect
+ golang.org/x/crypto v0.49.0 // indirect
+ golang.org/x/mod v0.34.0 // indirect
+ golang.org/x/net v0.52.0 // indirect
+ golang.org/x/sync v0.20.0 // indirect
+ golang.org/x/sys v0.42.0 // indirect
+ golang.org/x/text v0.35.0 // indirect
+ golang.org/x/time v0.15.0 // indirect
+ golang.org/x/tools v0.43.0 // indirect
+ google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
+
+exclude (
+ github.com/redis/go-redis/v9 v9.15.0
+ github.com/redis/go-redis/v9 v9.15.1
+ github.com/spf13/viper v1.18.0
+ github.com/spf13/viper v1.18.1
+ go.temporal.io/api v1.26.1
+)
diff --git a/tests/go.sum b/tests/go.sum
new file mode 100644
index 000000000..40dc3ab0b
--- /dev/null
+++ b/tests/go.sum
@@ -0,0 +1,320 @@
+code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
+code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
+github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
+github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc=
+github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg=
+github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
+github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
+github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
+github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emicklei/proto v1.14.3 h1:zEhlzNkpP8kN6utonKMzlPfIvy82t5Kb9mufaJxSe1Q=
+github.com/emicklei/proto v1.14.3/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
+github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
+github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
+github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
+github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
+github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
+github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
+github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
+github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
+github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
+github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
+github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
+github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
+github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
+github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
+github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ=
+github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
+github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
+github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
+github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
+github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
+github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY=
+github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
+github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/nexus-rpc/sdk-go v0.6.0 h1:QRgnP2zTbxEbiyWG/aXH8uSC5LV/Mg1fqb19jb4DBlo=
+github.com/nexus-rpc/sdk-go v0.6.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk=
+github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
+github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
+github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
+github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
+github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
+github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
+github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
+github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
+github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
+github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
+github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
+github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
+github.com/roadrunner-server/api/v4 v4.23.0 h1:lrVXgP4ozD/H5DrIdT181ldVhD1R9QT5qsi8qWUTDF4=
+github.com/roadrunner-server/api/v4 v4.23.0/go.mod h1:AlHuVVOklb7XF33Cf7IfmwOn3j4gGg37on9Xi6j08Bg=
+github.com/roadrunner-server/config/v5 v5.1.9 h1:ReWwts/prEvuC4yVJ0BRDmY5sxw/1c+hGTSdJ71hIQU=
+github.com/roadrunner-server/config/v5 v5.1.9/go.mod h1:R6YyTWahW61tWHOI2BfdkQU/0Zc/2d6/JbJ/KEvq8F8=
+github.com/roadrunner-server/context v1.3.0 h1:iyTXVORhPU2/26z7kdzEaggwG5P8yhIKUDLiePjylFQ=
+github.com/roadrunner-server/context v1.3.0/go.mod h1:KPAzAlnErXekQazW9t4h55U1S42Q2bk0WCaPQrezJw4=
+github.com/roadrunner-server/endure/v2 v2.6.2 h1:sIB4kTyE7gtT3fDhuYWUYn6Vt/dcPtiA6FoNS1eS+84=
+github.com/roadrunner-server/endure/v2 v2.6.2/go.mod h1:t/2+xpNYgGBwhzn83y2MDhvhZ19UVq1REcvqn7j7RB8=
+github.com/roadrunner-server/errors v1.4.1 h1:LKNeaCGiwd3t8IaL840ZNF3UA9yDQlpvHnKddnh0YRQ=
+github.com/roadrunner-server/errors v1.4.1/go.mod h1:qeffnIKG0e4j1dzGpa+OGY5VKSfMphizvqWIw8s2lAo=
+github.com/roadrunner-server/events v1.0.1 h1:waCkKhxhzdK3VcI1xG22l+h+0J+Nfdpxjhyy01Un+kI=
+github.com/roadrunner-server/events v1.0.1/go.mod h1:WZRqoEVaFm209t52EuoT7ISUtvX6BrCi6bI/7pjkVC0=
+github.com/roadrunner-server/goridge/v3 v3.8.3 h1:XmjrOFnI6ZbQTPaP39DEk8KwLUNTgjluK3pcZaW6ixQ=
+github.com/roadrunner-server/goridge/v3 v3.8.3/go.mod h1:4TZU8zgkKIZCsH51qwGMpvyXCT59u/8z6q8sCe4ZGAQ=
+github.com/roadrunner-server/grpc/v5 v5.3.0 h1:DZj7b6vU+1bo5oAAUil75JUuovAwZzXh41ZS7rUE6xQ=
+github.com/roadrunner-server/grpc/v5 v5.3.0/go.mod h1:A3G5eJZMQYnbzWWZ9crPSS557wPfwEc3uWr48RtbAVQ=
+github.com/roadrunner-server/gzip/v5 v5.3.0 h1:l6H4NQmX2RaaTWkeEt0q39ITLcMKL8FmBXm3ENUE4oI=
+github.com/roadrunner-server/gzip/v5 v5.3.0/go.mod h1:fXhfwelVKIJvon8jEdZrqobbE1zJoQkN3Ov7mO3V5XI=
+github.com/roadrunner-server/headers/v5 v5.2.0 h1:Q8SYxr+zhj5VskDPKq3JYyZA3XqcGxtYcb5uh4FytFY=
+github.com/roadrunner-server/headers/v5 v5.2.0/go.mod h1:BEeI6pI0CyVjE8gG6H5LVPRxZ2kDtMFoHupFE2SG2wU=
+github.com/roadrunner-server/http/v5 v5.3.0 h1:gsNdOQffWF3/hL7T+OTQat36SvCl5ZUjuotz41rONQ4=
+github.com/roadrunner-server/http/v5 v5.3.0/go.mod h1:JCM8jmgRtaPMFiKPcriOxNQ98ha821uzN5BQ5XJzc1I=
+github.com/roadrunner-server/informer/v5 v5.1.9 h1:yl334LMqUoWXfeP4299HgY9G7mq6kX6FVCSwT+cYdfQ=
+github.com/roadrunner-server/informer/v5 v5.1.9/go.mod h1:JPzSsDjLHExdQ9SbT9e8H/oB7pajgCScL/G70saQzSA=
+github.com/roadrunner-server/jobs/v5 v5.1.9 h1:28biT9tGTYSXV+FWJCLfEOCV3sJZ7VVd46oJLhIfJkM=
+github.com/roadrunner-server/jobs/v5 v5.1.9/go.mod h1:3qiklMKRqMHl0+TbrFQWzaNrtqDGkPFdgZC/dFqRBNU=
+github.com/roadrunner-server/memory/v5 v5.2.9 h1:niLep2dyUaYhrn+I12kTXjGUTUSbVJa+jyvNJrbt/hw=
+github.com/roadrunner-server/memory/v5 v5.2.9/go.mod h1:UcE3Sf0TYRmAG1HukXmzsztGD0nBi6RRhG75K11Fqpk=
+github.com/roadrunner-server/otel/v5 v5.5.0 h1:IYMw04K110sMANyZpauvQ3rRMnq0zZhwQbISHLisfQE=
+github.com/roadrunner-server/otel/v5 v5.5.0/go.mod h1:B1EaW4hC2VGUKCKLw9CZYKxuQid+2Zb7ojzXpRUb6oo=
+github.com/roadrunner-server/pool v1.1.3 h1:KMsiL6yuYBWGk73bdO0akwP+fJ63bxDF972JukCGsxI=
+github.com/roadrunner-server/pool v1.1.3/go.mod h1:8ceC7NvZKJRciv+KJmcyk5CeDugoel6GD+crm5kBFW0=
+github.com/roadrunner-server/priority_queue v1.0.6 h1:x8bcMyjWs2Z4ySbO9BTP8Dzy2prCuazJY9HHrVTmUVY=
+github.com/roadrunner-server/priority_queue v1.0.6/go.mod h1:aJ2D9s18+OGpFfNgwoIduraaFYBGv4FKElnpzqO+TBI=
+github.com/roadrunner-server/prometheus/v5 v5.2.0 h1:1KOacYOJL6ZN4vCf+SZIREFIqaYZ8WgWOiZifS88gns=
+github.com/roadrunner-server/prometheus/v5 v5.2.0/go.mod h1:tzN4CZataiCzNyY9V5E0leLqvDmcWySUVnSxcwxFIrc=
+github.com/roadrunner-server/proxy_ip_parser/v5 v5.1.9 h1:GoEgKJ97jozcJRecBMa5orMDWByaCwXUqg2zSUXl2M8=
+github.com/roadrunner-server/proxy_ip_parser/v5 v5.1.9/go.mod h1:DWaHaqpitzUbDVfK1M5ojC7bqUeOn6CAa5oxntU00po=
+github.com/roadrunner-server/resetter/v5 v5.1.9 h1:rH1nxkgvItbMEp1/JFZqcijOkkav4zo0E4wcVXBcXa8=
+github.com/roadrunner-server/resetter/v5 v5.1.9/go.mod h1:P5TfzCGQMNsUDPTrjOU7XFLdRz+DtFH/FmRla+lUF94=
+github.com/roadrunner-server/rpc/v5 v5.1.9 h1:AbRd2xEkWY8N3J4GUhoDeL+pnfwzKHazV4k40jZ+YDk=
+github.com/roadrunner-server/rpc/v5 v5.1.9/go.mod h1:TtbPY1cPvL46Mk9Dh4qwx33WO4R3m1lZkgL2q2MfHtA=
+github.com/roadrunner-server/send/v5 v5.2.0 h1:nQMaO7u9z+wYD1bInF/6ctImEaCJZFHscd3cjC1zNOc=
+github.com/roadrunner-server/send/v5 v5.2.0/go.mod h1:HmiJmpB6Lsd3jHLCy8pYG5J6E9R7IHURFr8icokNkog=
+github.com/roadrunner-server/server/v5 v5.2.10 h1:IshR3mlXJ/fh+zY17tN3Q7GgaanMzA7H9wwEz4PxCSA=
+github.com/roadrunner-server/server/v5 v5.2.10/go.mod h1:oxXhRo2EDykCk8ujhdjyJCb2rPpoTt8AVy46RHRfeeo=
+github.com/roadrunner-server/static/v5 v5.2.0 h1:xpZgIlvKWdWUJ37BtbNLtL/kN1rNFMEwFZbBnzWDeEM=
+github.com/roadrunner-server/static/v5 v5.2.0/go.mod h1:jp3eATJDSLmidXI25YDievkKp5XZ35qK6g4vHwpS0Ug=
+github.com/roadrunner-server/tcplisten v1.5.2 h1:nn8yXYrhRDkfQ9AAu4V075uT4fZRmOnpxkawgE+bWPA=
+github.com/roadrunner-server/tcplisten v1.5.2/go.mod h1:DufGBz7Dlx2KrNe/4RukEvGMTqZKB0Uve1GztwcyyR8=
+github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
+github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
+github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
+github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
+github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
+github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
+github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
+github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
+github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
+github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
+github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
+github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
+github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
+github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
+github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
+github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
+github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
+github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
+github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
+github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A=
+github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
+github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
+github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
+github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
+github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
+go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 h1:jP8unWI6q5kcb3gpGLjKDGaUa+JW+nHKWvpS/q+YuWA=
+go.opentelemetry.io/contrib/propagators/jaeger v1.42.0/go.mod h1:xd89e/pUyPatUP1C4z1UknD9jHptESO99tWyvd4mWD4=
+go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
+go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
+go.opentelemetry.io/otel/exporters/zipkin v1.42.0 h1:Z7ARHF7193vyVltPYcmuhSKPLf8dP5rtJZLtTQnbMH4=
+go.opentelemetry.io/otel/exporters/zipkin v1.42.0/go.mod h1:DW09+gaEg5kydlb9g8kp4Nos3yqo9YSA1uHXkeJihXc=
+go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
+go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
+go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
+go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
+go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
+go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
+go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
+go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
+go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
+go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
+go.temporal.io/api v1.62.3 h1:ivSDI/SUDyzgaZUDTHrggncOE2KxcRED93n13DVt02g=
+go.temporal.io/api v1.62.3/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
+go.temporal.io/sdk v1.41.0 h1:c9tayCQJDM5ZQdrqjGmjqk5ejxUtsEScJGF94sAVYpM=
+go.temporal.io/sdk v1.41.0/go.mod h1:/InXQT5guZ6AizYzpmzr5avQ/GMgq1ZObcKlKE2AhTc=
+go.temporal.io/sdk/contrib/opentelemetry v0.7.0 h1:GSna1HP+1ibNXZ9xlVdQU2zFVqdt5VcdF0dzpeaYccQ=
+go.temporal.io/sdk/contrib/opentelemetry v0.7.0/go.mod h1:oQJC6UIl3FbSYh4f2MlUAIYSE6FPw02X1Tw8/bOvfxg=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
+go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
+go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
+go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
+go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
+go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
+golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
+golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
+golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
+golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
+google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
+google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
+google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js=
+google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
+google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/tests/helpers/doc.go b/tests/helpers/doc.go
new file mode 100644
index 000000000..13f758002
--- /dev/null
+++ b/tests/helpers/doc.go
@@ -0,0 +1,5 @@
+// Package helpers provides RPC helper functions for end-to-end tests.
+// Each helper returns a func(t *testing.T) suitable for use with t.Run(),
+// performing operations like pushing jobs, pausing pipelines, and collecting
+// statistics via Goridge RPC.
+package helpers
diff --git a/tests/helpers/helpers.go b/tests/helpers/helpers.go
new file mode 100644
index 000000000..720ad84ad
--- /dev/null
+++ b/tests/helpers/helpers.go
@@ -0,0 +1,125 @@
+package helpers
+
+import (
+ "context"
+ "net"
+ "net/rpc"
+ "testing"
+ "time"
+
+ "github.com/google/uuid"
+ jobsProto "github.com/roadrunner-server/api/v4/build/jobs/v1"
+ goridgeRpc "github.com/roadrunner-server/goridge/v3/pkg/rpc"
+ "github.com/stretchr/testify/require"
+)
+
+const (
+ push = "jobs.Push"
+ pause = "jobs.Pause"
+ destroy = "jobs.Destroy"
+ resume = "jobs.Resume"
+
+ dialTimeout = 5 * time.Second
+)
+
+// rpcClient dials the given address with a timeout and returns a Goridge RPC
+// client. The client is automatically closed via t.Cleanup when the test ends.
+func rpcClient(t *testing.T, address string) *rpc.Client {
+ t.Helper()
+
+ ctx, cancel := context.WithTimeout(context.Background(), dialTimeout)
+ defer cancel()
+
+ conn, err := new(net.Dialer).DialContext(ctx, "tcp", address)
+ require.NoError(t, err)
+
+ client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn))
+ t.Cleanup(func() { _ = client.Close() })
+
+ return client
+}
+
+// callPipelines is a generic helper that calls the given RPC method
+// on the specified pipelines.
+func callPipelines(t *testing.T, address, method string, pipes []string) {
+ t.Helper()
+
+ client := rpcClient(t, address)
+
+ pipe := &jobsProto.Pipelines{Pipelines: make([]string, len(pipes))}
+ for i := range pipes {
+ pipe.GetPipelines()[i] = pipes[i]
+ }
+
+ er := &jobsProto.Empty{}
+ err := client.Call(method, pipe, er)
+ require.NoError(t, err)
+}
+
+// ResumePipes resumes the specified pipelines via RPC.
+func ResumePipes(address string, pipes ...string) func(t *testing.T) {
+ return func(t *testing.T) {
+ t.Helper()
+ callPipelines(t, address, resume, pipes)
+ }
+}
+
+// PausePipelines pauses the specified pipelines via RPC.
+func PausePipelines(address string, pipes ...string) func(t *testing.T) {
+ return func(t *testing.T) {
+ t.Helper()
+ callPipelines(t, address, pause, pipes)
+ }
+}
+
+// PushToPipe pushes a single job to the specified pipeline via RPC.
+func PushToPipe(pipeline string, autoAck bool, address string) func(t *testing.T) {
+ return func(t *testing.T) {
+ t.Helper()
+
+ client := rpcClient(t, address)
+
+ req := &jobsProto.PushRequest{Job: &jobsProto.Job{
+ Job: "some/php/namespace",
+ Id: uuid.NewString(),
+ Payload: []byte(`{"hello":"world"}`),
+ Headers: map[string]*jobsProto.HeaderValue{"test": {Value: []string{"test2"}}},
+ Options: &jobsProto.Options{
+ AutoAck: autoAck,
+ Priority: 1,
+ Pipeline: pipeline,
+ Topic: pipeline,
+ },
+ }}
+
+ er := &jobsProto.Empty{}
+ err := client.Call(push, req, er)
+ require.NoError(t, err)
+ }
+}
+
+// DestroyPipelines destroys the specified pipelines via RPC, retrying up to 10 times.
+func DestroyPipelines(address string, pipes ...string) func(t *testing.T) {
+ return func(t *testing.T) {
+ t.Helper()
+
+ client := rpcClient(t, address)
+
+ pipe := &jobsProto.Pipelines{Pipelines: make([]string, len(pipes))}
+ for i := range pipes {
+ pipe.GetPipelines()[i] = pipes[i]
+ }
+
+ var lastErr error
+ for range 10 {
+ er := &jobsProto.Empty{}
+ lastErr = client.Call(destroy, pipe, er)
+ if lastErr != nil {
+ time.Sleep(time.Second)
+ continue
+ }
+ return
+ }
+ require.NoError(t, lastErr)
+ }
+}
diff --git a/tests/mock/doc.go b/tests/mock/doc.go
new file mode 100644
index 000000000..2ddebe11a
--- /dev/null
+++ b/tests/mock/doc.go
@@ -0,0 +1,4 @@
+// Package mocklogger provides a mock logger plugin for integration tests.
+// It implements the Endure plugin interface and captures log entries for
+// assertion via ObservedLogs.
+package mocklogger
diff --git a/tests/mock/logger.go b/tests/mock/logger.go
new file mode 100644
index 000000000..7fc02b59d
--- /dev/null
+++ b/tests/mock/logger.go
@@ -0,0 +1,73 @@
+package mocklogger
+
+import (
+ "github.com/roadrunner-server/endure/v2/dep"
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+// Logger is the interface that the mock logger provides via Endure DI.
+type Logger interface {
+ NamedLogger(string) *zap.Logger
+}
+
+// ZapLoggerMock is a mock logger plugin for integration tests.
+// It captures all log entries for later assertion via ObservedLogs.
+type ZapLoggerMock struct {
+ l *zap.Logger
+}
+
+// ZapTestLogger creates a new mock logger plugin and returns the plugin
+// instance along with an ObservedLogs for asserting on log messages.
+func ZapTestLogger(enab zapcore.LevelEnabler) (*ZapLoggerMock, *ObservedLogs) {
+ core, logs := New(enab)
+ obsLog := zap.New(core, zap.Development())
+
+ return &ZapLoggerMock{
+ l: obsLog,
+ }, logs
+}
+
+func (z *ZapLoggerMock) Init() error {
+ return nil
+}
+
+func (z *ZapLoggerMock) Serve() chan error {
+ return make(chan error, 1)
+}
+
+func (z *ZapLoggerMock) Stop() error {
+ return z.l.Sync()
+}
+
+func (z *ZapLoggerMock) Provides() []*dep.Out {
+ return []*dep.Out{
+ dep.Bind((*Logger)(nil), z.ProvideLogger),
+ }
+}
+
+func (z *ZapLoggerMock) Weight() uint {
+ return 100
+}
+
+// ProvideLogger returns the Log instance for Endure dependency injection.
+func (z *ZapLoggerMock) ProvideLogger() *Log {
+ return NewLog(z.l)
+}
+
+// Log wraps a zap.Logger to satisfy the Logger interface.
+type Log struct {
+ base *zap.Logger
+}
+
+// NewLog creates a new Log from a zap.Logger.
+func NewLog(log *zap.Logger) *Log {
+ return &Log{
+ base: log,
+ }
+}
+
+// NamedLogger returns the underlying zap.Logger scoped with the given name.
+func (l *Log) NamedLogger(name string) *zap.Logger {
+ return l.base.Named(name)
+}
diff --git a/tests/mock/observer.go b/tests/mock/observer.go
new file mode 100644
index 000000000..de2fe2bbe
--- /dev/null
+++ b/tests/mock/observer.go
@@ -0,0 +1,197 @@
+package mocklogger
+
+// Copyright (c) 2017 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import (
+ "strings"
+ "sync"
+ "time"
+
+ "go.uber.org/zap/zapcore"
+)
+
+// LoggedEntry is an encoding-agnostic representation of a log message.
+type LoggedEntry struct {
+ zapcore.Entry
+ Context []zapcore.Field
+}
+
+// ContextMap returns a map for all fields in Context.
+func (e LoggedEntry) ContextMap() map[string]any {
+ encoder := zapcore.NewMapObjectEncoder()
+ for _, f := range e.Context {
+ f.AddTo(encoder)
+ }
+ return encoder.Fields
+}
+
+// ObservedLogs is a concurrency-safe, ordered collection of observed logs.
+type ObservedLogs struct {
+ mu sync.RWMutex
+ logs []LoggedEntry
+}
+
+// Len returns the number of items in the collection.
+func (o *ObservedLogs) Len() int {
+ o.mu.RLock()
+ n := len(o.logs)
+ o.mu.RUnlock()
+ return n
+}
+
+// All returns a copy of all the observed logs.
+func (o *ObservedLogs) All() []LoggedEntry {
+ o.mu.RLock()
+ ret := make([]LoggedEntry, len(o.logs))
+ copy(ret, o.logs)
+ o.mu.RUnlock()
+ return ret
+}
+
+// TakeAll returns a copy of all the observed logs, and truncates the observed
+// slice.
+func (o *ObservedLogs) TakeAll() []LoggedEntry {
+ o.mu.Lock()
+ ret := o.logs
+ o.logs = nil
+ o.mu.Unlock()
+ return ret
+}
+
+// AllUntimed returns a copy of all the observed logs, but overwrites the
+// observed timestamps with time.Time's zero value.
+func (o *ObservedLogs) AllUntimed() []LoggedEntry {
+ ret := o.All()
+ for i := range ret {
+ ret[i].Time = time.Time{}
+ }
+ return ret
+}
+
+// FilterLevelExact filters entries to those logged at exactly the given level.
+func (o *ObservedLogs) FilterLevelExact(level zapcore.Level) *ObservedLogs {
+ return o.Filter(func(e LoggedEntry) bool {
+ return e.Level == level
+ })
+}
+
+// FilterMessage filters entries to those that have the specified message.
+func (o *ObservedLogs) FilterMessage(msg string) *ObservedLogs {
+ return o.Filter(func(e LoggedEntry) bool {
+ return e.Message == msg
+ })
+}
+
+// FilterMessageSnippet filters entries to those that have a message containing
+// the specified snippet.
+func (o *ObservedLogs) FilterMessageSnippet(snippet string) *ObservedLogs {
+ return o.Filter(func(e LoggedEntry) bool {
+ return strings.Contains(e.Message, snippet)
+ })
+}
+
+// FilterField filters entries to those that have the specified field.
+func (o *ObservedLogs) FilterField(field zapcore.Field) *ObservedLogs {
+ return o.Filter(func(e LoggedEntry) bool {
+ for _, ctxField := range e.Context {
+ if ctxField.Equals(field) {
+ return true
+ }
+ }
+ return false
+ })
+}
+
+// FilterFieldKey filters entries to those that have the specified key.
+func (o *ObservedLogs) FilterFieldKey(key string) *ObservedLogs {
+ return o.Filter(func(e LoggedEntry) bool {
+ for _, ctxField := range e.Context {
+ if ctxField.Key == key {
+ return true
+ }
+ }
+ return false
+ })
+}
+
+// Filter returns a copy of this ObservedLogs containing only those entries
+// for which the provided function returns true.
+func (o *ObservedLogs) Filter(keep func(LoggedEntry) bool) *ObservedLogs {
+ o.mu.RLock()
+ defer o.mu.RUnlock()
+
+ var filtered []LoggedEntry
+ for _, entry := range o.logs {
+ if keep(entry) {
+ filtered = append(filtered, entry)
+ }
+ }
+ return &ObservedLogs{logs: filtered}
+}
+
+func (o *ObservedLogs) add(log LoggedEntry) {
+ o.mu.Lock()
+ o.logs = append(o.logs, log)
+ o.mu.Unlock()
+}
+
+// New creates a new Core that buffers logs in memory (without any encoding).
+func New(enab zapcore.LevelEnabler) (zapcore.Core, *ObservedLogs) {
+ ol := &ObservedLogs{}
+ return &contextObserver{
+ LevelEnabler: enab,
+ logs: ol,
+ }, ol
+}
+
+type contextObserver struct {
+ zapcore.LevelEnabler
+ logs *ObservedLogs
+ context []zapcore.Field
+}
+
+func (co *contextObserver) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
+ if co.Enabled(ent.Level) {
+ return ce.AddCore(ent, co)
+ }
+ return ce
+}
+
+func (co *contextObserver) With(fields []zapcore.Field) zapcore.Core {
+ return &contextObserver{
+ LevelEnabler: co.LevelEnabler,
+ logs: co.logs,
+ context: append(co.context[:len(co.context):len(co.context)], fields...),
+ }
+}
+
+func (co *contextObserver) Write(ent zapcore.Entry, fields []zapcore.Field) error {
+ all := make([]zapcore.Field, 0, len(fields)+len(co.context))
+ all = append(all, co.context...)
+ all = append(all, fields...)
+ co.logs.add(LoggedEntry{Entry: ent, Context: all})
+
+ return nil
+}
+
+func (co *contextObserver) Sync() error {
+ return nil
+}
diff --git a/tests/php_test_files/.gitignore b/tests/php_test_files/.gitignore
new file mode 100644
index 000000000..d1502b087
--- /dev/null
+++ b/tests/php_test_files/.gitignore
@@ -0,0 +1,2 @@
+vendor/
+composer.lock
diff --git a/tests/php_test_files/composer.json b/tests/php_test_files/composer.json
new file mode 100644
index 000000000..f8fdaa9d8
--- /dev/null
+++ b/tests/php_test_files/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "test/e2e",
+ "description": "PHP dependencies for RoadRunner e2e tests",
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "require": {
+ "nyholm/psr7": "^1.5",
+ "spiral/roadrunner-http": "^3.5",
+ "spiral/roadrunner-worker": "^3.5",
+ "spiral/roadrunner-jobs": "^4.0",
+ "spiral/roadrunner-grpc": "^3.0",
+ "spiral/goridge": "^4.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "": "grpc/src"
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "php-http/discovery": true
+ }
+ }
+}
diff --git a/tests/php_test_files/grpc/src/EchoService.php b/tests/php_test_files/grpc/src/EchoService.php
new file mode 100644
index 000000000..cb6c8a5b6
--- /dev/null
+++ b/tests/php_test_files/grpc/src/EchoService.php
@@ -0,0 +1,17 @@
+setMsg(strtoupper($in->getMsg()));
+ }
+}
diff --git a/tests/php_test_files/grpc/src/GPBMetadata/Service.php b/tests/php_test_files/grpc/src/GPBMetadata/Service.php
new file mode 100644
index 000000000..a114043b3
Binary files /dev/null and b/tests/php_test_files/grpc/src/GPBMetadata/Service.php differ
diff --git a/tests/php_test_files/grpc/src/Health/HealthCheckRequest.php b/tests/php_test_files/grpc/src/Health/HealthCheckRequest.php
new file mode 100644
index 000000000..94489af01
--- /dev/null
+++ b/tests/php_test_files/grpc/src/Health/HealthCheckRequest.php
@@ -0,0 +1,58 @@
+grpc.health.v1.HealthCheckRequest
+ */
+class HealthCheckRequest extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string service = 1;
+ */
+ protected $service = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $service
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Health::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string service = 1;
+ * @return string
+ */
+ public function getService()
+ {
+ return $this->service;
+ }
+
+ /**
+ * Generated from protobuf field string service = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setService($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->service = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/tests/php_test_files/grpc/src/Health/HealthCheckResponse.php b/tests/php_test_files/grpc/src/Health/HealthCheckResponse.php
new file mode 100644
index 000000000..2c02b0796
--- /dev/null
+++ b/tests/php_test_files/grpc/src/Health/HealthCheckResponse.php
@@ -0,0 +1,58 @@
+grpc.health.v1.HealthCheckResponse
+ */
+class HealthCheckResponse extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field .grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;
+ */
+ protected $status = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type int $status
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Health::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field .grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;
+ * @return int
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ /**
+ * Generated from protobuf field .grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;
+ * @param int $var
+ * @return $this
+ */
+ public function setStatus($var)
+ {
+ GPBUtil::checkEnum($var, \Health\HealthCheckResponse\ServingStatus::class);
+ $this->status = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/tests/php_test_files/grpc/src/Health/HealthCheckResponse/ServingStatus.php b/tests/php_test_files/grpc/src/Health/HealthCheckResponse/ServingStatus.php
new file mode 100644
index 000000000..e13fe2f81
--- /dev/null
+++ b/tests/php_test_files/grpc/src/Health/HealthCheckResponse/ServingStatus.php
@@ -0,0 +1,63 @@
+grpc.health.v1.HealthCheckResponse.ServingStatus
+ */
+class ServingStatus
+{
+ /**
+ * Generated from protobuf enum UNKNOWN = 0;
+ */
+ const UNKNOWN = 0;
+ /**
+ * Generated from protobuf enum SERVING = 1;
+ */
+ const SERVING = 1;
+ /**
+ * Generated from protobuf enum NOT_SERVING = 2;
+ */
+ const NOT_SERVING = 2;
+ /**
+ * Used only by the Watch method.
+ *
+ * Generated from protobuf enum SERVICE_UNKNOWN = 3;
+ */
+ const SERVICE_UNKNOWN = 3;
+
+ private static $valueToName = [
+ self::UNKNOWN => 'UNKNOWN',
+ self::SERVING => 'SERVING',
+ self::NOT_SERVING => 'NOT_SERVING',
+ self::SERVICE_UNKNOWN => 'SERVICE_UNKNOWN',
+ ];
+
+ public static function name($value)
+ {
+ if (!isset(self::$valueToName[$value])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no name defined for value %s', __CLASS__, $value));
+ }
+ return self::$valueToName[$value];
+ }
+
+
+ public static function value($name)
+ {
+ $const = __CLASS__ . '::' . strtoupper($name);
+ if (!defined($const)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Enum %s has no value defined for name %s', __CLASS__, $name));
+ }
+ return constant($const);
+ }
+}
+
+// Adding a class alias for backwards compatibility with the previous class name.
+class_alias(ServingStatus::class, \Health\HealthCheckResponse_ServingStatus::class);
+
diff --git a/tests/php_test_files/grpc/src/Health/HealthInterface.php b/tests/php_test_files/grpc/src/Health/HealthInterface.php
new file mode 100644
index 000000000..94db3d164
--- /dev/null
+++ b/tests/php_test_files/grpc/src/Health/HealthInterface.php
@@ -0,0 +1,31 @@
+setStatus(HealthCheckResponse\ServingStatus::SERVING);
+ return $out;
+ }
+
+ public function Watch(ContextInterface $ctx, HealthCheckRequest $in): HealthCheckResponse
+ {
+ $out = new HealthCheckResponse();
+ $out->setStatus(HealthCheckResponse\ServingStatus::SERVING);
+ return $out;
+ }
+}
diff --git a/tests/php_test_files/grpc/src/Service/EchoInterface.php b/tests/php_test_files/grpc/src/Service/EchoInterface.php
new file mode 100644
index 000000000..dcc97b03a
--- /dev/null
+++ b/tests/php_test_files/grpc/src/Service/EchoInterface.php
@@ -0,0 +1,22 @@
+service.Message
+ */
+class Message extends \Google\Protobuf\Internal\Message
+{
+ /**
+ * Generated from protobuf field string msg = 1;
+ */
+ protected $msg = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $data {
+ * Optional. Data for populating the Message object.
+ *
+ * @type string $msg
+ * }
+ */
+ public function __construct($data = NULL) {
+ \GPBMetadata\Service::initOnce();
+ parent::__construct($data);
+ }
+
+ /**
+ * Generated from protobuf field string msg = 1;
+ * @return string
+ */
+ public function getMsg()
+ {
+ return $this->msg;
+ }
+
+ /**
+ * Generated from protobuf field string msg = 1;
+ * @param string $var
+ * @return $this
+ */
+ public function setMsg($var)
+ {
+ GPBUtil::checkString($var, True);
+ $this->msg = $var;
+
+ return $this;
+ }
+
+}
+
diff --git a/tests/php_test_files/grpc/worker-grpc.php b/tests/php_test_files/grpc/worker-grpc.php
new file mode 100644
index 000000000..67b4aebb7
--- /dev/null
+++ b/tests/php_test_files/grpc/worker-grpc.php
@@ -0,0 +1,19 @@
+registerService(EchoInterface::class, new EchoService());
+$server->registerService(HealthInterface::class, new HealthService());
+
+$server->serve(Worker::create());
diff --git a/tests/php_test_files/http/client.php b/tests/php_test_files/http/client.php
new file mode 100644
index 000000000..47c12cd84
--- /dev/null
+++ b/tests/php_test_files/http/client.php
@@ -0,0 +1,56 @@
+
+ *
+ * handler_name: name of the PHP file in this directory (without .php)
+ * relay_type: "pipes", "tcp", or "unix"
+ */
+
+use Spiral\Goridge;
+use Spiral\RoadRunner;
+
+ini_set('display_errors', 'stderr');
+require dirname(__DIR__) . "/vendor/autoload.php";
+
+if (count($argv) < 3) {
+ die("need 2 arguments");
+}
+
+[$test, $goridge] = [$argv[1], $argv[2]];
+
+switch ($goridge) {
+ case "pipes":
+ $relay = new Goridge\StreamRelay(STDIN, STDOUT);
+ break;
+ case "tcp":
+ $relay = new Goridge\SocketRelay("127.0.0.1", 9007);
+ break;
+ case "unix":
+ $relay = new Goridge\SocketRelay(
+ "sock.unix",
+ null,
+ Goridge\SocketRelay::SOCK_UNIX
+ );
+ break;
+ default:
+ die("invalid protocol selection");
+}
+
+$psr7 = new RoadRunner\Http\PSR7Worker(
+ new RoadRunner\Worker($relay),
+ new \Nyholm\Psr7\Factory\Psr17Factory(),
+ new \Nyholm\Psr7\Factory\Psr17Factory(),
+ new \Nyholm\Psr7\Factory\Psr17Factory()
+);
+
+require_once sprintf("%s/%s.php", __DIR__, $test);
+
+while ($req = $psr7->waitRequest()) {
+ try {
+ $psr7->respond(handleRequest($req, new \Nyholm\Psr7\Response()));
+ } catch (\Throwable $e) {
+ $psr7->getWorker()->error((string)$e);
+ }
+}
diff --git a/tests/php_test_files/http/echo.php b/tests/php_test_files/http/echo.php
new file mode 100644
index 000000000..7a1948736
--- /dev/null
+++ b/tests/php_test_files/http/echo.php
@@ -0,0 +1,14 @@
+getBody()->write(strtoupper($req->getQueryParams()['hello'] ?? ''));
+ return $resp->withStatus(201);
+}
diff --git a/tests/php_test_files/jobs/jobs_ok.php b/tests/php_test_files/jobs/jobs_ok.php
new file mode 100644
index 000000000..2f37f320b
--- /dev/null
+++ b/tests/php_test_files/jobs/jobs_ok.php
@@ -0,0 +1,20 @@
+waitTask()) {
+ try {
+ $task->complete();
+ } catch (\Throwable $e) {
+ $task->error((string)$e);
+ }
+}
diff --git a/tests/proto/service/service.pb.go b/tests/proto/service/service.pb.go
new file mode 100644
index 000000000..c6dcdb3aa
--- /dev/null
+++ b/tests/proto/service/service.pb.go
@@ -0,0 +1,148 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.28.0-devel
+// protoc v3.21.2
+// source: service.proto
+
+package service
+
+import (
+ reflect "reflect"
+ sync "sync"
+
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Message struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
+}
+
+func (x *Message) Reset() {
+ *x = Message{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_service_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Message) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Message) ProtoMessage() {}
+
+func (x *Message) ProtoReflect() protoreflect.Message {
+ mi := &file_service_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Message.ProtoReflect.Descriptor instead.
+func (*Message) Descriptor() ([]byte, []int) {
+ return file_service_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Message) GetMsg() string {
+ if x != nil {
+ return x.Msg
+ }
+ return ""
+}
+
+var File_service_proto protoreflect.FileDescriptor
+
+var file_service_proto_rawDesc = []byte{
+ 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
+ 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x1b, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73,
+ 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x03, 0x6d, 0x73, 0x67, 0x32, 0x34, 0x0a, 0x04, 0x45, 0x63, 0x68, 0x6f, 0x12, 0x2c, 0x0a,
+ 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x10, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e,
+ 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x10, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
+ 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x0c, 0x5a, 0x0a, 0x2e,
+ 0x2f, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x33,
+}
+
+var (
+ file_service_proto_rawDescOnce sync.Once
+ file_service_proto_rawDescData = file_service_proto_rawDesc
+)
+
+func file_service_proto_rawDescGZIP() []byte {
+ file_service_proto_rawDescOnce.Do(func() {
+ file_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_service_proto_rawDescData)
+ })
+ return file_service_proto_rawDescData
+}
+
+var file_service_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_service_proto_goTypes = []interface{}{
+ (*Message)(nil), // 0: service.Message
+}
+var file_service_proto_depIdxs = []int32{
+ 0, // 0: service.Echo.Ping:input_type -> service.Message
+ 0, // 1: service.Echo.Ping:output_type -> service.Message
+ 1, // [1:2] is the sub-list for method output_type
+ 0, // [0:1] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_service_proto_init() }
+func file_service_proto_init() {
+ if File_service_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Message); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_service_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 1,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_service_proto_goTypes,
+ DependencyIndexes: file_service_proto_depIdxs,
+ MessageInfos: file_service_proto_msgTypes,
+ }.Build()
+ File_service_proto = out.File
+ file_service_proto_rawDesc = nil
+ file_service_proto_goTypes = nil
+ file_service_proto_depIdxs = nil
+}
diff --git a/tests/proto/service/service.proto b/tests/proto/service/service.proto
new file mode 100644
index 000000000..61a688e85
--- /dev/null
+++ b/tests/proto/service/service.proto
@@ -0,0 +1,10 @@
+syntax = "proto3";
+
+package service;
+option go_package = "./;service";
+
+service Echo {
+ rpc Ping(Message) returns (Message) {}
+}
+
+message Message { string msg = 1; }
\ No newline at end of file
diff --git a/tests/proto/service/service_grpc.pb.go b/tests/proto/service/service_grpc.pb.go
new file mode 100644
index 000000000..55d3166f4
--- /dev/null
+++ b/tests/proto/service/service_grpc.pb.go
@@ -0,0 +1,106 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.2.0
+// - protoc v3.21.2
+// source: service.proto
+
+package service
+
+import (
+ context "context"
+
+ grpc "google.golang.org/grpc"
+ codes "google.golang.org/grpc/codes"
+ status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// EchoClient is the client API for Echo service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type EchoClient interface {
+ Ping(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error)
+}
+
+type echoClient struct {
+ cc grpc.ClientConnInterface
+}
+
+func NewEchoClient(cc grpc.ClientConnInterface) EchoClient {
+ return &echoClient{cc}
+}
+
+func (c *echoClient) Ping(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) {
+ out := new(Message)
+ err := c.cc.Invoke(ctx, "/service.Echo/Ping", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// EchoServer is the server API for Echo service.
+// All implementations must embed UnimplementedEchoServer
+// for forward compatibility
+type EchoServer interface {
+ Ping(context.Context, *Message) (*Message, error)
+ mustEmbedUnimplementedEchoServer()
+}
+
+// UnimplementedEchoServer must be embedded to have forward compatible implementations.
+type UnimplementedEchoServer struct {
+}
+
+func (UnimplementedEchoServer) Ping(context.Context, *Message) (*Message, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
+}
+func (UnimplementedEchoServer) mustEmbedUnimplementedEchoServer() {}
+
+// UnsafeEchoServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to EchoServer will
+// result in compilation errors.
+type UnsafeEchoServer interface {
+ mustEmbedUnimplementedEchoServer()
+}
+
+func RegisterEchoServer(s grpc.ServiceRegistrar, srv EchoServer) {
+ s.RegisterService(&Echo_ServiceDesc, srv)
+}
+
+func _Echo_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(Message)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(EchoServer).Ping(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/service.Echo/Ping",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(EchoServer).Ping(ctx, req.(*Message))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+// Echo_ServiceDesc is the grpc.ServiceDesc for Echo service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var Echo_ServiceDesc = grpc.ServiceDesc{
+ ServiceName: "service.Echo",
+ HandlerType: (*EchoServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "Ping",
+ Handler: _Echo_Ping_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{},
+ Metadata: "service.proto",
+}
diff --git a/tests/testdata/sample.txt b/tests/testdata/sample.txt
new file mode 100644
index 000000000..b45921c37
--- /dev/null
+++ b/tests/testdata/sample.txt
@@ -0,0 +1,2 @@
+Hello from RoadRunner e2e static file test!
+This file is served by the static middleware plugin.