You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ma...@apache.org on 2026/03/23 12:41:01 UTC

(grails-core) branch feat/http-client-testing-support created (now f89bbf277d)

This is an automated email from the ASF dual-hosted git repository.

matrei pushed a change to branch feat/http-client-testing-support
in repository https://gitbox.apache.org/repos/asf/grails-core.git


      at f89bbf277d build: publish `grails-testing-support-http-client`

This branch includes the following new commits:

     new 93c0e5e00e docs: Add documentation for `grails-testing-support-http-client`
     new f89bbf277d build: publish `grails-testing-support-http-client`

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(grails-core) 01/02: docs: Add documentation for `grails-testing-support-http-client`

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

matrei pushed a commit to branch feat/http-client-testing-support
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 93c0e5e00e7310557f24696ef0d746d0553d80df
Author: Mattias Reichel <ma...@gmail.com>
AuthorDate: Mon Mar 23 13:06:42 2026 +0100

    docs: Add documentation for `grails-testing-support-http-client`
---
 grails-doc/src/en/guide/introduction/whatsNew.adoc |  15 +-
 .../src/en/guide/testing/integrationTesting.adoc   | 305 +++++++++++++++++++++
 grails-testing-support-http-client/README.md       |   5 +-
 3 files changed, 319 insertions(+), 6 deletions(-)

diff --git a/grails-doc/src/en/guide/introduction/whatsNew.adoc b/grails-doc/src/en/guide/introduction/whatsNew.adoc
index 97287f0a08..d8733bff6a 100644
--- a/grails-doc/src/en/guide/introduction/whatsNew.adoc
+++ b/grails-doc/src/en/guide/introduction/whatsNew.adoc
@@ -89,8 +89,19 @@ With the removal of Micronaut, and the fixes to the asset pipeline plugin, Grail
 
 The `g:form` tag now automatically provides csrf protection when Spring Security CSRF is enabled.
 
-==== Grails Banner versions and customization (Grails 7.1+)
+==== Banner versions and customization (Grails 7.1+)
 
-A Grails banner was introduced in Grails 7 that is displayed on application startup.
+A Grails banner was introduced in Grails 7 that is displayed in the console on application startup.
 From Grails 7.1 onwards, this banner now shows versions of foundational dependencies
 and can be xref:conf.adoc#customizing-the-banner[customized].
+
+==== HTTP Client Testing Support (Grails 7.1+)
+
+Grails 7.1 introduces a new `grails-testing-support-http-client` module that simplifies HTTP-based
+integration and functional tests.
+
+The module provides an `HttpClientSupport` trait with request helpers for common use cases,
+and fluent response assertions via returning a TestHttpResponse wrapper of the actual HttpResponse.
+
+See the xref:testing.adoc#httpClientTestingSupport[HTTP Client Testing Support] section in the Testing chapter
+for setup and examples.
diff --git a/grails-doc/src/en/guide/testing/integrationTesting.adoc b/grails-doc/src/en/guide/testing/integrationTesting.adoc
index 3f5ec55ab5..13eb1c14af 100644
--- a/grails-doc/src/en/guide/testing/integrationTesting.adoc
+++ b/grails-doc/src/en/guide/testing/integrationTesting.adoc
@@ -322,6 +322,311 @@ Example usage:
 ----
 
 
