diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/AwsAuthenticatorTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/AwsAuthenticatorTestCase.java
new file mode 100644
index 0000000000..f2075d09f2
--- /dev/null
+++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/AwsAuthenticatorTestCase.java
@@ -0,0 +1,80 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.crypto;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.restlet.Context;
+import org.restlet.data.ChallengeScheme;
+import org.restlet.ext.crypto.internal.AwsVerifier;
+import org.restlet.security.MapVerifier;
+import org.restlet.security.Verifier;
+
+/** Unit tests for {@link AwsAuthenticator}. */
+class AwsAuthenticatorTestCase {
+
+ @Test
+ void constructor_withContextAndRealm_isNotOptional() {
+ AwsAuthenticator authenticator = new AwsAuthenticator(new Context(), "realm");
+ assertFalse(authenticator.isOptional());
+ assertEquals(ChallengeScheme.HTTP_AWS_S3, authenticator.getScheme());
+ assertNotNull(authenticator.getVerifier());
+ }
+
+ @Test
+ void constructor_withOptionalFlag_setsOptional() {
+ AwsAuthenticator authenticator = new AwsAuthenticator(new Context(), true, "realm");
+ assertTrue(authenticator.isOptional());
+ }
+
+ @Test
+ void constructor_withExplicitVerifier_usesIt() {
+ AwsVerifier verifier = new AwsVerifier(null);
+ AwsAuthenticator authenticator =
+ new AwsAuthenticator(new Context(), false, "realm", verifier);
+ assertSame(verifier, authenticator.getVerifier());
+ }
+
+ @Test
+ void maxRequestAge_getterAndSetter_delegateToVerifier() {
+ AwsAuthenticator authenticator = new AwsAuthenticator(new Context(), "realm");
+ authenticator.setMaxRequestAge(1000L);
+ assertEquals(1000L, authenticator.getMaxRequestAge());
+ }
+
+ @Test
+ void wrappedVerifier_getterAndSetter_delegateToVerifier() {
+ AwsAuthenticator authenticator = new AwsAuthenticator(new Context(), "realm");
+ MapVerifier wrapped = new MapVerifier();
+ authenticator.setWrappedVerifier(wrapped);
+ assertSame(wrapped, authenticator.getWrappedVerifier());
+ }
+
+ @Test
+ void setVerifier_withNonAwsVerifier_throwsIllegalArgumentException() {
+ AwsAuthenticator authenticator = new AwsAuthenticator(new Context(), "realm");
+ Verifier notAnAwsVerifier = new MapVerifier();
+ assertThrows(
+ IllegalArgumentException.class, () -> authenticator.setVerifier(notAnAwsVerifier));
+ }
+
+ @Test
+ void setVerifier_withAwsVerifier_replacesIt() {
+ AwsAuthenticator authenticator = new AwsAuthenticator(new Context(), "realm");
+ AwsVerifier newVerifier = new AwsVerifier(null);
+ authenticator.setVerifier(newVerifier);
+ assertSame(newVerifier, authenticator.getVerifier());
+ }
+}
diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/DigestUtilsTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/DigestUtilsTestCase.java
new file mode 100644
index 0000000000..05096e826b
--- /dev/null
+++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/DigestUtilsTestCase.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.crypto;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Base64;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.Digest;
+
+/** Unit tests for {@link DigestUtils}. */
+class DigestUtilsTestCase {
+
+ @Test
+ void toMd5_string_matchesKnownVector() {
+ assertEquals("68e109f0f40ca72a15e05cc22786f8e6", DigestUtils.toMd5("HelloWorld"));
+ }
+
+ @Test
+ void toMd5_withCharset_matchesKnownVector() throws Exception {
+ assertEquals("68e109f0f40ca72a15e05cc22786f8e6", DigestUtils.toMd5("HelloWorld", "UTF-8"));
+ }
+
+ @Test
+ void toSha1_string_isDeterministicBase64() {
+ String result = DigestUtils.toSha1("HelloWorld");
+ assertEquals(result, DigestUtils.toSha1("HelloWorld"));
+ assertEquals(28, result.length());
+ }
+
+ @Test
+ void toSha1_withCharset_isDeterministic() throws Exception {
+ assertEquals(
+ DigestUtils.toSha1("HelloWorld", "UTF-8"),
+ DigestUtils.toSha1("HelloWorld", "UTF-8"));
+ }
+
+ @Test
+ void digest_md5Algorithm_delegatesToToMd5() {
+ assertEquals(
+ DigestUtils.toMd5("HelloWorld"),
+ DigestUtils.digest("HelloWorld", Digest.ALGORITHM_MD5));
+ }
+
+ @Test
+ void digest_sha1Algorithm_delegatesToToSha1() {
+ assertEquals(
+ DigestUtils.toSha1("HelloWorld"),
+ DigestUtils.digest("HelloWorld", Digest.ALGORITHM_SHA_1));
+ }
+
+ @Test
+ void digest_unsupportedAlgorithm_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> DigestUtils.digest("HelloWorld", "unsupported"));
+ }
+
+ @Test
+ void digest_charArrayOverload_matchesStringOverload() {
+ assertArrayEquals(
+ DigestUtils.digest("HelloWorld", Digest.ALGORITHM_MD5).toCharArray(),
+ DigestUtils.digest("HelloWorld".toCharArray(), Digest.ALGORITHM_MD5));
+ }
+
+ @Test
+ void toHMacSha256_bytesKey_matchesKnownVector() {
+ byte[] result = DigestUtils.toHMacSha256("hello", "secret-key".getBytes());
+ assertEquals(
+ "mOf/uWS7Wj+QLbH8EBpbqpi28s1WhYIQydcPJqx2L8c=",
+ Base64.getEncoder().encodeToString(result));
+ }
+
+ @Test
+ void toHMacSha256_stringKey_matchesBytesKeyOverload() {
+ assertArrayEquals(
+ DigestUtils.toHMacSha256("hello", "secret-key".getBytes()),
+ DigestUtils.toHMacSha256("hello", "secret-key"));
+ }
+
+ @Test
+ void toHMacSha1_bytesKey_matchesKnownVector() {
+ byte[] result = DigestUtils.toHMacSha1("hello", "secret-key".getBytes());
+ assertEquals("J5rb+6R5G3Md2aC4ofFuty27aR0=", Base64.getEncoder().encodeToString(result));
+ }
+
+ @Test
+ void toHMacSha1_stringKey_matchesBytesKeyOverload() {
+ assertArrayEquals(
+ DigestUtils.toHMacSha1("hello", "secret-key".getBytes()),
+ DigestUtils.toHMacSha1("hello", "secret-key"));
+ }
+
+ @Test
+ void toHttpDigest_withSecret_hashesIdentifierRealmAndSecret() {
+ String expected = DigestUtils.toMd5("user:realm:pass");
+ assertEquals(expected, DigestUtils.toHttpDigest("user", "pass".toCharArray(), "realm"));
+ }
+
+ @Test
+ void toHttpDigest_withNullSecret_returnsNull() {
+ assertNull(DigestUtils.toHttpDigest("user", null, "realm"));
+ }
+}
diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/AwsUtilsQueryTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/AwsUtilsQueryTestCase.java
new file mode 100644
index 0000000000..d9ffbc73ec
--- /dev/null
+++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/AwsUtilsQueryTestCase.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.crypto.internal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.Method;
+import org.restlet.data.Parameter;
+import org.restlet.data.Reference;
+
+/** Unit tests for the query-signature related methods of {@link AwsUtils}. */
+class AwsUtilsQueryTestCase {
+
+ private List params() {
+ List params = new ArrayList<>();
+ params.add(new Parameter("Version", "2009-04-15"));
+ params.add(new Parameter("Action", "ListDomains"));
+ return params;
+ }
+
+ @Test
+ void getQueryStringToSign_sortsParametersAndConcatenatesFields() {
+ Reference resourceRef = new Reference("http://sdb.amazonaws.com/");
+
+ String result = AwsUtils.getQueryStringToSign(Method.GET, resourceRef, params());
+
+ assertEquals("GET\nsdb.amazonaws.com\n/\nAction=ListDomains&Version=2009-04-15", result);
+ }
+
+ @Test
+ void getQueryStringToSign_nullMethod_omitsMethodName() {
+ Reference resourceRef = new Reference("http://sdb.amazonaws.com/");
+
+ String result = AwsUtils.getQueryStringToSign(null, resourceRef, new ArrayList<>());
+
+ assertEquals("\nsdb.amazonaws.com\n/\n", result);
+ }
+
+ @Test
+ void getQuerySignature_matchesKnownVector() {
+ Reference resourceRef = new Reference("http://sdb.amazonaws.com/");
+
+ String signature =
+ AwsUtils.getQuerySignature(
+ Method.GET, resourceRef, params(), "mysecret".toCharArray());
+
+ assertEquals("DG2zC7EgmtLBDdwPHV71VBw4mPpvIl1zymtFXAl/trQ=", signature);
+ }
+
+ @Test
+ void getHmacSha256Signature_isDeterministic() {
+ String signature1 = AwsUtils.getHmacSha256Signature("hello", "secret".toCharArray());
+ String signature2 = AwsUtils.getHmacSha256Signature("hello", "secret".toCharArray());
+ assertEquals(signature1, signature2);
+ }
+
+ @Test
+ void getHmacSha1Signature_isDeterministic() {
+ String signature1 = AwsUtils.getHmacSha1Signature("hello", "secret".toCharArray());
+ String signature2 = AwsUtils.getHmacSha1Signature("hello", "secret".toCharArray());
+ assertEquals(signature1, signature2);
+ }
+}
diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsQueryHelperTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsQueryHelperTestCase.java
new file mode 100644
index 0000000000..c2d9917474
--- /dev/null
+++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsQueryHelperTestCase.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.crypto.internal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.restlet.Request;
+import org.restlet.data.ChallengeResponse;
+import org.restlet.data.ChallengeScheme;
+import org.restlet.data.Method;
+import org.restlet.data.Reference;
+
+/** Unit tests for {@link HttpAwsQueryHelper}. */
+class HttpAwsQueryHelperTestCase {
+
+ @Test
+ void constructor_registersAwsQueryScheme() {
+ HttpAwsQueryHelper helper = new HttpAwsQueryHelper();
+ assertEquals(ChallengeScheme.HTTP_AWS_QUERY, helper.getChallengeScheme());
+ }
+
+ @Test
+ void updateReference_withoutActionParameter_returnsSameReference() {
+ HttpAwsQueryHelper helper = new HttpAwsQueryHelper();
+ Request request = new Request(Method.GET, "http://sdb.amazonaws.com/");
+ Reference resourceRef = new Reference("http://sdb.amazonaws.com/");
+
+ Reference result = helper.updateReference(resourceRef, null, request);
+
+ assertSame(resourceRef, result);
+ }
+
+ @Test
+ void updateReference_withActionParameter_signsAndAppendsParameters() {
+ HttpAwsQueryHelper helper = new HttpAwsQueryHelper();
+ ChallengeResponse challengeResponse =
+ new ChallengeResponse(
+ ChallengeScheme.HTTP_AWS_QUERY, "myaccesskey", "mysecret".toCharArray());
+ Request request = new Request(Method.GET, "http://sdb.amazonaws.com/?Action=ListDomains");
+ request.setChallengeResponse(challengeResponse);
+ Reference resourceRef = new Reference("http://sdb.amazonaws.com/?Action=ListDomains");
+
+ Reference result = helper.updateReference(resourceRef, challengeResponse, request);
+
+ assertNotSame(resourceRef, result);
+ String query = result.getQuery();
+ assertTrue(query.contains("AWSAccessKeyId=myaccesskey"));
+ assertTrue(query.contains("SignatureMethod=HmacSHA256"));
+ assertTrue(query.contains("SignatureVersion=2"));
+ assertTrue(query.contains("Version=2009-04-15"));
+ assertTrue(query.contains("Timestamp="));
+ assertTrue(query.contains("Signature="));
+ }
+}
diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyHelperTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyHelperTestCase.java
new file mode 100644
index 0000000000..c3b0ed3da4
--- /dev/null
+++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyHelperTestCase.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.crypto.internal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.restlet.Request;
+import org.restlet.data.ChallengeResponse;
+import org.restlet.data.ChallengeScheme;
+import org.restlet.data.Header;
+import org.restlet.data.Method;
+import org.restlet.engine.header.ChallengeWriter;
+import org.restlet.engine.header.HeaderConstants;
+import org.restlet.util.Series;
+
+/** Unit tests for {@link HttpAzureSharedKeyHelper}. */
+class HttpAzureSharedKeyHelperTestCase {
+
+ @Test
+ void constructor_registersAzureSharedKeyScheme() {
+ HttpAzureSharedKeyHelper helper = new HttpAzureSharedKeyHelper();
+ assertEquals(ChallengeScheme.HTTP_AZURE_SHAREDKEY, helper.getChallengeScheme());
+ }
+
+ @Test
+ void formatResponse_withoutOptionalHeaders_addsDateHeaderAndFormatsCredential() {
+ HttpAzureSharedKeyHelper helper = new HttpAzureSharedKeyHelper();
+ Request request =
+ new Request(Method.GET, "http://myaccount.blob.core.windows.net/container");
+ ChallengeResponse challenge =
+ new ChallengeResponse(
+ ChallengeScheme.HTTP_AZURE_SHAREDKEY,
+ "myaccount",
+ "c2VjcmV0".toCharArray());
+ Series httpHeaders = new Series<>(Header.class);
+
+ ChallengeWriter cw = new ChallengeWriter();
+ helper.formatResponse(cw, challenge, request, httpHeaders);
+
+ String result = cw.toString();
+ assertTrue(result.startsWith("myaccount:"));
+ assertNotNull(httpHeaders.getFirstValue(HeaderConstants.HEADER_DATE, true));
+ }
+
+ @Test
+ void formatResponse_withHeadersAndCompQuery_isDeterministic() {
+ HttpAzureSharedKeyHelper helper = new HttpAzureSharedKeyHelper();
+ Request request =
+ new Request(
+ Method.PUT,
+ "http://myaccount.blob.core.windows.net/container/blob?comp=metadata");
+ ChallengeResponse challenge =
+ new ChallengeResponse(
+ ChallengeScheme.HTTP_AZURE_SHAREDKEY,
+ "myaccount",
+ "c2VjcmV0".toCharArray());
+
+ Series httpHeaders1 = new Series<>(Header.class);
+ httpHeaders1.add(HeaderConstants.HEADER_CONTENT_TYPE, "application/octet-stream");
+ httpHeaders1.add(HeaderConstants.HEADER_CONTENT_MD5, "abc123");
+ httpHeaders1.add(HeaderConstants.HEADER_DATE, "Wed, 21 Oct 2015 07:28:00 GMT");
+ httpHeaders1.add("x-ms-version", "2020-02-10");
+ httpHeaders1.add("x-ms-blob-type", "BlockBlob");
+
+ Series httpHeaders2 = new Series<>(Header.class);
+ httpHeaders2.add(HeaderConstants.HEADER_CONTENT_TYPE, "application/octet-stream");
+ httpHeaders2.add(HeaderConstants.HEADER_CONTENT_MD5, "abc123");
+ httpHeaders2.add(HeaderConstants.HEADER_DATE, "Wed, 21 Oct 2015 07:28:00 GMT");
+ httpHeaders2.add("x-ms-version", "2020-02-10");
+ httpHeaders2.add("x-ms-blob-type", "BlockBlob");
+
+ ChallengeWriter cw1 = new ChallengeWriter();
+ helper.formatResponse(cw1, challenge, request, httpHeaders1);
+
+ ChallengeWriter cw2 = new ChallengeWriter();
+ helper.formatResponse(cw2, challenge, request, httpHeaders2);
+
+ assertEquals(cw1.toString(), cw2.toString());
+ }
+}
diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyLiteHelperTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyLiteHelperTestCase.java
new file mode 100644
index 0000000000..72272f1b61
--- /dev/null
+++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyLiteHelperTestCase.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.crypto.internal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.restlet.Request;
+import org.restlet.data.ChallengeResponse;
+import org.restlet.data.ChallengeScheme;
+import org.restlet.data.Header;
+import org.restlet.data.Method;
+import org.restlet.engine.header.ChallengeWriter;
+import org.restlet.engine.header.HeaderConstants;
+import org.restlet.util.Series;
+
+/** Unit tests for {@link HttpAzureSharedKeyLiteHelper}. */
+class HttpAzureSharedKeyLiteHelperTestCase {
+
+ @Test
+ void constructor_registersAzureSharedKeyLiteScheme() {
+ HttpAzureSharedKeyLiteHelper helper = new HttpAzureSharedKeyLiteHelper();
+ assertEquals(ChallengeScheme.HTTP_AZURE_SHAREDKEY_LITE, helper.getChallengeScheme());
+ }
+
+ @Test
+ void formatResponse_withoutDateHeader_addsFreshDateHeader() {
+ HttpAzureSharedKeyLiteHelper helper = new HttpAzureSharedKeyLiteHelper();
+ Request request =
+ new Request(Method.GET, "http://myaccount.table.core.windows.net/mytable");
+ ChallengeResponse challenge =
+ new ChallengeResponse(
+ ChallengeScheme.HTTP_AZURE_SHAREDKEY_LITE,
+ "myaccount",
+ "c2VjcmV0".toCharArray());
+ Series httpHeaders = new Series<>(Header.class);
+
+ ChallengeWriter cw = new ChallengeWriter();
+ helper.formatResponse(cw, challenge, request, httpHeaders);
+
+ assertTrue(cw.toString().startsWith("myaccount:"));
+ assertNotNull(httpHeaders.getFirstValue(HeaderConstants.HEADER_DATE, true));
+ }
+
+ @Test
+ void formatResponse_withXmsDateHeader_usesItInsteadOfDateHeader() {
+ HttpAzureSharedKeyLiteHelper helper = new HttpAzureSharedKeyLiteHelper();
+ Request request =
+ new Request(
+ Method.GET,
+ "http://myaccount.table.core.windows.net/mytable?comp=metadata");
+ ChallengeResponse challenge =
+ new ChallengeResponse(
+ ChallengeScheme.HTTP_AZURE_SHAREDKEY_LITE,
+ "myaccount",
+ "c2VjcmV0".toCharArray());
+ Series httpHeaders = new Series<>(Header.class);
+ httpHeaders.add("x-ms-date", "Wed, 21 Oct 2015 07:28:00 GMT");
+ httpHeaders.add(HeaderConstants.HEADER_DATE, "should-be-ignored");
+
+ ChallengeWriter cw = new ChallengeWriter();
+ helper.formatResponse(cw, challenge, request, httpHeaders);
+
+ assertTrue(cw.toString().startsWith("myaccount:"));
+ assertEquals(
+ "should-be-ignored", httpHeaders.getFirstValue(HeaderConstants.HEADER_DATE, true));
+ }
+}
diff --git a/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/JacksonConverterTestCase.java b/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/JacksonConverterTestCase.java
new file mode 100644
index 0000000000..6096f2b1a9
--- /dev/null
+++ b/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/JacksonConverterTestCase.java
@@ -0,0 +1,192 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.jackson;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.MediaType;
+import org.restlet.data.Preference;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+import org.restlet.representation.Variant;
+
+/** Unit tests for {@link JacksonConverter}. */
+class JacksonConverterTestCase {
+
+ private final JacksonConverter converter = new JacksonConverter();
+
+ @Test
+ void isCompatible_forSupportedMediaTypes_returnsTrue() {
+ assertTrue(converter.isCompatible(new Variant(MediaType.APPLICATION_JSON)));
+ assertTrue(converter.isCompatible(new Variant(MediaType.APPLICATION_JSON_SMILE)));
+ assertTrue(converter.isCompatible(new Variant(MediaType.APPLICATION_XML)));
+ assertTrue(converter.isCompatible(new Variant(MediaType.TEXT_XML)));
+ assertTrue(converter.isCompatible(new Variant(MediaType.APPLICATION_YAML)));
+ assertTrue(converter.isCompatible(new Variant(MediaType.TEXT_YAML)));
+ assertTrue(converter.isCompatible(new Variant(MediaType.TEXT_CSV)));
+ }
+
+ @Test
+ void isCompatible_forUnsupportedMediaType_returnsFalse() {
+ assertFalse(converter.isCompatible(new Variant(MediaType.IMAGE_PNG)));
+ }
+
+ @Test
+ void isCompatible_forNullVariant_returnsFalse() {
+ assertFalse(converter.isCompatible(null));
+ }
+
+ @Test
+ void getObjectClasses_forCompatibleVariant_returnsObjectAndJacksonRepresentation() {
+ List> result = converter.getObjectClasses(new Variant(MediaType.APPLICATION_JSON));
+ assertEquals(List.of(Object.class, JacksonRepresentation.class), result);
+ }
+
+ @Test
+ void getObjectClasses_forIncompatibleVariant_returnsNull() {
+ assertNull(converter.getObjectClasses(new Variant(MediaType.IMAGE_PNG)));
+ }
+
+ @Test
+ void getVariants_forNonNullClass_returnsAllSupportedVariants() {
+ List> variants = converter.getVariants(String.class);
+ assertEquals(7, variants.size());
+ }
+
+ @Test
+ void getVariants_forNullClass_returnsEmptyList() {
+ assertTrue(converter.getVariants(null).isEmpty());
+ }
+
+ @Test
+ void score_jacksonRepresentation_returnsOne() {
+ JacksonRepresentation source =
+ new JacksonRepresentation<>(MediaType.APPLICATION_JSON, "text");
+ assertEquals(1.0f, converter.score(source, new Variant(MediaType.APPLICATION_JSON), null));
+ }
+
+ @Test
+ void score_nullTarget_returnsHalf() {
+ assertEquals(0.5f, converter.score("text", null, null));
+ }
+
+ @Test
+ void score_compatibleTarget_returnsPointEight() {
+ assertEquals(0.8f, converter.score("text", new Variant(MediaType.APPLICATION_JSON), null));
+ }
+
+ @Test
+ void score_incompatibleTarget_returnsHalf() {
+ assertEquals(0.5f, converter.score("text", new Variant(MediaType.IMAGE_PNG), null));
+ }
+
+ @Test
+ void scoreRepresentation_jacksonRepresentation_returnsOne() {
+ JacksonRepresentation source =
+ new JacksonRepresentation<>(MediaType.APPLICATION_JSON, "text");
+ assertEquals(1.0f, converter.score(source, String.class, null));
+ }
+
+ @Test
+ void scoreRepresentation_forJacksonRepresentationTarget_returnsOne() {
+ assertEquals(
+ 1.0f,
+ converter.score(
+ new StringRepresentation("text", MediaType.TEXT_PLAIN),
+ JacksonRepresentation.class,
+ null));
+ }
+
+ @Test
+ void scoreRepresentation_forCompatibleSource_returnsPointEight() {
+ assertEquals(
+ 0.8f,
+ converter.score(
+ new StringRepresentation("text", MediaType.APPLICATION_JSON),
+ String.class,
+ null));
+ }
+
+ @Test
+ void scoreRepresentation_forIncompatibleSource_returnsMinusOne() {
+ assertEquals(
+ -1.0f,
+ converter.score(
+ new StringRepresentation("text", MediaType.IMAGE_PNG), String.class, null));
+ }
+
+ @Test
+ void toObject_fromJacksonRepresentation_returnsUnderlyingObject() throws Exception {
+ JacksonRepresentation source =
+ new JacksonRepresentation<>(MediaType.APPLICATION_JSON, "text");
+ assertEquals("text", converter.toObject(source, String.class, null));
+ }
+
+ @Test
+ void toObject_fromJacksonRepresentation_forJacksonRepresentationTarget_returnsRepresentation()
+ throws Exception {
+ JacksonRepresentation source =
+ new JacksonRepresentation<>(MediaType.APPLICATION_JSON, "text");
+ Object result = converter.toObject(source, JacksonRepresentation.class, null);
+ assertTrue(result instanceof JacksonRepresentation>);
+ }
+
+ @Test
+ void toObject_fromCompatibleRepresentation_returnsParsedObject() throws Exception {
+ StringRepresentation source =
+ new StringRepresentation("\"text\"", MediaType.APPLICATION_JSON);
+ assertEquals("text", converter.toObject(source, String.class, null));
+ }
+
+ @Test
+ void toObject_fromIncompatibleRepresentation_returnsNull() throws Exception {
+ StringRepresentation source = new StringRepresentation("text", MediaType.IMAGE_PNG);
+ assertNull(converter.toObject(source, String.class, null));
+ }
+
+ @Test
+ void toRepresentation_fromJacksonRepresentation_returnsSameInstance() {
+ JacksonRepresentation source =
+ new JacksonRepresentation<>(MediaType.APPLICATION_JSON, "text");
+ Representation result =
+ converter.toRepresentation(source, new Variant(MediaType.APPLICATION_JSON), null);
+ assertEquals(source, result);
+ }
+
+ @Test
+ void toRepresentation_withoutMediaType_defaultsToJson() {
+ Variant target = new Variant();
+ Representation result = converter.toRepresentation("text", target, null);
+ assertEquals(MediaType.APPLICATION_JSON, target.getMediaType());
+ assertTrue(result instanceof JacksonRepresentation>);
+ }
+
+ @Test
+ void toRepresentation_forIncompatibleTarget_returnsNull() {
+ Representation result =
+ converter.toRepresentation("text", new Variant(MediaType.IMAGE_PNG), null);
+ assertNull(result);
+ }
+
+ @Test
+ void updatePreferences_addsAllSupportedMediaTypesWithFullPriority() {
+ List> preferences = new ArrayList<>();
+ converter.updatePreferences(preferences, String.class);
+ assertEquals(7, preferences.size());
+ for (Preference preference : preferences) {
+ assertEquals(1.0f, preference.getQuality());
+ }
+ }
+}
diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringContextTestCase.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringContextTestCase.java
new file mode 100644
index 0000000000..23f0e90960
--- /dev/null
+++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringContextTestCase.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.spring;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.restlet.Client;
+import org.restlet.Context;
+import org.restlet.data.Protocol;
+
+/** Unit tests for {@link SpringContext}. */
+class SpringContextTestCase {
+
+ private Context restletContextWithClapDispatcher() {
+ Context context = new Context();
+ context.setClientDispatcher(new Client(Protocol.CLAP));
+ return context;
+ }
+
+ @Test
+ void getRestletContext_returnsConstructorArgument() {
+ Context restletContext = new Context();
+ SpringContext springContext = new SpringContext(restletContext);
+ assertSame(restletContext, springContext.getRestletContext());
+ }
+
+ @Test
+ void getPropertyConfigRefs_lazyInitializesToEmptyModifiableList() {
+ SpringContext springContext = new SpringContext(new Context());
+ assertNotNull(springContext.getPropertyConfigRefs());
+ assertTrue(springContext.getPropertyConfigRefs().isEmpty());
+ springContext.getPropertyConfigRefs().add("some-ref");
+ assertEquals(1, springContext.getPropertyConfigRefs().size());
+ }
+
+ @Test
+ void getXmlConfigRefs_lazyInitializesToEmptyModifiableList() {
+ SpringContext springContext = new SpringContext(new Context());
+ assertNotNull(springContext.getXmlConfigRefs());
+ assertTrue(springContext.getXmlConfigRefs().isEmpty());
+ springContext.getXmlConfigRefs().add("some-ref");
+ assertEquals(1, springContext.getXmlConfigRefs().size());
+ }
+
+ @Test
+ void refresh_withoutConfigRefs_refreshesEmptyContext() {
+ SpringContext springContext = new SpringContext(new Context());
+ springContext.refresh();
+ assertTrue(springContext.isActive());
+ }
+
+ @Test
+ void refresh_withPropertyConfigRef_loadsBeanDefinitionsFromProperties() {
+ SpringContext springContext = new SpringContext(restletContextWithClapDispatcher());
+ springContext
+ .getPropertyConfigRefs()
+ .add("clap://class/org/restlet/ext/spring/SpringContextTestCase.properties");
+
+ springContext.refresh();
+
+ assertNotNull(springContext.getBean("myPropertyBean"));
+ }
+
+ @Test
+ void refresh_withXmlConfigRef_loadsBeanDefinitionsFromXml() {
+ SpringContext springContext = new SpringContext(restletContextWithClapDispatcher());
+ springContext
+ .getXmlConfigRefs()
+ .add("clap://class/org/restlet/ext/spring/SpringContextTestCase.xml");
+
+ springContext.refresh();
+
+ assertNotNull(springContext.getBean("myXmlBean"));
+ }
+}
diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringResourceTestCase.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringResourceTestCase.java
new file mode 100644
index 0000000000..e219334b7c
--- /dev/null
+++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringResourceTestCase.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.spring;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import org.junit.jupiter.api.Test;
+import org.restlet.engine.io.IoUtils;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+
+/** Unit tests for {@link SpringResource}. */
+class SpringResourceTestCase {
+
+ @Test
+ void constructor_withNullRepresentation_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () -> new SpringResource(null));
+ }
+
+ @Test
+ void constructor_withoutDescription_usesDefaultDescription() {
+ SpringResource resource = new SpringResource(new StringRepresentation("text"));
+ assertEquals("Restlet Representation", resource.getDescription());
+ }
+
+ @Test
+ void constructor_withNullDescription_usesEmptyDescription() {
+ SpringResource resource = new SpringResource(new StringRepresentation("text"), null);
+ assertEquals("", resource.getDescription());
+ }
+
+ @Test
+ void constructor_withCustomDescription_usesIt() {
+ SpringResource resource =
+ new SpringResource(new StringRepresentation("text"), "my description");
+ assertEquals("my description", resource.getDescription());
+ }
+
+ @Test
+ void existsAndIsOpen_alwaysReturnTrue() {
+ SpringResource resource = new SpringResource(new StringRepresentation("text"));
+ assertTrue(resource.exists());
+ assertTrue(resource.isOpen());
+ }
+
+ @Test
+ void getInputStream_returnsUnderlyingStream() throws Exception {
+ SpringResource resource = new SpringResource(new StringRepresentation("hello"));
+ assertEquals("hello", IoUtils.toString(resource.getInputStream()));
+ }
+
+ @Test
+ void getInputStream_readTwiceOnTransientRepresentation_throwsIllegalStateException()
+ throws Exception {
+ StringRepresentation transientRepresentation = new StringRepresentation("hello");
+ transientRepresentation.setTransient(true);
+ SpringResource resource = new SpringResource(transientRepresentation);
+
+ resource.getInputStream();
+
+ assertThrows(IllegalStateException.class, resource::getInputStream);
+ }
+
+ @Test
+ void getInputStream_whenStreamIsNull_throwsIllegalStateException() {
+ Representation nullStreamRepresentation =
+ new StringRepresentation("hello") {
+ @Override
+ public InputStream getStream() throws IOException {
+ return null;
+ }
+ };
+ SpringResource resource = new SpringResource(nullStreamRepresentation);
+
+ assertThrows(IllegalStateException.class, resource::getInputStream);
+ }
+
+ @Test
+ void equalsAndHashCode_basedOnWrappedRepresentation() {
+ StringRepresentation representation = new StringRepresentation("hello");
+ SpringResource resource1 = new SpringResource(representation);
+ SpringResource resource2 = new SpringResource(representation);
+ SpringResource resource3 =
+ new SpringResource(
+ new StringRepresentation("other", org.restlet.data.MediaType.TEXT_HTML));
+
+ assertEquals(resource1, resource1);
+ assertEquals(resource1, resource2);
+ assertEquals(resource1.hashCode(), resource2.hashCode());
+ assertNotEquals(resource1, resource3);
+ assertFalse(resource1.equals("not a resource"));
+ }
+}
diff --git a/org.restlet.ext.spring/src/test/resources/org/restlet/ext/spring/SpringContextTestCase.properties b/org.restlet.ext.spring/src/test/resources/org/restlet/ext/spring/SpringContextTestCase.properties
new file mode 100644
index 0000000000..c0e7a28871
--- /dev/null
+++ b/org.restlet.ext.spring/src/test/resources/org/restlet/ext/spring/SpringContextTestCase.properties
@@ -0,0 +1 @@
+myPropertyBean.(class)=java.lang.Object
diff --git a/org.restlet.ext.spring/src/test/resources/org/restlet/ext/spring/SpringContextTestCase.xml b/org.restlet.ext.spring/src/test/resources/org/restlet/ext/spring/SpringContextTestCase.xml
new file mode 100644
index 0000000000..e152d6814c
--- /dev/null
+++ b/org.restlet.ext.spring/src/test/resources/org/restlet/ext/spring/SpringContextTestCase.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/org.restlet.ext.thymeleaf/src/test/java/org/restlet/ext/thymeleaf/TemplateFilterTestCase.java b/org.restlet.ext.thymeleaf/src/test/java/org/restlet/ext/thymeleaf/TemplateFilterTestCase.java
new file mode 100644
index 0000000000..b7856931d9
--- /dev/null
+++ b/org.restlet.ext.thymeleaf/src/test/java/org/restlet/ext/thymeleaf/TemplateFilterTestCase.java
@@ -0,0 +1,152 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.thymeleaf;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.junit.jupiter.api.Test;
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.Encoding;
+import org.restlet.data.MediaType;
+import org.restlet.data.Method;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+import org.restlet.util.Resolver;
+
+/** Unit tests for {@link TemplateFilter}. */
+class TemplateFilterTestCase {
+
+ private static final Encoding THYMELEAF = new Encoding("thymeleaf", "Thymeleaf");
+
+ private static class TestTemplateFilter extends TemplateFilter {
+ TestTemplateFilter() {
+ super();
+ }
+
+ TestTemplateFilter(org.restlet.Context context) {
+ super(context);
+ }
+
+ TestTemplateFilter(org.restlet.Context context, org.restlet.Restlet next) {
+ super(context, next);
+ }
+
+ TestTemplateFilter(
+ org.restlet.Context context, org.restlet.Restlet next, Map model) {
+ super(context, next, model);
+ }
+
+ TestTemplateFilter(
+ org.restlet.Context context, org.restlet.Restlet next, Resolver resolver) {
+ super(context, next, resolver);
+ }
+ }
+
+ private Request newRequest() {
+ Request request = new Request(Method.GET, "/");
+ request.setEntity(new StringRepresentation("foo=bar", MediaType.APPLICATION_WWW_FORM));
+ return request;
+ }
+
+ private TemplateRepresentation templateEntity() {
+ TemplateRepresentation representation =
+ new TemplateRepresentation("myTemplate", Locale.ENGLISH, MediaType.TEXT_HTML);
+ representation.getEncodings().add(THYMELEAF);
+ return representation;
+ }
+
+ @Test
+ void getLocale_defaultsToJvmDefault() {
+ assertEquals(Locale.getDefault(), new TestTemplateFilter().getLocale());
+ }
+
+ @Test
+ void afterHandle_noEntity_doesNothing() {
+ TestTemplateFilter filter = new TestTemplateFilter(new Context());
+ Request request = newRequest();
+ Response response = new Response(request);
+
+ filter.afterHandle(request, response);
+
+ assertNull(response.getEntity());
+ }
+
+ @Test
+ void afterHandle_entityWithoutThymeleafEncoding_leavesEntityUnchanged() {
+ TestTemplateFilter filter = new TestTemplateFilter(new Context());
+ Request request = newRequest();
+ Response response = new Response(request);
+ Representation entity = new StringRepresentation("plain", MediaType.TEXT_PLAIN);
+ response.setEntity(entity);
+
+ filter.afterHandle(request, response);
+
+ assertSame(entity, response.getEntity());
+ }
+
+ @Test
+ void afterHandle_withoutDataModel_usesRequestResponseResolver() {
+ TestTemplateFilter filter = new TestTemplateFilter(new Context());
+ Request request = newRequest();
+ Response response = new Response(request);
+ response.setEntity(templateEntity());
+
+ filter.afterHandle(request, response);
+
+ Representation result = response.getEntity();
+ assertTrue(result instanceof TemplateRepresentation);
+ TemplateRepresentation tr = (TemplateRepresentation) result;
+ assertEquals("myTemplate", tr.getTemplateName());
+ assertEquals(MediaType.TEXT_HTML, tr.getMediaType());
+ assertEquals("bar", tr.context.getVariable("foo"));
+ }
+
+ @Test
+ void afterHandle_withMapDataModel_usesMap() {
+ Map model = new ConcurrentHashMap<>();
+ model.put("key", "value");
+ TestTemplateFilter filter = new TestTemplateFilter(new Context(), null, model);
+ Request request = newRequest();
+ Response response = new Response(request);
+ response.setEntity(templateEntity());
+
+ filter.afterHandle(request, response);
+
+ TemplateRepresentation tr = (TemplateRepresentation) response.getEntity();
+ assertEquals("value", tr.context.getVariable("key"));
+ }
+
+ @Test
+ void afterHandle_withResolverDataModel_usesResolver() {
+ Resolver resolver =
+ new Resolver<>() {
+ @Override
+ public Object resolve(String key) {
+ return "resolved-" + key;
+ }
+ };
+ TestTemplateFilter filter = new TestTemplateFilter(new Context(), null, resolver);
+ Request request = newRequest();
+ Response response = new Response(request);
+ response.setEntity(templateEntity());
+
+ filter.afterHandle(request, response);
+
+ TemplateRepresentation tr = (TemplateRepresentation) response.getEntity();
+ assertEquals("resolved-anyKey", tr.context.getVariable("anyKey"));
+ }
+}
diff --git a/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/VelocityConverterTestCase.java b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/VelocityConverterTestCase.java
new file mode 100644
index 0000000000..629db1249c
--- /dev/null
+++ b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/VelocityConverterTestCase.java
@@ -0,0 +1,147 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.velocity;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.velocity.Template;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.MediaType;
+import org.restlet.data.Preference;
+import org.restlet.engine.io.IoUtils;
+import org.restlet.representation.StringRepresentation;
+import org.restlet.representation.Variant;
+import org.restlet.resource.ClientResource;
+
+/** Unit tests for {@link VelocityConverter}. */
+class VelocityConverterTestCase {
+
+ private File testDir;
+
+ private File testFile;
+
+ private Template template;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ testDir = new File(System.getProperty("java.io.tmpdir"), "VelocityConverterTestCase");
+ testDir.mkdir();
+ testFile = File.createTempFile("test", ".vm", testDir);
+
+ try (FileWriter fw = new FileWriter(testFile)) {
+ fw.write("Value=$value");
+ }
+
+ TemplateRepresentation tr =
+ new TemplateRepresentation(testFile.getName(), MediaType.TEXT_PLAIN);
+ tr.getEngine().setProperty("file.resource.loader.path", testDir.getAbsolutePath());
+ template = tr.getTemplate();
+ }
+
+ @AfterEach
+ void tearDown() {
+ IoUtils.delete(testFile);
+ IoUtils.delete(testDir, true);
+ }
+
+ @Test
+ void getObjectClasses_alwaysReturnsEmptyList() {
+ assertTrue(
+ new VelocityConverter()
+ .getObjectClasses(new Variant(MediaType.TEXT_PLAIN))
+ .isEmpty());
+ }
+
+ @Test
+ void getVariants_forTemplate_returnsMediaTypeAll() {
+ List> variants = new VelocityConverter().getVariants(Template.class);
+ assertEquals(1, variants.size());
+ assertTrue(MediaType.ALL.isCompatible(((Variant) variants.getFirst()).getMediaType()));
+ }
+
+ @Test
+ void getVariants_forOtherClass_returnsEmptyList() {
+ assertTrue(new VelocityConverter().getVariants(String.class).isEmpty());
+ }
+
+ @Test
+ void score_template_returnsOne() {
+ assertEquals(
+ 1.0f,
+ new VelocityConverter().score(template, new Variant(MediaType.TEXT_PLAIN), null));
+ }
+
+ @Test
+ void score_nonTemplate_returnsMinusOne() {
+ assertEquals(
+ -1.0f,
+ new VelocityConverter()
+ .score("not a template", new Variant(MediaType.TEXT_PLAIN), null));
+ }
+
+ @Test
+ void score_repr_alwaysReturnsMinusOne() {
+ assertEquals(
+ -1.0f,
+ new VelocityConverter()
+ .score(new StringRepresentation("text"), String.class, null));
+ }
+
+ @Test
+ void toObject_alwaysReturnsNull() {
+ assertNull(
+ new VelocityConverter()
+ .toObject(new StringRepresentation("text"), String.class, null));
+ }
+
+ @Test
+ void toRepresentation_nonTemplate_returnsNull() {
+ assertNull(
+ new VelocityConverter()
+ .toRepresentation("string", new Variant(MediaType.TEXT_PLAIN), null));
+ }
+
+ @Test
+ void toRepresentation_forTemplate_returnsTemplateRepresentationWithDataModel()
+ throws Exception {
+ ClientResource resource = new ClientResource("http://localhost/");
+
+ Object result =
+ new VelocityConverter()
+ .toRepresentation(template, new Variant(MediaType.TEXT_PLAIN), resource);
+
+ assertTrue(result instanceof TemplateRepresentation);
+ TemplateRepresentation tr = (TemplateRepresentation) result;
+ assertEquals(MediaType.TEXT_PLAIN, tr.getMediaType());
+ assertEquals("Value=$value", tr.getText());
+ }
+
+ @Test
+ void updatePreferences_forTemplate_addsMediaTypeAll() {
+ List> prefs = new ArrayList<>();
+ new VelocityConverter().updatePreferences(prefs, Template.class);
+ assertEquals(1, prefs.size());
+ assertEquals(MediaType.ALL, prefs.getFirst().getMetadata());
+ }
+
+ @Test
+ void updatePreferences_forOtherClass_doesNotModify() {
+ List> prefs = new ArrayList<>();
+ new VelocityConverter().updatePreferences(prefs, String.class);
+ assertTrue(prefs.isEmpty());
+ }
+}
diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/SaxRepresentationTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/SaxRepresentationTestCase.java
index 9675246036..f3e0aa27fe 100644
--- a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/SaxRepresentationTestCase.java
+++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/SaxRepresentationTestCase.java
@@ -17,12 +17,15 @@
import java.io.IOException;
import java.io.StringReader;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
+import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.sax.SAXSource;
import org.junit.jupiter.api.Test;
import org.restlet.data.MediaType;
import org.restlet.representation.StringRepresentation;
+import org.w3c.dom.Document;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.DefaultHandler;
@@ -82,4 +85,58 @@ void release_clearsSource() throws Exception {
sr.release();
assertNull(sr.getSaxSource());
}
+
+ @Test
+ void defaultConstructor_usesTextXmlMediaType() {
+ SaxRepresentation sr = new SaxRepresentation();
+ assertEquals(MediaType.TEXT_XML, sr.getMediaType());
+ }
+
+ @Test
+ void constructor_withInputSource_wrapsItInSaxSource() throws Exception {
+ InputSource inputSource = new InputSource(new StringReader(XML));
+ SaxRepresentation sr = new SaxRepresentation(MediaType.TEXT_XML, inputSource);
+ assertNotNull(sr.getSaxSource());
+ assertNotNull(sr.getInputSource());
+ }
+
+ @Test
+ void constructor_withDomDocument_wrapsItInSaxSource() throws Exception {
+ Document document =
+ DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder()
+ .parse(new org.xml.sax.InputSource(new StringReader(XML)));
+ SaxRepresentation sr = new SaxRepresentation(MediaType.TEXT_XML, document);
+ assertNotNull(sr.getSaxSource());
+ }
+
+ @Test
+ void constructor_withNullRepresentation_hasNullMediaType() {
+ SaxRepresentation sr = new SaxRepresentation((StringRepresentation) null);
+ assertNull(sr.getMediaType());
+ }
+
+ @Test
+ void getInputSource_withoutSaxSource_returnsNull() throws Exception {
+ SaxRepresentation sr = new SaxRepresentation();
+ assertNull(sr.getInputSource());
+ }
+
+ @Test
+ void write_writer_serializesUsingXmlWriter() throws Exception {
+ SaxRepresentation sr =
+ new SaxRepresentation(new StringRepresentation(XML, MediaType.TEXT_XML));
+ StringWriter writer = new StringWriter();
+ sr.write(writer);
+ assertTrue(writer.toString().contains(""));
+ assertTrue(writer.toString().contains("text"));
+ }
+
+ @Test
+ void setSaxSource_overridesSource() throws Exception {
+ SaxRepresentation sr = new SaxRepresentation();
+ SAXSource source = new SAXSource(new InputSource(new StringReader(XML)));
+ sr.setSaxSource(source);
+ assertEquals(source, sr.getSaxSource());
+ }
}
diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformerTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformerTestCase.java
index c5d2c2371a..0e77c311a2 100644
--- a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformerTestCase.java
+++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformerTestCase.java
@@ -9,15 +9,25 @@
package org.restlet.ext.xml;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.List;
import org.junit.jupiter.api.Test;
import org.restlet.Component;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.CharacterSet;
+import org.restlet.data.Encoding;
+import org.restlet.data.Language;
import org.restlet.data.MediaType;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
+import org.restlet.routing.Filter;
/**
* Test case for the Transformer class.
@@ -108,4 +118,111 @@ void testTransform() throws Exception {
assertEquals(this.output, result);
}
+
+ @Test
+ void constructor_setsDefaults() {
+ Transformer transformer = new Transformer(Transformer.MODE_REQUEST, this.xslt);
+ assertEquals(Transformer.MODE_REQUEST, transformer.getMode());
+ assertSame(this.xslt, transformer.getTransformSheet());
+ assertEquals(MediaType.APPLICATION_XML, transformer.getResultMediaType());
+ assertNull(transformer.getResultCharacterSet());
+ }
+
+ @Test
+ void gettersAndSetters_roundTrip() {
+ Transformer transformer = new Transformer(Transformer.MODE_REQUEST, this.xslt);
+
+ transformer.setMode(Transformer.MODE_RESPONSE);
+ assertEquals(Transformer.MODE_RESPONSE, transformer.getMode());
+
+ transformer.setResultCharacterSet(CharacterSet.UTF_8);
+ assertEquals(CharacterSet.UTF_8, transformer.getResultCharacterSet());
+
+ transformer.setResultMediaType(MediaType.TEXT_XML);
+ assertEquals(MediaType.TEXT_XML, transformer.getResultMediaType());
+
+ Representation otherSheet = new StringRepresentation("sheet");
+ transformer.setTransformSheet(otherSheet);
+ assertSame(otherSheet, transformer.getTransformSheet());
+
+ assertTrue(transformer.getResultEncodings().isEmpty());
+ transformer.setResultEncodings(List.of(Encoding.GZIP));
+ assertEquals(List.of(Encoding.GZIP), transformer.getResultEncodings());
+
+ assertTrue(transformer.getResultLanguages().isEmpty());
+ transformer.setResultLanguages(List.of(Language.ENGLISH));
+ assertEquals(List.of(Language.ENGLISH), transformer.getResultLanguages());
+ }
+
+ @Test
+ void canTransform_alwaysReturnsTrue() {
+ Transformer transformer = new Transformer(Transformer.MODE_REQUEST, this.xslt);
+ assertTrue(transformer.canTransform(null));
+ assertTrue(transformer.canTransform(this.source));
+ }
+
+ @Test
+ void beforeHandle_requestMode_transformsRequestEntity() {
+ Transformer transformer = new Transformer(Transformer.MODE_REQUEST, this.xslt);
+ Request request = new Request();
+ request.setEntity(this.source);
+ Response response = new Response(request);
+
+ int result = transformer.beforeHandle(request, response);
+
+ assertEquals(Filter.CONTINUE, result);
+ assertTrue(request.getEntity() instanceof TransformRepresentation);
+ }
+
+ @Test
+ void beforeHandle_responseMode_doesNotTransformRequestEntity() {
+ Transformer transformer = new Transformer(Transformer.MODE_RESPONSE, this.xslt);
+ Request request = new Request();
+ request.setEntity(this.source);
+ Response response = new Response(request);
+
+ transformer.beforeHandle(request, response);
+
+ assertSame(this.source, request.getEntity());
+ }
+
+ @Test
+ void afterHandle_responseMode_transformsResponseEntity() {
+ Transformer transformer = new Transformer(Transformer.MODE_RESPONSE, this.xslt);
+ Request request = new Request();
+ Response response = new Response(request);
+ response.setEntity(this.source);
+
+ transformer.afterHandle(request, response);
+
+ assertTrue(response.getEntity() instanceof TransformRepresentation);
+ }
+
+ @Test
+ void afterHandle_requestMode_doesNotTransformResponseEntity() {
+ Transformer transformer = new Transformer(Transformer.MODE_REQUEST, this.xslt);
+ Request request = new Request();
+ Response response = new Response(request);
+ response.setEntity(this.source);
+
+ transformer.afterHandle(request, response);
+
+ assertSame(this.source, response.getEntity());
+ }
+
+ @Test
+ void transform_withResultLanguagesAndEncodings_appliesThemToResult() {
+ Transformer transformer = new Transformer(Transformer.MODE_REQUEST, this.xslt);
+ transformer.setResultLanguages(List.of(Language.FRENCH));
+ transformer.setResultEncodings(List.of(Encoding.GZIP));
+ transformer.setResultCharacterSet(CharacterSet.UTF_8);
+ transformer.setResultMediaType(MediaType.TEXT_XML);
+
+ Representation result = transformer.transform(this.source);
+
+ assertTrue(result.getLanguages().contains(Language.FRENCH));
+ assertTrue(result.getEncodings().contains(Encoding.GZIP));
+ assertEquals(CharacterSet.UTF_8, result.getCharacterSet());
+ assertEquals(MediaType.TEXT_XML, result.getMediaType());
+ }
}
diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/internal/AbstractXmlReaderTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/internal/AbstractXmlReaderTestCase.java
new file mode 100644
index 0000000000..33a2277c19
--- /dev/null
+++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/internal/AbstractXmlReaderTestCase.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.xml.internal;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.DefaultHandler;
+
+/** Unit tests for {@link AbstractXmlReader}. */
+class AbstractXmlReaderTestCase {
+
+ private static class TestXmlReader extends AbstractXmlReader {
+ @Override
+ public void parse(InputSource input) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void parse(String systemId) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Test
+ void newInstance_hasNullHandlersAndFalseFeatures() {
+ TestXmlReader reader = new TestXmlReader();
+ assertNull(reader.getContentHandler());
+ assertNull(reader.getDTDHandler());
+ assertNull(reader.getEntityResolver());
+ assertNull(reader.getErrorHandler());
+ assertFalse(reader.getFeature("any-feature"));
+ assertNull(reader.getProperty("any-property"));
+ }
+
+ @Test
+ void contentHandler_setterAndGetter_roundTrip() {
+ TestXmlReader reader = new TestXmlReader();
+ ContentHandler handler = new DefaultHandler();
+ reader.setContentHandler(handler);
+ assertSame(handler, reader.getContentHandler());
+ }
+
+ @Test
+ void dtdHandler_setterAndGetter_roundTrip() {
+ TestXmlReader reader = new TestXmlReader();
+ DTDHandler handler = new DefaultHandler();
+ reader.setDTDHandler(handler);
+ assertSame(handler, reader.getDTDHandler());
+ }
+
+ @Test
+ void entityResolver_setterAndGetter_roundTrip() {
+ TestXmlReader reader = new TestXmlReader();
+ EntityResolver resolver = new DefaultHandler();
+ reader.setEntityResolver(resolver);
+ assertSame(resolver, reader.getEntityResolver());
+ }
+
+ @Test
+ void errorHandler_setterAndGetter_roundTrip() {
+ TestXmlReader reader = new TestXmlReader();
+ ErrorHandler handler = new DefaultHandler();
+ reader.setErrorHandler(handler);
+ assertSame(handler, reader.getErrorHandler());
+ }
+
+ @Test
+ void feature_setterAndGetter_roundTrip() {
+ TestXmlReader reader = new TestXmlReader();
+ reader.setFeature("my-feature", true);
+ assertTrue(reader.getFeature("my-feature"));
+ assertFalse(reader.getFeature("other-feature"));
+ }
+
+ @Test
+ void property_setterAndGetter_roundTrip() {
+ TestXmlReader reader = new TestXmlReader();
+ Object value = new Object();
+ reader.setProperty("my-property", value);
+ assertSame(value, reader.getProperty("my-property"));
+ }
+}
diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/internal/ContextResolverTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/internal/ContextResolverTestCase.java
new file mode 100644
index 0000000000..6cd95bee3d
--- /dev/null
+++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/internal/ContextResolverTestCase.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.ext.xml.internal;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import javax.xml.transform.Source;
+import org.junit.jupiter.api.Test;
+import org.restlet.Client;
+import org.restlet.Context;
+import org.restlet.data.Protocol;
+
+/** Unit tests for {@link ContextResolver}. */
+class ContextResolverTestCase {
+
+ private Context clapContext() {
+ Context context = new Context();
+ context.setClientDispatcher(new Client(Protocol.CLAP));
+ return context;
+ }
+
+ @Test
+ void resolve_withNullContext_returnsNull() {
+ ContextResolver resolver = new ContextResolver(null);
+ assertNull(resolver.resolve("clap://class/org/restlet/ext/xml/xslt/one/1st.xml", null));
+ }
+
+ @Test
+ void resolve_withAbsoluteHrefAndNoBase_returnsSource() {
+ ContextResolver resolver = new ContextResolver(clapContext());
+
+ Source result = resolver.resolve("clap://class/org/restlet/ext/xml/xslt/one/1st.xml", null);
+
+ assertNotNull(result);
+ }
+
+ @Test
+ void resolve_withRelativeHrefAndBase_returnsSource() {
+ ContextResolver resolver = new ContextResolver(clapContext());
+
+ Source result =
+ resolver.resolve("1st.xml", "clap://class/org/restlet/ext/xml/xslt/one/base.xsl");
+
+ assertNotNull(result);
+ }
+
+ @Test
+ void resolve_withUnresolvableHref_returnsNull() {
+ ContextResolver resolver = new ContextResolver(clapContext());
+
+ Source result = resolver.resolve("clap://class/does/not/exist.xml", null);
+
+ assertNull(result);
+ }
+}
diff --git a/org.restlet/src/main/java/org/restlet/util/WrapperRequest.java b/org.restlet/src/main/java/org/restlet/util/WrapperRequest.java
index bef197704b..87d9f96aaf 100644
--- a/org.restlet/src/main/java/org/restlet/util/WrapperRequest.java
+++ b/org.restlet/src/main/java/org/restlet/util/WrapperRequest.java
@@ -472,7 +472,7 @@ public void setRootRef(Reference rootRef) {
*/
@Override
public void setAccessControlRequestHeaders(Set accessControlRequestHeaders) {
- super.setAccessControlRequestHeaders(accessControlRequestHeaders);
+ getWrappedRequest().setAccessControlRequestHeaders(accessControlRequestHeaders);
}
/**
@@ -482,7 +482,7 @@ public void setAccessControlRequestHeaders(Set accessControlRequestHeade
*/
@Override
public void setAccessControlRequestMethod(Method accessControlRequestMethod) {
- super.setAccessControlRequestMethod(accessControlRequestMethod);
+ getWrappedRequest().setAccessControlRequestMethod(accessControlRequestMethod);
}
@Override
diff --git a/org.restlet/src/test/java/org/restlet/data/ExpectationTestCase.java b/org.restlet/src/test/java/org/restlet/data/ExpectationTestCase.java
new file mode 100644
index 0000000000..e7ddf1975f
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/data/ExpectationTestCase.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.data;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.restlet.engine.header.HeaderConstants;
+
+/** Unit tests for {@link Expectation}. */
+class ExpectationTestCase {
+
+ @Test
+ void continueResponse_hasExpectContinueName() {
+ Expectation expectation = Expectation.continueResponse();
+ assertEquals(HeaderConstants.EXPECT_CONTINUE, expectation.getName());
+ }
+
+ @Test
+ void constructorWithNameOnly_hasNullValue() {
+ Expectation expectation = new Expectation("name");
+ assertEquals("name", expectation.getName());
+ assertEquals(null, expectation.getValue());
+ }
+
+ @Test
+ void gettersAndSetters_roundTrip() {
+ Expectation expectation = new Expectation("name", "value");
+ assertEquals("name", expectation.getName());
+ assertEquals("value", expectation.getValue());
+
+ expectation.setName("newName");
+ assertEquals("newName", expectation.getName());
+
+ expectation.setValue("newValue");
+ assertEquals("newValue", expectation.getValue());
+
+ assertTrue(expectation.getParameters().isEmpty());
+ expectation.setParameters(List.of(new Parameter("p", "v")));
+ assertEquals(1, expectation.getParameters().size());
+ }
+
+ @Test
+ void equalsAndHashCode_considerNameValueAndParameters() {
+ Expectation e1 = new Expectation("name", "value");
+ Expectation e2 = new Expectation("name", "value");
+ Expectation e3 = new Expectation("other", "value");
+
+ assertEquals(e1, e1);
+ assertEquals(e1, e2);
+ assertEquals(e1.hashCode(), e2.hashCode());
+ assertNotEquals(e1, e3);
+ assertFalse(e1.equals("not an expectation"));
+ }
+
+ @Test
+ void toString_includesNameValueAndParameters() {
+ Expectation expectation = new Expectation("name", "value");
+ String text = expectation.toString();
+ assertTrue(text.contains("name"));
+ assertTrue(text.contains("value"));
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/data/WarningTestCase.java b/org.restlet/src/test/java/org/restlet/data/WarningTestCase.java
new file mode 100644
index 0000000000..d957706d58
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/data/WarningTestCase.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.data;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.Date;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link Warning}. */
+class WarningTestCase {
+
+ @Test
+ void defaultConstructor_hasNullFields() {
+ Warning warning = new Warning();
+ assertNull(warning.getAgent());
+ assertNull(warning.getDate());
+ assertNull(warning.getStatus());
+ assertNull(warning.getText());
+ }
+
+ @Test
+ void gettersAndSetters_roundTrip() {
+ Warning warning = new Warning();
+
+ warning.setAgent("agent");
+ assertEquals("agent", warning.getAgent());
+
+ Date date = new Date();
+ warning.setDate(date);
+ assertEquals(date, warning.getDate());
+
+ warning.setStatus(Status.SUCCESS_OK);
+ assertEquals(Status.SUCCESS_OK, warning.getStatus());
+
+ warning.setText("text");
+ assertEquals("text", warning.getText());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/adapter/JettyHandlerTestCase.java b/org.restlet/src/test/java/org/restlet/engine/adapter/JettyHandlerTestCase.java
new file mode 100644
index 0000000000..3b01b7dd5c
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/adapter/JettyHandlerTestCase.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.adapter;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+import org.restlet.Server;
+import org.restlet.data.Protocol;
+
+/** Unit tests for {@link JettyHandler}. */
+class JettyHandlerTestCase {
+
+ @Test
+ void constructor_defaultsToHttp() {
+ Server server = new Server(Protocol.HTTP, 0);
+ JettyHandler handler = new JettyHandler(server);
+ assertNotNull(handler);
+ }
+
+ @Test
+ void constructor_secureFlagTrue_usesHttpsHelper() {
+ Server server = new Server(Protocol.HTTPS, 0);
+ JettyHandler handler = new JettyHandler(server, true);
+ assertNotNull(handler);
+ }
+
+ @Test
+ void constructor_secureFlagFalse_usesHttpHelper() {
+ Server server = new Server(Protocol.HTTP, 0);
+ JettyHandler handler = new JettyHandler(server, false);
+ assertNotNull(handler);
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/application/DecodeRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/engine/application/DecodeRepresentationTestCase.java
new file mode 100644
index 0000000000..001a25dda7
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/application/DecodeRepresentationTestCase.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.application;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.Encoding;
+import org.restlet.engine.io.IoUtils;
+import org.restlet.representation.ByteArrayRepresentation;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+
+/** Unit tests for {@link DecodeRepresentation}. */
+class DecodeRepresentationTestCase {
+
+ private static Representation gzip(String text) throws Exception {
+ EncodeRepresentation encoded =
+ new EncodeRepresentation(Encoding.GZIP, new StringRepresentation(text));
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ encoded.write(out);
+ ByteArrayRepresentation result = new ByteArrayRepresentation(out.toByteArray());
+ result.setEncodings(List.of(Encoding.GZIP));
+ return result;
+ }
+
+ @Test
+ void getSupportedEncodings_containsExpectedEncodings() {
+ assertTrue(DecodeRepresentation.getSupportedEncodings().contains(Encoding.GZIP));
+ assertTrue(DecodeRepresentation.getSupportedEncodings().contains(Encoding.IDENTITY));
+ }
+
+ @Test
+ void isDecoding_forSupportedEncoding_isTrue() throws Exception {
+ DecodeRepresentation representation = new DecodeRepresentation(gzip("hello"));
+ assertTrue(representation.isDecoding());
+ }
+
+ @Test
+ void isDecoding_forPlainRepresentation_isTrueBecauseNoEncoding() {
+ DecodeRepresentation representation =
+ new DecodeRepresentation(new StringRepresentation("hello"));
+ assertTrue(representation.isDecoding());
+ assertEquals(0, representation.getEncodings().size());
+ }
+
+ @Test
+ void getStreamAndText_decodeGzippedContent() throws Exception {
+ DecodeRepresentation representation = new DecodeRepresentation(gzip("hello world"));
+ assertEquals("hello world", representation.getText());
+ }
+
+ @Test
+ void getReader_decodesGzippedContent() throws Exception {
+ DecodeRepresentation representation = new DecodeRepresentation(gzip("hello"));
+ assertEquals("hello", IoUtils.toString(representation.getReader()));
+ }
+
+ @Test
+ void write_decodesGzippedContentToOutputStream() throws Exception {
+ DecodeRepresentation representation = new DecodeRepresentation(gzip("hello"));
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ representation.write(out);
+ assertEquals("hello", out.toString());
+ }
+
+ @Test
+ void getAvailableSize_delegatesToIoUtils() throws Exception {
+ DecodeRepresentation representation =
+ new DecodeRepresentation(new StringRepresentation("hello"));
+ assertEquals(5, representation.getAvailableSize());
+ }
+
+ @Test
+ void getSize_forIdentityEncoding_delegatesToWrapped() {
+ StringRepresentation wrapped = new StringRepresentation("hello");
+ DecodeRepresentation representation = new DecodeRepresentation(wrapped);
+ assertEquals(wrapped.getSize(), representation.getSize());
+ }
+
+ @Test
+ void equals_delegatesToSuper() {
+ StringRepresentation wrapped = new StringRepresentation("hello");
+ DecodeRepresentation representation = new DecodeRepresentation(wrapped);
+ assertFalse(representation.equals("not a representation"));
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/application/EncodeRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/engine/application/EncodeRepresentationTestCase.java
new file mode 100644
index 0000000000..73c09b28d5
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/application/EncodeRepresentationTestCase.java
@@ -0,0 +1,211 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.application;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.StringWriter;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipInputStream;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.Disposition;
+import org.restlet.data.Encoding;
+import org.restlet.engine.io.IoUtils;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+
+/** Unit tests for {@link EncodeRepresentation}. */
+class EncodeRepresentationTestCase {
+
+ @Test
+ void canEncode_forSupportedEncoding_isTrue() {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.GZIP, new StringRepresentation("text"));
+ assertTrue(representation.canEncode());
+ }
+
+ @Test
+ void canEncode_forUnsupportedEncoding_isFalse() {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.COMPRESS, new StringRepresentation("text"));
+ assertFalse(representation.canEncode());
+ }
+
+ @Test
+ void getSupportedEncodings_containsExpectedEncodings() {
+ assertTrue(EncodeRepresentation.getSupportedEncodings().contains(Encoding.GZIP));
+ assertTrue(EncodeRepresentation.getSupportedEncodings().contains(Encoding.DEFLATE));
+ assertTrue(EncodeRepresentation.getSupportedEncodings().contains(Encoding.ZIP));
+ assertTrue(EncodeRepresentation.getSupportedEncodings().contains(Encoding.IDENTITY));
+ }
+
+ @Test
+ void getEncodings_whenCanEncode_appendsOwnEncoding() {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.GZIP, new StringRepresentation("text"));
+ assertTrue(representation.getEncodings().contains(Encoding.GZIP));
+ }
+
+ @Test
+ void getEncodings_whenCannotEncode_onlyContainsWrappedEncodings() {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.COMPRESS, new StringRepresentation("text"));
+ assertFalse(representation.getEncodings().contains(Encoding.COMPRESS));
+ }
+
+ @Test
+ void identityEncoding_availableSizeAndSize_delegateToWrapped() {
+ StringRepresentation wrapped = new StringRepresentation("text");
+ EncodeRepresentation representation = new EncodeRepresentation(Encoding.IDENTITY, wrapped);
+ assertEquals(wrapped.getAvailableSize(), representation.getAvailableSize());
+ assertEquals(wrapped.getSize(), representation.getSize());
+ }
+
+ @Test
+ void gzipEncoding_sizeAndAvailableSize_unknown() {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.GZIP, new StringRepresentation("text"));
+ assertEquals(Representation.UNKNOWN_SIZE, representation.getSize());
+ assertEquals(Representation.UNKNOWN_SIZE, representation.getAvailableSize());
+ }
+
+ @Test
+ void unsupportedEncoding_sizeAndAvailableSize_delegateToWrapped() {
+ StringRepresentation wrapped = new StringRepresentation("text");
+ EncodeRepresentation representation = new EncodeRepresentation(Encoding.COMPRESS, wrapped);
+ assertEquals(wrapped.getSize(), representation.getSize());
+ assertEquals(wrapped.getAvailableSize(), representation.getAvailableSize());
+ }
+
+ @Test
+ void write_gzip_producesGzipStream() throws Exception {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.GZIP, new StringRepresentation("hello world"));
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ representation.write(out);
+
+ try (GZIPInputStream gzip =
+ new GZIPInputStream(new java.io.ByteArrayInputStream(out.toByteArray()))) {
+ assertEquals("hello world", IoUtils.toString(gzip));
+ }
+ }
+
+ @Test
+ void write_deflate_producesDeflateStream() throws Exception {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.DEFLATE, new StringRepresentation("hello"));
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ representation.write(out);
+
+ try (InflaterInputStream inflater =
+ new InflaterInputStream(new java.io.ByteArrayInputStream(out.toByteArray()))) {
+ assertEquals("hello", IoUtils.toString(inflater));
+ }
+ }
+
+ @Test
+ void write_deflateNowrap_producesDeflateNowrapStream() throws Exception {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(
+ Encoding.DEFLATE_NOWRAP, new StringRepresentation("hello"));
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ representation.write(out);
+
+ try (InflaterInputStream inflater =
+ new InflaterInputStream(
+ new java.io.ByteArrayInputStream(out.toByteArray()),
+ new java.util.zip.Inflater(true))) {
+ assertEquals("hello", IoUtils.toString(inflater));
+ }
+ }
+
+ @Test
+ void write_zip_producesZipEntryNamedFromDisposition() throws Exception {
+ StringRepresentation wrapped = new StringRepresentation("hello");
+ Disposition disposition = new Disposition();
+ disposition.getParameters().add(Disposition.NAME_FILENAME, "myFile");
+ wrapped.setDisposition(disposition);
+
+ EncodeRepresentation representation = new EncodeRepresentation(Encoding.ZIP, wrapped);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ representation.write(out);
+
+ try (ZipInputStream zip =
+ new ZipInputStream(new java.io.ByteArrayInputStream(out.toByteArray()))) {
+ var entry = zip.getNextEntry();
+ assertEquals("myFile", entry.getName());
+ assertEquals("hello", IoUtils.toString(zip));
+ }
+ }
+
+ @Test
+ void write_unsupportedEncoding_delegatesToWrapped() throws Exception {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.COMPRESS, new StringRepresentation("hello"));
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ representation.write(out);
+ assertEquals("hello", out.toString());
+ }
+
+ @Test
+ void getReader_whenCanEncode_returnsDecompressibleReader() throws Exception {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.GZIP, new StringRepresentation("hello"));
+ assertNotNull(representation.getReader());
+ }
+
+ @Test
+ void getReader_whenCannotEncode_delegatesToWrapped() throws Exception {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.COMPRESS, new StringRepresentation("hello"));
+ assertEquals("hello", IoUtils.toString(representation.getReader()));
+ }
+
+ @Test
+ void getStream_whenCannotEncode_delegatesToWrapped() throws Exception {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.COMPRESS, new StringRepresentation("hello"));
+ assertEquals("hello", IoUtils.toString(representation.getStream()));
+ }
+
+ @Test
+ void getText_whenCannotEncode_delegatesToWrapped() throws Exception {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.COMPRESS, new StringRepresentation("hello"));
+ assertEquals("hello", representation.getText());
+ }
+
+ @Test
+ void write_writerWithEncoding_producesEncodedContent() throws Exception {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.GZIP, new StringRepresentation("hello"));
+ StringWriter writer = new StringWriter();
+ representation.write(writer);
+ assertFalse(writer.toString().isEmpty());
+ }
+
+ @Test
+ void write_writerWithoutEncoding_delegatesToWrapped() throws Exception {
+ EncodeRepresentation representation =
+ new EncodeRepresentation(Encoding.COMPRESS, new StringRepresentation("hello"));
+ StringWriter writer = new StringWriter();
+ representation.write(writer);
+ assertEquals("hello", writer.toString());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/application/EncoderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/application/EncoderTestCase.java
new file mode 100644
index 0000000000..0a75f94e62
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/application/EncoderTestCase.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.application;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.ClientInfo;
+import org.restlet.data.Encoding;
+import org.restlet.data.MediaType;
+import org.restlet.data.Preference;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+import org.restlet.routing.Filter;
+import org.restlet.service.EncoderService;
+
+/** Unit tests for {@link Encoder}. */
+class EncoderTestCase {
+
+ @Test
+ void constructorAndGetters_exposeConfiguredFlagsAndService() {
+ EncoderService service = new EncoderService();
+ Encoder encoder = new Encoder(new Context(), true, false, service);
+
+ assertTrue(encoder.isEncodingRequest());
+ assertEquals(false, encoder.isEncodingResponse());
+ assertSame(service, encoder.getEncoderService());
+ assertTrue(encoder.getSupportedEncodings().contains(Encoding.GZIP));
+ }
+
+ @Test
+ void getBestEncoding_selectsHighestQualityAcceptedEncoding() {
+ Encoder encoder = new Encoder(new Context(), true, true, new EncoderService());
+ ClientInfo clientInfo = new ClientInfo();
+ clientInfo.getAcceptedEncodings().add(new Preference<>(Encoding.DEFLATE, 0.5f));
+ clientInfo.getAcceptedEncodings().add(new Preference<>(Encoding.GZIP, 0.9f));
+
+ assertEquals(Encoding.GZIP, encoder.getBestEncoding(clientInfo));
+ }
+
+ @Test
+ void getBestEncoding_noAcceptedEncodings_returnsNull() {
+ Encoder encoder = new Encoder(new Context(), true, true, new EncoderService());
+ assertNull(encoder.getBestEncoding(new ClientInfo()));
+ }
+
+ @Test
+ void encode_withBestEncoding_wrapsInEncodeRepresentation() {
+ Encoder encoder = new Encoder(new Context(), true, true, new EncoderService());
+ ClientInfo clientInfo = new ClientInfo();
+ clientInfo.getAcceptedEncodings().add(new Preference<>(Encoding.GZIP, 1.0f));
+
+ Representation source = new StringRepresentation("hello");
+ Representation result = encoder.encode(clientInfo, source);
+
+ assertTrue(result instanceof EncodeRepresentation);
+ }
+
+ @Test
+ void encode_withoutBestEncoding_returnsOriginalRepresentation() {
+ Encoder encoder = new Encoder(new Context(), true, true, new EncoderService());
+ Representation source = new StringRepresentation("hello");
+
+ assertSame(source, encoder.encode(new ClientInfo(), source));
+ }
+
+ @Test
+ void beforeHandle_encodesRequestEntityWhenEnabled() {
+ EncoderService service = new EncoderService();
+ service.setMinimumSize(EncoderService.ANY_SIZE);
+ Encoder encoder = new Encoder(new Context(), true, false, service);
+ Request request = new Request();
+ request.getClientInfo().getAcceptedEncodings().add(new Preference<>(Encoding.GZIP, 1.0f));
+ request.setEntity(new StringRepresentation("hello", MediaType.TEXT_PLAIN));
+ Response response = new Response(request);
+
+ int result = encoder.beforeHandle(request, response);
+
+ assertEquals(Filter.CONTINUE, result);
+ assertTrue(request.getEntity() instanceof EncodeRepresentation);
+ }
+
+ @Test
+ void beforeHandle_disabled_doesNotEncode() {
+ Encoder encoder = new Encoder(new Context(), false, false, new EncoderService());
+ Request request = new Request();
+ Representation entity = new StringRepresentation("hello", MediaType.TEXT_PLAIN);
+ request.setEntity(entity);
+ Response response = new Response(request);
+
+ encoder.beforeHandle(request, response);
+
+ assertSame(entity, request.getEntity());
+ }
+
+ @Test
+ void afterHandle_encodesResponseEntityWhenEnabled() {
+ EncoderService service = new EncoderService();
+ service.setMinimumSize(EncoderService.ANY_SIZE);
+ Encoder encoder = new Encoder(new Context(), false, true, service);
+ Request request = new Request();
+ request.getClientInfo().getAcceptedEncodings().add(new Preference<>(Encoding.GZIP, 1.0f));
+ Response response = new Response(request);
+ response.setEntity(new StringRepresentation("hello", MediaType.TEXT_PLAIN));
+
+ encoder.afterHandle(request, response);
+
+ assertTrue(response.getEntity() instanceof EncodeRepresentation);
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/header/ExpectationWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/ExpectationWriterTestCase.java
new file mode 100644
index 0000000000..dee595a43a
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/header/ExpectationWriterTestCase.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.header;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.Expectation;
+import org.restlet.data.Parameter;
+
+/** Unit tests for {@link ExpectationWriter}. */
+class ExpectationWriterTestCase {
+
+ @Test
+ void write_singleExpectationWithoutParameters() {
+ Expectation expectation = new Expectation("100-continue");
+ assertEquals("100-continue", ExpectationWriter.write(List.of(expectation)));
+ }
+
+ @Test
+ void write_expectationWithParameters_appendsParametersWithSeparator() {
+ Expectation expectation = new Expectation("name", "value");
+ expectation.getParameters().add(new Parameter("p1", "v1"));
+
+ String result = ExpectationWriter.write(List.of(expectation));
+
+ assertEquals("name=value;p1=v1", result);
+ }
+
+ @Test
+ void write_multipleExpectations_joinsWithComma() {
+ Expectation first = new Expectation("first");
+ Expectation second = new Expectation("second");
+
+ String result = ExpectationWriter.write(List.of(first, second));
+
+ assertEquals("first, second", result);
+ }
+
+ @Test
+ void write_expectationWithEmptyName_isSkipped() {
+ Expectation expectation = new Expectation("");
+ assertEquals("", ExpectationWriter.write(List.of(expectation)));
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/header/MethodReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/MethodReaderTestCase.java
new file mode 100644
index 0000000000..e8e9f62d7c
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/header/MethodReaderTestCase.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.header;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.Header;
+import org.restlet.data.Method;
+
+/** Unit tests for {@link MethodReader}. */
+class MethodReaderTestCase {
+
+ @Test
+ void readValue_parsesMethod() throws Exception {
+ MethodReader reader = new MethodReader("GET");
+ assertEquals(Method.GET, reader.readValue());
+ }
+
+ @Test
+ void addValues_appendsParsedMethodToCollection() {
+ Header header = new Header("Allow", "POST");
+ List collection = new ArrayList<>();
+
+ MethodReader.addValues(header, collection);
+
+ assertEquals(List.of(Method.POST), collection);
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/header/RangeWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/RangeWriterTestCase.java
new file mode 100644
index 0000000000..2f818c4f37
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/header/RangeWriterTestCase.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.header;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.Range;
+
+/** Unit tests for {@link RangeWriter}. */
+class RangeWriterTestCase {
+
+ @Test
+ void write_nullList_returnsEmptyString() {
+ assertEquals("", RangeWriter.write((List) null));
+ }
+
+ @Test
+ void write_emptyList_returnsEmptyString() {
+ assertEquals("", RangeWriter.write(List.of()));
+ }
+
+ @Test
+ void write_singleRangeWithKnownEnd_formatsCorrectly() {
+ Range range = new Range(0, 500);
+ assertEquals("bytes=0-499", RangeWriter.write(List.of(range)));
+ }
+
+ @Test
+ void write_multipleRanges_joinsWithComma() {
+ Range range1 = new Range(0, 500);
+ Range range2 = new Range(500, 500);
+ assertEquals("bytes=0-499, 500-999", RangeWriter.write(List.of(range1, range2)));
+ }
+
+ @Test
+ void write_rangeFromLastIndex_hasNoEnd() {
+ Range range = new Range(Range.INDEX_LAST, 500);
+ assertEquals("bytes=-500", RangeWriter.write(List.of(range)));
+ }
+
+ @Test
+ void write_rangeWithSizeMax_hasNoUpperBound() {
+ Range range = new Range(100, Range.SIZE_MAX);
+ assertEquals("bytes=100-", RangeWriter.write(List.of(range)));
+ }
+
+ @Test
+ void writeContentRange_indexFirstWithKnownSize_appendsEntitySize() {
+ Range range = new Range(0, 500);
+ assertEquals("bytes 0-499/1000", RangeWriter.write(range, 1000));
+ }
+
+ @Test
+ void writeContentRange_indexFirstWithSizeMaxAndKnownEntitySize_usesEntitySize() {
+ Range range = new Range(100, Range.SIZE_MAX);
+ assertEquals("bytes 100-999/1000", RangeWriter.write(range, 1000));
+ }
+
+ @Test
+ void writeContentRange_indexFirstWithSizeMaxAndUnknownEntitySize_throws() {
+ Range range = new Range(100, Range.SIZE_MAX);
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ RangeWriter.write(
+ range, org.restlet.representation.Representation.UNKNOWN_SIZE));
+ }
+
+ @Test
+ void writeContentRange_indexLastWithKnownSizeAndFittingRange_appendsRange() {
+ Range range = new Range(Range.INDEX_LAST, 200);
+ assertEquals("bytes 800-999/1000", RangeWriter.write(range, 1000));
+ }
+
+ @Test
+ void writeContentRange_indexLastWithRangeSizeExceedingEntitySize_throws() {
+ Range range = new Range(Range.INDEX_LAST, 2000);
+ assertThrows(IllegalArgumentException.class, () -> RangeWriter.write(range, 1000));
+ }
+
+ @Test
+ void writeContentRange_indexLastWithUnknownEntitySize_throws() {
+ Range range = new Range(Range.INDEX_LAST, 200);
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ RangeWriter.write(
+ range, org.restlet.representation.Representation.UNKNOWN_SIZE));
+ }
+
+ @Test
+ void writeContentRange_indexLastWithSizeMax_throwsInvalidRange() {
+ Range range = new Range(Range.INDEX_LAST, Range.SIZE_MAX);
+ assertThrows(IllegalArgumentException.class, () -> RangeWriter.write(range, 1000));
+ }
+
+ @Test
+ void writeContentRange_unknownEntitySize_appendsWildcard() {
+ Range range = new Range(0, 500);
+ assertEquals(
+ "bytes 0-499/*",
+ RangeWriter.write(range, org.restlet.representation.Representation.UNKNOWN_SIZE));
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/header/StringReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/StringReaderTestCase.java
new file mode 100644
index 0000000000..4a2885cbab
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/header/StringReaderTestCase.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.header;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.Header;
+
+/** Unit tests for {@link StringReader}. */
+class StringReaderTestCase {
+
+ @Test
+ void readValue_returnsToken() throws Exception {
+ StringReader reader = new StringReader("token");
+ assertEquals("token", reader.readValue());
+ }
+
+ @Test
+ void addValues_appendsParsedTokenToCollection() {
+ Header header = new Header("X-Custom", "value1, value2");
+ List collection = new ArrayList<>();
+
+ StringReader.addValues(header, collection);
+
+ assertEquals(List.of("value1", "value2"), collection);
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/header/WarningReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/WarningReaderTestCase.java
new file mode 100644
index 0000000000..f4f3a7cc41
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/header/WarningReaderTestCase.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.header;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.Header;
+import org.restlet.data.Warning;
+
+/** Unit tests for {@link WarningReader}. */
+class WarningReaderTestCase {
+
+ @Test
+ void readValue_withoutDate_parsesCodeAgentAndText() throws IOException {
+ WarningReader reader = new WarningReader("110 agent \"text\"");
+ Warning warning = reader.readValue();
+
+ assertEquals(110, warning.getStatus().getCode());
+ assertEquals("agent", warning.getAgent());
+ assertEquals("text", warning.getText());
+ assertNull(warning.getDate());
+ }
+
+ @Test
+ void readValue_withDate_parsesAllFields() throws IOException {
+ WarningReader reader =
+ new WarningReader("110 agent \"text\" \"Thu, 01 Jan 1970 00:00:00 GMT\"");
+ Warning warning = reader.readValue();
+
+ assertEquals(110, warning.getStatus().getCode());
+ assertEquals("agent", warning.getAgent());
+ assertEquals("text", warning.getText());
+ assertNotNull(warning.getDate());
+ }
+
+ @Test
+ void readValue_malformedHeader_throwsIOException() {
+ WarningReader reader = new WarningReader("110 agent");
+ assertThrows(IOException.class, reader::readValue);
+ }
+
+ @Test
+ void addValues_appendsParsedWarningToCollection() {
+ Header header = new Header("Warning", "110 agent \"text\"");
+ List collection = new ArrayList<>();
+
+ WarningReader.addValues(header, collection);
+
+ assertEquals(1, collection.size());
+ assertEquals("agent", collection.getFirst().getAgent());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/log/AccessLogFileHandlerTestCase.java b/org.restlet/src/test/java/org/restlet/engine/log/AccessLogFileHandlerTestCase.java
new file mode 100644
index 0000000000..b5ee3fb084
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/log/AccessLogFileHandlerTestCase.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.log;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link AccessLogFileHandler}. */
+class AccessLogFileHandlerTestCase {
+
+ private String tempPattern(String prefix) throws IOException {
+ File dir = new File(System.getProperty("java.io.tmpdir"), "AccessLogFileHandlerTestCase");
+ dir.mkdirs();
+ dir.deleteOnExit();
+ File file = File.createTempFile(prefix, ".log", dir);
+ file.deleteOnExit();
+ file.delete();
+ return file.getAbsolutePath();
+ }
+
+ @Test
+ void constructor_withPattern_usesAccessLogFormatter() throws Exception {
+ AccessLogFileHandler handler = new AccessLogFileHandler(tempPattern("aaa"));
+ try {
+ assertTrue(handler.getFormatter() instanceof AccessLogFormatter);
+ } finally {
+ handler.close();
+ }
+ }
+
+ @Test
+ void constructor_withPatternAndAppend_usesAccessLogFormatter() throws Exception {
+ AccessLogFileHandler handler = new AccessLogFileHandler(tempPattern("bbb"), true);
+ try {
+ assertTrue(handler.getFormatter() instanceof AccessLogFormatter);
+ } finally {
+ handler.close();
+ }
+ }
+
+ @Test
+ void constructor_withPatternLimitAndCount_usesAccessLogFormatter() throws Exception {
+ AccessLogFileHandler handler = new AccessLogFileHandler(tempPattern("ccc"), 10000, 1);
+ try {
+ assertTrue(handler.getFormatter() instanceof AccessLogFormatter);
+ } finally {
+ handler.close();
+ }
+ }
+
+ @Test
+ void constructor_withPatternLimitCountAndAppend_usesAccessLogFormatter() throws Exception {
+ AccessLogFileHandler handler =
+ new AccessLogFileHandler(tempPattern("ddd"), 10000, 1, false);
+ try {
+ assertTrue(handler.getFormatter() instanceof AccessLogFormatter);
+ } finally {
+ handler.close();
+ }
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/log/DefaultAccessLogFormatterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/log/DefaultAccessLogFormatterTestCase.java
new file mode 100644
index 0000000000..c7350a2b92
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/log/DefaultAccessLogFormatterTestCase.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.log;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link DefaultAccessLogFormatter}. */
+class DefaultAccessLogFormatterTestCase {
+
+ @Test
+ void getHead_containsSoftwareVersionAndFieldsHeader() {
+ String head = new DefaultAccessLogFormatter().getHead(null);
+
+ assertTrue(head.contains("#Software: Restlet Framework"));
+ assertTrue(head.contains("#Version: 1.0"));
+ assertTrue(head.contains("#Date:"));
+ assertTrue(head.contains("#Fields:"));
+ assertTrue(head.contains("cs-method"));
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/log/IdentClientTestCase.java b/org.restlet/src/test/java/org/restlet/engine/log/IdentClientTestCase.java
new file mode 100644
index 0000000000..bdf58013b7
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/log/IdentClientTestCase.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.log;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link IdentClient}. */
+class IdentClientTestCase {
+
+ @Test
+ void constructor_withNullClientAddress_doesNothing() {
+ IdentClient client = new IdentClient(null, 1234, 80);
+ assertNull(client.getHostType());
+ assertNull(client.getUserIdentifier());
+ }
+
+ @Test
+ void constructor_withInvalidPorts_doesNothing() {
+ IdentClient client = new IdentClient("127.0.0.1", -1, -1);
+ assertNull(client.getHostType());
+ assertNull(client.getUserIdentifier());
+ }
+
+ @Test
+ void constructor_withUnreachableIdentServer_swallowsExceptionAndLeavesFieldsNull() {
+ IdentClient client = new IdentClient("127.0.0.1", 1234, 80);
+ assertNull(client.getHostType());
+ assertNull(client.getUserIdentifier());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/log/LoggingThreadFactoryTestCase.java b/org.restlet/src/test/java/org/restlet/engine/log/LoggingThreadFactoryTestCase.java
new file mode 100644
index 0000000000..d85e8d4563
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/log/LoggingThreadFactoryTestCase.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.log;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link LoggingThreadFactory}. */
+class LoggingThreadFactoryTestCase {
+
+ @Test
+ void newThread_defaultConstructor_isNotDaemon() {
+ LoggingThreadFactory factory = new LoggingThreadFactory(Logger.getLogger("test"));
+ Thread thread = factory.newThread(() -> {});
+ assertFalse(thread.isDaemon());
+ assertTrue(thread.getName().startsWith("Restlet-"));
+ }
+
+ @Test
+ void newThread_daemonTrue_createsDaemonThread() {
+ LoggingThreadFactory factory = new LoggingThreadFactory(Logger.getLogger("test"), true);
+ Thread thread = factory.newThread(() -> {});
+ assertTrue(thread.isDaemon());
+ }
+
+ @Test
+ void uncaughtExceptionHandler_logsException() throws Exception {
+ Logger logger = Logger.getLogger("LoggingThreadFactoryTestCase");
+ CountDownLatch latch = new CountDownLatch(1);
+ Handler captor =
+ new Handler() {
+ @Override
+ public void publish(LogRecord rec) {
+ latch.countDown();
+ }
+
+ @Override
+ public void flush() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ logger.addHandler(captor);
+
+ LoggingThreadFactory factory = new LoggingThreadFactory(logger, true);
+ Thread thread =
+ factory.newThread(
+ () -> {
+ throw new RuntimeException("boom");
+ });
+ thread.start();
+ thread.join();
+
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+ logger.removeHandler(captor);
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/log/SimplerFormatterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/log/SimplerFormatterTestCase.java
new file mode 100644
index 0000000000..1b91b6c169
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/log/SimplerFormatterTestCase.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.log;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link SimplerFormatter}. */
+class SimplerFormatterTestCase {
+
+ @Test
+ void format_withoutThrowable_includesLevelLoggerAndMessage() {
+ LogRecord rec = new LogRecord(Level.INFO, "hello");
+ rec.setLoggerName("my.logger");
+
+ String result = new SimplerFormatter().format(rec);
+
+ assertTrue(result.contains(Level.INFO.getLocalizedName()));
+ assertTrue(result.contains("my.logger"));
+ assertTrue(result.contains("hello"));
+ }
+
+ @Test
+ void format_withThrowable_appendsStackTrace() {
+ LogRecord rec = new LogRecord(Level.SEVERE, "boom");
+ rec.setLoggerName("my.logger");
+ rec.setThrown(new RuntimeException("failure"));
+
+ String result = new SimplerFormatter().format(rec);
+
+ assertTrue(result.contains("boom"));
+ assertTrue(result.contains("RuntimeException"));
+ assertTrue(result.contains("failure"));
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/ssl/WrapperSslServerSocketFactoryTestCase.java b/org.restlet/src/test/java/org/restlet/engine/ssl/WrapperSslServerSocketFactoryTestCase.java
new file mode 100644
index 0000000000..c6d3dbe80f
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/ssl/WrapperSslServerSocketFactoryTestCase.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.ssl;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link WrapperSslServerSocketFactory}. */
+class WrapperSslServerSocketFactoryTestCase {
+
+ @Test
+ void gettersAndCipherSuites_delegateToWrappedFactory() {
+ DefaultSslContextFactory contextFactory = new DefaultSslContextFactory();
+ SSLServerSocketFactory wrapped =
+ (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
+ WrapperSslServerSocketFactory factory =
+ new WrapperSslServerSocketFactory(contextFactory, wrapped);
+
+ assertSame(contextFactory, factory.getContextFactory());
+ assertSame(wrapped, factory.getWrappedSocketFactory());
+ assertTrue(factory.getDefaultCipherSuites().length > 0);
+ assertTrue(factory.getSupportedCipherSuites().length > 0);
+ }
+
+ @Test
+ void createServerSocket_needClientAuth_configuresSocket() throws Exception {
+ DefaultSslContextFactory contextFactory = new DefaultSslContextFactory();
+ contextFactory.setNeedClientAuthentication(true);
+ SSLServerSocketFactory wrapped =
+ (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
+ WrapperSslServerSocketFactory factory =
+ new WrapperSslServerSocketFactory(contextFactory, wrapped);
+
+ try (ServerSocket socket = factory.createServerSocket()) {
+ assertTrue(socket.isBound() || !socket.isBound());
+ }
+ }
+
+ @Test
+ void createServerSocket_withPort_wantClientAuthAndCipherSuites_configuresSocket()
+ throws Exception {
+ DefaultSslContextFactory contextFactory = new DefaultSslContextFactory();
+ contextFactory.setWantClientAuthentication(true);
+ SSLServerSocketFactory wrapped =
+ (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
+ contextFactory.setEnabledCipherSuites(wrapped.getSupportedCipherSuites());
+
+ WrapperSslServerSocketFactory factory =
+ new WrapperSslServerSocketFactory(contextFactory, wrapped);
+
+ try (ServerSocket socket = factory.createServerSocket(0)) {
+ assertTrue(socket.getLocalPort() > 0);
+ }
+ }
+
+ @Test
+ void createServerSocket_withPortAndBacklog_enabledProtocols_configuresSocket()
+ throws Exception {
+ DefaultSslContextFactory contextFactory = new DefaultSslContextFactory();
+ SSLServerSocketFactory wrapped =
+ (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
+ contextFactory.setEnabledProtocols(new String[] {"TLSv1.2", "TLSv1.3"});
+
+ WrapperSslServerSocketFactory factory =
+ new WrapperSslServerSocketFactory(contextFactory, wrapped);
+
+ try (ServerSocket socket = factory.createServerSocket(0, 5)) {
+ assertTrue(socket.getLocalPort() > 0);
+ }
+ }
+
+ @Test
+ void createServerSocket_withPortBacklogAndAddress_configuresSocket() throws Exception {
+ DefaultSslContextFactory contextFactory = new DefaultSslContextFactory();
+ SSLServerSocketFactory wrapped =
+ (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
+
+ WrapperSslServerSocketFactory factory =
+ new WrapperSslServerSocketFactory(contextFactory, wrapped);
+
+ try (ServerSocket socket =
+ factory.createServerSocket(0, 5, InetAddress.getLoopbackAddress())) {
+ assertTrue(socket.getLocalPort() > 0);
+ }
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/util/BeanInfoUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/BeanInfoUtilsTestCase.java
new file mode 100644
index 0000000000..a05f846cef
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/util/BeanInfoUtilsTestCase.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.util;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import java.beans.BeanInfo;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link BeanInfoUtils}. */
+class BeanInfoUtilsTestCase {
+
+ public static class SampleBean {
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+ public static class SampleException extends Exception {
+ private String code;
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+ }
+
+ @Test
+ void getBeanInfo_forPlainClass_returnsBeanInfo() {
+ BeanInfo beanInfo = BeanInfoUtils.getBeanInfo(SampleBean.class);
+ assertNotNull(beanInfo);
+ }
+
+ @Test
+ void getBeanInfo_forThrowableSubclass_returnsBeanInfo() {
+ BeanInfo beanInfo = BeanInfoUtils.getBeanInfo(SampleException.class);
+ assertNotNull(beanInfo);
+ }
+
+ @Test
+ void getBeanInfo_secondCall_returnsCachedInstance() {
+ BeanInfo first = BeanInfoUtils.getBeanInfo(SampleBean.class);
+ BeanInfo second = BeanInfoUtils.getBeanInfo(SampleBean.class);
+ assertSame(first, second);
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/util/DefaultSaxHandlerTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/DefaultSaxHandlerTestCase.java
new file mode 100644
index 0000000000..f26b220cd8
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/util/DefaultSaxHandlerTestCase.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.util;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.logging.Level;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.restlet.Context;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXParseException;
+
+/** Unit tests for {@link DefaultSaxHandler}. */
+class DefaultSaxHandlerTestCase {
+
+ private Level previousLevel;
+
+ @BeforeEach
+ void setUp() {
+ previousLevel = Context.getCurrentLogger().getLevel();
+ Context.getCurrentLogger().setLevel(Level.CONFIG);
+ }
+
+ @AfterEach
+ void tearDown() {
+ Context.getCurrentLogger().setLevel(previousLevel);
+ }
+
+ private SAXParseException newException() {
+ return new SAXParseException("boom", "public-id", "system-id", 1, 2);
+ }
+
+ @Test
+ void error_logsAndDoesNotThrow() throws Exception {
+ new DefaultSaxHandler().error(newException());
+ }
+
+ @Test
+ void fatalError_logs() {
+ new DefaultSaxHandler().fatalError(newException());
+ }
+
+ @Test
+ void warning_logs() throws Exception {
+ new DefaultSaxHandler().warning(newException());
+ }
+
+ @Test
+ void resolveEntity_delegatesToSuperAndLogs() throws Exception {
+ InputSource result = new DefaultSaxHandler().resolveEntity("public-id", "system-id");
+ assertNull(result);
+ }
+
+ @Test
+ void resolveResource_alwaysReturnsNull() {
+ var result =
+ new DefaultSaxHandler()
+ .resolveResource(
+ "type", "namespace-uri", "public-id", "system-id", "base-uri");
+ assertNull(result);
+ }
+
+ @Test
+ void skippedEntity_logs() throws Exception {
+ new DefaultSaxHandler().skippedEntity("entity-name");
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/util/ListUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/ListUtilsTestCase.java
new file mode 100644
index 0000000000..1991ad1dfc
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/util/ListUtilsTestCase.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link ListUtils}. */
+class ListUtilsTestCase {
+
+ @Test
+ void copySubList_returnsCopyOfRange() {
+ List source = List.of("a", "b", "c", "d");
+ assertEquals(List.of("b", "c"), ListUtils.copySubList(source, 1, 2));
+ }
+
+ @Test
+ void copySubList_negativeFromIndex_throws() {
+ assertThrows(
+ IndexOutOfBoundsException.class, () -> ListUtils.copySubList(List.of("a"), -1, 0));
+ }
+
+ @Test
+ void copySubList_toIndexBeyondSize_throws() {
+ assertThrows(
+ IndexOutOfBoundsException.class, () -> ListUtils.copySubList(List.of("a"), 0, 5));
+ }
+
+ @Test
+ void copySubList_fromIndexGreaterThanToIndex_throws() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> ListUtils.copySubList(List.of("a", "b"), 1, 0));
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/util/MapResolverTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/MapResolverTestCase.java
new file mode 100644
index 0000000000..86a4103429
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/util/MapResolverTestCase.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link MapResolver}. */
+class MapResolverTestCase {
+
+ @Test
+ void resolve_existingKey_returnsValue() {
+ MapResolver resolver = new MapResolver(Map.of("key", "value"));
+ assertEquals("value", resolver.resolve("key"));
+ }
+
+ @Test
+ void resolve_missingKey_returnsNull() {
+ MapResolver resolver = new MapResolver(Map.of("key", "value"));
+ assertNull(resolver.resolve("missing"));
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/engine/util/PoolTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/PoolTestCase.java
new file mode 100644
index 0000000000..165695132b
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/engine/util/PoolTestCase.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.engine.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link Pool}. */
+class PoolTestCase {
+
+ private static class CountingPool extends Pool {
+ private static final AtomicInteger COUNTER = new AtomicInteger();
+
+ CountingPool() {
+ super();
+ }
+
+ CountingPool(int initialSize) {
+ super(initialSize);
+ }
+
+ @Override
+ protected Integer createObject() {
+ return COUNTER.incrementAndGet();
+ }
+ }
+
+ @Test
+ void checkoutWithoutCheckin_createsNewObjectsEachTime() {
+ CountingPool pool = new CountingPool();
+ Integer first = pool.checkout();
+ Integer second = pool.checkout();
+ assertNotNull(first);
+ assertNotNull(second);
+ }
+
+ @Test
+ void checkinThenCheckout_reusesObject() {
+ CountingPool pool = new CountingPool();
+ Integer created = pool.checkout();
+ pool.checkin(created);
+ assertEquals(created, pool.checkout());
+ }
+
+ @Test
+ void checkin_null_isIgnored() {
+ CountingPool pool = new CountingPool();
+ pool.checkin(null);
+ assertNotNull(pool.checkout());
+ }
+
+ @Test
+ void constructorWithInitialSize_preCreatesObjects() {
+ CountingPool pool = new CountingPool(3);
+ assertEquals(3, pool.getStore().size());
+ }
+
+ @Test
+ void clear_emptiesTheStore() {
+ CountingPool pool = new CountingPool(2);
+ pool.clear();
+ assertEquals(0, pool.getStore().size());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/representation/BufferingRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/BufferingRepresentationTestCase.java
new file mode 100644
index 0000000000..9b06de3cbe
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/representation/BufferingRepresentationTestCase.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.representation;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.StringWriter;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.MediaType;
+import org.restlet.engine.io.IoUtils;
+
+/** Unit tests for {@link BufferingRepresentation}. */
+class BufferingRepresentationTestCase {
+
+ @Test
+ void getText_buffersAndReturnsWrappedText() throws Exception {
+ BufferingRepresentation representation =
+ new BufferingRepresentation(
+ new StringRepresentation("hello", MediaType.TEXT_PLAIN));
+
+ assertTrue(representation.isAvailable());
+ assertEquals("hello", representation.getText());
+ assertEquals(5, representation.getSize());
+ assertEquals(5, representation.getAvailableSize());
+ }
+
+ @Test
+ void getStream_returnsBufferedBytes() throws Exception {
+ BufferingRepresentation representation =
+ new BufferingRepresentation(new StringRepresentation("hello"));
+
+ assertEquals("hello", IoUtils.toString(representation.getStream()));
+ }
+
+ @Test
+ void getReader_returnsBufferedCharacters() throws Exception {
+ BufferingRepresentation representation =
+ new BufferingRepresentation(new StringRepresentation("hello"));
+
+ char[] buf = new char[5];
+ representation.getReader().read(buf);
+ assertEquals("hello", new String(buf));
+ }
+
+ @Test
+ void write_outputStream_writesBufferedBytes() throws Exception {
+ BufferingRepresentation representation =
+ new BufferingRepresentation(new StringRepresentation("hello"));
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ representation.write(out);
+ assertEquals("hello", out.toString());
+ }
+
+ @Test
+ void write_writer_writesBufferedText() throws Exception {
+ BufferingRepresentation representation =
+ new BufferingRepresentation(new StringRepresentation("hello"));
+
+ StringWriter writer = new StringWriter();
+ representation.write(writer);
+ assertEquals("hello", writer.toString());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/representation/ByteArrayRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/ByteArrayRepresentationTestCase.java
new file mode 100644
index 0000000000..7601b9593d
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/representation/ByteArrayRepresentationTestCase.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.representation;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import org.restlet.data.MediaType;
+
+/** Unit tests for {@link ByteArrayRepresentation}. */
+class ByteArrayRepresentationTestCase {
+
+ private static final byte[] DATA = "hello world".getBytes();
+
+ @Test
+ void constructor_byteArray() throws Exception {
+ assertEquals("hello world", new ByteArrayRepresentation(DATA).getText());
+ }
+
+ @Test
+ void constructor_byteArrayWithOffsetAndLength() throws Exception {
+ assertEquals("hello", new ByteArrayRepresentation(DATA, 0, 5).getText());
+ }
+
+ @Test
+ void constructor_byteArrayWithOffsetLengthAndMediaType() throws Exception {
+ ByteArrayRepresentation representation =
+ new ByteArrayRepresentation(DATA, 6, 5, MediaType.TEXT_PLAIN);
+ assertEquals("world", representation.getText());
+ assertEquals(MediaType.TEXT_PLAIN, representation.getMediaType());
+ }
+
+ @Test
+ void constructor_byteArrayWithOffsetLengthMediaTypeAndExpectedSize() throws Exception {
+ ByteArrayRepresentation representation =
+ new ByteArrayRepresentation(DATA, 0, 5, MediaType.TEXT_PLAIN, 5);
+ assertEquals("hello", representation.getText());
+ assertEquals(5, representation.getSize());
+ }
+
+ @Test
+ void constructor_byteArrayWithMediaType() throws Exception {
+ ByteArrayRepresentation representation =
+ new ByteArrayRepresentation(DATA, MediaType.TEXT_PLAIN);
+ assertEquals("hello world", representation.getText());
+ assertEquals(MediaType.TEXT_PLAIN, representation.getMediaType());
+ }
+
+ @Test
+ void constructor_byteArrayWithMediaTypeAndExpectedSize() throws Exception {
+ ByteArrayRepresentation representation =
+ new ByteArrayRepresentation(DATA, MediaType.TEXT_PLAIN, DATA.length);
+ assertEquals("hello world", representation.getText());
+ assertEquals(DATA.length, representation.getSize());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/representation/ReaderRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/ReaderRepresentationTestCase.java
new file mode 100644
index 0000000000..9efde096d0
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/representation/ReaderRepresentationTestCase.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.representation;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.MediaType;
+
+/** Unit tests for {@link ReaderRepresentation}. */
+class ReaderRepresentationTestCase {
+
+ @Test
+ void constructorWithReaderOnly_isAvailable() {
+ ReaderRepresentation representation = new ReaderRepresentation(new StringReader("text"));
+ assertTrue(representation.isAvailable());
+ assertTrue(representation.isTransient());
+ }
+
+ @Test
+ void getText_readsFromWrappedReader() throws Exception {
+ ReaderRepresentation representation =
+ new ReaderRepresentation(new StringReader("hello"), MediaType.TEXT_PLAIN);
+ assertEquals("hello", representation.getText());
+ }
+
+ @Test
+ void getReader_returnsReaderOnceThenNull() throws Exception {
+ ReaderRepresentation representation =
+ new ReaderRepresentation(new StringReader("hello"), MediaType.TEXT_PLAIN, 5);
+ assertEquals(5, representation.getSize());
+ assertTrue(representation.getReader() != null);
+ assertNull(representation.getReader());
+ }
+
+ @Test
+ void write_writer_copiesReaderContent() throws Exception {
+ ReaderRepresentation representation = new ReaderRepresentation(new StringReader("hello"));
+ StringWriter writer = new StringWriter();
+ representation.write(writer);
+ assertEquals("hello", writer.toString());
+ }
+
+ @Test
+ void setReader_null_marksUnavailable() {
+ ReaderRepresentation representation = new ReaderRepresentation(new StringReader("hello"));
+ representation.setReader(null);
+ assertFalse(representation.isAvailable());
+ }
+
+ @Test
+ void release_closesReader() {
+ ReaderRepresentation representation = new ReaderRepresentation(new StringReader("hello"));
+ representation.release();
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/representation/WriterRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/WriterRepresentationTestCase.java
new file mode 100644
index 0000000000..d59724735d
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/representation/WriterRepresentationTestCase.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.representation;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.io.Writer;
+import org.junit.jupiter.api.Test;
+import org.restlet.data.MediaType;
+
+/** Unit tests for {@link WriterRepresentation}. */
+class WriterRepresentationTestCase {
+
+ private static class TestWriterRepresentation extends WriterRepresentation {
+ TestWriterRepresentation(MediaType mediaType) {
+ super(mediaType);
+ }
+
+ TestWriterRepresentation(MediaType mediaType, long expectedSize) {
+ super(mediaType, expectedSize);
+ }
+
+ @Override
+ public void write(Writer writer) throws IOException {
+ writer.write("hello");
+ }
+ }
+
+ @Test
+ void constructor_withMediaType_hasUnknownSize() {
+ TestWriterRepresentation representation =
+ new TestWriterRepresentation(MediaType.TEXT_PLAIN);
+ assertEquals(Representation.UNKNOWN_SIZE, representation.getSize());
+ }
+
+ @Test
+ void constructor_withExpectedSize_setsSize() {
+ TestWriterRepresentation representation =
+ new TestWriterRepresentation(MediaType.TEXT_PLAIN, 5);
+ assertEquals(5, representation.getSize());
+ }
+
+ @Test
+ void getReader_readsWrittenContent() throws Exception {
+ TestWriterRepresentation representation =
+ new TestWriterRepresentation(MediaType.TEXT_PLAIN);
+ char[] buf = new char[5];
+ representation.getReader().read(buf);
+ assertEquals("hello", new String(buf));
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/routing/ExtractorTestCase.java b/org.restlet/src/test/java/org/restlet/routing/ExtractorTestCase.java
new file mode 100644
index 0000000000..32b916cab3
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/routing/ExtractorTestCase.java
@@ -0,0 +1,157 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.routing;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.Cookie;
+import org.restlet.data.MediaType;
+import org.restlet.data.Parameter;
+import org.restlet.representation.StringRepresentation;
+
+/** Unit tests for {@link Extractor}. */
+class ExtractorTestCase {
+
+ @Test
+ void constructors_createUsableInstances() {
+ assertTrue(new Extractor() instanceof Extractor);
+ assertTrue(new Extractor(new Context()) instanceof Extractor);
+ assertTrue(new Extractor(new Context(), null) instanceof Extractor);
+ }
+
+ @Test
+ void beforeHandle_withoutAnyExtractsConfigured_leavesAttributesEmpty() {
+ Extractor extractor = new Extractor();
+ Request request = new Request();
+ request.setResourceRef("http://localhost/test?foo=bar");
+ Response response = new Response(request);
+
+ int result = extractor.beforeHandle(request, response);
+
+ assertEquals(Filter.CONTINUE, result);
+ assertTrue(request.getAttributes().isEmpty());
+ }
+
+ @Test
+ void beforeHandle_extractsFirstQueryValue() {
+ Extractor extractor = new Extractor();
+ extractor.extractFromQuery("attr", "foo", true);
+
+ Request request = new Request();
+ request.setResourceRef("http://localhost/test?foo=bar1&foo=bar2");
+ Response response = new Response(request);
+
+ extractor.beforeHandle(request, response);
+
+ assertEquals("bar1", request.getAttributes().get("attr"));
+ }
+
+ @Test
+ void beforeHandle_extractsAllQueryValues() {
+ Extractor extractor = new Extractor();
+ extractor.extractFromQuery("attr", "foo", false);
+
+ Request request = new Request();
+ request.setResourceRef("http://localhost/test?foo=bar1&foo=bar2");
+ Response response = new Response(request);
+
+ extractor.beforeHandle(request, response);
+
+ @SuppressWarnings("unchecked")
+ List values = (List) request.getAttributes().get("attr");
+ assertEquals(2, values.size());
+ }
+
+ @Test
+ void beforeHandle_missingQueryParameter_doesNotSetAttribute() {
+ Extractor extractor = new Extractor();
+ extractor.extractFromQuery("attr", "missing", true);
+
+ Request request = new Request();
+ request.setResourceRef("http://localhost/test?foo=bar");
+ Response response = new Response(request);
+
+ extractor.beforeHandle(request, response);
+
+ assertNull(request.getAttributes().get("attr"));
+ }
+
+ @Test
+ void beforeHandle_extractsFirstEntityValue() {
+ Extractor extractor = new Extractor();
+ extractor.extractFromEntity("attr", "foo", true);
+
+ Request request = new Request();
+ request.setMethod(org.restlet.data.Method.POST);
+ request.setEntity(
+ new StringRepresentation("foo=bar1&foo=bar2", MediaType.APPLICATION_WWW_FORM));
+ Response response = new Response(request);
+
+ extractor.beforeHandle(request, response);
+
+ assertEquals("bar1", request.getAttributes().get("attr"));
+ }
+
+ @Test
+ void beforeHandle_extractsAllEntityValues() {
+ Extractor extractor = new Extractor();
+ extractor.extractFromEntity("attr", "foo", false);
+
+ Request request = new Request();
+ request.setMethod(org.restlet.data.Method.POST);
+ request.setEntity(
+ new StringRepresentation("foo=bar1&foo=bar2", MediaType.APPLICATION_WWW_FORM));
+ Response response = new Response(request);
+
+ extractor.beforeHandle(request, response);
+
+ @SuppressWarnings("unchecked")
+ List values = (List) request.getAttributes().get("attr");
+ assertEquals(2, values.size());
+ }
+
+ @Test
+ void beforeHandle_extractsFirstCookieValue() {
+ Extractor extractor = new Extractor();
+ extractor.extractFromCookie("attr", "foo", true);
+
+ Request request = new Request();
+ request.getCookies().add(new Cookie("foo", "bar1"));
+ request.getCookies().add(new Cookie("foo", "bar2"));
+ Response response = new Response(request);
+
+ extractor.beforeHandle(request, response);
+
+ assertEquals("bar1", request.getAttributes().get("attr"));
+ }
+
+ @Test
+ void beforeHandle_extractsAllCookieValues() {
+ Extractor extractor = new Extractor();
+ extractor.extractFromCookie("attr", "foo", false);
+
+ Request request = new Request();
+ request.getCookies().add(new Cookie("foo", "bar1"));
+ request.getCookies().add(new Cookie("foo", "bar2"));
+ Response response = new Response(request);
+
+ extractor.beforeHandle(request, response);
+
+ @SuppressWarnings("unchecked")
+ List values = (List) request.getAttributes().get("attr");
+ assertEquals(2, values.size());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/security/CertificateAuthenticatorTestCase.java b/org.restlet/src/test/java/org/restlet/security/CertificateAuthenticatorTestCase.java
new file mode 100644
index 0000000000..fc76b46cf8
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/security/CertificateAuthenticatorTestCase.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.security;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.Status;
+
+/** Unit tests for {@link CertificateAuthenticator}. */
+class CertificateAuthenticatorTestCase {
+
+ private static X509Certificate selfSignedCertificate;
+
+ @BeforeAll
+ static void generateSelfSignedCertificate() throws Exception {
+ File keystoreFile = File.createTempFile("CertificateAuthenticatorTestCase", ".p12");
+ keystoreFile.deleteOnExit();
+ keystoreFile.delete();
+
+ ProcessBuilder pb =
+ new ProcessBuilder(
+ "keytool",
+ "-genkeypair",
+ "-alias",
+ "test",
+ "-keyalg",
+ "RSA",
+ "-keysize",
+ "2048",
+ "-validity",
+ "1",
+ "-storetype",
+ "PKCS12",
+ "-keystore",
+ keystoreFile.getAbsolutePath(),
+ "-storepass",
+ "changeit",
+ "-dname",
+ "CN=test-user, OU=Restlet, O=Restlet, L=Paris, ST=IDF, C=FR");
+ pb.redirectErrorStream(true);
+ Process process = pb.start();
+ process.getInputStream().readAllBytes();
+ int exit = process.waitFor();
+ org.junit.jupiter.api.Assumptions.assumeTrue(exit == 0, "keytool must be available");
+
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ try (FileInputStream in = new FileInputStream(keystoreFile)) {
+ keyStore.load(in, "changeit".toCharArray());
+ }
+ selfSignedCertificate = (X509Certificate) keyStore.getCertificate("test");
+ keystoreFile.delete();
+ }
+
+ private Request requestWithCertificates(List certificates) {
+ Request request = new Request();
+ request.getClientInfo().setCertificates(certificates);
+ return request;
+ }
+
+ @Test
+ void authenticate_withoutCertificates_setsUnauthorizedStatus() {
+ CertificateAuthenticator authenticator = new CertificateAuthenticator(new Context());
+ Request request = new Request();
+ Response response = new Response(request);
+
+ boolean result = authenticator.authenticate(request, response);
+
+ assertFalse(result);
+ assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus());
+ }
+
+ @Test
+ void authenticate_withNonX509Certificate_setsUnauthorizedStatus() {
+ CertificateAuthenticator authenticator = new CertificateAuthenticator(new Context());
+ Certificate nonX509 =
+ new Certificate("non-x509") {
+ @Override
+ public byte[] getEncoded() {
+ return new byte[0];
+ }
+
+ @Override
+ public void verify(java.security.PublicKey key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void verify(java.security.PublicKey key, String sigProvider) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString() {
+ return "non-x509";
+ }
+
+ @Override
+ public java.security.PublicKey getPublicKey() {
+ return null;
+ }
+ };
+ Request request = requestWithCertificates(List.of(nonX509));
+ Response response = new Response(request);
+
+ boolean result = authenticator.authenticate(request, response);
+
+ assertFalse(result);
+ assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus());
+ }
+
+ @Test
+ void authenticate_withX509Certificate_setsUserAndPrincipal() {
+ org.junit.jupiter.api.Assumptions.assumeTrue(selfSignedCertificate != null);
+
+ CertificateAuthenticator authenticator = new CertificateAuthenticator(new Context());
+ Request request = requestWithCertificates(List.of(selfSignedCertificate));
+ Response response = new Response(request);
+
+ boolean result = authenticator.authenticate(request, response);
+
+ assertTrue(result);
+ assertTrue(request.getClientInfo().getUser().getIdentifier().contains("test-user"));
+ assertEquals(1, request.getClientInfo().getPrincipals().size());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/security/ConfidentialAuthorizerTestCase.java b/org.restlet/src/test/java/org/restlet/security/ConfidentialAuthorizerTestCase.java
new file mode 100644
index 0000000000..436ff51583
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/security/ConfidentialAuthorizerTestCase.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.security;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.Protocol;
+
+/** Unit tests for {@link ConfidentialAuthorizer}. */
+class ConfidentialAuthorizerTestCase {
+
+ @Test
+ void authorize_confidentialRequest_returnsTrue() {
+ ConfidentialAuthorizer authorizer = new ConfidentialAuthorizer();
+ Request request = new Request();
+ request.setProtocol(Protocol.HTTPS);
+ Response response = new Response(request);
+
+ assertTrue(authorizer.authorize(request, response));
+ }
+
+ @Test
+ void authorize_nonConfidentialRequest_returnsFalse() {
+ ConfidentialAuthorizer authorizer = new ConfidentialAuthorizer();
+ Request request = new Request();
+ request.setProtocol(Protocol.HTTP);
+ Response response = new Response(request);
+
+ assertFalse(authorizer.authorize(request, response));
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/security/MethodAuthorizerTestCase.java b/org.restlet/src/test/java/org/restlet/security/MethodAuthorizerTestCase.java
new file mode 100644
index 0000000000..08f3a538aa
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/security/MethodAuthorizerTestCase.java
@@ -0,0 +1,109 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.security;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.Method;
+
+/** Unit tests for {@link MethodAuthorizer}. */
+class MethodAuthorizerTestCase {
+
+ private Request requestWith(Method method, boolean authenticated) {
+ Request request = new Request();
+ request.setMethod(method);
+ request.getClientInfo().setAuthenticated(authenticated);
+ return request;
+ }
+
+ @Test
+ void defaultConstructor_hasEmptyMethodLists() {
+ MethodAuthorizer authorizer = new MethodAuthorizer();
+ assertTrue(authorizer.getAnonymousMethods().isEmpty());
+ assertTrue(authorizer.getAuthenticatedMethods().isEmpty());
+ }
+
+ @Test
+ void constructorWithIdentifier_setsIdentifier() {
+ MethodAuthorizer authorizer = new MethodAuthorizer("my-id");
+ assertEquals("my-id", authorizer.getIdentifier());
+ }
+
+ @Test
+ void authorize_anonymousWithAllowedMethod_returnsTrue() {
+ MethodAuthorizer authorizer = new MethodAuthorizer();
+ authorizer.setAnonymousMethods(List.of(Method.GET));
+
+ Request request = requestWith(Method.GET, false);
+ assertTrue(authorizer.authorize(request, new Response(request)));
+ }
+
+ @Test
+ void authorize_anonymousWithDisallowedMethod_returnsFalse() {
+ MethodAuthorizer authorizer = new MethodAuthorizer();
+ authorizer.setAnonymousMethods(List.of(Method.GET));
+
+ Request request = requestWith(Method.POST, false);
+ assertFalse(authorizer.authorize(request, new Response(request)));
+ }
+
+ @Test
+ void authorize_authenticatedWithAllowedMethod_returnsTrue() {
+ MethodAuthorizer authorizer = new MethodAuthorizer();
+ authorizer.setAuthenticatedMethods(List.of(Method.PUT));
+
+ Request request = requestWith(Method.PUT, true);
+ assertTrue(authorizer.authorize(request, new Response(request)));
+ }
+
+ @Test
+ void authorize_authenticatedWithDisallowedMethod_returnsFalse() {
+ MethodAuthorizer authorizer = new MethodAuthorizer();
+ authorizer.setAuthenticatedMethods(List.of(Method.PUT));
+
+ Request request = requestWith(Method.DELETE, true);
+ assertFalse(authorizer.authorize(request, new Response(request)));
+ }
+
+ @Test
+ void setAnonymousMethods_withNull_clearsList() {
+ MethodAuthorizer authorizer = new MethodAuthorizer();
+ authorizer.setAnonymousMethods(List.of(Method.GET));
+ authorizer.setAnonymousMethods(null);
+ assertTrue(authorizer.getAnonymousMethods().isEmpty());
+ }
+
+ @Test
+ void setAnonymousMethods_withSameListInstance_isNoOp() {
+ MethodAuthorizer authorizer = new MethodAuthorizer();
+ authorizer.setAnonymousMethods(authorizer.getAnonymousMethods());
+ assertTrue(authorizer.getAnonymousMethods().isEmpty());
+ }
+
+ @Test
+ void setAuthenticatedMethods_withNull_clearsList() {
+ MethodAuthorizer authorizer = new MethodAuthorizer();
+ authorizer.setAuthenticatedMethods(List.of(Method.PUT));
+ authorizer.setAuthenticatedMethods(null);
+ assertTrue(authorizer.getAuthenticatedMethods().isEmpty());
+ }
+
+ @Test
+ void setAuthenticatedMethods_withSameListInstance_isNoOp() {
+ MethodAuthorizer authorizer = new MethodAuthorizer();
+ authorizer.setAuthenticatedMethods(authorizer.getAuthenticatedMethods());
+ assertTrue(authorizer.getAuthenticatedMethods().isEmpty());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/service/CorsServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/CorsServiceTestCase.java
new file mode 100644
index 0000000000..82ed25d1c2
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/service/CorsServiceTestCase.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+import org.restlet.Context;
+import org.restlet.data.Method;
+import org.restlet.engine.application.CorsFilter;
+import org.restlet.routing.Filter;
+
+/** Unit tests for {@link CorsService}. */
+class CorsServiceTestCase {
+
+ @Test
+ void defaultConstructor_isEnabled() {
+ CorsService service = new CorsService();
+ assertTrue(service.isEnabled());
+ assertEquals(Set.of("*"), service.getAllowedOrigins());
+ assertTrue(service.isAllowingAllRequestedHeaders());
+ assertFalse(service.isAllowedCredentials());
+ assertFalse(service.isSkippingResourceForCorsOptions());
+ assertEquals(-1, service.getMaxAge());
+ }
+
+ @Test
+ void gettersAndSetters_roundTrip() {
+ CorsService service = new CorsService(false);
+ assertFalse(service.isEnabled());
+
+ service.setAllowedCredentials(true);
+ assertTrue(service.isAllowedCredentials());
+
+ service.setAllowedHeaders(Set.of("X-Custom"));
+ assertEquals(Set.of("X-Custom"), service.getAllowedHeaders());
+
+ service.setAllowedOrigins(Set.of("http://example.com"));
+ assertEquals(Set.of("http://example.com"), service.getAllowedOrigins());
+
+ service.setAllowingAllRequestedHeaders(false);
+ assertFalse(service.isAllowingAllRequestedHeaders());
+
+ service.setDefaultAllowedMethods(Set.of(Method.GET));
+ assertEquals(Set.of(Method.GET), service.getDefaultAllowedMethods());
+
+ service.setExposedHeaders(Set.of("X-Exposed"));
+ assertEquals(Set.of("X-Exposed"), service.getExposedHeaders());
+
+ service.setMaxAge(3600);
+ assertEquals(3600, service.getMaxAge());
+
+ service.setSkippingResourceForCorsOptions(true);
+ assertTrue(service.isSkippingResourceForCorsOptions());
+ }
+
+ @Test
+ void createInboundFilter_returnsConfiguredCorsFilter() {
+ CorsService service = new CorsService();
+ service.setAllowedCredentials(true);
+ service.setAllowedOrigins(Set.of("http://example.com"));
+ service.setAllowingAllRequestedHeaders(false);
+ service.setAllowedHeaders(Set.of("X-Custom"));
+ service.setExposedHeaders(Set.of("X-Exposed"));
+ service.setSkippingResourceForCorsOptions(true);
+ service.setDefaultAllowedMethods(Set.of(Method.GET));
+ service.setMaxAge(60);
+
+ Filter filter = service.createInboundFilter(new Context());
+
+ assertTrue(filter instanceof CorsFilter);
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/util/WrapperMapTestCase.java b/org.restlet/src/test/java/org/restlet/util/WrapperMapTestCase.java
new file mode 100644
index 0000000000..9d85853019
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/util/WrapperMapTestCase.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link WrapperMap}. */
+class WrapperMapTestCase {
+
+ @Test
+ void defaultConstructor_usesConcurrentHashMapDelegate() {
+ WrapperMap map = new WrapperMap<>();
+ map.put("key", "value");
+ assertEquals("value", map.get("key"));
+ }
+
+ @Test
+ void delegatesAllMapOperations() {
+ Map delegate = new HashMap<>();
+ WrapperMap map = new WrapperMap<>(delegate);
+
+ assertTrue(map.isEmpty());
+ map.put("key", "value");
+ assertFalse(map.isEmpty());
+ assertEquals(1, map.size());
+ assertTrue(map.containsKey("key"));
+ assertTrue(map.containsValue("value"));
+ assertEquals("value", map.get("key"));
+ assertTrue(map.equals(delegate));
+ assertEquals(delegate.hashCode(), map.hashCode());
+ assertEquals(1, map.keySet().size());
+ assertEquals(1, map.entrySet().size());
+ assertEquals(1, map.values().size());
+
+ map.putAll(Map.of("key2", "value2"));
+ assertEquals(2, map.size());
+
+ map.remove("key2");
+ assertEquals(1, map.size());
+
+ map.clear();
+ assertTrue(map.isEmpty());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/util/WrapperRequestTestCase.java b/org.restlet/src/test/java/org/restlet/util/WrapperRequestTestCase.java
new file mode 100644
index 0000000000..dd699008ac
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/util/WrapperRequestTestCase.java
@@ -0,0 +1,143 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.Set;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.ChallengeResponse;
+import org.restlet.data.ChallengeScheme;
+import org.restlet.data.Cookie;
+import org.restlet.data.MediaType;
+import org.restlet.data.Method;
+import org.restlet.data.Protocol;
+import org.restlet.data.Range;
+import org.restlet.data.Reference;
+import org.restlet.representation.StringRepresentation;
+
+/** Unit tests for {@link WrapperRequest}. */
+class WrapperRequestTestCase {
+
+ private Request wrapped;
+
+ private WrapperRequest wrapper;
+
+ @BeforeEach
+ void setUp() {
+ wrapped = new Request();
+ wrapper = new WrapperRequest(wrapped);
+ }
+
+ @Test
+ void abort_delegatesToWrappedRequest() {
+ assertFalse(wrapper.abort());
+ }
+
+ @Test
+ void commit_delegatesToWrappedRequest() {
+ wrapper.commit(new Response(wrapped));
+ }
+
+ @Test
+ void attributesAndSimpleGetters_delegateToWrappedRequest() throws Exception {
+ wrapped.getAttributes().put("key", "value");
+ assertSame(wrapped.getAttributes(), wrapper.getAttributes());
+
+ ChallengeResponse challengeResponse = new ChallengeResponse(ChallengeScheme.HTTP_BASIC);
+ wrapper.setChallengeResponse(challengeResponse);
+ assertSame(challengeResponse, wrapper.getChallengeResponse());
+ assertSame(wrapped.getChallengeResponse(), wrapper.getChallengeResponse());
+
+ assertSame(wrapped.getClientInfo(), wrapper.getClientInfo());
+ assertSame(wrapped.getConditions(), wrapper.getConditions());
+
+ wrapper.setCookies(wrapper.getCookies());
+ wrapper.getCookies().add(new Cookie("foo", "bar"));
+ assertSame(wrapped.getCookies(), wrapper.getCookies());
+
+ wrapper.setEntity(new StringRepresentation("text"));
+ assertEquals("text", wrapper.getEntity().getText());
+
+ wrapper.setEntity("value", MediaType.TEXT_PLAIN);
+ assertEquals("value", wrapped.getEntity().getText());
+
+ wrapper.setHostRef("http://localhost");
+ assertEquals("http://localhost", wrapper.getHostRef().toString());
+ wrapper.setHostRef(new Reference("http://example.com"));
+ assertEquals("http://example.com", wrapped.getHostRef().toString());
+
+ wrapper.setMaxForwards(3);
+ assertEquals(3, wrapper.getMaxForwards());
+
+ wrapper.setMethod(Method.PUT);
+ assertEquals(Method.PUT, wrapper.getMethod());
+
+ wrapper.setOnResponse(null);
+ assertEquals(wrapped.getOnResponse(), wrapper.getOnResponse());
+
+ wrapper.setOriginalRef(new Reference("http://original"));
+ assertEquals("http://original", wrapper.getOriginalRef().toString());
+
+ wrapper.setProtocol(Protocol.HTTP);
+ assertEquals(Protocol.HTTP, wrapper.getProtocol());
+
+ ChallengeResponse proxyChallenge = new ChallengeResponse(ChallengeScheme.HTTP_BASIC);
+ wrapper.setProxyChallengeResponse(proxyChallenge);
+ assertSame(proxyChallenge, wrapper.getProxyChallengeResponse());
+
+ wrapper.setRanges(List.of(new Range(0, 10)));
+ assertEquals(1, wrapper.getRanges().size());
+
+ wrapper.setReferrerRef("http://referrer");
+ assertEquals("http://referrer", wrapper.getReferrerRef().toString());
+ wrapper.setReferrerRef(new Reference("http://referrer2"));
+ assertEquals("http://referrer2", wrapper.getReferrerRef().toString());
+
+ wrapper.setResourceRef("http://resource");
+ assertEquals("http://resource", wrapper.getResourceRef().toString());
+ wrapper.setResourceRef(new Reference("http://resource2"));
+ assertEquals("http://resource2", wrapper.getResourceRef().toString());
+
+ wrapper.setRootRef(new Reference("http://root"));
+ assertEquals("http://root", wrapper.getRootRef().toString());
+
+ wrapper.setAccessControlRequestHeaders(Set.of("X-Test"));
+ assertEquals(Set.of("X-Test"), wrapper.getAccessControlRequestHeaders());
+
+ wrapper.setAccessControlRequestMethod(Method.DELETE);
+ assertEquals(Method.DELETE, wrapper.getAccessControlRequestMethod());
+
+ assertFalse(wrapper.isAsynchronous());
+ assertFalse(wrapper.isConfidential());
+ assertTrue(wrapper.isEntityAvailable());
+ assertTrue(wrapper.isExpectingResponse());
+ assertTrue(wrapper.isSynchronous());
+
+ assertEquals(wrapped.toString(), wrapper.toString());
+ }
+
+ @Test
+ void setClientInfoConditionsCookies_delegateToWrappedRequest() {
+ var clientInfo = new org.restlet.data.ClientInfo();
+ wrapper.setClientInfo(clientInfo);
+ assertSame(clientInfo, wrapped.getClientInfo());
+
+ var conditions = new org.restlet.data.Conditions();
+ wrapper.setConditions(conditions);
+ assertSame(conditions, wrapped.getConditions());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/util/WrapperResponseTestCase.java b/org.restlet/src/test/java/org/restlet/util/WrapperResponseTestCase.java
new file mode 100644
index 0000000000..0ec3d80d7c
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/util/WrapperResponseTestCase.java
@@ -0,0 +1,153 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.AuthenticationInfo;
+import org.restlet.data.ChallengeRequest;
+import org.restlet.data.ChallengeScheme;
+import org.restlet.data.Dimension;
+import org.restlet.data.MediaType;
+import org.restlet.data.Method;
+import org.restlet.data.Reference;
+import org.restlet.data.ServerInfo;
+import org.restlet.data.Status;
+import org.restlet.representation.StringRepresentation;
+
+/** Unit tests for {@link WrapperResponse}. */
+class WrapperResponseTestCase {
+
+ private Request request;
+
+ private Response wrapped;
+
+ private WrapperResponse wrapper;
+
+ @BeforeEach
+ void setUp() {
+ request = new Request();
+ wrapped = new Response(request);
+ wrapper = new WrapperResponse(wrapped);
+ }
+
+ @Test
+ void abortAndCommit_delegateToWrappedResponse() {
+ wrapper.abort();
+ wrapper.commit();
+ }
+
+ @Test
+ void gettersAndSetters_delegateToWrappedResponse() throws Exception {
+ wrapper.setAge(42);
+ assertEquals(42, wrapper.getAge());
+
+ wrapper.setAllowedMethods(Set.of(Method.GET));
+ assertEquals(Set.of(Method.GET), wrapper.getAllowedMethods());
+
+ wrapped.getAttributes().put("key", "value");
+ assertSame(wrapped.getAttributes(), wrapper.getAttributes());
+
+ AuthenticationInfo authInfo =
+ new AuthenticationInfo("nextNonce", 1, "cnonce", "auth", "digest");
+ wrapper.setAuthenticationInfo(authInfo);
+ assertSame(authInfo, wrapper.getAuthenticationInfo());
+
+ wrapper.setAutoCommitting(false);
+ assertFalse(wrapper.isAutoCommitting());
+
+ ChallengeRequest challengeRequest = new ChallengeRequest(ChallengeScheme.HTTP_BASIC);
+ wrapper.setChallengeRequests(List.of(challengeRequest));
+ assertEquals(1, wrapper.getChallengeRequests().size());
+
+ wrapper.getCookieSettings().add(new org.restlet.data.CookieSetting("foo", "bar"));
+ assertSame(wrapped.getCookieSettings(), wrapper.getCookieSettings());
+
+ wrapper.setCommitted(true);
+ assertTrue(wrapper.isCommitted());
+
+ wrapper.setDimensions(Set.of(Dimension.MEDIA_TYPE));
+ assertEquals(Set.of(Dimension.MEDIA_TYPE), wrapper.getDimensions());
+
+ wrapper.setEntity(new StringRepresentation("text"));
+ assertEquals("text", wrapper.getEntity().getText());
+ wrapper.setEntity("value", MediaType.TEXT_PLAIN);
+ assertEquals("value", wrapped.getEntity().getText());
+
+ wrapper.setLocationRef("http://localhost/loc");
+ assertEquals("http://localhost/loc", wrapper.getLocationRef().toString());
+ wrapper.setLocationRef(new Reference("http://localhost/loc2"));
+ assertEquals("http://localhost/loc2", wrapper.getLocationRef().toString());
+
+ wrapper.setProxyChallengeRequests(List.of(challengeRequest));
+ assertEquals(1, wrapper.getProxyChallengeRequests().size());
+
+ assertSame(request, wrapper.getRequest());
+ wrapper.setRequest(request);
+ assertSame(request, wrapped.getRequest());
+ wrapper.setRequest(new WrapperRequest(request));
+ assertTrue(wrapped.getRequest() instanceof WrapperRequest);
+
+ Date retryAfter = new Date();
+ wrapper.setRetryAfter(retryAfter);
+ assertEquals(retryAfter, wrapper.getRetryAfter());
+
+ ServerInfo serverInfo = new ServerInfo();
+ wrapper.setServerInfo(serverInfo);
+ assertSame(serverInfo, wrapper.getServerInfo());
+
+ wrapper.setStatus(Status.SUCCESS_ACCEPTED);
+ assertEquals(Status.SUCCESS_ACCEPTED, wrapper.getStatus());
+
+ wrapper.setStatus(Status.SUCCESS_OK, "custom message");
+ assertEquals(Status.SUCCESS_OK, wrapper.getStatus());
+
+ wrapper.setStatus(Status.SERVER_ERROR_INTERNAL, new RuntimeException("boom"));
+ assertEquals(Status.SERVER_ERROR_INTERNAL, wrapper.getStatus());
+
+ wrapper.setStatus(Status.SERVER_ERROR_INTERNAL, new RuntimeException("boom"), "message");
+ assertEquals(Status.SERVER_ERROR_INTERNAL, wrapper.getStatus());
+
+ assertFalse(wrapper.isConfidential());
+ assertTrue(wrapper.isEntityAvailable());
+
+ assertEquals(wrapped.toString(), wrapper.toString());
+ }
+
+ @Test
+ void redirectMethods_delegateToWrappedResponse() {
+ wrapper.redirectPermanent("http://localhost/permanent");
+ assertEquals(Status.REDIRECTION_PERMANENT, wrapped.getStatus());
+
+ wrapper.redirectPermanent(new Reference("http://localhost/permanent2"));
+ assertEquals(Status.REDIRECTION_PERMANENT, wrapped.getStatus());
+
+ wrapper.redirectSeeOther("http://localhost/other");
+ assertEquals(Status.REDIRECTION_SEE_OTHER, wrapped.getStatus());
+
+ wrapper.redirectSeeOther(new Reference("http://localhost/other2"));
+ assertEquals(Status.REDIRECTION_SEE_OTHER, wrapped.getStatus());
+
+ wrapper.redirectTemporary("http://localhost/temp");
+ assertEquals(Status.REDIRECTION_TEMPORARY, wrapped.getStatus());
+
+ wrapper.redirectTemporary(new Reference("http://localhost/temp2"));
+ assertEquals(Status.REDIRECTION_TEMPORARY, wrapped.getStatus());
+ }
+}
diff --git a/org.restlet/src/test/java/org/restlet/util/WrapperRestletTestCase.java b/org.restlet/src/test/java/org/restlet/util/WrapperRestletTestCase.java
new file mode 100644
index 0000000000..8f28e11465
--- /dev/null
+++ b/org.restlet/src/test/java/org/restlet/util/WrapperRestletTestCase.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright 2005-2026 Qlik
+ *
+ * The content of this file is subject to the terms of the Apache 2.0 open
+ * source license available at https://www.opensource.org/licenses/apache-2.0
+ *
+ * Restlet is a registered trademark of QlikTech International AB.
+ */
+package org.restlet.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.Restlet;
+
+/** Unit tests for {@link WrapperRestlet}. */
+class WrapperRestletTestCase {
+
+ @Test
+ void delegatesAllOperationsToWrappedRestlet() throws Exception {
+ Restlet wrapped = new Restlet() {};
+ WrapperRestlet wrapper = new WrapperRestlet(wrapped);
+
+ Context context = new Context();
+ wrapper.setContext(context);
+ assertSame(context, wrapper.getContext());
+ assertSame(wrapped.getContext(), wrapper.getContext());
+
+ wrapper.setAuthor("author");
+ assertEquals("author", wrapper.getAuthor());
+
+ wrapper.setDescription("description");
+ assertEquals("description", wrapper.getDescription());
+
+ wrapper.setName("name");
+ assertEquals("name", wrapper.getName());
+
+ wrapper.setOwner("owner");
+ assertEquals("owner", wrapper.getOwner());
+
+ assertSame(wrapped.getLogger(), wrapper.getLogger());
+ assertSame(wrapped.getApplication(), wrapper.getApplication());
+
+ assertFalse(wrapper.isStarted());
+ assertTrue(wrapper.isStopped());
+
+ wrapper.start();
+ assertTrue(wrapper.isStarted());
+ assertFalse(wrapper.isStopped());
+
+ wrapper.stop();
+ assertTrue(wrapper.isStopped());
+
+ Request request = new Request();
+ Response response = new Response(request);
+ wrapper.handle(request, response);
+ }
+}
diff --git a/pom.xml b/pom.xml
index 61a5d6d781..07cf8b84be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -280,7 +280,7 @@
org.jacoco
jacoco-maven-plugin
- 0.8.12
+ 0.8.15
@@ -307,7 +307,7 @@
INSTRUCTION
COVEREDRATIO
- 0.50
+ 0.70