+[[httpClientTestingSupport]]
+==== HTTP Client Testing Support
+
+For integration and functional tests that need to interact with live HTTP endpoints, Grails provides the
+`grails-testing-support-http-client` module.
+
+Add the dependency to your integration test configuration:
+
+[source,groovy]
+----
+dependencies {
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
+}
+----
+
+You can then implement the `HttpClientSupport` trait in your Spock specification and use helper methods for
+request building and response assertions:
+
+[source,groovy]
+----
+import java.net.http.HttpClient
+import java.net.http.HttpTimeoutException
+import java.time.Duration
+
+import spock.lang.Specification
+
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.http.client.HttpClientSupport
+import org.apache.grails.testing.http.client.MultipartBody
+
+@Integration
+class DemoSpec extends Specification implements HttpClientSupport {
+
+    void 'simple GET with status and body assertions'() {
+        when: 'invoking a GET request to the /health endpoint'
+        def response = http('/health')
+
+        then: 'verifies status, headers and body contents via fluent API'
+        response.assertStatus(200)
+                .assertHeaders('Content-Type': 'application/json')
+                .assertContains('UP')
+
+
+        and: 'can also verify all these under the hood in one go'
+        response.assertContains(200, 'Content-Type': 'application/json', 'UP')
+    }
+
+    void 'GET with headers and header assertions'() {
+        when:
+        def response = http('/api/info', 'Accept': 'application/json')
+
+        then:
+        response.assertHeadersIgnoreCase(200, 'content-type': 'application/json;charset=UTF-8')
+    }
+
+    void 'Posting and verifying JSON'() {
+        when: 'you post a map it is turned into a JSON payload'
+        def response = httpPostJson('/api/books', [title: 'Grails in Action', pages: 500])
+
+        then: 'you can also verify JSON response bodies with maps'
+        response.assertJson(201, [id: 1, title: 'Grails in Action', pages: 500])
+
+        and: 'you can also verify against a JSON string'
+        response.assertJson('''
+            {
+                "id": 1,
+                "title": "Grails in Action",
+                "pages": 500
+            }
+        ''')
+
+        and: 'the canonicalized JSON tree structure is compared, not strings'
+        response.assertJson('{ "pages": 500, "id": 1, "title": "Grails in Action" }')
+
+        and: 'if you want to compare strings, use assertContains/assertNotContains'
+        response.assertNotContains('{ "id": 1, "title": "Grails in Action", "pages": 500 }')
+
+        and: 'you can also use assertJsonContains to verify sub trees in the response json'
+        response.assertJsonContains([title: 'Grails in Action'])
+    }
+
+    void 'Posting and verifying XML'() {
+        when: 'you can post XML using a builder'
+        def response = httpPostXml('/api/books') {
+            book {
+                title('Grails in Action')
+                pages(500)
+            }
+        }
+
+        then: 'and you can verify XML responses with assertions on GPathResult'
+        with(response.xml()) {
+            title.text() == 'Grails in Action'
+            pages.text().toInteger() == 500
+        }
+    }
+
+    void 'custom request and custom client configuration'() {
+        when: 'you need a custom configured client'
+        def noRedirectClient = newHttpClientWith {
+            followRedirects(HttpClient.Redirect.NEVER)
+        }
+
+        and: 'or a custom configured request'
+        def request = newHttpRequestWith('/api/slow') {
+            timeout(Duration.ofSeconds(1))
+        }
+
+        when:
+        sendHttpRequest(request, noRedirectClient)
+
+        then:
+        thrown(HttpTimeoutException)
+    }
+
+    void 'multipart upload'() {
+        given:
+        def body = MultipartBody.builder()
+                .addPart('description', 'test file')
+                .addPart('file', 'hello.txt', 'text/plain', 'hello world')
+                .build()
+
+        when:
+        def response = httpPostMultipart('/api/upload', body)
+
+        then:
+        response.assertStatus(200)
+    }
+}
+----
+
+`HttpClientSupport` uses the JDK `HttpClient` under the hood and applies default request and connect timeouts of
+60 seconds and a redirect policy of always following redirects. You can use helpers `newHttpRequestWith(Closure configurer)`
+and `newHttpClientWith(Closure configurer)` to customize your own client and requests starting from the defaults.
+
+Additional request helper methods available include `httpDelete()`, `httpHead()`, `httpOptions()`, `httpPatch()`,
+`httpPut()`, `httpTrace()`, and many assertion methods on responses.
+
+===== Custom JSON Parsing
+
+When response JSON needs non-default `JsonSlurper` behavior, configure it fluently on the response wrapper:
+
+[source,groovy]
+----
+import groovy.json.JsonParserType
+
+given:
+def response = http('/config')
+
+expect:
+response
+    .withJsonSlurper(parserType: JsonParserType.LAX, checkDates: true)
+    .assertJsonContains('{featureFlag:true}')
+----
+
+Supported named options mirror the `JsonSlurper` settings exposed by `JsonUtils.SlurperConfig`, including
+`parserType`, `checkDates`, `chop`, `lazyChop`, and `maxSizeForInMemory`.
+
+===== Custom XML Parsing
+
+Response XML parsing uses a secure default `XmlSlurper` configuration. It is namespace-aware, non-validating,
+allows inline `DOCTYPE` declarations, and disables external entity expansion plus external DTD loading.
+
+When a test needs different XML parsing behavior, override it fluently on the response wrapper:
+
+[source,groovy]
+----
+import groovy.xml.XmlSlurper
+
+given:
+def response = http('/feed')
+
+when:
+def xml = response.withXmlSlurper(factory: { new XmlSlurper(false, false) }).xml()
+
+then:
+xml.channel.item.size() == 10
+----
+
+`withXmlSlurper(...)` accepts an `XmlUtils.SlurperConfig`. The default configuration is secure; the
+`factory` hook is available for tests that need a fully custom parser instance.
+
+===== JSON payload formatting
+
+JSON request bodies can be sent in three ways depending on how much formatting control you need.
+
+For exact control over the payload text, pass a pre-rendered JSON string to the request helper:
+
+[source,groovy]
+----
+httpPostJson('/products', '{"name":"Widget","tags":["new","sale"]}')
+----
+
+For the common case, use the `http[Post|Put|Patch]Json` helpers with a `Map`. They serialize the payload with
+Groovy's default `JsonOutput.toJson(...)` behavior:
+
+[source,groovy]
+----
+httpPostJson('/products', [name: 'Widget', qty: 2])
+----
+
+When you want custom JSON formatting, pass a `JsonGenerator` alongside the `Map` payload. This lets you control
+rendering details such as null handling, field exclusions, converters, and date formatting:
+
+[source,groovy]
+----
+import groovy.json.JsonGenerator
+
+def jsonGenerator = new JsonGenerator.Options()
+    .excludeNulls()
+    .dateFormat('yyyy-MM-dd')
+    .build()
+
+httpPutJson('/products/1', jsonGenerator, [
+    name: 'Widget',
+    discontinuedAt: null,
+    releaseDate: new Date()
+])
+----
+
+The same `JsonGenerator` overload pattern is available on `httpPostJson(...)`, `httpPutJson(...)`, and
+`httpPatchJson(...)`, including the variants that accept request headers and an explicit `HttpClient`.
+
+===== XML formatting
+
+XML request bodies can be generated with Groovy MarkupBuilder DSL. For the `http[Post|Patch|Put]Xml` methods,
+`HttpClientSupport` accepts an optional `XmlUtils.Format` instance, which constructor takes named params to
+configure the format:
+
+[source,groovy]
+----
+import org.apache.grails.testing.http.client.utils.XmlUtils
+
+when:
+def response = httpPostXml('/products', new XmlUtils.Format(
+    omitDeclaration: false,
+    prettyPrint: true,
+    indent: '  ',
+    lineSeparator: '\n',
+    doctype: '<!DOCTYPE product SYSTEM "product.dtd">',
+    omitNullAttributes: true,
+    omitEmptyAttributes: true,
+    spaceInEmptyElements: false,
+    escapeAttributes: true
+)) {
+    product(id: '1', description: null, sku: '') {
+        name('Widget')
+        empty()
+    }
+}
+
+then:
+response.assertStatus(201)
+
+----
+
+When you need the same formatting control for other request helpers, render the XML first and then pass the
+result to `httpPost`, `httpPut`, or `httpPatch` with `application/xml`:
+
+[source,groovy]
+----
+import org.apache.grails.testing.http.client.utils.XmlUtils
+
+def payload = XmlUtils.toXml(
+    omitDeclaration: false,
+    prettyPrint: true,
+    indent: '    ',
+    lineSeparator: '\n',
+    doctype: '<!DOCTYPE product SYSTEM "product.dtd">',
+    omitNullAttributes: true,
+    omitEmptyAttributes: true,
+    spaceInEmptyElements: false,
+    escapeAttributes: true
+) {
+    product(id: '1', description: null, sku: '') {
+        name('Widget')
+        empty()
+    }
+}
+
+httpPut('/products/1', payload, 'application/xml')
+----
+
+Supported `XmlUtils.Format` options include:
+
+- Declaration and document metadata: `omitDeclaration`, `xmlVersion`, `charset`, `doubleQuotes`, `doctype`
+- Pretty-printing layout: `prettyPrint`, `indent`, `lineSeparator`
+- Empty elements and attribute handling: `expandEmptyElements`, `spaceInEmptyElements`,
+`omitEmptyAttributes`, `omitNullAttributes`
+- Attribute escaping: `escapeAttributes`
+
+`XmlUtils.toXml(...)` also supports inline named options, so shorter cases can be written as:
+
+[source,groovy]
+----
+
+def payload = XmlUtils.toXml(omitNullAttributes: true, spaceInEmptyElements: false) {
+    product(name: 'Widget', description: null) {
+        empty()
+    }
+}
+
+httpPost('/products', payload, 'application/xml')
+----
+
 ==== Autowiring
 
 
diff --git a/grails-testing-support-http-client/README.md b/grails-testing-support-http-client/README.md
index 369fd00edb..b30fffec10 100644
--- a/grails-testing-support-http-client/README.md
+++ b/grails-testing-support-http-client/README.md
@@ -107,9 +107,6 @@ rendering details such as null handling, field exclusions, converters, and date
 
 ```groovy
 import groovy.json.JsonGenerator
-import java.time.LocalDate
-import java.time.ZoneOffset
-import java.util.Date
 
 def jsonGenerator = new JsonGenerator.Options()
     .excludeNulls()
@@ -119,7 +116,7 @@ def jsonGenerator = new JsonGenerator.Options()
 httpPutJson('/products/1', jsonGenerator, [
     name: 'Widget',
     discontinuedAt: null,
-    releaseDate: Date.from(LocalDate.parse('2026-03-18').atStartOfDay(ZoneOffset.UTC).toInstant())
+    releaseDate: new Date()
 ])
 ```
 


(grails-core) 02/02: build: publish `grails-testing-support-http-client`

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

matrei pushed a commit to branch feat/http-client-testing-support
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit f89bbf277dbfec9432cdc794bd4753532b1fe959
Author: Mattias Reichel <ma...@gmail.com>
AuthorDate: Mon Mar 23 13:38:33 2026 +0100

    build: publish `grails-testing-support-http-client`
---
 gradle/publish-root-config.gradle                                     | 1 +
 grails-test-examples/app1/build.gradle                                | 2 +-
 grails-test-examples/async-events-pubsub-demo/build.gradle            | 2 +-
 grails-test-examples/cache/build.gradle                               | 2 +-
 grails-test-examples/demo33/build.gradle                              | 2 +-
 .../hibernate5/grails-multiple-datasources/build.gradle               | 2 +-
 grails-test-examples/hibernate5/issue450/build.gradle                 | 2 +-
 grails-test-examples/issue-11102/build.gradle                         | 2 +-
 grails-test-examples/issue-11767/build.gradle                         | 2 +-
 grails-test-examples/issue-15228/build.gradle                         | 2 +-
 grails-test-examples/issue-views-182/build.gradle                     | 2 +-
 grails-test-examples/micronaut/build.gradle                           | 2 +-
 grails-test-examples/views-functional-tests/build.gradle              | 2 +-
 grails-testing-support-http-client/build.gradle                       | 4 +++-
 14 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/gradle/publish-root-config.gradle b/gradle/publish-root-config.gradle
index 1b56b0ccb8..ba3d27d51a 100644
--- a/gradle/publish-root-config.gradle
+++ b/gradle/publish-root-config.gradle
@@ -79,6 +79,7 @@ def publishedProjects = [
         'grails-testing-support-dbcleanup-core',
         'grails-testing-support-dbcleanup-h2',
         'grails-testing-support-dbcleanup-postgresql',
+        'grails-testing-support-http-client',
         'grails-testing-support-mongodb',
         'grails-testing-support-views-gson',
         'grails-testing-support-web',
diff --git a/grails-test-examples/app1/build.gradle b/grails-test-examples/app1/build.gradle
index 943f634bc4..1b88f62649 100644
--- a/grails-test-examples/app1/build.gradle
+++ b/grails-test-examples/app1/build.gradle
@@ -67,7 +67,7 @@ dependencies {
     implementation project(':grails-test-examples-plugins-loadafter')
 
     integrationTestImplementation testFixtures('org.apache.grails:grails-geb')
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
 }
 
 tasks.withType(JavaExec).configureEach {
diff --git a/grails-test-examples/async-events-pubsub-demo/build.gradle b/grails-test-examples/async-events-pubsub-demo/build.gradle
index 48adb0a84e..94d7a3f41b 100644
--- a/grails-test-examples/async-events-pubsub-demo/build.gradle
+++ b/grails-test-examples/async-events-pubsub-demo/build.gradle
@@ -67,7 +67,7 @@ dependencies {
 
     testImplementation 'org.apache.grails:grails-testing-support-web'
 
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
 }
 
 bootRun {
diff --git a/grails-test-examples/cache/build.gradle b/grails-test-examples/cache/build.gradle
index fe0e47eb6c..766b389017 100644
--- a/grails-test-examples/cache/build.gradle
+++ b/grails-test-examples/cache/build.gradle
@@ -68,7 +68,7 @@ dependencies {
     testImplementation 'org.spockframework:spock-core'
 
     integrationTestImplementation testFixtures('org.apache.grails:grails-geb')
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
 
 }
 
diff --git a/grails-test-examples/demo33/build.gradle b/grails-test-examples/demo33/build.gradle
index 716ce7290f..e25e30ed8e 100644
--- a/grails-test-examples/demo33/build.gradle
+++ b/grails-test-examples/demo33/build.gradle
@@ -67,7 +67,7 @@ dependencies {
     testImplementation 'org.apache.grails:grails-testing-support-web'
     testImplementation 'org.apache.grails:grails-testing-support-datamapping'
 
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
 }
 
 tasks.named('compileTestGroovy').configure {
diff --git a/grails-test-examples/hibernate5/grails-multiple-datasources/build.gradle b/grails-test-examples/hibernate5/grails-multiple-datasources/build.gradle
index df871d562f..8f1d82d81b 100644
--- a/grails-test-examples/hibernate5/grails-multiple-datasources/build.gradle
+++ b/grails-test-examples/hibernate5/grails-multiple-datasources/build.gradle
@@ -53,7 +53,7 @@ dependencies {
 
     testRuntimeOnly 'org.apache.grails:grails-testing-support-web'
 
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
     integrationTestImplementation 'org.spockframework:spock-core'
 }
 
diff --git a/grails-test-examples/hibernate5/issue450/build.gradle b/grails-test-examples/hibernate5/issue450/build.gradle
index 436ee615f4..8c84030dea 100644
--- a/grails-test-examples/hibernate5/issue450/build.gradle
+++ b/grails-test-examples/hibernate5/issue450/build.gradle
@@ -57,7 +57,7 @@ dependencies {
     runtimeOnly 'org.springframework.boot:spring-boot-starter-logging'
     runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat'
 
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
     integrationTestImplementation 'org.apache.grails.testing:grails-testing-support-core'
     integrationTestImplementation 'org.spockframework:spock-core'
 
diff --git a/grails-test-examples/issue-11102/build.gradle b/grails-test-examples/issue-11102/build.gradle
index 5dece38a26..26c2f8bf6b 100644
--- a/grails-test-examples/issue-11102/build.gradle
+++ b/grails-test-examples/issue-11102/build.gradle
@@ -73,7 +73,7 @@ dependencies {
     testImplementation 'org.apache.grails:grails-testing-support-datamapping'
 
     integrationTestImplementation testFixtures('org.apache.grails:grails-geb')
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
 }
 
 tasks.named('assetCompile').configure {
diff --git a/grails-test-examples/issue-11767/build.gradle b/grails-test-examples/issue-11767/build.gradle
index a2cdd46a07..2c2ea87176 100644
--- a/grails-test-examples/issue-11767/build.gradle
+++ b/grails-test-examples/issue-11767/build.gradle
@@ -36,7 +36,7 @@ dependencies {
     testImplementation 'org.apache.grails:grails-testing-support-web'
     testImplementation 'org.spockframework:spock-core'
 
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
 }
 
 apply {
diff --git a/grails-test-examples/issue-15228/build.gradle b/grails-test-examples/issue-15228/build.gradle
index d357a733c5..e7a1867aba 100644
--- a/grails-test-examples/issue-15228/build.gradle
+++ b/grails-test-examples/issue-15228/build.gradle
@@ -42,7 +42,7 @@ dependencies {
     testImplementation 'org.apache.grails.testing:grails-testing-support-core'
 
     integrationTestImplementation 'com.fasterxml.jackson.core:jackson-databind'
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
 }
 
 apply {
diff --git a/grails-test-examples/issue-views-182/build.gradle b/grails-test-examples/issue-views-182/build.gradle
index 0afeab41aa..8368815139 100644
--- a/grails-test-examples/issue-views-182/build.gradle
+++ b/grails-test-examples/issue-views-182/build.gradle
@@ -67,7 +67,7 @@ dependencies {
     testImplementation 'org.apache.grails:grails-testing-support-web'
     testImplementation 'org.apache.grails:grails-testing-support-datamapping'
 
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
 }
 
 bootRun {
diff --git a/grails-test-examples/micronaut/build.gradle b/grails-test-examples/micronaut/build.gradle
index 0e57d69c21..c84a77244e 100644
--- a/grails-test-examples/micronaut/build.gradle
+++ b/grails-test-examples/micronaut/build.gradle
@@ -61,7 +61,7 @@ dependencies {
     integrationTestImplementation testFixtures('org.apache.grails:grails-geb')
     integrationTestImplementation "io.github.cjstehno.ersatz:ersatz:$ersatzVersion"
     integrationTestImplementation "io.github.cjstehno.ersatz:ersatz-groovy:$ersatzVersion"
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
 }
 
 apply {
diff --git a/grails-test-examples/views-functional-tests/build.gradle b/grails-test-examples/views-functional-tests/build.gradle
index db349fe0e5..3c366ec903 100644
--- a/grails-test-examples/views-functional-tests/build.gradle
+++ b/grails-test-examples/views-functional-tests/build.gradle
@@ -73,7 +73,7 @@ dependencies {
     testImplementation 'org.apache.grails.testing:grails-testing-support-core'
 
     integrationTestImplementation 'com.fasterxml.jackson.core:jackson-databind'
-    integrationTestImplementation project(':grails-testing-support-http-client')
+    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
 }
 
 apply {
diff --git a/grails-testing-support-http-client/build.gradle b/grails-testing-support-http-client/build.gradle
index 5c83201142..232dd9198b 100644
--- a/grails-testing-support-http-client/build.gradle
+++ b/grails-testing-support-http-client/build.gradle
@@ -9,7 +9,7 @@
  *
  *    https://www.apache.org/licenses/LICENSE-2.0
  *
- *  Unless required by applicable law or agreed in writing,
+ *  Unless required by applicable law or agreed to in writing,
  *  software distributed under the License is distributed on an
  *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  *  KIND, either express or implied.  See the License for the
@@ -22,6 +22,8 @@ plugins {
     id 'java-library'
     id 'org.apache.grails.buildsrc.properties'
     id 'org.apache.grails.buildsrc.compile'
+    id 'org.apache.grails.buildsrc.publish'
+    id 'org.apache.grails.buildsrc.sbom'
     id 'org.apache.grails.gradle.grails-code-style'
 }