diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/DigestAuthenticatorTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/DigestAuthenticatorTestCase.java new file mode 100644 index 0000000000..0d5a7698b6 --- /dev/null +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/DigestAuthenticatorTestCase.java @@ -0,0 +1,183 @@ +/** + * 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 java.util.Base64; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.Reference; +import org.restlet.security.MapVerifier; +import org.restlet.security.Verifier; + +/** Unit tests for {@link DigestAuthenticator}. */ +class DigestAuthenticatorTestCase { + + @Test + void constructor_withRealmAndServerKey_setsDefaults() { + DigestAuthenticator authenticator = + new DigestAuthenticator(new Context(), "realm", "mySecretKey"); + + assertFalse(authenticator.isOptional()); + assertEquals(ChallengeScheme.HTTP_DIGEST, authenticator.getScheme()); + assertEquals("realm", authenticator.getRealm()); + assertEquals("mySecretKey", authenticator.getServerKey()); + assertEquals(5 * 60 * 1000L, authenticator.getMaxServerNonceAge()); + assertNotNull(authenticator.getVerifier()); + } + + @Test + void constructor_withExplicitDomainRefs_usesProvidedList() { + List domainRefs = Collections.singletonList(new Reference("/protected/")); + DigestAuthenticator authenticator = + new DigestAuthenticator(new Context(), true, "realm", domainRefs, "key"); + + assertTrue(authenticator.isOptional()); + assertSame(domainRefs, authenticator.getDomainRefs()); + } + + @Test + void getDomainRefs_withNoDomainRefsProvided_lazilyDefaultsToRoot() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + + List domainRefs = authenticator.getDomainRefs(); + + assertEquals(1, domainRefs.size()); + assertEquals("/", domainRefs.getFirst().toString()); + // Second call returns the same cached list instance. + assertSame(domainRefs, authenticator.getDomainRefs()); + } + + @Test + void setDomainRefs_updatesGetDomainRefs() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + List domainRefs = Collections.singletonList(new Reference("/other/")); + + authenticator.setDomainRefs(domainRefs); + + assertSame(domainRefs, authenticator.getDomainRefs()); + } + + @Test + void generateServerNonce_returnsDecodableBase64Value() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + + String nonce = authenticator.generateServerNonce(); + + assertNotNull(nonce); + String decoded = new String(Base64.getDecoder().decode(nonce)); + assertTrue(decoded.contains(":")); + } + + @Test + void createChallengeRequest_stale_populatesDomainRefsStaleAndNonce() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + + ChallengeRequest challengeRequest = authenticator.createChallengeRequest(true); + + assertEquals(ChallengeScheme.HTTP_DIGEST, challengeRequest.getScheme()); + assertEquals("realm", challengeRequest.getRealm()); + assertTrue(challengeRequest.isStale()); + assertNotNull(challengeRequest.getServerNonce()); + assertEquals(authenticator.getDomainRefs(), challengeRequest.getDomainRefs()); + } + + @Test + void createChallengeRequest_notStale_setsStaleFalse() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + + ChallengeRequest challengeRequest = authenticator.createChallengeRequest(false); + + assertFalse(challengeRequest.isStale()); + } + + @Test + void getHashedSecret_httpDigestScheme_hashesIdentifierRealmAndSecret() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + + String expected = DigestUtils.toHttpDigest("scott", "tiger".toCharArray(), "realm"); + + assertEquals(expected, authenticator.getHashedSecret("scott", "tiger".toCharArray())); + } + + @Test + void maxServerNonceAge_getterAndSetter() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + + authenticator.setMaxServerNonceAge(12345L); + + assertEquals(12345L, authenticator.getMaxServerNonceAge()); + } + + @Test + void serverKey_getterAndSetter() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + + authenticator.setServerKey("anotherKey"); + + assertEquals("anotherKey", authenticator.getServerKey()); + } + + @Test + void getVerifier_returnsDigestVerifierSetByConstructor() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + + assertNotNull(authenticator.getVerifier()); + } + + @Test + void setVerifier_withNonDigestVerifier_throwsIllegalArgumentException() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + Verifier notADigestVerifier = new MapVerifier(); + + assertThrows( + IllegalArgumentException.class, + () -> authenticator.setVerifier(notADigestVerifier)); + } + + @Test + void setVerifier_withDigestVerifier_replacesIt() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + DigestVerifier newVerifier = + new DigestVerifier<>(null, new MapVerifier(), null); + + authenticator.setVerifier(newVerifier); + + assertSame(newVerifier, authenticator.getVerifier()); + } + + @Test + void setWrappedAlgorithm_delegatesToVerifier() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + + authenticator.setWrappedAlgorithm("SHA-1"); + + assertEquals("SHA-1", authenticator.getVerifier().getWrappedAlgorithm()); + } + + @Test + void setWrappedVerifier_delegatesToVerifier() { + DigestAuthenticator authenticator = new DigestAuthenticator(new Context(), "realm", "key"); + MapVerifier wrapped = new MapVerifier(); + + authenticator.setWrappedVerifier(wrapped); + + assertSame(wrapped, authenticator.getVerifier().getWrappedVerifier()); + } +} diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/CryptoUtilsTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/CryptoUtilsTestCase.java new file mode 100644 index 0000000000..5f26ca49d6 --- /dev/null +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/CryptoUtilsTestCase.java @@ -0,0 +1,79 @@ +/** + * 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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.util.Base64; +import org.junit.jupiter.api.Test; +import org.restlet.ext.crypto.DigestUtils; + +/** Unit tests for {@link CryptoUtils}. */ +class CryptoUtilsTestCase { + + private static final String ALGORITHM = "AES"; + + private static final byte[] SECRET_KEY_BYTES = + "0123456789ABCDEF".getBytes(StandardCharsets.UTF_8); + + @Test + void encryptThenDecrypt_bytesKey_roundTripsContent() throws GeneralSecurityException { + byte[] encrypted = CryptoUtils.encrypt(ALGORITHM, SECRET_KEY_BYTES, "hello, world"); + + String decrypted = CryptoUtils.decrypt(ALGORITHM, SECRET_KEY_BYTES, encrypted); + + assertEquals("hello, world", decrypted); + } + + @Test + void encryptThenDecrypt_base64Key_roundTripsContent() throws GeneralSecurityException { + String base64Secret = Base64.getEncoder().encodeToString(SECRET_KEY_BYTES); + + byte[] encrypted = CryptoUtils.encrypt(ALGORITHM, base64Secret, "another secret message"); + String decrypted = CryptoUtils.decrypt(ALGORITHM, base64Secret, encrypted); + + assertEquals("another secret message", decrypted); + } + + @Test + void encrypt_unsupportedAlgorithm_throwsGeneralSecurityException() { + assertThrows( + GeneralSecurityException.class, + () -> CryptoUtils.encrypt("NoSuchAlgorithm", SECRET_KEY_BYTES, "content")); + } + + @Test + void decrypt_unsupportedAlgorithm_throwsGeneralSecurityException() { + assertThrows( + GeneralSecurityException.class, + () -> + CryptoUtils.decrypt( + "NoSuchAlgorithm", + SECRET_KEY_BYTES, + "content".getBytes(StandardCharsets.UTF_8))); + } + + @Test + void makeNonce_returnsBase64EncodedTimestampAndDigest() { + String nonce = CryptoUtils.makeNonce("mySecretKey"); + + assertNotNull(nonce); + String decoded = new String(Base64.getDecoder().decode(nonce), StandardCharsets.UTF_8); + assertTrue(decoded.contains(":")); + + String timestampPart = decoded.substring(0, decoded.indexOf(':')); + String digestPart = decoded.substring(decoded.indexOf(':') + 1); + assertEquals(DigestUtils.toMd5(timestampPart + ":" + "mySecretKey"), digestPart); + } +} diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpDigestVerifierTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpDigestVerifierTestCase.java new file mode 100644 index 0000000000..47c6f60f0b --- /dev/null +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpDigestVerifierTestCase.java @@ -0,0 +1,311 @@ +/** + * 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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.Digest; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.engine.security.AuthenticatorUtils; +import org.restlet.ext.crypto.DigestAuthenticator; +import org.restlet.ext.crypto.DigestUtils; +import org.restlet.security.MapVerifier; +import org.restlet.security.Verifier; + +/** Unit tests for {@link HttpDigestVerifier}. */ +class HttpDigestVerifierTestCase { + + private static final String REALM = "TestRealm"; + + private static final String SERVER_KEY = "mySecretServerKey"; + + private static final String RESOURCE_PATH = "/protected/resource"; + + private DigestAuthenticator authenticator; + + private HttpDigestVerifier verifier; + + private String serverNonce; + + private String ha1; + + private Request createRequest() { + Request request = new Request(Method.GET, "http://example.com" + RESOURCE_PATH); + return request; + } + + private String computeResponse(String ha1Value, String nonce, String requestUri) { + String ha2 = DigestUtils.toMd5(Method.GET.toString() + ":" + requestUri); + return DigestUtils.toMd5(ha1Value + ":" + nonce + ":" + ha2); + } + + private String computeResponseWithQop( + String ha1Value, + String nonce, + String nc, + String cnonce, + String qop, + String requestUri) { + String ha2 = DigestUtils.toMd5(Method.GET.toString() + ":" + requestUri); + return DigestUtils.toMd5( + ha1Value + + ":" + + nonce + + ":" + + AuthenticatorUtils.formatNonceCount(Integer.parseInt(nc)) + + ":" + + cnonce + + ":" + + qop + + ":" + + ha2); + } + + private ChallengeResponse createChallengeResponse( + String identifier, + char[] responseSecret, + String quality, + Reference digestRef, + String clientNonce, + String serverNonceValue, + int serverNonceCount) { + return new ChallengeResponse( + ChallengeScheme.HTTP_DIGEST, + null, + identifier, + responseSecret, + Digest.ALGORITHM_NONE, + REALM, + quality, + digestRef, + null, + null, + clientNonce, + serverNonceValue, + serverNonceCount, + 0L); + } + + @BeforeEach + void setUpEach() { + authenticator = new DigestAuthenticator(new Context(), REALM, SERVER_KEY); + MapVerifier mapVerifier = new MapVerifier(); + mapVerifier.getLocalSecrets().put("scott", "tiger".toCharArray()); + authenticator.setWrappedVerifier(mapVerifier); + verifier = (HttpDigestVerifier) authenticator.getVerifier(); + + serverNonce = authenticator.generateServerNonce(); + ha1 = DigestUtils.toHttpDigest("scott", "tiger".toCharArray(), REALM); + } + + @Test + void verify_noChallengeResponse_returnsMissing() { + Request request = createRequest(); + Response response = new Response(request); + + assertEquals(Verifier.RESULT_MISSING, verifier.verify(request, response)); + } + + @Test + void verify_nullSecret_returnsInvalid() { + Request request = createRequest(); + Response response = new Response(request); + ChallengeResponse cr = + createChallengeResponse( + "scott", null, null, new Reference(RESOURCE_PATH), null, serverNonce, 0); + request.setChallengeResponse(cr); + + assertEquals(Verifier.RESULT_INVALID, verifier.verify(request, response)); + } + + @Test + void verify_serverNonceForgedWithWrongSecretKey_returnsInvalid() { + Request request = createRequest(); + Response response = new Response(request); + String forgedNonce = CryptoUtils.makeNonce("wrongServerKey"); + String expected = computeResponse(ha1, forgedNonce, RESOURCE_PATH); + ChallengeResponse cr = + createChallengeResponse( + "scott", + expected.toCharArray(), + null, + new Reference(RESOURCE_PATH), + null, + forgedNonce, + 0); + request.setChallengeResponse(cr); + + assertEquals(Verifier.RESULT_INVALID, verifier.verify(request, response)); + } + + @Test + void verify_staleServerNonce_returnsStale() { + authenticator.setMaxServerNonceAge(-1L); + Request request = createRequest(); + Response response = new Response(request); + String expected = computeResponse(ha1, serverNonce, RESOURCE_PATH); + ChallengeResponse cr = + createChallengeResponse( + "scott", + expected.toCharArray(), + null, + new Reference(RESOURCE_PATH), + null, + serverNonce, + 0); + request.setChallengeResponse(cr); + + assertEquals(Verifier.RESULT_STALE, verifier.verify(request, response)); + } + + @Test + void verify_missingDigestRef_returnsMissing() { + Request request = createRequest(); + Response response = new Response(request); + String expected = computeResponse(ha1, serverNonce, RESOURCE_PATH); + ChallengeResponse cr = + createChallengeResponse( + "scott", expected.toCharArray(), null, null, null, serverNonce, 0); + request.setChallengeResponse(cr); + + assertEquals(Verifier.RESULT_MISSING, verifier.verify(request, response)); + } + + @Test + void verify_digestRefDoesNotMatchRequestUri_returnsInvalid() { + Request request = createRequest(); + Response response = new Response(request); + String expected = computeResponse(ha1, serverNonce, RESOURCE_PATH); + ChallengeResponse cr = + createChallengeResponse( + "scott", + expected.toCharArray(), + null, + new Reference("/some/other/path"), + null, + serverNonce, + 0); + request.setChallengeResponse(cr); + + assertEquals(Verifier.RESULT_INVALID, verifier.verify(request, response)); + } + + @Test + void verify_unknownIdentifier_returnsInvalid() { + Request request = createRequest(); + Response response = new Response(request); + String expected = computeResponse(ha1, serverNonce, RESOURCE_PATH); + ChallengeResponse cr = + createChallengeResponse( + "unknownUser", + expected.toCharArray(), + null, + new Reference(RESOURCE_PATH), + null, + serverNonce, + 0); + request.setChallengeResponse(cr); + + assertEquals(Verifier.RESULT_INVALID, verifier.verify(request, response)); + } + + @Test + void verify_wrongPassword_returnsInvalid() { + Request request = createRequest(); + Response response = new Response(request); + String wrongHa1 = DigestUtils.toHttpDigest("scott", "wrongPassword".toCharArray(), REALM); + String computedWithWrongPassword = computeResponse(wrongHa1, serverNonce, RESOURCE_PATH); + ChallengeResponse cr = + createChallengeResponse( + "scott", + computedWithWrongPassword.toCharArray(), + null, + new Reference(RESOURCE_PATH), + null, + serverNonce, + 0); + request.setChallengeResponse(cr); + + assertEquals(Verifier.RESULT_INVALID, verifier.verify(request, response)); + } + + @Test + void verify_validCredentialsWithoutQop_returnsValid() { + Request request = createRequest(); + Response response = new Response(request); + String expected = computeResponse(ha1, serverNonce, RESOURCE_PATH); + ChallengeResponse cr = + createChallengeResponse( + "scott", + expected.toCharArray(), + null, + new Reference(RESOURCE_PATH), + null, + serverNonce, + 0); + request.setChallengeResponse(cr); + + assertEquals(Verifier.RESULT_VALID, verifier.verify(request, response)); + assertEquals("scott", request.getClientInfo().getUser().getIdentifier()); + } + + @Test + void verify_validCredentialsWithQop_returnsValid() { + Request request = createRequest(); + Response response = new Response(request); + String nc = "1"; + String clientNonce = "abcdef0123456789"; + String qop = "auth"; + String expected = + computeResponseWithQop(ha1, serverNonce, nc, clientNonce, qop, RESOURCE_PATH); + ChallengeResponse cr = + createChallengeResponse( + "scott", + expected.toCharArray(), + qop, + new Reference(RESOURCE_PATH), + clientNonce, + serverNonce, + Integer.parseInt(nc)); + request.setChallengeResponse(cr); + + assertEquals(Verifier.RESULT_VALID, verifier.verify(request, response)); + } + + @Test + void digest_withNonHttpDigestAlgorithm_fallsBackToSuper() { + verifier.setAlgorithm(Digest.ALGORITHM_MD5); + char[] result = verifier.getWrappedSecretDigest("scott"); + + assertEquals(DigestUtils.toMd5("tiger"), new String(result)); + } + + @Test + void getDigestAuthenticator_returnsConstructorValue() { + assertEquals(authenticator, verifier.getDigestAuthenticator()); + } + + @Test + void setDigestAuthenticator_updatesGetter() { + DigestAuthenticator other = + new DigestAuthenticator(new Context(), "OtherRealm", "otherKey"); + + verifier.setDigestAuthenticator(other); + + assertEquals(other, verifier.getDigestAuthenticator()); + } +} diff --git a/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/ContextTemplateLoaderTestCase.java b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/ContextTemplateLoaderTestCase.java index e7c56f1cfa..3165cabf5c 100644 --- a/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/ContextTemplateLoaderTestCase.java +++ b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/ContextTemplateLoaderTestCase.java @@ -116,4 +116,15 @@ void getLastModified_withNullModificationDate_returnsMinusOne() { StringRepresentation rep = new StringRepresentation("content"); assertEquals(-1L, loader.getLastModified(rep)); } + + @Test + void getReader_returnsReaderWithGivenCharacterSet() throws IOException { + ContextTemplateLoader loader = new ContextTemplateLoader(null, "clap://test"); + StringRepresentation rep = new StringRepresentation("template content"); + try (java.io.Reader reader = loader.getReader(rep, "UTF-8")) { + char[] buffer = new char[64]; + int read = reader.read(buffer); + assertEquals("template content", new String(buffer, 0, read)); + } + } } diff --git a/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java index 520c3931e4..626f57d0a2 100644 --- a/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java +++ b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java @@ -11,19 +11,29 @@ 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 static org.junit.jupiter.api.Assertions.assertTrue; import freemarker.template.Configuration; import freemarker.template.Template; import java.io.File; import java.io.FileWriter; +import java.io.IOException; +import java.io.StringWriter; import java.nio.file.Files; import java.util.Map; import org.junit.jupiter.api.Test; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.CharacterSet; import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Status; import org.restlet.engine.io.IoUtils; +import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.restlet.representation.Variant; +import org.restlet.resource.ServerResource; /** * Unit test for the FreeMarker extension. @@ -128,4 +138,105 @@ void testFreemarkerConverterScore() { converter.toRepresentation( "notATemplate", new Variant(MediaType.TEXT_PLAIN), null)); } + + @Test + void testFreemarkerConverterWithTemplate() throws Exception { + FreemarkerConverter converter = new FreemarkerConverter(); + + Configuration fmc = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); + StringRepresentation templateSource = new StringRepresentation("Hello ${m}"); + templateSource.setCharacterSet(CharacterSet.UTF_8); + Template template = TemplateRepresentation.getTemplate(fmc, templateSource); + assertNotNull(template); + + assertEquals(1.0f, converter.score(template, new Variant(MediaType.TEXT_HTML), null)); + + Request request = new Request(Method.GET, "/test"); + Response response = new Response(request); + ServerResource resource = new ServerResource() {}; + resource.setRequest(request); + resource.setResponse(response); + + Representation representation = + converter.toRepresentation(template, new Variant(MediaType.TEXT_HTML), resource); + assertNotNull(representation); + assertTrue(representation instanceof TemplateRepresentation); + assertEquals("Hello GET", representation.getText()); + } + + @Test + void testTemplateRepresentationConstructorsWithDefaultConfiguration() throws IOException { + StringRepresentation rep = new StringRepresentation("Hello ${name}"); + rep.setCharacterSet(CharacterSet.UTF_8); + + // Constructor(Representation, MediaType) - uses a default Configuration + TemplateRepresentation tr1 = new TemplateRepresentation(rep, MediaType.TEXT_PLAIN); + assertNotNull(tr1.getTemplate()); + assertNull(tr1.getDataModel()); + + // Constructor(Representation, Object dataModel, MediaType) - default Configuration + StringRepresentation rep2 = new StringRepresentation("Hello ${name}"); + rep2.setCharacterSet(CharacterSet.UTF_8); + TemplateRepresentation tr2 = + new TemplateRepresentation(rep2, Map.of("name", "world"), MediaType.TEXT_PLAIN); + assertEquals(Map.of("name", "world"), tr2.getDataModel()); + assertEquals("Hello world", tr2.getText()); + + // Constructor(Representation, Configuration, Object dataModel, MediaType) + Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); + StringRepresentation rep3 = new StringRepresentation("Hello ${name}"); + rep3.setCharacterSet(CharacterSet.UTF_8); + TemplateRepresentation tr3 = + new TemplateRepresentation( + rep3, cfg, Map.of("name", "restlet"), MediaType.TEXT_PLAIN); + assertEquals("Hello restlet", tr3.getText()); + } + + @Test + void testTemplateRepresentationWithoutExplicitCharacterSet() { + // No character set is set on the source representation: the UTF-8 fallback path is used. + StringRepresentation rep = new StringRepresentation("Hello ${name}"); + Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); + Template template = TemplateRepresentation.getTemplate(cfg, rep); + assertNotNull(template); + } + + @Test + void testTemplateRepresentationEqualsAndHashCode() { + TemplateRepresentation tr = + new TemplateRepresentation((Template) null, MediaType.TEXT_PLAIN); + assertEquals(tr, tr); + assertEquals(tr.hashCode(), tr.hashCode()); + } + + @Test + void testTemplateRepresentationSetDataModelFromRequestResponse() throws IOException { + Request request = new Request(Method.GET, "/test"); + Response response = new Response(request); + response.setStatus(Status.SUCCESS_OK); + + Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); + StringRepresentation rep = new StringRepresentation("Method=${m}"); + rep.setCharacterSet(CharacterSet.UTF_8); + TemplateRepresentation tr = new TemplateRepresentation(rep, cfg, MediaType.TEXT_PLAIN); + + Object dataModel = tr.setDataModel(request, response); + assertNotNull(dataModel); + assertEquals(dataModel, tr.getDataModel()); + assertEquals("Method=GET", tr.getText()); + } + + @Test + void testTemplateRepresentationWriteWithTemplateException() { + // A missing variable used with the "?number" built-in raises a TemplateException while + // processing, which write() should translate into an IOException. + Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); + StringRepresentation rep = new StringRepresentation("${missingVar}"); + rep.setCharacterSet(CharacterSet.UTF_8); + TemplateRepresentation tr = + new TemplateRepresentation(rep, cfg, Map.of(), MediaType.TEXT_PLAIN); + + StringWriter writer = new StringWriter(); + assertThrows(IOException.class, () -> tr.write(writer)); + } } diff --git a/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/TemplateFilterTestCase.java b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/TemplateFilterTestCase.java index dec470bd1f..919b8a769e 100644 --- a/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/TemplateFilterTestCase.java +++ b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/TemplateFilterTestCase.java @@ -9,7 +9,11 @@ package org.restlet.ext.freemarker; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import freemarker.template.Configuration; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.restlet.Application; @@ -23,6 +27,7 @@ import org.restlet.data.Protocol; import org.restlet.engine.Engine; import org.restlet.resource.Directory; +import org.restlet.util.Resolver; /** * Test case for template filters. @@ -46,6 +51,85 @@ void representationShouldNotBeUsedAsTemplate() throws Exception { assertEquals("Method=${m}/Path=${rp}", response.getEntity().getText()); } + @Test + void defaultConstructor_setsDefaultConfiguration() { + TemplateFilter filter = new TemplateFilter(); + assertNotNull(filter.getConfiguration()); + } + + @Test + void contextConstructor_setsDefaultConfiguration() { + TemplateFilter filter = new TemplateFilter(new Context()); + assertNotNull(filter.getConfiguration()); + } + + @Test + void contextAndNextConstructor_setsDefaultConfiguration() { + Directory directory = + new Directory( + new Context(), + LocalReference.createClapReference( + TemplateFilterTestCase.class.getPackage())); + TemplateFilter filter = new TemplateFilter(new Context(), directory); + assertNotNull(filter.getConfiguration()); + } + + @Test + void constructorWithObjectDataModel_setsDataModel() { + Object dataModel = "myDataModel"; + TemplateFilter filter = new TemplateFilter(new Context(), null, dataModel); + assertSame(dataModel, filter.getDataModel()); + } + + @Test + void constructorWithResolverDataModel_setsDataModel() { + Resolver resolver = + new Resolver<>() { + @Override + public Object resolve(String name) { + return "resolved-" + name; + } + }; + TemplateFilter filter = new TemplateFilter(new Context(), null, resolver); + assertSame(resolver, filter.getDataModel()); + } + + @Test + void setConfiguration_replacesConfiguration() { + TemplateFilter filter = new TemplateFilter(); + Configuration original = filter.getConfiguration(); + Configuration replacement = + new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); + filter.setConfiguration(replacement); + assertNotSame(original, filter.getConfiguration()); + assertSame(replacement, filter.getConfiguration()); + } + + @Test + void setDataModel_replacesDataModel() { + TemplateFilter filter = new TemplateFilter(); + filter.setDataModel("myModel"); + assertEquals("myModel", filter.getDataModel()); + } + + @Test + void createDataModel_withNoConfiguredDataModel_createsResolverHashModel() { + TemplateFilter filter = new TemplateFilter(); + Request request = new Request(Method.GET, "/test"); + Response response = new Response(request); + Object dataModel = filter.createDataModel(request, response); + assertNotNull(dataModel); + } + + @Test + void createDataModel_withConfiguredDataModel_returnsConfiguredDataModel() { + TemplateFilter filter = new TemplateFilter(); + filter.setDataModel("myModel"); + Request request = new Request(Method.GET, "/test"); + Response response = new Response(request); + assertEquals("myModel", filter.createDataModel(request, response)); + } + @BeforeAll static void setUpTestCases() { Engine.clearThreadLocalVariables(); diff --git a/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/internal/ResolverHashModelTestCase.java b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/internal/ResolverHashModelTestCase.java new file mode 100644 index 0000000000..8c728b15c4 --- /dev/null +++ b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/internal/ResolverHashModelTestCase.java @@ -0,0 +1,77 @@ +/** + * 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.freemarker.internal; + +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.assertSame; + +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; +import freemarker.template.TemplateScalarModel; +import org.junit.jupiter.api.Test; +import org.restlet.util.Resolver; + +class ResolverHashModelTestCase { + + @Test + void get_withUnresolvedKey_returnsNull() throws TemplateModelException { + ResolverHashModel model = + new ResolverHashModel( + new Resolver<>() { + @Override + public Object resolve(String name) { + return null; + } + }); + assertNull(model.get("anyKey")); + } + + @Test + void get_withScalarValue_returnsScalarModel() throws TemplateModelException { + ResolverHashModel model = + new ResolverHashModel( + new Resolver<>() { + @Override + public Object resolve(String name) { + return "value-" + name; + } + }); + TemplateModel result = model.get("myKey"); + assertEquals("value-myKey", ((TemplateScalarModel) result).getAsString()); + } + + @Test + void get_withTemplateModelValue_returnsSameInstance() throws TemplateModelException { + TemplateScalarModel scalar = () -> "already-a-template-model"; + ResolverHashModel model = + new ResolverHashModel( + new Resolver<>() { + @Override + public Object resolve(String name) { + return scalar; + } + }); + assertSame(scalar, model.get("myKey")); + } + + @Test + void isEmpty_alwaysReturnsFalse() throws TemplateModelException { + ResolverHashModel model = + new ResolverHashModel( + new Resolver<>() { + @Override + public Object resolve(String name) { + return null; + } + }); + assertFalse(model.isEmpty()); + } +} diff --git a/org.restlet.ext.gson/src/test/java/org/restlet/ext/gson/GsonTestCase.java b/org.restlet.ext.gson/src/test/java/org/restlet/ext/gson/GsonTestCase.java index 36b7535afd..464d65b88d 100644 --- a/org.restlet.ext.gson/src/test/java/org/restlet/ext/gson/GsonTestCase.java +++ b/org.restlet.ext.gson/src/test/java/org/restlet/ext/gson/GsonTestCase.java @@ -10,17 +10,25 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; 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 com.google.gson.GsonBuilder; import com.google.gson.annotations.Since; import java.io.IOException; +import java.text.DateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import org.joda.time.DateTime; 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.resource.VariantInfo; import org.restlet.representation.EmptyRepresentation; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; @@ -219,4 +227,185 @@ final void testToRepresentationObjectVariantResource() { Representation rep1 = gsonConverter.toRepresentation(user, v1, null); assertNull(rep1); } + + @Test + final void testToRepresentation_withGsonRepresentationSource_returnsSameInstance() { + Representation gsonRep = new GsonRepresentation<>(user); + Variant v = new Variant(MediaType.APPLICATION_JSON); + + Representation result = gsonConverter.toRepresentation(gsonRep, v, null); + + assertSame(gsonRep, result); + } + + @Test + final void testToRepresentation_withNullTargetMediaType_setsJsonMediaType() { + Variant v = new Variant(); + assertNull(v.getMediaType()); + + Representation result = gsonConverter.toRepresentation(user, v, null); + + assertEquals(MediaType.APPLICATION_JSON, v.getMediaType()); + assertNotNull(result); + assertEquals(MediaType.APPLICATION_JSON, result.getMediaType()); + } + + @Test + final void testGetObjectClasses_withJsonVariant_returnsObjectAndGsonRepresentationClasses() { + Variant jsonVariant = new Variant(MediaType.APPLICATION_JSON); + + List> classes = gsonConverter.getObjectClasses(jsonVariant); + + assertNotNull(classes); + assertTrue(classes.contains(Object.class)); + assertTrue(classes.contains(GsonRepresentation.class)); + } + + @Test + final void testGetObjectClasses_withIncompatibleVariant_returnsNull() { + Variant xmlVariant = new Variant(MediaType.APPLICATION_XML); + + List> classes = gsonConverter.getObjectClasses(xmlVariant); + + assertNull(classes); + } + + @Test + final void testGetVariants_withNonNullSource_returnsJsonVariant() { + List variants = gsonConverter.getVariants(Object.class); + + assertNotNull(variants); + assertEquals(1, variants.size()); + assertTrue(variants.get(0).isCompatible(new Variant(MediaType.APPLICATION_JSON))); + } + + @Test + final void testGetVariants_withNullSource_returnsEmptyList() { + List variants = gsonConverter.getVariants(null); + + assertNotNull(variants); + assertTrue(variants.isEmpty()); + } + + @Test + final void testScoreObjectVariantResource_withNullTarget_returnsHalf() { + float score = gsonConverter.score(user, null, null); + + assertEquals(0.5F, score); + } + + @Test + final void testScoreObjectVariantResource_withIncompatibleTarget_returnsHalf() { + Variant v = new Variant(MediaType.APPLICATION_XML); + + float score = gsonConverter.score(user, v, null); + + assertEquals(0.5F, score); + } + + @Test + final void testScoreRepresentationClassOfTResource_withGsonRepresentationTarget_returnsOne() { + Representation source = new StringRepresentation("plain text", MediaType.TEXT_PLAIN); + + float score = gsonConverter.score(source, GsonRepresentation.class, null); + + assertEquals(1.0F, score); + } + + @Test + final void testScoreRepresentationClassOfTResource_withNoMatch_returnsMinusOne() { + Representation source = new StringRepresentation("plain text", MediaType.TEXT_PLAIN); + + float score = gsonConverter.score(source, User.class, null); + + assertEquals(-1.0F, score); + } + + @Test + final void testToObject_withPlainJsonRepresentation_createsGsonRepresentationInternally() + throws IOException { + final String userAsJsonString = + "{\"loginId\":\"hello\",\"password\":\"secret\",\"rate\":1,\"active\":true,\"createAt\":\"2012-05-20T15:41:01.489+08:00\",\"lastLogin\":\"2012-05-20T15:41:01.489+08:00\"}"; + Representation source = + new StringRepresentation(userAsJsonString, MediaType.APPLICATION_JSON); + + User result = gsonConverter.toObject(source, User.class, null); + + assertNotNull(result); + assertEquals("hello", result.getLoginId()); + } + + @Test + final void testToObject_withTargetGsonRepresentationClass_returnsGsonRepresentation() + throws IOException { + final String userAsJsonString = + "{\"loginId\":\"hello\",\"password\":\"secret\",\"rate\":1,\"active\":true,\"createAt\":\"2012-05-20T15:41:01.489+08:00\",\"lastLogin\":\"2012-05-20T15:41:01.489+08:00\"}"; + Representation source = + new StringRepresentation(userAsJsonString, MediaType.APPLICATION_JSON); + + Object result = gsonConverter.toObject(source, GsonRepresentation.class, null); + + assertInstanceOf(GsonRepresentation.class, result); + } + + @Test + final void testToObject_withIncompatibleRepresentation_returnsNull() throws IOException { + Representation source = new StringRepresentation("plain text", MediaType.TEXT_PLAIN); + + User result = gsonConverter.toObject(source, User.class, null); + + assertNull(result); + } + + @Test + final void testUpdatePreferences_addsJsonPreference() { + List> preferences = new ArrayList<>(); + + gsonConverter.updatePreferences(preferences, User.class); + + assertFalse(preferences.isEmpty()); + assertEquals(MediaType.APPLICATION_JSON, preferences.get(0).getMetadata()); + } + + @Test + final void testSetObject_updatesWrappedObject() throws IOException { + GsonRepresentation rep = new GsonRepresentation<>(user); + User other = new User("other", "pwd", 2, false, new Date(), new Date()); + + rep.setObject(other); + + assertEquals(other, rep.getObject()); + } + + @Test + @SuppressWarnings({"unchecked", "rawtypes"}) + final void testSetObjectClass_updatesObjectClass() { + GsonRepresentation rep = new GsonRepresentation<>(user); + + rep.setObjectClass(String.class); + + assertEquals(String.class, rep.getObjectClass()); + } + + @Test + final void testSetBuilder_updatesBuilder() { + GsonRepresentation rep = new GsonRepresentation<>(user); + GsonBuilder customBuilder = new GsonBuilder(); + customBuilder.setDateFormat(DateFormat.SHORT, DateFormat.SHORT); + + rep.setBuilder(customBuilder); + + assertSame(customBuilder, rep.getBuilder()); + } + + @Test + final void testWrite_withSourceRepresentation_delegatesToIt() throws IOException { + final String userAsJsonString = + "{\"loginId\":\"hello\",\"password\":\"secret\",\"rate\":1,\"active\":true,\"createAt\":\"2012-05-20T15:41:01.489+08:00\",\"lastLogin\":\"2012-05-20T15:41:01.489+08:00\"}"; + Representation source = + new StringRepresentation(userAsJsonString, MediaType.APPLICATION_JSON); + GsonRepresentation gsonRep = new GsonRepresentation<>(source, User.class); + + assertEquals(userAsJsonString, gsonRep.getText()); + } } diff --git a/org.restlet.ext.jaas/src/test/java/org/restlet/ext/jaas/JaasVerifierTestCase.java b/org.restlet.ext.jaas/src/test/java/org/restlet/ext/jaas/JaasVerifierTestCase.java index 1590d5639f..d68478e9a6 100644 --- a/org.restlet.ext.jaas/src/test/java/org/restlet/ext/jaas/JaasVerifierTestCase.java +++ b/org.restlet.ext.jaas/src/test/java/org/restlet/ext/jaas/JaasVerifierTestCase.java @@ -12,17 +12,97 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.security.Principal; +import java.util.Collections; +import java.util.Map; +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; +import javax.security.auth.login.Configuration; +import javax.security.auth.spi.LoginModule; import org.junit.jupiter.api.Test; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.Method; import org.restlet.data.Reference; +import org.restlet.security.Role; +import org.restlet.security.User; import org.restlet.security.Verifier; /** Unit tests for {@link JaasVerifier}. */ class JaasVerifierTestCase { + /** Simple principal implementation used to exercise the JAAS login flow. */ + public static class TestPrincipal implements Principal { + private final String name; + + public TestPrincipal(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + } + + /** Login module that always succeeds and adds a new principal to the subject. */ + public static class AddingPrincipalLoginModule implements LoginModule { + private Subject subject; + + @Override + public void initialize( + Subject subject, + CallbackHandler callbackHandler, + Map sharedState, + Map options) { + this.subject = subject; + } + + @Override + public boolean login() { + return true; + } + + @Override + public boolean commit() { + subject.getPrincipals().add(new TestPrincipal("newUser")); + return true; + } + + @Override + public boolean abort() { + return false; + } + + @Override + public boolean logout() { + return true; + } + } + + /** + * Creates a JAAS {@link Configuration} backed by a single login module entry for the given + * login module class. + */ + private static Configuration createConfiguration( + Class loginModuleClass) { + return new Configuration() { + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + return new AppConfigurationEntry[] { + new AppConfigurationEntry( + loginModuleClass.getName(), + LoginModuleControlFlag.REQUIRED, + Collections.emptyMap()) + }; + } + }; + } + @Test void testConstructor() { JaasVerifier verifier = new JaasVerifier("myApp"); @@ -67,4 +147,86 @@ void testVerifyReturnsInvalidWhenLoginFails() { assertEquals(Verifier.RESULT_INVALID, result); } + + @Test + void testSetConfiguration() { + JaasVerifier verifier = new JaasVerifier("myApp"); + Configuration configuration = createConfiguration(AddingPrincipalLoginModule.class); + + verifier.setConfiguration(configuration); + + assertEquals(configuration, verifier.getConfiguration()); + } + + @Test + void testVerifySucceedsAndMergesPrincipalsWithoutDuplicates() { + JaasVerifier verifier = new JaasVerifier("testRealm"); + verifier.setConfiguration(createConfiguration(AddingPrincipalLoginModule.class)); + + Request request = new Request(Method.GET, new Reference("http://localhost/test")); + Response response = new Response(request); + + User user = new User("alice"); + Role role = new Role(null, "admin"); + Principal extraPrincipal = new TestPrincipal("extra"); + + request.getClientInfo().setUser(user); + request.getClientInfo().getRoles().add(role); + request.getClientInfo().getPrincipals().add(extraPrincipal); + + int result = verifier.verify(request, response); + + assertEquals(Verifier.RESULT_VALID, result); + // The user and existing principals must not be duplicated in the principals list. + assertEquals( + 1, Collections.frequency(request.getClientInfo().getPrincipals(), extraPrincipal)); + assertTrue( + request.getClientInfo().getPrincipals().stream() + .noneMatch(principal -> principal.equals(user))); + // The principal added by the login module must be merged into the principals list. + assertTrue( + request.getClientInfo().getPrincipals().stream() + .anyMatch( + principal -> + principal instanceof TestPrincipal + && "newUser".equals(principal.getName()))); + // No new user was created since one was already set. + assertEquals(user, request.getClientInfo().getUser()); + } + + @Test + void testVerifyCreatesUserWhenPrincipalMatchesConfiguredClassName() { + JaasVerifier verifier = new JaasVerifier("testRealm"); + verifier.setConfiguration(createConfiguration(AddingPrincipalLoginModule.class)); + verifier.setUserPrincipalClassName(TestPrincipal.class.getName()); + + Request request = new Request(Method.GET, new Reference("http://localhost/test")); + Response response = new Response(request); + + assertNull(request.getClientInfo().getUser()); + + int result = verifier.verify(request, response); + + assertEquals(Verifier.RESULT_VALID, result); + assertNotNull(request.getClientInfo().getUser()); + assertEquals("newUser", request.getClientInfo().getUser().getIdentifier()); + } + + @Test + void testVerifyDoesNotOverwriteExistingUserEvenWhenPrincipalClassMatches() { + JaasVerifier verifier = new JaasVerifier("testRealm"); + verifier.setConfiguration(createConfiguration(AddingPrincipalLoginModule.class)); + verifier.setUserPrincipalClassName(TestPrincipal.class.getName()); + + Request request = new Request(Method.GET, new Reference("http://localhost/test")); + Response response = new Response(request); + + User existingUser = new User("bob"); + request.getClientInfo().setUser(existingUser); + + int result = verifier.verify(request, response); + + assertEquals(Verifier.RESULT_VALID, result); + assertEquals(existingUser, request.getClientInfo().getUser()); + } } diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringComponentTestCase.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringComponentTestCase.java new file mode 100644 index 0000000000..267226886e --- /dev/null +++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringComponentTestCase.java @@ -0,0 +1,116 @@ +/** + * 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.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.restlet.Client; +import org.restlet.Restlet; +import org.restlet.Server; +import org.restlet.data.Protocol; + +/** Unit tests for {@link SpringComponent}. */ +class SpringComponentTestCase { + + private static class TestRestlet extends Restlet {} + + private SpringComponent component; + + @BeforeEach + void setUpEach() { + this.component = new SpringComponent(); + } + + @AfterEach + void tearDownEach() { + this.component = null; + } + + @Test + void testSetClientAddsClientInstance() { + Client client = new Client(Protocol.FILE); + this.component.setClient(client); + assertEquals(1, this.component.getClients().size(), "Wrong number of clients"); + assertTrue(this.component.getClients().contains(client), "Client instance not added"); + } + + @Test + void testSetClientAddsProtocolInstance() { + this.component.setClient(Protocol.HTTP); + assertEquals(1, this.component.getClients().size(), "Wrong number of clients"); + assertEquals(Protocol.HTTP, this.component.getClients().get(0).getProtocols().get(0)); + } + + @Test + void testSetClientAddsProtocolNamedByString() { + this.component.setClient("FILE"); + assertEquals(1, this.component.getClients().size(), "Wrong number of clients"); + assertEquals(Protocol.FILE, this.component.getClients().get(0).getProtocols().get(0)); + } + + @Test + void testSetClientsListIgnoresNullAndUnknownEntries() { + List clients = Arrays.asList("FILE", null, 42); + this.component.setClientsList(clients); + assertEquals(1, this.component.getClients().size(), "Only the valid entry should be added"); + } + + @Test + void testSetClientsListWithMultipleEntries() { + Client client = new Client(Protocol.FILE); + List clients = Arrays.asList("HTTP", Protocol.HTTPS, client); + this.component.setClientsList(clients); + assertEquals(3, this.component.getClients().size(), "Wrong number of clients"); + } + + @Test + void testSetDefaultTargetAttachesToDefaultHost() { + TestRestlet target = new TestRestlet(); + this.component.setDefaultTarget(target); + assertEquals( + target, + this.component.getDefaultHost().getRoutes().get(0).getNext(), + "Default target not attached to the default host"); + } + + @Test + void testSetServerAddsServerInstance() { + Server server = new Server(Protocol.HTTP, 0); + this.component.setServer(server); + assertEquals(1, this.component.getServers().size(), "Wrong number of servers"); + assertTrue(this.component.getServers().contains(server), "Server instance not added"); + } + + @Test + void testSetServerAddsProtocolNamedByString() { + this.component.setServer("HTTP"); + assertEquals(1, this.component.getServers().size(), "Wrong number of servers"); + assertEquals(Protocol.HTTP, this.component.getServers().get(0).getProtocols().get(0)); + } + + @Test + void testSetServersListIgnoresNullAndUnknownEntries() { + List servers = Arrays.asList("HTTP", null, 3.14); + this.component.setServersList(servers); + assertEquals(1, this.component.getServers().size(), "Only the valid entry should be added"); + } + + @Test + void testSetServersListWithEmptyList() { + this.component.setServersList(Collections.emptyList()); + assertEquals(0, this.component.getServers().size(), "No servers should be added"); + } +} diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringFinderTestCase.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringFinderTestCase.java new file mode 100644 index 0000000000..4bb0ef607f --- /dev/null +++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringFinderTestCase.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.spring; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.resource.ServerResource; +import org.restlet.routing.Router; + +/** Unit tests for {@link SpringFinder}. */ +class SpringFinderTestCase { + + public static class SamplePrivateConstructorResource extends ServerResource { + private SamplePrivateConstructorResource() {} + } + + public static class SampleResource extends ServerResource {} + + @Test + void testConstructorWithContext() { + Context context = new Context(); + SpringFinder finder = new SpringFinder(context); + assertNotNull(finder.getContext(), "Finder context should not be null"); + } + + @Test + void testConstructorWithContextAndTargetClass() { + Context context = new Context(); + SpringFinder finder = new SpringFinder(context, SampleResource.class); + assertInstanceOf( + SampleResource.class, + finder.create(), + "Finder should create instances of the target class"); + } + + @Test + void testConstructorWithNoArguments() { + SpringFinder finder = new SpringFinder(); + assertNull(finder.getTargetClass(), "Target class should be null by default"); + } + + @Test + void testConstructorWithParentRestlet() { + Context context = new Context(); + Restlet parent = new Router(context); + SpringFinder finder = new SpringFinder(parent); + assertNotNull(finder.getContext(), "Finder context should come from the parent Restlet"); + } + + @Test + void testCreateDelegatesToNoArgCreate() { + SpringFinder finder = new SpringFinder(); + finder.setTargetClass(SampleResource.class); + + Request request = new Request(); + Response response = new Response(request); + + ServerResource resource = finder.create(request, response); + assertInstanceOf( + SampleResource.class, resource, "Should create an instance of SampleResource"); + } + + @Test + void testCreateLongFormDelegatesToNoArgCreate() { + SpringFinder finder = new SpringFinder(); + finder.setTargetClass(SampleResource.class); + + Request request = new Request(); + Response response = new Response(request); + + ServerResource resource = finder.create(SampleResource.class, request, response); + assertInstanceOf( + SampleResource.class, resource, "Should create an instance of SampleResource"); + } + + @Test + void testCreateReturnsInstanceOfTargetClass() { + SpringFinder finder = new SpringFinder(); + finder.setTargetClass(SampleResource.class); + + ServerResource resource = finder.create(); + assertInstanceOf( + SampleResource.class, resource, "Should create an instance of SampleResource"); + } + + @Test + void testCreateReturnsNullWhenInstantiationFails() { + SpringFinder finder = new SpringFinder(); + finder.setTargetClass(SamplePrivateConstructorResource.class); + + ServerResource resource = finder.create(); + assertNull(resource, "Should return null when the target class cannot be instantiated"); + } + + @Test + void testCreateReturnsNullWhenNoTargetClassSet() { + SpringFinder finder = new SpringFinder(); + assertNull(finder.create(), "Should return null when no target class is set"); + } +} diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringHostTestCase.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringHostTestCase.java new file mode 100644 index 0000000000..083ada46a9 --- /dev/null +++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringHostTestCase.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.spring; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.restlet.Component; +import org.restlet.Context; +import org.restlet.Restlet; +import org.restlet.ext.spring.resources.UserResource; + +/** Unit tests for {@link SpringHost}. */ +class SpringHostTestCase { + + private static class TestRestlet extends Restlet {} + + private SpringHost host; + + @BeforeEach + void setUpEach() { + this.host = new SpringHost(new Context()); + } + + @AfterEach + void tearDownEach() { + this.host = null; + } + + @Test + void testConstructorWithComponent() { + Component component = new Component(); + SpringHost componentHost = new SpringHost(component); + assertNotNull(componentHost.getContext(), "Host context should come from the component"); + } + + @Test + void testSetAttachmentSetsContextOnContextlessRestlet() { + TestRestlet target = new TestRestlet(); + this.host.setAttachment("/target", target); + + assertNotNull(target.getContext(), "Target should have received a context"); + assertEquals(1, this.host.getRoutes().size(), "One route should have been attached"); + } + + @Test + void testSetAttachmentWithClass() { + this.host.setAttachment("/user", UserResource.class); + assertEquals(1, this.host.getRoutes().size(), "One route should have been attached"); + } + + @Test + void testSetAttachmentsAttachesAllRoutes() { + Map routes = new HashMap<>(); + routes.put("/one", new TestRestlet()); + routes.put("/two", new TestRestlet()); + this.host.setAttachments(routes); + assertEquals(2, this.host.getRoutes().size(), "Both routes should have been attached"); + } + + @Test + void testSetDefaultAttachment() { + TestRestlet target = new TestRestlet(); + this.host.setDefaultAttachment(target); + assertEquals(1, this.host.getRoutes().size(), "Default route should have been attached"); + assertEquals( + target, + this.host.getRoutes().get(0).getNext(), + "Default route should point to target"); + } + + @Test + void testSetAttachmentInstanceMethodSingleRoute() { + this.host.setAttachments(Collections.singletonMap("/one", new TestRestlet())); + assertEquals(1, this.host.getRoutes().size(), "One route should have been attached"); + } +} diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringRouterTestCase.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringRouterTestCase.java new file mode 100644 index 0000000000..b28da36336 --- /dev/null +++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringRouterTestCase.java @@ -0,0 +1,130 @@ +/** + * 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.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Restlet; +import org.restlet.ext.spring.resources.UserResource; +import org.restlet.routing.Route; +import org.restlet.routing.Router; +import org.restlet.routing.TemplateRoute; + +/** Unit tests for {@link SpringRouter}. */ +class SpringRouterTestCase { + + private static class TestRestlet extends Restlet {} + + private SpringRouter router; + + @BeforeEach + void setUpEach() { + this.router = new SpringRouter(); + } + + @AfterEach + void tearDownEach() { + this.router = null; + } + + @Test + void testConstructorWithContext() { + SpringRouter contextRouter = new SpringRouter(new Context()); + assertNotNull(contextRouter.getContext(), "Router context should not be null"); + } + + @Test + void testConstructorWithNoArguments() { + assertEquals(0, this.router.getRoutes().size(), "New router should have no routes"); + } + + @Test + void testConstructorWithParentRestlet() { + Router parent = new Router(new Context()); + SpringRouter childRouter = new SpringRouter(parent); + assertNotNull( + childRouter.getContext(), "Router context should come from the parent Restlet"); + } + + @Test + void testSetAttachmentIgnoresUnknownObjectType() { + SpringRouter.setAttachment(this.router, "/unknown", 42); + assertEquals(0, this.router.getRoutes().size(), "No route should have been attached"); + } + + @Test + void testSetAttachmentIgnoresUnresolvableClassName() { + SpringRouter.setAttachment(this.router, "/missing", "com.example.DoesNotExist"); + assertEquals(0, this.router.getRoutes().size(), "No route should have been attached"); + } + + @Test + void testSetAttachmentIgnoresStringForNonResourceClass() { + SpringRouter.setAttachment(this.router, "/notaresource", "java.lang.String"); + assertEquals(0, this.router.getRoutes().size(), "No route should have been attached"); + } + + @Test + void testSetAttachmentWithClass() { + SpringRouter.setAttachment(this.router, "/user", UserResource.class); + assertEquals(1, this.router.getRoutes().size(), "One route should have been attached"); + } + + @Test + void testSetAttachmentWithClassName() { + SpringRouter.setAttachment( + this.router, "/user", "org.restlet.ext.spring.resources.UserResource"); + assertEquals(1, this.router.getRoutes().size(), "One route should have been attached"); + } + + @Test + void testSetAttachmentWithRestlet() { + TestRestlet target = new TestRestlet(); + SpringRouter.setAttachment(this.router, "/target", target); + + Route route = this.router.getRoutes().get(0); + assertInstanceOf(TemplateRoute.class, route, "Attached route should be a TemplateRoute"); + assertEquals(target, route.getNext(), "Attached route should point to the given Restlet"); + } + + @Test + void testSetAttachmentsAttachesAllRoutes() { + Map routes = new HashMap<>(); + routes.put("/one", new TestRestlet()); + routes.put("/two", new TestRestlet()); + SpringRouter.setAttachments(this.router, routes); + assertEquals(2, this.router.getRoutes().size(), "Both routes should have been attached"); + } + + @Test + void testSetAttachmentsInstanceMethod() { + this.router.setAttachments(Collections.singletonMap("/one", new TestRestlet())); + assertEquals(1, this.router.getRoutes().size(), "One route should have been attached"); + } + + @Test + void testSetDefaultAttachment() { + TestRestlet target = new TestRestlet(); + this.router.setDefaultAttachment(target); + assertEquals(1, this.router.getRoutes().size(), "Default route should have been attached"); + assertEquals( + target, + this.router.getRoutes().get(0).getNext(), + "Default route should point to target"); + } +} diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringServerTestCase.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringServerTestCase.java new file mode 100644 index 0000000000..21333d076c --- /dev/null +++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringServerTestCase.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.ext.spring; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Properties; +import org.junit.jupiter.api.Test; +import org.restlet.data.Protocol; + +/** Unit tests for {@link SpringServer}. */ +class SpringServerTestCase { + + @Test + void testConstructorWithProtocol() { + SpringServer server = new SpringServer("HTTP"); + assertTrue( + server.getProtocols().contains(Protocol.HTTP), + "Server should support HTTP protocol"); + } + + @Test + void testConstructorWithProtocolAndAddressAndPort() { + SpringServer server = new SpringServer("HTTP", "127.0.0.1", 8112); + assertTrue( + server.getProtocols().contains(Protocol.HTTP), + "Server should support HTTP protocol"); + assertEquals(8112, server.getPort(), "Wrong server port"); + assertEquals("127.0.0.1", server.getAddress(), "Wrong server address"); + } + + @Test + void testConstructorWithProtocolAndPort() { + SpringServer server = new SpringServer("HTTP", 8111); + assertTrue( + server.getProtocols().contains(Protocol.HTTP), + "Server should support HTTP protocol"); + assertEquals(8111, server.getPort(), "Wrong server port"); + } + + @Test + void testSetParametersAddsAllProperties() { + SpringServer server = new SpringServer("HTTP"); + Properties parameters = new Properties(); + parameters.setProperty("key1", "value1"); + parameters.setProperty("key2", "value2"); + + server.setParameters(parameters); + + assertEquals("value1", server.getContext().getParameters().getFirstValue("key1")); + assertEquals("value2", server.getContext().getParameters().getFirstValue("key2")); + } + + @Test + void testSetParametersWithEmptyProperties() { + SpringServer server = new SpringServer("HTTP"); + server.setParameters(new Properties()); + assertEquals( + 0, + server.getContext().getParameters().subList("key1").size(), + "No parameters should have been added"); + } +} diff --git a/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/RepresentationResourceLoaderTestCase.java b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/RepresentationResourceLoaderTestCase.java new file mode 100644 index 0000000000..b5bafac839 --- /dev/null +++ b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/RepresentationResourceLoaderTestCase.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.ext.velocity; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.Reader; +import java.util.Date; +import org.apache.velocity.Template; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.util.ExtProperties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; + +/** Unit tests for {@link RepresentationResourceLoader}. */ +class RepresentationResourceLoaderTestCase { + + /** A representation whose reader always fails, to simulate an I/O error while loading. */ + private static class BrokenRepresentation extends StringRepresentation { + BrokenRepresentation(String text, MediaType mediaType) { + super(text, mediaType); + } + + @Override + public Reader getReader() throws IOException { + throw new IOException("Simulated I/O error"); + } + } + + @AfterEach + void tearDown() { + RepresentationResourceLoader.getStore().clear(); + } + + @Test + void getResourceReader_sourceInStore_returnsStoredRepresentationContent() throws Exception { + RepresentationResourceLoader.getStore() + .put("storedKey", new StringRepresentation("stored-content")); + RepresentationResourceLoader loader = new RepresentationResourceLoader(null); + + Reader reader = loader.getResourceReader("storedKey", "UTF-8"); + + assertEquals("stored-content", IoUtils.toString(reader)); + } + + @Test + void getResourceReader_sourceNotInStoreWithDefault_returnsDefaultRepresentationContent() + throws Exception { + Representation defaultRepresentation = new StringRepresentation("default-content"); + RepresentationResourceLoader loader = + new RepresentationResourceLoader(defaultRepresentation); + + Reader reader = loader.getResourceReader("unknownKey", "UTF-8"); + + assertEquals("default-content", IoUtils.toString(reader)); + } + + @Test + void getResourceReader_sourceNotInStoreAndNoDefault_throwsResourceNotFoundException() { + RepresentationResourceLoader loader = new RepresentationResourceLoader(null); + + assertThrows( + ResourceNotFoundException.class, + () -> loader.getResourceReader("missingKey", "UTF-8")); + } + + @Test + void getResourceReader_whenReadingFails_wrapsIOExceptionInResourceNotFoundException() { + RepresentationResourceLoader loader = + new RepresentationResourceLoader( + new BrokenRepresentation("content", MediaType.TEXT_PLAIN)); + + ResourceNotFoundException e = + assertThrows( + ResourceNotFoundException.class, + () -> loader.getResourceReader("anyKey", "UTF-8")); + assertTrue(e.getCause() instanceof IOException); + } + + @Test + void getLastModified_resourceInStore_returnsStoredModificationDateMillis() { + Date date = new Date(1_000_000L); + Representation representation = new StringRepresentation("content"); + representation.setModificationDate(date); + RepresentationResourceLoader.getStore().put("modKey", representation); + + Template resource = new Template(); + resource.setName("modKey"); + + RepresentationResourceLoader loader = new RepresentationResourceLoader(null); + assertEquals(date.getTime(), loader.getLastModified(resource)); + } + + @Test + void getLastModified_resourceNotInStore_returnsZero() { + Template resource = new Template(); + resource.setName("absentKey"); + + RepresentationResourceLoader loader = new RepresentationResourceLoader(null); + assertEquals(0L, loader.getLastModified(resource)); + } + + @Test + void isSourceModified_whenLastModifiedDiffers_returnsTrue() { + Date date = new Date(2_000_000L); + Representation representation = new StringRepresentation("content"); + representation.setModificationDate(date); + RepresentationResourceLoader.getStore().put("modKey2", representation); + + Template resource = new Template(); + resource.setName("modKey2"); + resource.setLastModified(date.getTime() - 1000); + + RepresentationResourceLoader loader = new RepresentationResourceLoader(null); + assertTrue(loader.isSourceModified(resource)); + } + + @Test + void isSourceModified_whenLastModifiedMatches_returnsFalse() { + Date date = new Date(3_000_000L); + Representation representation = new StringRepresentation("content"); + representation.setModificationDate(date); + RepresentationResourceLoader.getStore().put("modKey3", representation); + + Template resource = new Template(); + resource.setName("modKey3"); + resource.setLastModified(date.getTime()); + + RepresentationResourceLoader loader = new RepresentationResourceLoader(null); + assertFalse(loader.isSourceModified(resource)); + } + + @Test + void init_doesNotThrow() { + RepresentationResourceLoader loader = new RepresentationResourceLoader(null); + assertDoesNotThrow(() -> loader.init(new ExtProperties())); + } +} diff --git a/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/TemplateFilterTestCase.java b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/TemplateFilterTestCase.java index 10a065535f..4a0451afec 100644 --- a/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/TemplateFilterTestCase.java +++ b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/TemplateFilterTestCase.java @@ -9,7 +9,12 @@ package org.restlet.ext.velocity; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import java.io.IOException; +import java.io.Reader; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.restlet.Application; @@ -18,11 +23,16 @@ import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; +import org.restlet.data.Encoding; import org.restlet.data.LocalReference; +import org.restlet.data.MediaType; import org.restlet.data.Method; import org.restlet.data.Protocol; +import org.restlet.data.Status; import org.restlet.engine.Engine; +import org.restlet.representation.StringRepresentation; import org.restlet.resource.Directory; +import org.restlet.util.Resolver; /** * Test case for template filters. @@ -46,6 +56,113 @@ void representationShouldNotBeUsedAsTemplate() throws Exception { assertEquals("Method=${m}/Path=${rp}", response.getEntity().getText()); } + /** A Restlet that always answers with a fixed Velocity-encoded entity. */ + private static class VelocityEntityRestlet extends Restlet { + private final String content; + + VelocityEntityRestlet(String content) { + this.content = content; + } + + @Override + public void handle(Request request, Response response) { + StringRepresentation entity = new StringRepresentation(content, MediaType.TEXT_PLAIN); + entity.getEncodings().add(Encoding.VELOCITY); + response.setEntity(entity); + } + } + + /** A representation whose reader always fails, used to trigger a ResourceNotFoundException. */ + private static class BrokenVelocityRepresentation extends StringRepresentation { + BrokenVelocityRepresentation(String text, MediaType mediaType) { + super(text, mediaType); + getEncodings().add(Encoding.VELOCITY); + } + + @Override + public Reader getReader() throws IOException { + throw new IOException("Simulated I/O error"); + } + } + + @Test + void defaultConstructor_createsUsableFilter() { + TemplateFilter filter = new TemplateFilter(); + assertNull(filter.getContext()); + } + + @Test + void constructorWithContextOnly_setsContext() { + Context context = new Context(); + TemplateFilter filter = new TemplateFilter(context); + assertEquals(context, filter.getContext()); + } + + @Test + void constructorWithMapDataModel_usesMapDataModelWhenHandling() { + Map dataModel = new HashMap<>(); + dataModel.put("value", "fromMap"); + + TemplateFilter filter = + new TemplateFilter( + new Context(), new VelocityEntityRestlet("Value=$value"), dataModel); + + Response response = filter.handle(new Request(Method.GET, "/")); + + assertEquals(MediaType.TEXT_PLAIN, response.getEntity().getMediaType()); + assertEquals(true, response.getEntity() instanceof TemplateRepresentation); + } + + @Test + void constructorWithResolverDataModel_usesResolverDataModelWhenHandling() { + Resolver resolver = + new Resolver() { + @Override + public Object resolve(String name) { + return "value".equals(name) ? "fromResolver" : null; + } + }; + + TemplateFilter filter = + new TemplateFilter( + new Context(), new VelocityEntityRestlet("Value=$value"), resolver); + + Response response = filter.handle(new Request(Method.GET, "/")); + + assertEquals(MediaType.TEXT_PLAIN, response.getEntity().getMediaType()); + assertEquals(true, response.getEntity() instanceof TemplateRepresentation); + } + + @Test + void afterHandle_whenTemplateResourceCannotBeRead_setsStatusNotFound() { + TemplateFilter filter = + new TemplateFilter( + new Context(), + new Restlet() { + @Override + public void handle(Request request, Response response) { + response.setEntity( + new BrokenVelocityRepresentation( + "content", MediaType.TEXT_PLAIN)); + } + }); + + Response response = filter.handle(new Request(Method.GET, "/")); + + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, response.getStatus()); + } + + @Test + void afterHandle_whenTemplateHasParseError_setsStatusInternalServerError() { + TemplateFilter filter = + new TemplateFilter( + new Context(), new VelocityEntityRestlet("#if($unclosedDirective)")); + + Response response = filter.handle(new Request(Method.GET, "/")); + + assertEquals(Status.SERVER_ERROR_INTERNAL, response.getStatus()); + } + /** * Internal class used for test purpose * diff --git a/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/TemplateRepresentationTestCase.java b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/TemplateRepresentationTestCase.java new file mode 100644 index 0000000000..f8f5ecdc43 --- /dev/null +++ b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/TemplateRepresentationTestCase.java @@ -0,0 +1,198 @@ +/** + * 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.TreeMap; +import org.apache.velocity.Template; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; +import org.restlet.representation.WriterRepresentation; +import org.restlet.util.Resolver; + +/** + * Unit tests for {@link TemplateRepresentation}, focused on its less commonly exercised branches. + */ +class TemplateRepresentationTestCase { + + /** A minimal representation that never exposes a character set nor a modification date. */ + private static class RawRepresentation extends WriterRepresentation { + private final String content; + + RawRepresentation(String content, MediaType mediaType) { + super(mediaType); + this.content = content; + } + + @Override + public void write(Writer writer) throws IOException { + writer.write(content); + } + } + + private File testDir; + + @AfterEach + void tearDown() { + if (testDir != null) { + IoUtils.delete(testDir, true); + } + } + + private static org.apache.velocity.context.Context getInternalContext(TemplateRepresentation tr) + throws Exception { + Field field = TemplateRepresentation.class.getDeclaredField("context"); + field.setAccessible(true); + return (org.apache.velocity.context.Context) field.get(tr); + } + + private static Resolver knownValueResolver() { + return new Resolver() { + @Override + public Object resolve(String name) { + return "known".equals(name) ? "value1" : null; + } + }; + } + + @Test + void resolverContext_containsKey_reflectsResolverPresenceOfValue() throws Exception { + TemplateRepresentation tr = new TemplateRepresentation("template.vm", MediaType.TEXT_PLAIN); + tr.setDataModel(knownValueResolver()); + + org.apache.velocity.context.Context context = getInternalContext(tr); + + assertTrue(context.containsKey("known")); + assertFalse(context.containsKey("unknown")); + } + + @Test + void resolverContext_get_delegatesToResolver() throws Exception { + TemplateRepresentation tr = new TemplateRepresentation("template.vm", MediaType.TEXT_PLAIN); + tr.setDataModel(knownValueResolver()); + + org.apache.velocity.context.Context context = getInternalContext(tr); + + assertEquals("value1", context.get("known")); + } + + @Test + void resolverContext_getKeys_returnsNull() throws Exception { + TemplateRepresentation tr = new TemplateRepresentation("template.vm", MediaType.TEXT_PLAIN); + tr.setDataModel(knownValueResolver()); + + org.apache.velocity.context.Context context = getInternalContext(tr); + + assertNull(context.getKeys()); + } + + @Test + void resolverContext_put_returnsNullAndDoesNotStoreValue() throws Exception { + TemplateRepresentation tr = new TemplateRepresentation("template.vm", MediaType.TEXT_PLAIN); + tr.setDataModel(knownValueResolver()); + + org.apache.velocity.context.Context context = getInternalContext(tr); + + assertNull(context.put("known", "other")); + // The resolver is still consulted; nothing was actually stored. + assertEquals("value1", context.get("known")); + } + + @Test + void resolverContext_remove_returnsNull() throws Exception { + TemplateRepresentation tr = new TemplateRepresentation("template.vm", MediaType.TEXT_PLAIN); + tr.setDataModel(knownValueResolver()); + + org.apache.velocity.context.Context context = getInternalContext(tr); + + assertNull(context.remove("known")); + } + + @Test + void constructorWithTemplateAndMapDataModel_mergesProvidedValues() throws Exception { + testDir = new File(System.getProperty("java.io.tmpdir"), "TemplateRepresentationTestCase"); + testDir.mkdir(); + File testFile = File.createTempFile("test", ".vm", testDir); + + try (FileWriter fw = new FileWriter(testFile)) { + fw.write("Value=$value"); + } + + TemplateRepresentation base = + new TemplateRepresentation(testFile.getName(), MediaType.TEXT_PLAIN); + base.getEngine().setProperty("file.resource.loader.path", testDir.getAbsolutePath()); + Template template = base.getTemplate(); + + Map map = new TreeMap<>(); + map.put("value", "myValue2"); + + TemplateRepresentation tr = new TemplateRepresentation(template, map, MediaType.TEXT_PLAIN); + + assertEquals(MediaType.TEXT_PLAIN, tr.getMediaType()); + assertEquals("Value=myValue2", tr.getText()); + } + + @Test + void constructorWithRepresentationAndMediaType_withoutCharsetOrModDate_usesDefaults() + throws Exception { + RawRepresentation raw = new RawRepresentation("Value=fixed", MediaType.TEXT_PLAIN); + + TemplateRepresentation tr = new TemplateRepresentation(raw, MediaType.TEXT_PLAIN); + + assertEquals("Value=fixed", tr.getText()); + } + + @Test + void constructorWithRepresentationMapAndMediaType_withoutCharset_usesDefaultCharset() + throws Exception { + RawRepresentation raw = new RawRepresentation("Value=$value", MediaType.TEXT_PLAIN); + Map map = new TreeMap<>(); + map.put("value", "myValue3"); + + TemplateRepresentation tr = new TemplateRepresentation(raw, map, MediaType.TEXT_PLAIN); + + assertEquals("Value=myValue3", tr.getText()); + } + + @Test + void getTemplate_whenTemplateCannotBeLoaded_logsWarningAndReturnsNull() { + Context previous = Context.getCurrent(); + try { + Context.setCurrent(new Context()); + TemplateRepresentation tr = + new TemplateRepresentation("doesNotExist.vm", MediaType.TEXT_PLAIN); + assertNull(tr.getTemplate()); + } finally { + Context.setCurrent(previous); + } + } + + @Test + void getText_whenTemplateCannotBeLoaded_throwsIOException() { + TemplateRepresentation tr = + new TemplateRepresentation("doesNotExist2.vm", MediaType.TEXT_PLAIN); + + IOException e = assertThrows(IOException.class, tr::getText); + assertTrue(e.getMessage().startsWith("Template processing error.")); + } +} diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/DomRepresentationTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/DomRepresentationTestCase.java index 662901e8d9..cb59dc67e7 100644 --- a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/DomRepresentationTestCase.java +++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/DomRepresentationTestCase.java @@ -12,15 +12,20 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; +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.io.StringReader; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.dom.DOMSource; import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; import org.restlet.data.MediaType; +import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.w3c.dom.Document; +import org.xml.sax.InputSource; /** Unit tests for {@link DomRepresentation}. */ class DomRepresentationTestCase { @@ -91,4 +96,53 @@ void release_onDocumentWrapper_createsNewDocumentOnNextAccess() throws Exception assertNotNull(newDoc); assertNotSame(doc, newDoc); } + + @Test + void constructor_withNullRepresentation_hasNullMediaTypeAndIsUnavailable() { + DomRepresentation dr = new DomRepresentation((Representation) null); + assertNull(dr.getMediaType()); + assertFalse(dr.isAvailable()); + } + + @Test + void getInputSource_withoutXmlRepresentation_returnsInputSourceWithNullStream() + throws Exception { + DomRepresentation dr = new DomRepresentation(); + InputSource inputSource = dr.getInputSource(); + assertNotNull(inputSource); + assertNull(inputSource.getByteStream()); + } + + @Test + void getInputSource_withUnavailableXmlRepresentation_returnsInputSourceWithNullStream() + throws Exception { + StringRepresentation rep = new StringRepresentation(XML, MediaType.TEXT_XML); + rep.setAvailable(false); + DomRepresentation dr = new DomRepresentation(rep); + InputSource inputSource = dr.getInputSource(); + assertNull(inputSource.getByteStream()); + } + + @Test + void write_withExplicitCharacterSet_usesItForEncoding() throws Exception { + DomRepresentation dr = + new DomRepresentation(new StringRepresentation(XML, MediaType.TEXT_XML)); + dr.setCharacterSet(CharacterSet.UTF_8); + String result = dr.getText(); + assertTrue(result.contains("root")); + } + + @Test + void write_withDoctypePublicAndSystemIds_appliesDoctypeOutputProperties() throws Exception { + String xmlWithDoctype = + "" + + "" + + "hello"; + DomRepresentation dr = + new DomRepresentation(new StringRepresentation(xmlWithDoctype, MediaType.TEXT_XML)); + // Avoid any network/filesystem access for the external DTD referenced above. + dr.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader(""))); + String result = dr.getText(); + assertTrue(result.contains("root")); + } } diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/NodeListTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/NodeListTestCase.java new file mode 100644 index 0000000000..50a0e3f7f2 --- /dev/null +++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/NodeListTestCase.java @@ -0,0 +1,86 @@ +/** + * 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; + +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 javax.xml.parsers.DocumentBuilderFactory; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** Unit tests for {@link NodeList}. */ +class NodeListTestCase { + + private static final String XML = ""; + + private static org.w3c.dom.NodeList domNodeList() throws Exception { + Document document = + DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .parse(new org.xml.sax.InputSource(new java.io.StringReader(XML))); + return document.getDocumentElement().getChildNodes(); + } + + @Test + void get_returnsWrappedNodeAtIndex() throws Exception { + NodeList nodeList = new NodeList(domNodeList()); + assertEquals("a", nodeList.get(0).getNodeName()); + assertEquals("b", nodeList.get(1).getNodeName()); + assertEquals("c", nodeList.get(2).getNodeName()); + } + + @Test + void item_returnsSameNodeAsGet() throws Exception { + NodeList nodeList = new NodeList(domNodeList()); + Node viaGet = nodeList.get(1); + Node viaItem = nodeList.item(1); + assertEquals(viaGet, viaItem); + } + + @Test + void getLength_matchesUnderlyingNodeListLength() throws Exception { + org.w3c.dom.NodeList wrapped = domNodeList(); + NodeList nodeList = new NodeList(wrapped); + assertEquals(wrapped.getLength(), nodeList.getLength()); + } + + @Test + void size_matchesGetLength() throws Exception { + NodeList nodeList = new NodeList(domNodeList()); + assertEquals(nodeList.getLength(), nodeList.size()); + } + + @Test + void isEmpty_falseWhenNodesPresent() throws Exception { + NodeList nodeList = new NodeList(domNodeList()); + assertFalse(nodeList.isEmpty()); + } + + @Test + void iterator_iteratesOverAllWrappedNodes() throws Exception { + NodeList nodeList = new NodeList(domNodeList()); + int count = 0; + for (Node node : nodeList) { + assertNotNull(node); + count++; + } + assertEquals(3, count); + } + + @Test + void asList_supportsStandardListOperations() throws Exception { + NodeList nodeList = new NodeList(domNodeList()); + assertTrue(nodeList.contains(nodeList.get(0))); + assertEquals(0, nodeList.indexOf(nodeList.get(0))); + } +} 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 f3e0aa27fe..09f06b94d5 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 @@ -24,6 +24,7 @@ import javax.xml.transform.sax.SAXSource; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; +import org.restlet.data.Reference; import org.restlet.representation.StringRepresentation; import org.w3c.dom.Document; import org.xml.sax.Attributes; @@ -139,4 +140,46 @@ void setSaxSource_overridesSource() throws Exception { sr.setSaxSource(source); assertEquals(source, sr.getSaxSource()); } + + @Test + void getSaxSource_withXmlRepresentationSource_delegatesToItsSaxSource() throws Exception { + DomRepresentation domSource = + new DomRepresentation(new StringRepresentation(XML, MediaType.TEXT_XML)); + SaxRepresentation sr = new SaxRepresentation(domSource); + assertNotNull(sr.getSaxSource()); + } + + @Test + void getSaxSource_withCustomFeatures_stillBuildsSource() throws Exception { + StringRepresentation rep = new StringRepresentation(XML, MediaType.TEXT_XML); + SaxRepresentation sr = new SaxRepresentation(rep); + sr.setNamespaceAware(true); + sr.setXIncludeAware(true); + sr.setSecureProcessing(true); + sr.setExpandingEntityRefs(false); + assertNotNull(sr.getSaxSource()); + } + + @Test + void getSaxSource_withLocationRef_setsSystemId() throws Exception { + StringRepresentation rep = new StringRepresentation(XML, MediaType.TEXT_XML); + rep.setLocationRef(new Reference("http://example.com/loc.xml")); + SaxRepresentation sr = new SaxRepresentation(rep); + assertEquals("http://example.com/loc.xml", sr.getSaxSource().getSystemId()); + } + + @Test + void parse_withMalformedXml_throwsIOException() { + SaxRepresentation sr = + new SaxRepresentation(new StringRepresentation("not xml", MediaType.TEXT_XML)); + assertThrows(IOException.class, () -> sr.parse(new DefaultHandler())); + } + + @Test + void release_releasesUnderlyingXmlRepresentation() throws Exception { + StringRepresentation rep = new StringRepresentation(XML, MediaType.TEXT_XML); + SaxRepresentation sr = new SaxRepresentation(rep); + sr.release(); + assertFalse(rep.isAvailable()); + } } diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformRepresentationTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformRepresentationTestCase.java index 8bd92bd79f..aebc8eaff0 100644 --- a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformRepresentationTestCase.java +++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformRepresentationTestCase.java @@ -9,11 +9,31 @@ package org.restlet.ext.xml; 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.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.URIResolver; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; +import org.restlet.data.Reference; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; +import org.xml.sax.XMLFilter; /** * Test case for the {@link TransformRepresentation} class. @@ -74,4 +94,189 @@ void testDoubleTransform() throws Exception { final String result = tr2.getText(); assertEquals(this.output2, result); } + + @Test + void toSaxSource_withXmlRepresentationSource_usesItsSaxSource() throws Exception { + DomRepresentation domSource = new DomRepresentation(this.source); + TransformRepresentation tr = new TransformRepresentation(domSource, this.xslt1); + assertEquals(this.output1, tr.getText()); + } + + @Test + void toSaxSource_defaultCase_setsSystemIdFromLocationRef() throws Exception { + StringRepresentation rep = new StringRepresentation("", MediaType.TEXT_XML); + rep.setLocationRef(new Reference("http://example.com/src.xml")); + assertEquals( + "http://example.com/src.xml", + TransformRepresentation.toSaxSource(rep).getSystemId()); + } + + @Test + void constructor_withPrecompiledTemplates_transformsUsingThem() throws Exception { + Templates templates = + TransformerFactory.newInstance() + .newTemplates(new StreamSource(this.xslt1.getStream())); + TransformRepresentation tr = + new TransformRepresentation((URIResolver) null, this.source, templates); + assertEquals(this.output1, tr.getText()); + } + + @Test + void getTransformerHandler_returnsHandlerWhenTemplatesAvailable() throws Exception { + TransformRepresentation tr = new TransformRepresentation(this.source, this.xslt1); + TransformerHandler handler = tr.getTransformerHandler(); + assertNotNull(handler); + } + + @Test + void getTransformerHandler_withoutTransformSheetOrTemplates_returnsNull() throws Exception { + TransformRepresentation tr = + new TransformRepresentation(this.source, (Representation) null); + assertNull(tr.getTransformerHandler()); + } + + @Test + void getXmlFilter_returnsFilterWhenTemplatesAvailable() throws Exception { + TransformRepresentation tr = new TransformRepresentation(this.source, this.xslt1); + XMLFilter filter = tr.getXmlFilter(); + assertNotNull(filter); + } + + @Test + void getXmlFilter_withoutTransformSheetOrTemplates_returnsNull() throws Exception { + TransformRepresentation tr = + new TransformRepresentation(this.source, (Representation) null); + assertNull(tr.getXmlFilter()); + } + + @Test + void getTemplates_withTransformSheetLocationRef_setsSystemId() throws Exception { + StringRepresentation sheetWithLoc = + new StringRepresentation( + "" + + "" + + "" + + "" + + "" + + "", + MediaType.TEXT_XML); + sheetWithLoc.setLocationRef(new Reference("http://example.com/sheet.xsl")); + TransformRepresentation tr = new TransformRepresentation(this.source, sheetWithLoc); + assertNotNull(tr.getTemplates()); + } + + @Test + void getTransformer_appliesErrorListenerUriResolverParametersAndOutputProperties() + throws Exception { + TransformRepresentation tr = new TransformRepresentation(this.source, this.xslt1); + ErrorListener listener = + new ErrorListener() { + @Override + public void warning(TransformerException exception) {} + + @Override + public void error(TransformerException exception) {} + + @Override + public void fatalError(TransformerException exception) + throws TransformerException { + throw exception; + } + }; + tr.setErrorListener(listener); + tr.setUriResolver((href, base) -> null); + tr.getParameters().put("p1", "v1"); + tr.getOutputProperties().put(javax.xml.transform.OutputKeys.INDENT, "yes"); + + Transformer transformer = tr.getTransformer(); + + assertNotNull(transformer); + assertEquals("v1", transformer.getParameter("p1")); + } + + @Test + void release_clearsSourceTransformSheetTemplatesAndUriResolver() throws Exception { + TransformRepresentation tr = + new TransformRepresentation((href, base) -> null, this.source, this.xslt1); + // Force templates to be lazily created before release. + assertNotNull(tr.getTemplates()); + assertNotNull(tr.getSourceRepresentation()); + assertNotNull(tr.getTransformSheet()); + assertNotNull(tr.getUriResolver()); + + tr.release(); + + assertNull(tr.getSourceRepresentation()); + assertNull(tr.getTransformSheet()); + assertNull(tr.getUriResolver()); + } + + @Test + void settersAndGetters_roundTrip() throws Exception { + TransformRepresentation tr = new TransformRepresentation(this.source, this.xslt1); + + ErrorListener listener = + new ErrorListener() { + @Override + public void warning(TransformerException exception) {} + + @Override + public void error(TransformerException exception) {} + + @Override + public void fatalError(TransformerException exception) {} + }; + tr.setErrorListener(listener); + assertSame(listener, tr.getErrorListener()); + + Map outputProps = new HashMap<>(); + tr.setOutputProperties(outputProps); + assertSame(outputProps, tr.getOutputProperties()); + + Map params = new HashMap<>(); + tr.setParameters(params); + assertSame(params, tr.getParameters()); + + Representation newSource = new StringRepresentation("", MediaType.TEXT_XML); + tr.setSourceRepresentation(newSource); + assertSame(newSource, tr.getSourceRepresentation()); + + Templates templates = + TransformerFactory.newInstance() + .newTemplates(new StreamSource(this.xslt1.getStream())); + tr.setTemplates(templates); + assertSame(templates, tr.getTemplates()); + + Representation newSheet = new StringRepresentation("", MediaType.TEXT_XML); + tr.setTransformSheet(newSheet); + assertSame(newSheet, tr.getTransformSheet()); + + URIResolver resolver = (href, base) -> null; + tr.setUriResolver(resolver); + assertSame(resolver, tr.getUriResolver()); + } + + @Test + void transform_withoutTransformSheetOrTemplates_logsWarningAndDoesNothing() throws Exception { + TransformRepresentation tr = + new TransformRepresentation(this.source, (Representation) null); + StreamResult result = new StreamResult(new StringWriter()); + tr.transform(new StreamSource(new StringReader("")), result); + } + + @Test + void doubleTransform_withFailingInnerTransform_wrapsTransformerExceptionAsIOException() { + Representation failingXslt = + new StringRepresentation( + "" + + "" + + "" + + "boom" + + "" + + "", + MediaType.TEXT_XML); + TransformRepresentation tr1 = new TransformRepresentation(this.source, failingXslt); + TransformRepresentation tr2 = new TransformRepresentation(tr1, this.xslt2); + assertThrows(IOException.class, tr2::getText); + } } diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/XmlRepresentationTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/XmlRepresentationTestCase.java index 4863c4fee6..248b372487 100644 --- a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/XmlRepresentationTestCase.java +++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/XmlRepresentationTestCase.java @@ -8,17 +8,28 @@ */ package org.restlet.ext.xml; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.StringReader; import java.util.Iterator; +import javax.xml.XMLConstants; import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; +import org.restlet.data.Reference; +import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; import org.w3c.dom.Node; /** @@ -143,4 +154,288 @@ void setIgnoringExtraWhitespaces_alsoSetsValidatingDtd() { assertTrue(dr.isIgnoringExtraWhitespaces()); assertTrue(dr.isValidatingDtd()); } + + private static final String XSD = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + + private static final String VALID_FOR_XSD = "hello"; + + @Test + void getSaxSource_static_withRepresentation_returnsNonNull() throws Exception { + assertNotNull( + XmlRepresentation.getSaxSource(new StringRepresentation(XML, MediaType.TEXT_XML))); + } + + @Test + void getSaxSource_static_withNullRepresentation_returnsNull() throws Exception { + assertNull(XmlRepresentation.getSaxSource(null)); + } + + @Test + void getSaxSource_static_withLocationRef_setsSystemId() throws Exception { + StringRepresentation rep = new StringRepresentation(XML, MediaType.TEXT_XML); + rep.setLocationRef(new Reference("http://example.com/doc.xml")); + assertEquals( + "http://example.com/doc.xml", XmlRepresentation.getSaxSource(rep).getSystemId()); + } + + @Test + void getSaxSource_instance_returnsNonNull() throws Exception { + assertNotNull(dom().getSaxSource()); + } + + @Test + void equals_sameInstance_returnsTrue() { + DomRepresentation dr = dom(); + assertTrue(dr.equals(dr)); + } + + @Test + void hashCode_isStable() { + DomRepresentation dr = dom(); + assertEquals(dr.hashCode(), dr.hashCode()); + } + + @Test + void release_clearsNamespacesMap() { + DomRepresentation dr = dom(); + dr.getNamespaces().put("ex", "http://example.com/"); + dr.release(); + assertTrue(dr.getNamespaces().isEmpty()); + } + + @Test + void setEntityResolver_getEntityResolver_roundTrip() { + DomRepresentation dr = dom(); + org.xml.sax.EntityResolver resolver = (publicId, systemId) -> null; + dr.setEntityResolver(resolver); + assertEquals(resolver, dr.getEntityResolver()); + } + + @Test + void setErrorHandler_getErrorHandler_roundTrip() { + DomRepresentation dr = dom(); + org.xml.sax.ErrorHandler handler = new org.xml.sax.helpers.DefaultHandler(); + dr.setErrorHandler(handler); + assertEquals(handler, dr.getErrorHandler()); + } + + @Test + void setExpandingEntityRefs_isExpandingEntityRefs_roundTrip() { + DomRepresentation dr = dom(); + dr.setExpandingEntityRefs(true); + assertTrue(dr.isExpandingEntityRefs()); + } + + @Test + void setIgnoringComments_isIgnoringComments_roundTrip() { + DomRepresentation dr = dom(); + dr.setIgnoringComments(true); + assertTrue(dr.isIgnoringComments()); + } + + @Test + void setNamespaces_getNamespaces_roundTrip() { + DomRepresentation dr = dom(); + java.util.Map map = new java.util.HashMap<>(); + map.put("a", "b"); + dr.setNamespaces(map); + assertEquals(map, dr.getNamespaces()); + } + + @Test + void setXIncludeAware_isXIncludeAware_roundTrip() { + DomRepresentation dr = dom(); + assertFalse(dr.isXIncludeAware()); + dr.setXIncludeAware(true); + assertTrue(dr.isXIncludeAware()); + } + + @Test + void setSchema_compiledSchema_getSchema_roundTrip() throws Exception { + Schema schema = + SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI) + .newSchema(new StreamSource(new StringReader(XSD))); + DomRepresentation dr = dom(); + dr.setSchema(schema); + assertEquals(schema, dr.getSchema()); + } + + @Test + void setSchema_withW3cSchemaRepresentation_compilesSchema() { + DomRepresentation dr = dom(); + dr.setSchema(new StringRepresentation(XSD, MediaType.APPLICATION_W3C_SCHEMA)); + assertNotNull(dr.getSchema()); + } + + @Test + void setSchema_withNullRepresentation_leavesSchemaNull() { + DomRepresentation dr = dom(); + dr.setSchema((Representation) null); + assertNull(dr.getSchema()); + } + + @Test + void setSchema_withRelaxNgCompactMediaType_doesNotThrow() { + DomRepresentation dr = dom(); + assertDoesNotThrow( + () -> + dr.setSchema( + new StringRepresentation( + "not a schema", MediaType.APPLICATION_RELAXNG_COMPACT))); + } + + @Test + void setSchema_withRelaxNgXmlMediaType_doesNotThrow() { + DomRepresentation dr = dom(); + assertDoesNotThrow( + () -> + dr.setSchema( + new StringRepresentation( + "", MediaType.APPLICATION_RELAXNG_XML))); + } + + @Test + void setSchema_withUnknownMediaType_doesNotThrow() { + DomRepresentation dr = dom(); + assertDoesNotThrow( + () -> dr.setSchema(new StringRepresentation("data", MediaType.TEXT_PLAIN))); + assertNull(dr.getSchema()); + } + + @Test + void validate_withCompiledSchema_succeedsForValidDocument() throws Exception { + Schema schema = + SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI) + .newSchema(new StreamSource(new StringReader(XSD))); + DomRepresentation dr = + new DomRepresentation(new StringRepresentation(VALID_FOR_XSD, MediaType.TEXT_XML)); + dr.validate(schema); + } + + @Test + void validate_withSchemaRepresentation_succeedsForValidDocument() throws Exception { + DomRepresentation dr = + new DomRepresentation(new StringRepresentation(VALID_FOR_XSD, MediaType.TEXT_XML)); + dr.validate(new StringRepresentation(XSD, MediaType.APPLICATION_W3C_SCHEMA)); + } + + @Test + void validate_withSchemaRepresentationAndResult_writesAugmentedResult() throws Exception { + DomRepresentation dr = + new DomRepresentation(new StringRepresentation(VALID_FOR_XSD, MediaType.TEXT_XML)); + // The validated source is a SAXSource, so the result must be null or a SAXResult. + javax.xml.transform.sax.SAXResult result = + new javax.xml.transform.sax.SAXResult(new org.xml.sax.helpers.DefaultHandler()); + dr.validate(new StringRepresentation(XSD, MediaType.APPLICATION_W3C_SCHEMA), result); + } + + @Test + void getStreamSource_withLocationRef_setsSystemId() throws Exception { + DomRepresentation dr = dom(); + dr.setLocationRef(new Reference("http://example.com/catalog.xml")); + assertEquals("http://example.com/catalog.xml", dr.getStreamSource().getSystemId()); + } + + @Test + void getText_withMalformedXml_throwsIllegalArgumentException() { + DomRepresentation dr = + new DomRepresentation(new StringRepresentation("not xml", MediaType.TEXT_XML)); + assertThrows(IllegalArgumentException.class, () -> dr.getText("/a")); + } + + @Test + void getText_withInvalidXPathExpression_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> dom().getText("///invalid[[")); + } + + @Test + void getDomSource_baseImplementation_returnsWrappedDocument() throws Exception { + SaxRepresentation sr = + new SaxRepresentation(new StringRepresentation(XML, MediaType.TEXT_XML)); + javax.xml.transform.dom.DOMSource source = sr.getDomSource(); + assertNotNull(source); + assertNotNull(source.getNode()); + } + + @Test + void getDomSource_withLocationRef_setsSystemId() throws Exception { + StringRepresentation rep = new StringRepresentation(XML, MediaType.TEXT_XML); + SaxRepresentation sr = new SaxRepresentation(rep); + sr.setLocationRef(new Reference("http://example.com/loc.xml")); + assertEquals("http://example.com/loc.xml", sr.getDomSource().getSystemId()); + } + + @Test + void getDomSource_withMalformedXml_throwsIOException() { + SaxRepresentation sr = + new SaxRepresentation(new StringRepresentation("not xml", MediaType.TEXT_XML)); + assertThrows(java.io.IOException.class, sr::getDomSource); + } + + @Test + void getDocument_baseImplementation_parsesSourceRepresentation() throws Exception { + SaxRepresentation sr = + new SaxRepresentation(new StringRepresentation(XML, MediaType.TEXT_XML)); + Document document = sr.getDocument(); + assertNotNull(document); + assertEquals("catalog", document.getDocumentElement().getTagName()); + } + + @Test + void getDocumentBuilder_withVariousOptions_stillBuilds() throws Exception { + DomRepresentation dr = dom(); + dr.setCoalescing(true); + dr.setIgnoringComments(true); + dr.setXIncludeAware(true); + assertNotNull(dr.getDocumentBuilder()); + } + + @Test + void appendTextContent_coversCommentCdataAndProcessingInstructionNodes() throws Exception { + Document doc = + javax.xml.parsers.DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .newDocument(); + assertEquals("a comment", XmlRepresentation.getTextContent(doc.createComment("a comment"))); + assertEquals( + "cdata text", + XmlRepresentation.getTextContent(doc.createCDATASection("cdata text"))); + assertEquals( + "pi data", + XmlRepresentation.getTextContent( + doc.createProcessingInstruction("target", "pi data"))); + } + + @Test + void appendTextContent_coversAttributeAndDocumentFragmentNodes() throws Exception { + Document doc = + javax.xml.parsers.DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .newDocument(); + Attr attr = doc.createAttribute("attr"); + attr.setValue("attr-value"); + assertEquals("attr-value", XmlRepresentation.getTextContent(attr)); + + DocumentFragment fragment = doc.createDocumentFragment(); + fragment.appendChild(doc.createTextNode("frag-text")); + assertEquals("frag-text", XmlRepresentation.getTextContent(fragment)); + } + + @Test + void appendTextContent_unhandledNodeType_returnsEmptyString() throws Exception { + Document doc = + javax.xml.parsers.DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .newDocument(); + // Document nodes are not text-bearing and fall into the default (no-op) branch. + assertEquals("", XmlRepresentation.getTextContent(doc)); + } } diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/XmlWriterTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/XmlWriterTestCase.java index a11b05b763..d129e41a23 100644 --- a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/XmlWriterTestCase.java +++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/XmlWriterTestCase.java @@ -15,7 +15,11 @@ import java.io.ByteArrayOutputStream; import java.io.StringWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import javax.xml.parsers.SAXParserFactory; import org.junit.jupiter.api.Test; +import org.xml.sax.XMLReader; import org.xml.sax.helpers.AttributesImpl; /** Unit tests for {@link XmlWriter}. */ @@ -245,4 +249,225 @@ void dataFormat_withIndentStep_producesNewlinesAndIndentation() throws Exception assertTrue(result.contains("\n")); assertTrue(result.contains(" ")); } + + @Test + void constructor_noArgs_usesStandardOutByDefault() { + XmlWriter w = new XmlWriter(); + assertNotNull(w.getWriter()); + } + + @Test + void constructor_nullWriter_fallsBackToStandardOut() { + XmlWriter w = new XmlWriter((Writer) null); + assertNotNull(w.getWriter()); + } + + @Test + void setOutput_null_fallsBackToStandardOut() { + XmlWriter w = new XmlWriter(sw()); + w.setOutput(null); + assertNotNull(w.getWriter()); + } + + @Test + void constructor_outputStreamAndCharset_writesDocument() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XmlWriter w = new XmlWriter(baos, StandardCharsets.UTF_8); + w.startDocument(); + w.dataElement("root", "ok"); + w.endDocument(); + assertTrue(baos.toString(StandardCharsets.UTF_8).contains("ok")); + } + + @Test + void constructor_outputStreamAndCharsetEncoder_writesDocument() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XmlWriter w = new XmlWriter(baos, StandardCharsets.UTF_8.newEncoder()); + w.startDocument(); + w.dataElement("root", "ok"); + w.endDocument(); + assertTrue(baos.toString(StandardCharsets.UTF_8).contains("ok")); + } + + @Test + void constructor_outputStreamAndCharsetName_writesDocument() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XmlWriter w = new XmlWriter(baos, "UTF-8"); + w.startDocument(); + w.dataElement("root", "ok"); + w.endDocument(); + assertTrue(baos.toString("UTF-8").contains("ok")); + } + + @Test + void constructor_withXmlReaderParent_canBeUsedAsFilter() throws Exception { + XMLReader parent = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); + XmlWriter w = new XmlWriter(parent); + StringWriter out = sw(); + w.setOutput(out); + w.startDocument(); + w.startElement("root"); + w.endElement("root"); + w.endDocument(); + assertTrue(out.toString().contains("")); + } + + @Test + void constructor_withXmlReaderParentAndWriter_writesDocument() throws Exception { + XMLReader parent = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); + StringWriter out = sw(); + XmlWriter w = new XmlWriter(parent, out); + w.startDocument(); + w.dataElement("root", "value"); + w.endDocument(); + assertTrue(out.toString().contains("value")); + } + + @Test + void startElement_uriAndLocalName_writesStartTag() throws Exception { + StringWriter out = sw(); + XmlWriter w = new XmlWriter(out); + w.startDocument(); + w.startElement("", "root"); + w.endElement("", "root"); + w.endDocument(); + assertTrue(out.toString().contains("")); + assertTrue(out.toString().contains("")); + } + + @Test + void emptyElement_asRootElement_forcesNamespaceDeclarationOnRoot() throws Exception { + StringWriter out = sw(); + XmlWriter w = new XmlWriter(out); + w.forceNSDecl("http://example.com/root-ns", "r"); + w.startDocument(); + w.emptyElement("http://example.com/root-ns", "root", "r:root", new AttributesImpl()); + w.endDocument(); + String result = out.toString(); + assertTrue(result.contains("xmlns:r=\"http://example.com/root-ns\"")); + assertTrue(result.contains("")); + } + + @Test + void writeAttributes_withXmlnsAttribute_forcesDefaultNamespaceDecl() throws Exception { + StringWriter out = sw(); + XmlWriter w = new XmlWriter(out); + w.setPrefix("http://example.com/default-ns", ""); + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", "xmlns", "CDATA", "http://example.com/default-ns"); + w.startDocument(); + w.startElement("", "root", "root", atts); + w.endElement("root"); + w.endDocument(); + assertTrue(out.toString().contains("xmlns=\"http://example.com/default-ns\"")); + } + + @Test + void writeAttributes_withXmlnsPrefixAttribute_forcesPrefixedNamespaceDecl() throws Exception { + StringWriter out = sw(); + XmlWriter w = new XmlWriter(out); + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "foo", "xmlns:foo", "CDATA", "http://example.com/foo-ns"); + w.startDocument(); + w.startElement("", "root", "root", atts); + w.endElement("root"); + w.endDocument(); + assertTrue(out.toString().contains("xmlns:foo=\"http://example.com/foo-ns\"")); + } + + @Test + void characters_withQuoteChar_notEscaped() throws Exception { + StringWriter out = sw(); + XmlWriter w = new XmlWriter(out); + w.startDocument(); + w.startElement("root"); + w.characters("say \"hi\""); + w.endElement("root"); + w.endDocument(); + assertTrue(out.toString().contains("say \"hi\"")); + } + + @Test + void startElement_withQNameNoColon_usesDefaultNamespace() throws Exception { + StringWriter out = sw(); + XmlWriter w = new XmlWriter(out); + AttributesImpl atts = new AttributesImpl(); + w.startDocument(); + w.startElement("http://example.com/newns", "root", "root", atts); + w.endElement("http://example.com/newns", "root", "root"); + w.endDocument(); + assertTrue(out.toString().contains("xmlns=\"http://example.com/newns\"")); + } + + @Test + void startElement_withQNameColon_usesQNamePrefix() throws Exception { + StringWriter out = sw(); + XmlWriter w = new XmlWriter(out); + AttributesImpl atts = new AttributesImpl(); + w.startDocument(); + w.startElement("http://example.com/newns2", "root", "ns:root", atts); + w.endElement("http://example.com/newns2", "root", "ns:root"); + w.endDocument(); + String result = out.toString(); + assertTrue(result.contains("xmlns:ns=\"http://example.com/newns2\"")); + assertTrue(result.contains(" void updatePreferences(List> preferences, Class entity) { if (Form.class.isAssignableFrom(entity)) { updatePreferences(preferences, MediaType.APPLICATION_WWW_FORM, 1.0F); + } else if (String.class.isAssignableFrom(entity) || Reader.class.isAssignableFrom(entity)) { + updatePreferences(preferences, TEXT_PLAIN, 1.0F); + updatePreferences(preferences, MediaType.TEXT_ALL, 0.5F); } else if (Serializable.class.isAssignableFrom(entity)) { if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED) { updatePreferences(preferences, MediaType.APPLICATION_JAVA_OBJECT, 1.0F); @@ -296,9 +299,6 @@ public void updatePreferences(List> preferences, Class if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED) { updatePreferences(preferences, MediaType.APPLICATION_JAVA_OBJECT_XML, 1.0F); } - } else if (String.class.isAssignableFrom(entity) || Reader.class.isAssignableFrom(entity)) { - updatePreferences(preferences, TEXT_PLAIN, 1.0F); - updatePreferences(preferences, MediaType.TEXT_ALL, 0.5F); } else if (InputStream.class.isAssignableFrom(entity)) { updatePreferences(preferences, APPLICATION_OCTET_STREAM, 1.0F); updatePreferences(preferences, MediaType.APPLICATION_ALL, 0.5F); diff --git a/org.restlet/src/main/java/org/restlet/engine/header/HeaderReader.java b/org.restlet/src/main/java/org/restlet/engine/header/HeaderReader.java index 02eba3f670..b5346e955f 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/HeaderReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/HeaderReader.java @@ -387,13 +387,14 @@ public String readComment() throws IOException { while (result == null) { next = read(); - if (isCommentText(next)) { - buffer.append((char) next); - } else if (isQuoteCharacter(next)) { + if (isQuoteCharacter(next)) { // Start of a quoted pair (escape sequence) buffer.append((char) read()); + } else if (isCommentText(next)) { + buffer.append((char) next); } else if (next == '(') { // Nested comment + unread(); buffer.append('(').append(readComment()).append(')'); } else if (next == ')') { // End of comment @@ -490,11 +491,11 @@ public String readQuotedString() throws IOException { while (result == null) { next = read(); - if (isQuotedText(next)) { - buffer.append((char) next); - } else if (isQuoteCharacter(next)) { + if (isQuoteCharacter(next)) { // Start of a quoted pair (escape sequence) buffer.append((char) read()); + } else if (isQuotedText(next)) { + buffer.append((char) next); } else if (isDoubleQuote(next)) { // End of quoted string result = buffer.toString(); diff --git a/org.restlet/src/main/java/org/restlet/engine/header/WarningWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/WarningWriter.java index 1d1fb4d130..2402cbf37b 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/WarningWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/WarningWriter.java @@ -54,6 +54,7 @@ public WarningWriter append(Warning warning) { appendQuotedString(text); if (warning.getDate() != null) { + append(" "); appendQuotedString(DateUtils.format(warning.getDate())); } diff --git a/org.restlet/src/main/java/org/restlet/representation/Variant.java b/org.restlet/src/main/java/org/restlet/representation/Variant.java index d6d14a780f..f68650032b 100644 --- a/org.restlet/src/main/java/org/restlet/representation/Variant.java +++ b/org.restlet/src/main/java/org/restlet/representation/Variant.java @@ -199,8 +199,7 @@ public MediaType getMediaType() { @Override public int hashCode() { - return SystemUtils.hashCode( - super.hashCode(), characterSet, encodings, locationRef, languages, mediaType); + return SystemUtils.hashCode(characterSet, encodings, locationRef, languages, mediaType); } /** diff --git a/org.restlet/src/main/java/org/restlet/util/NonNullItemsList.java b/org.restlet/src/main/java/org/restlet/util/NonNullItemsList.java index 34b300cc21..99b2c939fb 100644 --- a/org.restlet/src/main/java/org/restlet/util/NonNullItemsList.java +++ b/org.restlet/src/main/java/org/restlet/util/NonNullItemsList.java @@ -62,8 +62,9 @@ public boolean addAll(final int index, final Collection elements) { @Override public boolean equals(final Object obj) { - return super.equals(obj) - && Objects.equals(exceptionMessage, ((NonNullItemsList) obj).exceptionMessage); + return (obj instanceof NonNullItemsList that) + && super.equals(obj) + && Objects.equals(exceptionMessage, that.exceptionMessage); } @Override diff --git a/org.restlet/src/test/java/org/restlet/data/CacheDirectiveTestCase.java b/org.restlet/src/test/java/org/restlet/data/CacheDirectiveTestCase.java new file mode 100644 index 0000000000..081ea717a2 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/CacheDirectiveTestCase.java @@ -0,0 +1,214 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.CacheDirective}. */ +class CacheDirectiveTestCase { + + @Test + void maxAge_setsDigitValue() { + CacheDirective directive = CacheDirective.maxAge(3600); + assertEquals("max-age", directive.getName()); + assertEquals("3600", directive.getValue()); + assertTrue(directive.isDigit()); + } + + @Test + void maxStale_noArgument_hasNullValue() { + CacheDirective directive = CacheDirective.maxStale(); + assertEquals("max-stale", directive.getName()); + assertNull(directive.getValue()); + assertFalse(directive.isDigit()); + } + + @Test + void maxStale_withArgument_setsDigitValue() { + CacheDirective directive = CacheDirective.maxStale(120); + assertEquals("max-stale", directive.getName()); + assertEquals("120", directive.getValue()); + assertTrue(directive.isDigit()); + } + + @Test + void minFresh_setsDigitValue() { + CacheDirective directive = CacheDirective.minFresh(30); + assertEquals("min-fresh", directive.getName()); + assertEquals("30", directive.getValue()); + assertTrue(directive.isDigit()); + } + + @Test + void mustRevalidate_hasNoValue() { + CacheDirective directive = CacheDirective.mustRevalidate(); + assertEquals("must-revalidate", directive.getName()); + assertNull(directive.getValue()); + } + + @Test + void noCache_noArgument_hasNoValue() { + CacheDirective directive = CacheDirective.noCache(); + assertEquals("no-cache", directive.getName()); + assertNull(directive.getValue()); + } + + @Test + void noCache_withFieldName_quotesTheName() { + CacheDirective directive = CacheDirective.noCache("X-Custom"); + assertEquals("no-cache", directive.getName()); + assertEquals("\"X-Custom\"", directive.getValue()); + } + + @Test + void noCache_withFieldNames_joinsWithComma() { + CacheDirective directive = CacheDirective.noCache(Arrays.asList("A", "B")); + assertEquals("\"A\",\"B\"", directive.getValue()); + } + + @Test + void noCache_withNullFieldNames_producesEmptyValue() { + CacheDirective directive = CacheDirective.noCache((java.util.List) null); + assertEquals("", directive.getValue()); + } + + @Test + void noStore_hasNoValue() { + CacheDirective directive = CacheDirective.noStore(); + assertEquals("no-store", directive.getName()); + assertNull(directive.getValue()); + } + + @Test + void noTransform_hasNoValue() { + CacheDirective directive = CacheDirective.noTransform(); + assertEquals("no-transform", directive.getName()); + } + + @Test + void onlyIfCached_hasNoValue() { + CacheDirective directive = CacheDirective.onlyIfCached(); + assertEquals("only-if-cached", directive.getName()); + } + + @Test + void privateInfo_noArgument_hasNoValue() { + CacheDirective directive = CacheDirective.privateInfo(); + assertEquals("private", directive.getName()); + assertNull(directive.getValue()); + } + + @Test + void privateInfo_withFieldName_quotesTheName() { + CacheDirective directive = CacheDirective.privateInfo("X-Custom"); + assertEquals("private", directive.getName()); + assertEquals("\"X-Custom\"", directive.getValue()); + } + + @Test + void privateInfo_withFieldNames_joinsWithComma() { + CacheDirective directive = CacheDirective.privateInfo(Arrays.asList("A", "B")); + assertEquals("\"A\",\"B\"", directive.getValue()); + } + + @Test + void proxyMustRevalidate_hasNoValue() { + CacheDirective directive = CacheDirective.proxyMustRevalidate(); + assertEquals("proxy-revalidate", directive.getName()); + } + + @Test + void publicInfo_hasNoValue() { + CacheDirective directive = CacheDirective.publicInfo(); + assertEquals("public", directive.getName()); + } + + @Test + void sharedMaxAge_setsDigitValue() { + CacheDirective directive = CacheDirective.sharedMaxAge(600); + assertEquals("s-maxage", directive.getName()); + assertEquals("600", directive.getValue()); + assertTrue(directive.isDigit()); + } + + @Test + void constructor_withNameOnly_hasNoValueAndNotDigit() { + CacheDirective directive = new CacheDirective("custom"); + assertEquals("custom", directive.getName()); + assertNull(directive.getValue()); + assertFalse(directive.isDigit()); + } + + @Test + void constructor_withNameAndValue_isNotDigitByDefault() { + CacheDirective directive = new CacheDirective("custom", "value"); + assertEquals("value", directive.getValue()); + assertFalse(directive.isDigit()); + } + + @Test + void settersUpdateState() { + CacheDirective directive = new CacheDirective("custom", "value"); + directive.setName("other"); + directive.setValue("otherValue"); + directive.setDigit(true); + assertEquals("other", directive.getName()); + assertEquals("otherValue", directive.getValue()); + assertTrue(directive.isDigit()); + } + + @Test + void equals_sameNameValueDigit_returnsTrue() { + CacheDirective d1 = new CacheDirective("max-age", "10", true); + CacheDirective d2 = new CacheDirective("max-age", "10", true); + assertEquals(d1, d2); + assertEquals(d1.hashCode(), d2.hashCode()); + } + + @Test + void equals_sameInstance_returnsTrue() { + CacheDirective directive = CacheDirective.noStore(); + assertEquals(directive, directive); + } + + @Test + void equals_differentType_returnsFalse() { + CacheDirective directive = CacheDirective.noStore(); + assertNotEquals(directive, "no-store"); + } + + @Test + void equals_differentDigitFlag_returnsFalse() { + CacheDirective d1 = new CacheDirective("max-age", "10", true); + CacheDirective d2 = new CacheDirective("max-age", "10", false); + assertNotEquals(d1, d2); + } + + @Test + void equals_differentValue_returnsFalse() { + CacheDirective d1 = CacheDirective.maxAge(10); + CacheDirective d2 = CacheDirective.maxAge(20); + assertNotEquals(d1, d2); + } + + @Test + void toString_containsFields() { + CacheDirective directive = new CacheDirective("no-store", null, false); + String result = directive.toString(); + assertTrue(result.contains("no-store")); + assertTrue(result.contains("digit=false")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/ChallengeMessageTestCase.java b/org.restlet/src/test/java/org/restlet/data/ChallengeMessageTestCase.java new file mode 100644 index 0000000000..f235330093 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/ChallengeMessageTestCase.java @@ -0,0 +1,103 @@ +/** + * 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.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.junit.jupiter.api.Test; +import org.restlet.util.Series; + +/** + * Test {@link org.restlet.data.ChallengeMessage}, exercised through the concrete {@link + * ChallengeRequest} subclass since ChallengeMessage is abstract. + */ +class ChallengeMessageTestCase { + + @Test + void constructor_withSchemeOnly_hasNullRealmAndDigestAlgorithm() { + ChallengeMessage message = new ChallengeRequest(ChallengeScheme.HTTP_BASIC); + assertEquals(ChallengeScheme.HTTP_BASIC, message.getScheme()); + assertNull(message.getRealm()); + assertEquals(Digest.ALGORITHM_MD5, message.getDigestAlgorithm()); + assertNull(message.getOpaque()); + assertNull(message.getServerNonce()); + assertNull(message.getRawValue()); + } + + @Test + void constructor_withSchemeAndRealm() { + ChallengeMessage message = new ChallengeRequest(ChallengeScheme.HTTP_BASIC, "myRealm"); + assertEquals("myRealm", message.getRealm()); + } + + @Test + void getParameters_lazilyCreatesEmptySeries() { + ChallengeMessage message = new ChallengeRequest(ChallengeScheme.HTTP_BASIC); + assertNotNull(message.getParameters()); + assertEquals(0, message.getParameters().size()); + } + + @Test + void setParameters_replacesSeries() { + ChallengeMessage message = new ChallengeRequest(ChallengeScheme.HTTP_BASIC); + Series parameters = new Series<>(Parameter.class); + parameters.add("k", "v"); + message.setParameters(parameters); + assertSame(parameters, message.getParameters()); + } + + @Test + void settersUpdateState() { + ChallengeMessage message = new ChallengeRequest(ChallengeScheme.HTTP_BASIC); + message.setDigestAlgorithm(Digest.ALGORITHM_SHA_256); + message.setOpaque("opaque-value"); + message.setRawValue("raw-value"); + message.setRealm("realm"); + message.setScheme(ChallengeScheme.HTTP_DIGEST); + message.setServerNonce("nonce"); + + assertEquals(Digest.ALGORITHM_SHA_256, message.getDigestAlgorithm()); + assertEquals("opaque-value", message.getOpaque()); + assertEquals("raw-value", message.getRawValue()); + assertEquals("realm", message.getRealm()); + assertEquals(ChallengeScheme.HTTP_DIGEST, message.getScheme()); + assertEquals("nonce", message.getServerNonce()); + } + + @Test + void equals_sameSchemeRealmAndParameters_returnsTrue() { + ChallengeMessage m1 = new ChallengeRequest(ChallengeScheme.HTTP_BASIC, "realm"); + ChallengeMessage m2 = new ChallengeRequest(ChallengeScheme.HTTP_BASIC, "realm"); + assertEquals(m1, m2); + assertEquals(m1.hashCode(), m2.hashCode()); + } + + @Test + void equals_sameInstance_returnsTrue() { + ChallengeMessage message = new ChallengeRequest(ChallengeScheme.HTTP_BASIC); + assertEquals(message, message); + } + + @Test + void equals_differentType_returnsFalse() { + ChallengeMessage message = new ChallengeRequest(ChallengeScheme.HTTP_BASIC); + assertNotEquals(message, "not a challenge message"); + } + + @Test + void equals_differentRealm_returnsFalse() { + ChallengeMessage m1 = new ChallengeRequest(ChallengeScheme.HTTP_BASIC, "realm1"); + ChallengeMessage m2 = new ChallengeRequest(ChallengeScheme.HTTP_BASIC, "realm2"); + assertNotEquals(m1, m2); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/ChallengeRequestTestCase.java b/org.restlet/src/test/java/org/restlet/data/ChallengeRequestTestCase.java new file mode 100644 index 0000000000..02b93da36d --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/ChallengeRequestTestCase.java @@ -0,0 +1,124 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.ChallengeRequest}. */ +class ChallengeRequestTestCase { + + @Test + void constructor_withSchemeOnly_hasDefaults() { + ChallengeRequest request = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST); + assertEquals(ChallengeScheme.HTTP_DIGEST, request.getScheme()); + assertFalse(request.isStale()); + assertNull(request.getRealm()); + } + + @Test + void constructor_withRealm() { + ChallengeRequest request = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST, "myRealm"); + assertEquals("myRealm", request.getRealm()); + } + + @Test + void getDomainRefs_defaultsToRootReference() { + ChallengeRequest request = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST); + List refs = request.getDomainRefs(); + assertEquals(1, refs.size()); + assertEquals("/", refs.get(0).toString()); + } + + @Test + void setDomainRefs_replacesList() { + ChallengeRequest request = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST); + request.setDomainRefs(Arrays.asList(new Reference("/a"), new Reference("/b"))); + assertEquals(2, request.getDomainRefs().size()); + } + + @Test + void setDomainUris_convertsStringsToReferences() { + ChallengeRequest request = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST); + request.setDomainUris(Arrays.asList("/a", "/b")); + List refs = request.getDomainRefs(); + assertEquals(2, refs.size()); + assertEquals("/a", refs.get(0).toString()); + assertEquals("/b", refs.get(1).toString()); + } + + @Test + void setDomainUris_withNull_setsNullDomainRefs() { + ChallengeRequest request = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST); + request.setDomainUris(Arrays.asList("/a")); + request.setDomainUris(null); + // getDomainRefs() re-initializes the lazy default when null. + assertEquals(1, request.getDomainRefs().size()); + assertEquals("/", request.getDomainRefs().get(0).toString()); + } + + @Test + void getQualityOptions_defaultsToAuthentication() { + ChallengeRequest request = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST); + List options = request.getQualityOptions(); + assertEquals(1, options.size()); + assertEquals(ChallengeMessage.QUALITY_AUTHENTICATION, options.get(0)); + } + + @Test + void setQualityOptions_replacesList() { + ChallengeRequest request = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST); + request.setQualityOptions(Arrays.asList(ChallengeMessage.QUALITY_AUTHENTICATION_INTEGRITY)); + assertEquals(1, request.getQualityOptions().size()); + assertEquals( + ChallengeMessage.QUALITY_AUTHENTICATION_INTEGRITY, + request.getQualityOptions().get(0)); + } + + @Test + void setStale_updatesFlag() { + ChallengeRequest request = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST); + request.setStale(true); + assertTrue(request.isStale()); + } + + @Test + void equals_sameSchemeRealmAndParameters_returnsTrue() { + ChallengeRequest r1 = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST, "realm"); + ChallengeRequest r2 = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST, "realm"); + assertEquals(r1, r2); + assertEquals(r1.hashCode(), r2.hashCode()); + } + + @Test + void equals_sameInstance_returnsTrue() { + ChallengeRequest request = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST); + assertEquals(request, request); + } + + @Test + void equals_differentType_returnsFalse() { + ChallengeRequest request = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST); + assertNotEquals(request, "not a challenge request"); + } + + @Test + void equals_differentRealm_returnsFalse() { + ChallengeRequest r1 = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST, "realm1"); + ChallengeRequest r2 = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST, "realm2"); + assertNotEquals(r1, r2); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/ChallengeResponseTestCase.java b/org.restlet/src/test/java/org/restlet/data/ChallengeResponseTestCase.java new file mode 100644 index 0000000000..783adb9ef7 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/ChallengeResponseTestCase.java @@ -0,0 +1,124 @@ +/** + * 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.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.ChallengeResponse}. */ +class ChallengeResponseTestCase { + + @Test + void constructor_withSchemeOnly_hasNoCredentials() { + ChallengeResponse response = new ChallengeResponse(ChallengeScheme.HTTP_BASIC); + assertEquals(ChallengeScheme.HTTP_BASIC, response.getScheme()); + assertNull(response.getIdentifier()); + assertNull(response.getSecret()); + } + + @Test + void constructor_withIdentifierAndCharArraySecret() { + ChallengeResponse response = + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass".toCharArray()); + assertEquals("user", response.getIdentifier()); + assertArrayEquals("pass".toCharArray(), response.getSecret()); + } + + @Test + void constructor_withIdentifierAndStringSecret() { + ChallengeResponse response = + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass"); + assertEquals("user", response.getIdentifier()); + assertArrayEquals("pass".toCharArray(), response.getSecret()); + } + + @Test + void constructor_withNullStringSecret_hasNullSecret() { + ChallengeResponse response = + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", (String) null); + assertNull(response.getSecret()); + } + + @Test + void getPrincipal_returnsIdentifierBackedPrincipal() { + ChallengeResponse response = + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass"); + assertEquals("user", response.getPrincipal().getName()); + } + + @Test + void settersUpdateState() { + ChallengeResponse response = new ChallengeResponse(ChallengeScheme.HTTP_BASIC); + Reference digestRef = new Reference("http://example.com/resource"); + response.setClientNonce("clientNonce"); + response.setDigestRef(digestRef); + response.setIdentifier("user"); + response.setQuality("auth"); + response.setSecret("secret"); + response.setSecretAlgorithm(Digest.ALGORITHM_MD5); + response.setServerNonceCount(5); + response.setTimeIssued(12345L); + + assertEquals("clientNonce", response.getClientNonce()); + assertEquals(digestRef, response.getDigestRef()); + assertEquals("user", response.getIdentifier()); + assertEquals("auth", response.getQuality()); + assertArrayEquals("secret".toCharArray(), response.getSecret()); + assertEquals(Digest.ALGORITHM_MD5, response.getSecretAlgorithm()); + assertEquals(5, response.getServerNonceCount()); + assertEquals(12345L, response.getTimeIssued()); + } + + @Test + void getServerNonceCountAsHex_formatsAsEightHexCharacters() { + ChallengeResponse response = new ChallengeResponse(ChallengeScheme.HTTP_BASIC); + response.setServerNonceCount(255); + assertEquals("000000ff", response.getServerNonceCountAsHex()); + } + + @Test + void equals_sameRawValueIdentifierSchemeAndSecret_returnsTrue() { + ChallengeResponse r1 = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass"); + ChallengeResponse r2 = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass"); + assertEquals(r1, r2); + assertEquals(r1.hashCode(), r2.hashCode()); + } + + @Test + void equals_sameInstance_returnsTrue() { + ChallengeResponse response = + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass"); + assertEquals(response, response); + } + + @Test + void equals_differentType_returnsFalse() { + ChallengeResponse response = + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass"); + assertNotEquals(response, "not a challenge response"); + } + + @Test + void equals_differentIdentifier_returnsFalse() { + ChallengeResponse r1 = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user1", "pass"); + ChallengeResponse r2 = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user2", "pass"); + assertNotEquals(r1, r2); + } + + @Test + void equals_differentSecret_returnsFalse() { + ChallengeResponse r1 = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass1"); + ChallengeResponse r2 = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass2"); + assertNotEquals(r1, r2); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/ChallengeSchemeTestCase.java b/org.restlet/src/test/java/org/restlet/data/ChallengeSchemeTestCase.java new file mode 100644 index 0000000000..0cebb4cbe6 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/ChallengeSchemeTestCase.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.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.ChallengeScheme}. */ +class ChallengeSchemeTestCase { + + @Test + void valueOf_knownName_returnsSharedConstant() { + assertSame(ChallengeScheme.HTTP_BASIC, ChallengeScheme.valueOf("HTTP_BASIC")); + } + + @Test + void valueOf_isCaseInsensitive() { + assertSame(ChallengeScheme.HTTP_BASIC, ChallengeScheme.valueOf("http_basic")); + } + + @Test + void valueOf_unknownName_createsNewInstance() { + ChallengeScheme scheme = ChallengeScheme.valueOf("CUSTOM_SCHEME"); + assertEquals("CUSTOM_SCHEME", scheme.getName()); + assertNull(scheme.getTechnicalName()); + assertNull(scheme.getDescription()); + } + + @Test + void valueOf_nullName_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> ChallengeScheme.valueOf(null)); + } + + @Test + void constructor_withNameAndTechnicalName() { + ChallengeScheme scheme = new ChallengeScheme("SCHEME", "TECH"); + assertEquals("SCHEME", scheme.getName()); + assertEquals("TECH", scheme.getTechnicalName()); + assertNull(scheme.getDescription()); + } + + @Test + void constructor_withDescription() { + ChallengeScheme scheme = new ChallengeScheme("SCHEME", "TECH", "A description"); + assertEquals("A description", scheme.getDescription()); + } + + @Test + void equals_isCaseInsensitiveOnName() { + ChallengeScheme scheme1 = new ChallengeScheme("scheme", "tech"); + ChallengeScheme scheme2 = new ChallengeScheme("SCHEME", "other"); + assertEquals(scheme1, scheme2); + assertEquals(scheme1.hashCode(), scheme2.hashCode()); + } + + @Test + void equals_differentType_returnsFalse() { + ChallengeScheme scheme = ChallengeScheme.HTTP_BASIC; + assertNotEquals(scheme, "Basic"); + } + + @Test + void equals_differentName_returnsFalse() { + assertNotEquals(ChallengeScheme.HTTP_BASIC, ChallengeScheme.HTTP_DIGEST); + } + + @Test + void toString_returnsName() { + assertEquals(ChallengeScheme.HTTP_BASIC.getName(), ChallengeScheme.HTTP_BASIC.toString()); + } + + @Test + void hashCode_isCaseInsensitive() { + ChallengeScheme scheme1 = new ChallengeScheme("scheme", "tech"); + ChallengeScheme scheme2 = new ChallengeScheme("SCHEME", "tech"); + assertEquals(scheme1.hashCode(), scheme2.hashCode()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/CharacterSetTestCase.java b/org.restlet/src/test/java/org/restlet/data/CharacterSetTestCase.java new file mode 100644 index 0000000000..b91c1499b2 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/CharacterSetTestCase.java @@ -0,0 +1,130 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.CharacterSet}. */ +class CharacterSetTestCase { + + @Test + void constructor_withName_uppercasesName() { + CharacterSet characterSet = new CharacterSet("utf-8"); + assertEquals("UTF-8", characterSet.getName()); + } + + @Test + void constructor_withNameAndDescription() { + CharacterSet characterSet = new CharacterSet("MY-CHARSET", "a description"); + assertEquals("MY-CHARSET", characterSet.getName()); + assertEquals("a description", characterSet.getDescription()); + } + + @Test + void constructor_withJavaCharset_usesCharsetNameAndDisplayName() { + java.nio.charset.Charset charset = java.nio.charset.Charset.forName("UTF-8"); + CharacterSet characterSet = new CharacterSet(charset); + assertEquals(charset.name(), characterSet.getName()); + assertEquals(charset.displayName(), characterSet.getDescription()); + } + + @Test + void valueOf_returnsSharedConstant() { + assertSame(CharacterSet.UTF_8, CharacterSet.valueOf("UTF-8")); + assertSame(CharacterSet.ISO_8859_1, CharacterSet.valueOf("ISO-8859-1")); + assertSame(CharacterSet.US_ASCII, CharacterSet.valueOf("US-ASCII")); + assertSame(CharacterSet.ALL, CharacterSet.valueOf("*")); + } + + @Test + void valueOf_resolvesAliasNames() { + assertSame(CharacterSet.US_ASCII, CharacterSet.valueOf("ASCII")); + assertSame(CharacterSet.ISO_8859_1, CharacterSet.valueOf("latin1")); + assertSame(CharacterSet.MACINTOSH, CharacterSet.valueOf("MACROMAN")); + // "arabic" resolves (via the IANA name mapping) to the ISO-8859-6 name, + // but valueOf() only returns the shared singleton for a subset of + // constants, so a new (but equals()-equivalent) instance is created. + assertEquals(CharacterSet.ISO_8859_6, CharacterSet.valueOf("arabic")); + } + + @Test + void valueOf_unknownName_createsNewInstance() { + CharacterSet characterSet = CharacterSet.valueOf("CUSTOM-CHARSET"); + assertEquals("CUSTOM-CHARSET", characterSet.getName()); + } + + @Test + void valueOf_nullOrEmptyName_returnsNull() { + assertNull(CharacterSet.valueOf((String) null)); + assertNull(CharacterSet.valueOf("")); + } + + @Test + void equals_isCaseInsensitive() { + CharacterSet cs1 = new CharacterSet("utf-8"); + CharacterSet cs2 = new CharacterSet("UTF-8"); + assertEquals(cs1, cs2); + assertEquals(cs1.hashCode(), cs2.hashCode()); + } + + @Test + void equals_differentType_returnsFalse() { + assertNotEquals(CharacterSet.UTF_8, "UTF-8"); + } + + @Test + void getParent_forAll_returnsNull() { + assertNull(CharacterSet.ALL.getParent()); + } + + @Test + void getParent_forSpecificCharset_returnsAll() { + assertEquals(CharacterSet.ALL, CharacterSet.UTF_8.getParent()); + } + + @Test + void includes_allIncludesEverything() { + assertTrue(CharacterSet.ALL.includes(CharacterSet.UTF_8)); + assertFalse(CharacterSet.UTF_8.includes(CharacterSet.ALL)); + } + + @Test + void includes_nullIsAlwaysIncluded() { + assertTrue(CharacterSet.UTF_8.includes(null)); + } + + @Test + void includes_sameCharacterSet_returnsTrue() { + assertTrue(CharacterSet.UTF_8.includes(CharacterSet.UTF_8)); + } + + @Test + void isCompatible_reflexiveAndSymmetricViaAll() { + assertTrue(CharacterSet.UTF_8.isCompatible(CharacterSet.ALL)); + assertTrue(CharacterSet.ALL.isCompatible(CharacterSet.UTF_8)); + assertFalse(CharacterSet.UTF_8.isCompatible(CharacterSet.ISO_8859_1)); + } + + @Test + void toCharset_returnsMatchingNioCharset() { + assertEquals(java.nio.charset.StandardCharsets.UTF_8, CharacterSet.UTF_8.toCharset()); + } + + @Test + void toString_returnsName() { + assertEquals("UTF-8", CharacterSet.UTF_8.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/ConditionsTestCase.java b/org.restlet/src/test/java/org/restlet/data/ConditionsTestCase.java new file mode 100644 index 0000000000..8a9b2ed84b --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/ConditionsTestCase.java @@ -0,0 +1,188 @@ +/** + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.Date; +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.Conditions}. */ +class ConditionsTestCase { + + @Test + void newInstance_hasNoConditions() { + Conditions conditions = new Conditions(); + assertFalse(conditions.hasSome()); + assertFalse(conditions.hasSomeRange()); + assertTrue(conditions.getMatch().isEmpty()); + assertTrue(conditions.getNoneMatch().isEmpty()); + assertNull(conditions.getModifiedSince()); + assertNull(conditions.getUnmodifiedSince()); + assertNull(conditions.getRangeDate()); + assertNull(conditions.getRangeTag()); + } + + @Test + void setMatch_marksConditionsAsPresent() { + Conditions conditions = new Conditions(); + conditions.setMatch(Collections.singletonList(Tag.ALL)); + assertTrue(conditions.hasSome()); + assertEquals(1, conditions.getMatch().size()); + } + + @Test + void setNoneMatch_marksConditionsAsPresent() { + Conditions conditions = new Conditions(); + conditions.setNoneMatch(Collections.singletonList(new Tag("abc"))); + assertTrue(conditions.hasSome()); + } + + @Test + void setModifiedSince_marksConditionsAsPresent() { + Conditions conditions = new Conditions(); + conditions.setModifiedSince(new Date()); + assertTrue(conditions.hasSome()); + assertNotNull(conditions.getModifiedSince()); + } + + @Test + void setUnmodifiedSince_marksConditionsAsPresent() { + Conditions conditions = new Conditions(); + conditions.setUnmodifiedSince(new Date()); + assertTrue(conditions.hasSome()); + assertNotNull(conditions.getUnmodifiedSince()); + } + + @Test + void setRangeTag_marksRangeConditionsAsPresent() { + Conditions conditions = new Conditions(); + conditions.setRangeTag(new Tag("abc")); + assertTrue(conditions.hasSomeRange()); + } + + @Test + void setRangeDate_marksRangeConditionsAsPresent() { + Conditions conditions = new Conditions(); + conditions.setRangeDate(new Date()); + assertTrue(conditions.hasSomeRange()); + } + + @Test + void getRangeStatus_matchingTag_returnsOk() { + Conditions conditions = new Conditions(); + Tag tag = new Tag("abc"); + conditions.setRangeTag(tag); + assertEquals(Status.SUCCESS_OK, conditions.getRangeStatus(tag, null)); + } + + @Test + void getRangeStatus_allTag_returnsOk() { + Conditions conditions = new Conditions(); + conditions.setRangeTag(Tag.ALL); + assertEquals(Status.SUCCESS_OK, conditions.getRangeStatus(new Tag("anything"), null)); + } + + @Test + void getRangeStatus_nonMatchingTag_returnsPreconditionFailed() { + Conditions conditions = new Conditions(); + conditions.setRangeTag(new Tag("abc")); + assertEquals( + Status.CLIENT_ERROR_PRECONDITION_FAILED, + conditions.getRangeStatus(new Tag("other"), null)); + } + + @Test + void getRangeStatus_matchingDate_returnsOk() { + Conditions conditions = new Conditions(); + Date date = new Date(1000); + conditions.setRangeDate(date); + assertEquals(Status.SUCCESS_OK, conditions.getRangeStatus(null, date)); + } + + @Test + void getRangeStatus_noConditionsSet_returnsPreconditionFailed() { + Conditions conditions = new Conditions(); + assertEquals( + Status.CLIENT_ERROR_PRECONDITION_FAILED, conditions.getRangeStatus(null, null)); + } + + @Test + void getStatus_ifMatchNotSatisfied_returnsPreconditionFailed() { + Conditions conditions = new Conditions(); + conditions.setMatch(Collections.singletonList(new Tag("expected"))); + Status status = conditions.getStatus(Method.GET, true, new Tag("actual"), null); + assertEquals(Status.CLIENT_ERROR_PRECONDITION_FAILED, status); + } + + @Test + void getStatus_ifMatchSatisfied_returnsNull() { + Conditions conditions = new Conditions(); + Tag tag = new Tag("abc"); + conditions.setMatch(Collections.singletonList(tag)); + Status status = conditions.getStatus(Method.GET, true, tag, null); + assertNull(status); + } + + @Test + void getStatus_ifMatchStarWithNoEntity_returnsPreconditionFailed() { + Conditions conditions = new Conditions(); + conditions.setMatch(Collections.singletonList(Tag.ALL)); + Status status = conditions.getStatus(Method.GET, false, null, null); + assertEquals(Status.CLIENT_ERROR_PRECONDITION_FAILED.getCode(), status.getCode()); + } + + @Test + void getStatus_ifNoneMatchSatisfiedOnGet_returnsNotModified() { + Conditions conditions = new Conditions(); + Tag tag = new Tag("abc"); + conditions.setNoneMatch(Collections.singletonList(tag)); + Status status = conditions.getStatus(Method.GET, true, tag, null); + assertEquals(Status.REDIRECTION_NOT_MODIFIED, status); + } + + @Test + void getStatus_ifNoneMatchSatisfiedOnPut_returnsPreconditionFailed() { + Conditions conditions = new Conditions(); + Tag tag = new Tag("abc"); + conditions.setNoneMatch(Collections.singletonList(tag)); + Status status = conditions.getStatus(Method.PUT, true, tag, null); + assertEquals(Status.CLIENT_ERROR_PRECONDITION_FAILED, status); + } + + @Test + void getStatus_ifModifiedSinceInFuture_returnsNull() { + Conditions conditions = new Conditions(); + Date future = new Date(System.currentTimeMillis() + 1_000_000_000L); + conditions.setModifiedSince(future); + Status status = conditions.getStatus(Method.GET, true, null, new Date()); + assertNull(status); + } + + @Test + void getStatus_ifUnmodifiedSinceViolated_returnsPreconditionFailed() { + Conditions conditions = new Conditions(); + Date past = new Date(1000); + conditions.setUnmodifiedSince(past); + Date modificationDate = new Date(2000); + Status status = conditions.getStatus(Method.GET, true, null, modificationDate); + assertEquals(Status.CLIENT_ERROR_PRECONDITION_FAILED, status); + } + + @Test + void getStatus_noConditions_returnsNull() { + Conditions conditions = new Conditions(); + assertNull(conditions.getStatus(Method.GET, true, new Tag("abc"), new Date())); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/CookieSettingTestCase.java b/org.restlet/src/test/java/org/restlet/data/CookieSettingTestCase.java new file mode 100644 index 0000000000..b95ded8f41 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/CookieSettingTestCase.java @@ -0,0 +1,155 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.CookieSetting}. */ +class CookieSettingTestCase { + + @Test + void defaultConstructor_hasDefaultValues() { + CookieSetting cookie = new CookieSetting(); + assertEquals(0, cookie.getVersion()); + assertNull(cookie.getName()); + assertNull(cookie.getValue()); + assertEquals(-1, cookie.getMaxAge()); + assertFalse(cookie.isSecure()); + assertFalse(cookie.isAccessRestricted()); + } + + @Test + void preferredConstructor_setsNameAndValue() { + CookieSetting cookie = new CookieSetting("session", "abc123"); + assertEquals("session", cookie.getName()); + assertEquals("abc123", cookie.getValue()); + assertEquals(0, cookie.getVersion()); + } + + @Test + void constructor_withVersionNameValue() { + CookieSetting cookie = new CookieSetting(1, "session", "abc123"); + assertEquals(1, cookie.getVersion()); + assertEquals("session", cookie.getName()); + assertEquals("abc123", cookie.getValue()); + assertEquals(-1, cookie.getMaxAge()); + } + + @Test + void constructor_withPathAndDomain() { + CookieSetting cookie = new CookieSetting(1, "session", "abc123", "/app", "example.com"); + assertEquals("/app", cookie.getPath()); + assertEquals("example.com", cookie.getDomain()); + } + + @Test + void constructor_withCommentMaxAgeSecure() { + CookieSetting cookie = + new CookieSetting( + 1, "session", "abc123", "/app", "example.com", "a comment", 3600, true); + assertEquals("a comment", cookie.getComment()); + assertEquals(3600, cookie.getMaxAge()); + assertTrue(cookie.isSecure()); + assertFalse(cookie.isAccessRestricted()); + } + + @Test + void constructor_withAccessRestricted() { + CookieSetting cookie = + new CookieSetting( + 1, + "session", + "abc123", + "/app", + "example.com", + "a comment", + 3600, + true, + true); + assertTrue(cookie.isAccessRestricted()); + } + + @Test + void settersUpdateState() { + CookieSetting cookie = new CookieSetting(); + cookie.setComment("comment"); + cookie.setMaxAge(60); + cookie.setSecure(true); + cookie.setAccessRestricted(true); + + assertEquals("comment", cookie.getComment()); + assertEquals(60, cookie.getMaxAge()); + assertTrue(cookie.isSecure()); + assertTrue(cookie.isAccessRestricted()); + } + + @Test + void getDescription_returnsFixedText() { + assertEquals("Cookie setting", new CookieSetting().getDescription()); + } + + @Test + void equals_sameFields_returnsTrue() { + CookieSetting c1 = + new CookieSetting( + 1, "session", "abc123", "/app", "example.com", "comment", 3600, true); + CookieSetting c2 = + new CookieSetting( + 1, "session", "abc123", "/app", "example.com", "comment", 3600, true); + assertEquals(c1, c2); + assertEquals(c1.hashCode(), c2.hashCode()); + } + + @Test + void equals_sameInstance_returnsTrue() { + CookieSetting cookie = new CookieSetting("session", "abc123"); + assertEquals(cookie, cookie); + } + + @Test + void equals_differentType_returnsFalse() { + CookieSetting cookie = new CookieSetting("session", "abc123"); + assertNotEquals(cookie, new Cookie("session", "abc123")); + } + + @Test + void equals_differentMaxAge_returnsFalse() { + CookieSetting c1 = new CookieSetting(0, "session", "abc123", null, null, null, 10, false); + CookieSetting c2 = new CookieSetting(0, "session", "abc123", null, null, null, 20, false); + assertNotEquals(c1, c2); + } + + @Test + void equals_differentComment_returnsFalse() { + CookieSetting c1 = new CookieSetting(0, "session", "abc123", null, null, "a", 10, false); + CookieSetting c2 = new CookieSetting(0, "session", "abc123", null, null, "b", 10, false); + assertNotEquals(c1, c2); + } + + @Test + void toString_containsFields() { + CookieSetting cookie = + new CookieSetting( + 1, "session", "abc123", "/app", "example.com", "comment", 3600, true, true); + String result = cookie.toString(); + assertTrue(result.contains("session")); + assertTrue(result.contains("abc123")); + assertTrue(result.contains("example.com")); + assertTrue(result.contains("/app")); + assertTrue(result.contains("comment")); + assertTrue(result.contains("secure=true")); + assertTrue(result.contains("accessRestricted=true")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/DigestTestCase.java b/org.restlet/src/test/java/org/restlet/data/DigestTestCase.java new file mode 100644 index 0000000000..cc30299095 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/DigestTestCase.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.data; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.Digest}. */ +class DigestTestCase { + + @Test + void constructor_withValueOnly_usesMd5Algorithm() { + Digest digest = new Digest(new byte[] {1, 2, 3}); + assertEquals(Digest.ALGORITHM_MD5, digest.getAlgorithm()); + assertArrayEquals(new byte[] {1, 2, 3}, digest.getValue()); + } + + @Test + void constructor_withAlgorithm_setsAlgorithm() { + Digest digest = new Digest(Digest.ALGORITHM_SHA_256, new byte[] {4, 5, 6}); + assertEquals(Digest.ALGORITHM_SHA_256, digest.getAlgorithm()); + assertArrayEquals(new byte[] {4, 5, 6}, digest.getValue()); + } + + @Test + void getValue_returnsDefensiveCopy() { + byte[] source = {1, 2, 3}; + Digest digest = new Digest(source); + source[0] = 99; + assertArrayEquals(new byte[] {1, 2, 3}, digest.getValue()); + + byte[] returned = digest.getValue(); + returned[0] = 42; + assertArrayEquals(new byte[] {1, 2, 3}, digest.getValue()); + } + + @Test + void equals_sameAlgorithmAndValue_returnsTrue() { + Digest digest1 = new Digest(Digest.ALGORITHM_MD5, new byte[] {1, 2, 3}); + Digest digest2 = new Digest(Digest.ALGORITHM_MD5, new byte[] {1, 2, 3}); + assertEquals(digest1, digest2); + assertEquals(digest1.hashCode(), digest2.hashCode()); + } + + @Test + void equals_differentAlgorithm_returnsFalse() { + Digest digest1 = new Digest(Digest.ALGORITHM_MD5, new byte[] {1, 2, 3}); + Digest digest2 = new Digest(Digest.ALGORITHM_SHA_1, new byte[] {1, 2, 3}); + assertNotEquals(digest1, digest2); + } + + @Test + void equals_differentValue_returnsFalse() { + Digest digest1 = new Digest(Digest.ALGORITHM_MD5, new byte[] {1, 2, 3}); + Digest digest2 = new Digest(Digest.ALGORITHM_MD5, new byte[] {9, 9, 9}); + assertNotEquals(digest1, digest2); + } + + @Test + void equals_differentType_returnsFalse() { + Digest digest = new Digest(new byte[] {1}); + assertNotEquals(digest, "not a digest"); + } + + @Test + void toString_containsAlgorithmAndValue() { + Digest digest = new Digest(Digest.ALGORITHM_MD5, new byte[] {1, 2, 3}); + String result = digest.toString(); + assertTrue(result.contains(Digest.ALGORITHM_MD5)); + assertTrue(result.contains("[1, 2, 3]")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/DimensionTestCase.java b/org.restlet/src/test/java/org/restlet/data/DimensionTestCase.java new file mode 100644 index 0000000000..903f1f704d --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/DimensionTestCase.java @@ -0,0 +1,54 @@ +/** + * 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.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.Dimension}. */ +class DimensionTestCase { + + @Test + void values_containsAllExpectedConstants() { + Dimension[] values = Dimension.values(); + assertEquals(10, values.length); + assertArrayEquals( + new Dimension[] { + Dimension.AUTHORIZATION, + Dimension.CHARACTER_SET, + Dimension.CLIENT_ADDRESS, + Dimension.CLIENT_AGENT, + Dimension.UNSPECIFIED, + Dimension.ENCODING, + Dimension.LANGUAGE, + Dimension.MEDIA_TYPE, + Dimension.TIME, + Dimension.ORIGIN + }, + values); + } + + @Test + void valueOf_returnsMatchingConstant() { + assertEquals(Dimension.MEDIA_TYPE, Dimension.valueOf("MEDIA_TYPE")); + } + + @Test + void valueOf_unknownName_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> Dimension.valueOf("UNKNOWN")); + } + + @Test + void name_returnsDeclaredConstantName() { + assertEquals("TIME", Dimension.TIME.name()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/DispositionTestCase.java b/org.restlet/src/test/java/org/restlet/data/DispositionTestCase.java new file mode 100644 index 0000000000..7cdff40c91 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/DispositionTestCase.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.data; + +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 java.util.Date; +import org.junit.jupiter.api.Test; +import org.restlet.util.Series; + +/** Test {@link org.restlet.data.Disposition}. */ +class DispositionTestCase { + + @Test + void defaultConstructor_usesNoneType() { + Disposition disposition = new Disposition(); + assertEquals(Disposition.TYPE_NONE, disposition.getType()); + } + + @Test + void constructor_withType() { + Disposition disposition = new Disposition(Disposition.TYPE_ATTACHMENT); + assertEquals(Disposition.TYPE_ATTACHMENT, disposition.getType()); + } + + @Test + void constructor_withTypeAndParameters() { + Series parameters = new Form(); + parameters.add(Disposition.NAME_FILENAME, "file.txt"); + Disposition disposition = new Disposition(Disposition.TYPE_ATTACHMENT, parameters); + assertEquals("file.txt", disposition.getFilename()); + } + + @Test + void getParameters_lazilyCreatesEmptySeries() { + Disposition disposition = new Disposition(); + assertNotNull(disposition.getParameters()); + assertEquals(0, disposition.getParameters().size()); + } + + @Test + void setType_updatesType() { + Disposition disposition = new Disposition(); + disposition.setType(Disposition.TYPE_INLINE); + assertEquals(Disposition.TYPE_INLINE, disposition.getType()); + } + + @Test + void setFilename_andGetFilename_roundTrip() { + Disposition disposition = new Disposition(); + disposition.setFilename("report.pdf"); + assertEquals("report.pdf", disposition.getFilename()); + } + + @Test + void setSize_storesSizeParameter() { + Disposition disposition = new Disposition(); + disposition.setSize(1024L); + assertEquals( + "1024", disposition.getParameters().getFirstValue(Disposition.NAME_SIZE, true)); + } + + @Test + void setCreationDate_storesFormattedDate() { + Disposition disposition = new Disposition(); + Date date = new Date(0); + disposition.setCreationDate(date); + assertNotNull( + disposition.getParameters().getFirstValue(Disposition.NAME_CREATION_DATE, true)); + } + + @Test + void setModificationDate_storesFormattedDate() { + Disposition disposition = new Disposition(); + disposition.setModificationDate(new Date(0)); + assertNotNull( + disposition + .getParameters() + .getFirstValue(Disposition.NAME_MODIFICATION_DATE, true)); + } + + @Test + void setReadDate_storesFormattedDate() { + Disposition disposition = new Disposition(); + disposition.setReadDate(new Date(0)); + assertNotNull(disposition.getParameters().getFirstValue(Disposition.NAME_READ_DATE, true)); + } + + @Test + void addDate_appendsAdditionalDateParameter() { + Disposition disposition = new Disposition(); + disposition.addDate(Disposition.NAME_CREATION_DATE, new Date(0)); + disposition.addDate(Disposition.NAME_CREATION_DATE, new Date(1000)); + assertEquals(2, disposition.getParameters().subList(Disposition.NAME_CREATION_DATE).size()); + } + + @Test + void setParameters_replacesParameterSeries() { + Disposition disposition = new Disposition(); + Series parameters = new Form(); + parameters.add("custom", "value"); + disposition.setParameters(parameters); + assertSame(parameters, disposition.getParameters()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/EncodingTestCase.java b/org.restlet/src/test/java/org/restlet/data/EncodingTestCase.java new file mode 100644 index 0000000000..3ad113e612 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/EncodingTestCase.java @@ -0,0 +1,99 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.Encoding}. */ +class EncodingTestCase { + + @Test + void constructor_withName_setsDefaultDescription() { + Encoding encoding = new Encoding("custom"); + assertEquals("custom", encoding.getName()); + assertEquals("Encoding applied to a representation", encoding.getDescription()); + } + + @Test + void constructor_withNameAndDescription() { + Encoding encoding = new Encoding("custom", "my description"); + assertEquals("my description", encoding.getDescription()); + } + + @Test + void valueOf_returnsSharedConstant() { + assertSame(Encoding.GZIP, Encoding.valueOf("gzip")); + assertSame(Encoding.ZIP, Encoding.valueOf("ZIP")); + assertSame(Encoding.COMPRESS, Encoding.valueOf("compress")); + assertSame(Encoding.DEFLATE, Encoding.valueOf("deflate")); + assertSame(Encoding.DEFLATE_NOWRAP, Encoding.valueOf("deflate-no-wrap")); + assertSame(Encoding.IDENTITY, Encoding.valueOf("identity")); + assertSame(Encoding.FREEMARKER, Encoding.valueOf("freemarker")); + assertSame(Encoding.VELOCITY, Encoding.valueOf("velocity")); + assertSame(Encoding.ALL, Encoding.valueOf("*")); + } + + @Test + void valueOf_unknownName_createsNewInstance() { + Encoding encoding = Encoding.valueOf("custom-encoding"); + assertEquals("custom-encoding", encoding.getName()); + } + + @Test + void valueOf_nullOrEmptyName_returnsNull() { + assertNull(Encoding.valueOf(null)); + assertNull(Encoding.valueOf("")); + } + + @Test + void equals_isCaseInsensitive() { + Encoding e1 = new Encoding("gzip"); + Encoding e2 = new Encoding("GZIP"); + assertEquals(e1, e2); + assertEquals(e1.hashCode(), e2.hashCode()); + } + + @Test + void equals_differentType_returnsFalse() { + assertNotEquals(Encoding.GZIP, "gzip"); + } + + @Test + void getParent_forAll_returnsNull() { + assertNull(Encoding.ALL.getParent()); + } + + @Test + void getParent_forSpecificEncoding_returnsAll() { + assertEquals(Encoding.ALL, Encoding.GZIP.getParent()); + } + + @Test + void includes_allIncludesEverything() { + assertTrue(Encoding.ALL.includes(Encoding.GZIP)); + assertFalse(Encoding.GZIP.includes(Encoding.ALL)); + } + + @Test + void includes_nullIsAlwaysIncluded() { + assertTrue(Encoding.GZIP.includes(null)); + } + + @Test + void includes_sameEncoding_returnsTrue() { + assertTrue(Encoding.GZIP.includes(Encoding.GZIP)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/HeaderTestCase.java b/org.restlet/src/test/java/org/restlet/data/HeaderTestCase.java new file mode 100644 index 0000000000..720dc44993 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/HeaderTestCase.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.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.Header}. */ +class HeaderTestCase { + + @Test + void defaultConstructor_hasNullNameAndValue() { + Header header = new Header(); + assertNull(header.getName()); + assertNull(header.getValue()); + } + + @Test + void constructor_setsNameAndValue() { + Header header = new Header("Content-Type", "text/plain"); + assertEquals("Content-Type", header.getName()); + assertEquals("text/plain", header.getValue()); + } + + @Test + void settersUpdateState() { + Header header = new Header(); + header.setName("X-Custom"); + header.setValue("value"); + assertEquals("X-Custom", header.getName()); + assertEquals("value", header.getValue()); + } + + @Test + void equals_sameNameAndValue_returnsTrue() { + Header header1 = new Header("X-Custom", "value"); + Header header2 = new Header("X-Custom", "value"); + assertEquals(header1, header2); + assertEquals(header1.hashCode(), header2.hashCode()); + } + + @Test + void equals_sameInstance_returnsTrue() { + Header header = new Header("X-Custom", "value"); + assertEquals(header, header); + } + + @Test + void equals_differentValue_returnsFalse() { + Header header1 = new Header("X-Custom", "value1"); + Header header2 = new Header("X-Custom", "value2"); + assertNotEquals(header1, header2); + } + + @Test + void equals_differentType_returnsFalse() { + Header header = new Header("X-Custom", "value"); + assertNotEquals(header, "X-Custom"); + } + + @Test + void toString_containsNameAndValue() { + Header header = new Header("X-Custom", "value"); + assertEquals("[X-Custom: value]", header.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/LocalReferenceTestCase.java b/org.restlet/src/test/java/org/restlet/data/LocalReferenceTestCase.java new file mode 100644 index 0000000000..579ce6c9d2 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/LocalReferenceTestCase.java @@ -0,0 +1,163 @@ +/** + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.LocalReference}. */ +class LocalReferenceTestCase { + + @Test + void constructor_fromUriString() { + LocalReference reference = new LocalReference("file:///tmp/test.txt"); + assertEquals("file", reference.getScheme()); + } + + @Test + void constructor_fromReference() { + Reference base = new Reference("file:///tmp/test.txt"); + LocalReference reference = new LocalReference(base); + assertEquals("file", reference.getScheme()); + } + + @Test + void createFileReference_fromFile() { + LocalReference reference = LocalReference.createFileReference(new File("/tmp/test.txt")); + assertEquals("file", reference.getScheme()); + assertNotNull(reference.getFile()); + } + + @Test + void createFileReference_fromPath() { + LocalReference reference = LocalReference.createFileReference("/tmp/test.txt"); + assertEquals("file", reference.getScheme()); + assertEquals("", reference.getAuthority()); + } + + @Test + void createFileReference_withHostName() { + LocalReference reference = LocalReference.createFileReference("localhost", "/tmp/test.txt"); + assertEquals("localhost", reference.getAuthority()); + } + + @Test + void createClapReference_fromPackage() { + LocalReference reference = LocalReference.createClapReference(String.class.getPackage()); + assertEquals("clap", reference.getScheme()); + assertTrue(reference.toString().contains("java/lang")); + } + + @Test + void createClapReference_fromPath() { + LocalReference reference = LocalReference.createClapReference("/org/restlet/Restlet.class"); + assertEquals("clap", reference.getScheme()); + } + + @Test + void createClapReference_withAuthorityType() { + LocalReference reference = + LocalReference.createClapReference( + LocalReference.CLAP_SYSTEM, "/org/restlet/Restlet.class"); + assertEquals("system", reference.getAuthority()); + assertEquals(LocalReference.CLAP_SYSTEM, reference.getClapAuthorityType()); + } + + @Test + void getClapAuthorityType_defaultsToClapDefault() { + LocalReference reference = LocalReference.createClapReference("/org/restlet/Restlet.class"); + assertEquals(LocalReference.CLAP_DEFAULT, reference.getClapAuthorityType()); + } + + @Test + void getClapAuthorityType_forNonClapReference_returnsZero() { + LocalReference reference = new LocalReference("http://example.com"); + assertEquals(0, reference.getClapAuthorityType()); + } + + @Test + void createRiapReference_buildsRiapUri() { + LocalReference reference = + LocalReference.createRiapReference(LocalReference.RIAP_COMPONENT, "/myApp/path"); + assertEquals("riap", reference.getScheme()); + assertEquals(LocalReference.RIAP_COMPONENT, reference.getRiapAuthorityType()); + } + + @Test + void getRiapAuthorityType_forNonRiapReference_returnsZero() { + LocalReference reference = new LocalReference("http://example.com"); + assertEquals(0, reference.getRiapAuthorityType()); + } + + @Test + void createJarReference_buildsJarUri() { + Reference jarFile = new Reference("http://example.com/archive.jar"); + LocalReference reference = LocalReference.createJarReference(jarFile, "entry/path.txt"); + assertEquals("jar", reference.getScheme()); + assertEquals("entry/path.txt", reference.getJarEntryPath()); + assertEquals("http://example.com/archive.jar", reference.getJarFileRef().toString()); + } + + @Test + void getJarEntryPath_forNonJarReference_returnsNull() { + LocalReference reference = new LocalReference("http://example.com"); + assertNull(reference.getJarEntryPath()); + } + + @Test + void createZipReference_buildsZipUri() { + Reference zipFile = new Reference("file:///tmp/archive.zip"); + LocalReference reference = LocalReference.createZipReference(zipFile, "entry.txt"); + assertEquals("zip", reference.getScheme()); + } + + @Test + void getFile_forRemoteHost_throwsRuntimeException() { + LocalReference reference = + LocalReference.createFileReference("remotehost", "/tmp/test.txt"); + assertThrows(RuntimeException.class, reference::getFile); + } + + @Test + void getFile_forNonFileReference_returnsNull() { + LocalReference reference = new LocalReference("http://example.com"); + assertNull(reference.getFile()); + } + + @Test + void getAuthorityName_mapsAuthorityConstants() { + assertEquals("", LocalReference.getAuthorityName(LocalReference.CLAP_DEFAULT)); + assertEquals("class", LocalReference.getAuthorityName(LocalReference.CLAP_CLASS)); + assertEquals("system", LocalReference.getAuthorityName(LocalReference.CLAP_SYSTEM)); + assertEquals("thread", LocalReference.getAuthorityName(LocalReference.CLAP_THREAD)); + assertEquals( + "application", LocalReference.getAuthorityName(LocalReference.RIAP_APPLICATION)); + assertEquals("component", LocalReference.getAuthorityName(LocalReference.RIAP_COMPONENT)); + assertEquals("host", LocalReference.getAuthorityName(LocalReference.RIAP_HOST)); + } + + @Test + void localizePath_convertsSlashesToSystemSeparator() { + String result = LocalReference.localizePath("a/b/c"); + assertEquals("a" + File.separatorChar + "b" + File.separatorChar + "c", result); + } + + @Test + void normalizePath_convertsSystemSeparatorToSlash() { + String path = "a" + File.separatorChar + "b"; + String result = LocalReference.normalizePath(path); + assertEquals("a/b", result); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/MetadataTestCase.java b/org.restlet/src/test/java/org/restlet/data/MetadataTestCase.java new file mode 100644 index 0000000000..3f2f968761 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/MetadataTestCase.java @@ -0,0 +1,100 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Test {@link org.restlet.data.Metadata}, exercised through the concrete {@link CharacterSet} and + * {@link Encoding} subclasses since Metadata is abstract. + */ +class MetadataTestCase { + + @Test + void getName_returnsConfiguredValue() { + Metadata metadata = new CharacterSet("UTF-8", "description"); + assertEquals("UTF-8", metadata.getName()); + } + + @Test + void getDescription_returnsConfiguredValue() { + Metadata metadata = new CharacterSet("UTF-8", "description"); + assertEquals("description", metadata.getDescription()); + } + + @Test + void constructor_withNameOnly_hasNullDescription() { + Encoding encoding = new Encoding("custom", null); + assertNull(encoding.getDescription()); + } + + @Test + void equals_acrossDifferentMetadataSubtypes_returnsFalse() { + // Although the abstract Metadata.equals() only compares names, every + // concrete subclass (CharacterSet, Encoding, ...) overrides equals() + // with its own "instanceof" check, so two different Metadata subtypes + // sharing the same name are never considered equal. + Metadata characterSet = new CharacterSet("SAME-NAME"); + Metadata encoding = new Encoding("SAME-NAME"); + assertFalse(characterSet.equals(encoding)); + assertFalse(encoding.equals(characterSet)); + } + + @Test + void equals_differentName_returnsFalse() { + Metadata cs1 = new CharacterSet("A"); + Metadata cs2 = new CharacterSet("B"); + assertFalse(((Metadata) cs1).equals(cs2)); + } + + @Test + void hashCode_matchesNameHashCode() { + // CharacterSet overrides hashCode() to be case-insensitive (lowercases + // the name), so this exercises that override rather than the base + // Metadata implementation. + Metadata metadata = new CharacterSet("UTF-8"); + assertEquals("utf-8".hashCode(), metadata.hashCode()); + } + + @Test + void hashCode_forNullName_returnsZero() { + Metadata metadata = new CharacterSet((String) null); + assertEquals(0, metadata.hashCode()); + } + + @Test + void toString_returnsName() { + Metadata metadata = new Encoding("gzip"); + assertEquals("gzip", metadata.toString()); + } + + @Test + void isCompatible_withNull_returnsFalse() { + Metadata metadata = CharacterSet.UTF_8; + assertFalse(metadata.isCompatible(null)); + } + + @Test + void isCompatible_delegatesToIncludes() { + assertTrue(CharacterSet.ALL.isCompatible(CharacterSet.UTF_8)); + assertTrue(CharacterSet.UTF_8.isCompatible(CharacterSet.ALL)); + assertFalse(CharacterSet.UTF_8.isCompatible(CharacterSet.ISO_8859_1)); + } + + @Test + void getParent_isAbstractButImplementedBySubclasses() { + assertNull(CharacterSet.ALL.getParent()); + assertEquals(CharacterSet.ALL, CharacterSet.UTF_8.getParent()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/ParameterTestCase.java b/org.restlet/src/test/java/org/restlet/data/ParameterTestCase.java new file mode 100644 index 0000000000..53b77aa068 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/ParameterTestCase.java @@ -0,0 +1,123 @@ +/** + * 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.assertNotEquals; +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.util.Series; + +/** Test {@link org.restlet.data.Parameter}. */ +class ParameterTestCase { + + @Test + void defaultConstructor_hasNullNameAndValue() { + Parameter parameter = new Parameter(); + assertNull(parameter.getName()); + assertNull(parameter.getValue()); + } + + @Test + void constructor_setsNameAndValue() { + Parameter parameter = new Parameter("name", "value"); + assertEquals("name", parameter.getName()); + assertEquals("value", parameter.getValue()); + } + + @Test + void create_withNonNullValue_createsParameter() throws Exception { + Parameter parameter = Parameter.create("name", "value"); + assertEquals("name", parameter.getName()); + assertEquals("value", parameter.getValue()); + } + + @Test + void create_withNullValue_createsParameterWithNullValue() throws Exception { + Parameter parameter = Parameter.create("name", null); + assertEquals("name", parameter.getName()); + assertNull(parameter.getValue()); + } + + @Test + void settersUpdateState() { + Parameter parameter = new Parameter(); + parameter.setName("name"); + parameter.setValue("value"); + assertEquals("name", parameter.getName()); + assertEquals("value", parameter.getValue()); + } + + @Test + void compareTo_ordersByName() { + Parameter a = new Parameter("a", "1"); + Parameter b = new Parameter("b", "2"); + assertTrue(a.compareTo(b) < 0); + assertTrue(b.compareTo(a) > 0); + assertEquals(0, a.compareTo(new Parameter("a", "other"))); + } + + @Test + void equals_sameNameAndValue_returnsTrue() { + Parameter p1 = new Parameter("name", "value"); + Parameter p2 = new Parameter("name", "value"); + assertEquals(p1, p2); + assertEquals(p1.hashCode(), p2.hashCode()); + } + + @Test + void equals_sameInstance_returnsTrue() { + Parameter parameter = new Parameter("name", "value"); + assertEquals(parameter, parameter); + } + + @Test + void equals_differentType_returnsFalse() { + Parameter parameter = new Parameter("name", "value"); + assertNotEquals(parameter, "name=value"); + } + + @Test + void equals_differentValue_returnsFalse() { + Parameter p1 = new Parameter("name", "value1"); + Parameter p2 = new Parameter("name", "value2"); + assertNotEquals(p1, p2); + } + + @Test + void toString_containsNameAndValue() { + Parameter parameter = new Parameter("name", "value"); + assertEquals("[name=value]", parameter.toString()); + } + + @Test + void encode_appendsNameAndEncodedValue() throws Exception { + Parameter parameter = new Parameter("name", "a value"); + String encoded = parameter.encode(CharacterSet.UTF_8); + assertEquals("name=a%20value", encoded); + } + + @Test + void encode_withNullValue_omitsEqualsSign() throws Exception { + Parameter parameter = new Parameter("name", null); + String encoded = parameter.encode(CharacterSet.UTF_8); + assertEquals("name", encoded); + } + + @Test + void createSeries_returnsSeriesContainingThisParameter() { + Parameter parameter = new Parameter("name", "value"); + Series series = parameter.createSeries(); + assertEquals(1, series.size()); + assertSame(parameter, series.get(0)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/PreferenceTestCase.java b/org.restlet/src/test/java/org/restlet/data/PreferenceTestCase.java new file mode 100644 index 0000000000..0b4de6f2e8 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/PreferenceTestCase.java @@ -0,0 +1,81 @@ +/** + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.junit.jupiter.api.Test; +import org.restlet.util.Series; + +/** Test {@link org.restlet.data.Preference}. */ +class PreferenceTestCase { + + @Test + void defaultConstructor_hasNullMetadataAndFullQuality() { + Preference preference = new Preference<>(); + assertNull(preference.getMetadata()); + assertEquals(1F, preference.getQuality()); + } + + @Test + void constructor_withMetadata_hasFullQuality() { + Preference preference = new Preference<>(MediaType.TEXT_PLAIN); + assertEquals(MediaType.TEXT_PLAIN, preference.getMetadata()); + assertEquals(1F, preference.getQuality()); + } + + @Test + void constructor_withMetadataAndQuality() { + Preference preference = new Preference<>(MediaType.TEXT_PLAIN, 0.5F); + assertEquals(0.5F, preference.getQuality()); + } + + @Test + void constructor_withParameters() { + Series parameters = new Form(); + parameters.add("q", "0.8"); + Preference preference = new Preference<>(MediaType.TEXT_PLAIN, 0.8F, parameters); + assertEquals(1, preference.getParameters().size()); + } + + @Test + void getParameters_lazilyCreatesEmptySeries() { + Preference preference = new Preference<>(MediaType.TEXT_PLAIN); + assertNotNull(preference.getParameters()); + assertEquals(0, preference.getParameters().size()); + } + + @Test + void settersUpdateState() { + Preference preference = new Preference<>(); + preference.setMetadata(MediaType.APPLICATION_JSON); + preference.setQuality(0.3F); + Series parameters = new Form(); + preference.setParameters(parameters); + + assertEquals(MediaType.APPLICATION_JSON, preference.getMetadata()); + assertEquals(0.3F, preference.getQuality()); + assertSame(parameters, preference.getParameters()); + } + + @Test + void toString_withMetadata_includesNameAndQuality() { + Preference preference = new Preference<>(MediaType.TEXT_PLAIN, 0.5F); + assertEquals("text/plain:0.5", preference.toString()); + } + + @Test + void toString_withoutMetadata_returnsEmptyString() { + Preference preference = new Preference<>(); + assertEquals("", preference.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/ProductTestCase.java b/org.restlet/src/test/java/org/restlet/data/ProductTestCase.java new file mode 100644 index 0000000000..3fc83deecd --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/ProductTestCase.java @@ -0,0 +1,43 @@ +/** + * 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 org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.Product}. */ +class ProductTestCase { + + @Test + void constructor_setsAllProperties() { + Product product = new Product("Restlet", "2.5", "a framework"); + assertEquals("Restlet", product.getName()); + assertEquals("2.5", product.getVersion()); + assertEquals("a framework", product.getComment()); + } + + @Test + void constructor_withNullComment_returnsNullComment() { + Product product = new Product("Restlet", "2.5", null); + assertNull(product.getComment()); + } + + @Test + void settersUpdateState() { + Product product = new Product("Restlet", "2.5", "a framework"); + product.setName("Jetty"); + product.setVersion("9.0"); + product.setComment("a server"); + assertEquals("Jetty", product.getName()); + assertEquals("9.0", product.getVersion()); + assertEquals("a server", product.getComment()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/ProtocolTestCase.java b/org.restlet/src/test/java/org/restlet/data/ProtocolTestCase.java new file mode 100644 index 0000000000..bfaeaab312 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/ProtocolTestCase.java @@ -0,0 +1,137 @@ +/** + * 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.assertNotSame; +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; + +/** Test {@link org.restlet.data.Protocol}. */ +class ProtocolTestCase { + + @Test + void constructor_withSchemeNameOnly_derivesNameAndDescription() { + Protocol protocol = new Protocol("myproto"); + assertEquals("myproto", protocol.getSchemeName()); + assertEquals("MYPROTO", protocol.getName()); + assertEquals("MYPROTO Protocol", protocol.getDescription()); + assertEquals(Protocol.UNKNOWN_PORT, protocol.getDefaultPort()); + assertFalse(protocol.isConfidential()); + assertNull(protocol.getVersion()); + } + + @Test + void constructor_withDefaultPort() { + Protocol protocol = new Protocol("proto", "PROTO", "description", 1234); + assertEquals(1234, protocol.getDefaultPort()); + assertFalse(protocol.isConfidential()); + } + + @Test + void constructor_withConfidentiality() { + Protocol protocol = new Protocol("proto", "PROTO", "description", 1234, true); + assertTrue(protocol.isConfidential()); + } + + @Test + void constructor_withVersion() { + Protocol protocol = new Protocol("proto", "PROTO", "description", 1234, true, "1.0"); + assertEquals("1.0", protocol.getVersion()); + assertEquals("PROTO", protocol.getTechnicalName()); + } + + @Test + void constructor_withDefaultPortAndVersion() { + Protocol protocol = new Protocol("proto", "PROTO", "description", 1234, "2.0"); + assertEquals("2.0", protocol.getVersion()); + assertFalse(protocol.isConfidential()); + } + + @Test + void constructor_withDistinctTechnicalName() { + Protocol protocol = new Protocol("https", "HTTPS", "HTTP", "description", 443, true, "1.1"); + assertEquals("HTTPS", protocol.getName()); + assertEquals("HTTP", protocol.getTechnicalName()); + } + + @Test + void valueOf_knownScheme_returnsSharedConstant() { + assertSame(Protocol.HTTP, Protocol.valueOf("http")); + assertSame(Protocol.HTTPS, Protocol.valueOf("HTTPS")); + assertSame(Protocol.FTP, Protocol.valueOf("ftp")); + assertSame(Protocol.FILE, Protocol.valueOf("file")); + assertSame(Protocol.CLAP, Protocol.valueOf("clap")); + assertSame(Protocol.JAR, Protocol.valueOf("jar")); + assertSame(Protocol.JDBC, Protocol.valueOf("jdbc")); + assertSame(Protocol.RIAP, Protocol.valueOf("riap")); + assertSame(Protocol.WAR, Protocol.valueOf("war")); + assertSame(Protocol.ZIP, Protocol.valueOf("zip")); + } + + @Test + void valueOf_unknownScheme_createsNewProtocol() { + Protocol protocol = Protocol.valueOf("custom"); + assertEquals("CUSTOM", protocol.getName()); + } + + @Test + void valueOf_nullOrEmptyScheme_returnsNull() { + assertNull(Protocol.valueOf((String) null)); + assertNull(Protocol.valueOf("")); + } + + @Test + void valueOf_withMatchingVersion_returnsSameConstant() { + Protocol protocol = Protocol.valueOf("http", "1.1"); + assertSame(Protocol.HTTP, protocol); + } + + @Test + void valueOf_withDifferentVersion_createsNewInstanceWithVersion() { + Protocol protocol = Protocol.valueOf("http", "2.0"); + assertNotSame(Protocol.HTTP, protocol); + assertEquals("2.0", protocol.getVersion()); + assertEquals(Protocol.HTTP.getSchemeName(), protocol.getSchemeName()); + assertEquals(Protocol.HTTP.getDefaultPort(), protocol.getDefaultPort()); + } + + @Test + void equals_isCaseInsensitiveOnName() { + Protocol p1 = new Protocol("proto", "NAME", "d", 1, false); + Protocol p2 = new Protocol("other", "name", "d2", 2, true); + assertEquals(p1, p2); + assertEquals(p1.hashCode(), p2.hashCode()); + } + + @Test + void equals_differentType_returnsFalse() { + assertNotEquals(Protocol.HTTP, "HTTP"); + } + + @Test + void equals_differentName_returnsFalse() { + assertNotEquals(Protocol.HTTP, Protocol.HTTPS); + } + + @Test + void toString_withVersion_includesVersion() { + assertEquals("HTTP/1.1", Protocol.HTTP.toString()); + } + + @Test + void toString_withoutVersion_omitsSlash() { + assertEquals("FTP", Protocol.FTP.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/ReferenceListTestCase.java b/org.restlet/src/test/java/org/restlet/data/ReferenceListTestCase.java new file mode 100644 index 0000000000..189910507d --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/ReferenceListTestCase.java @@ -0,0 +1,142 @@ +/** + * 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 static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; + +/** Test {@link org.restlet.data.ReferenceList}. */ +class ReferenceListTestCase { + + @Test + void defaultConstructor_isEmpty() { + ReferenceList list = new ReferenceList(); + assertTrue(list.isEmpty()); + assertNull(list.getIdentifier()); + } + + @Test + void constructor_withInitialCapacity_isEmpty() { + ReferenceList list = new ReferenceList(10); + assertTrue(list.isEmpty()); + } + + @Test + void constructor_withDelegate_wrapsGivenList() { + List delegate = new ArrayList<>(); + delegate.add(new Reference("http://example.com/a")); + ReferenceList list = new ReferenceList(delegate); + assertEquals(1, list.size()); + } + + @Test + void addUri_createsReferenceAndAppendsIt() { + ReferenceList list = new ReferenceList(); + assertTrue(list.add("http://example.com/a")); + assertEquals(1, list.size()); + assertEquals("http://example.com/a", list.get(0).toString()); + } + + @Test + void setAndGetIdentifier_withReference() { + ReferenceList list = new ReferenceList(); + Reference identifier = new Reference("http://example.com/"); + list.setIdentifier(identifier); + assertEquals(identifier, list.getIdentifier()); + } + + @Test + void setIdentifier_withStringUri_createsReference() { + ReferenceList list = new ReferenceList(); + list.setIdentifier("http://example.com/"); + assertEquals("http://example.com/", list.getIdentifier().toString()); + } + + @Test + void constructor_fromUriListRepresentation_parsesLines() throws IOException { + String content = + "# http://example.com/\r\n" + + "http://example.com/a\r\n" + + "http://example.com/b\r\n"; + Representation representation = new StringRepresentation(content, MediaType.TEXT_URI_LIST); + ReferenceList list = new ReferenceList(representation); + + assertEquals("http://example.com/", list.getIdentifier().toString()); + assertEquals(2, list.size()); + assertEquals("http://example.com/a", list.get(0).toString()); + assertEquals("http://example.com/b", list.get(1).toString()); + } + + @Test + void constructor_fromUriListRepresentation_withoutIdentifierComment() throws IOException { + String content = "http://example.com/a\r\n"; + Representation representation = new StringRepresentation(content, MediaType.TEXT_URI_LIST); + ReferenceList list = new ReferenceList(representation); + + assertNull(list.getIdentifier()); + assertEquals(1, list.size()); + } + + @Test + void getTextRepresentation_containsIdentifierAndReferences() throws IOException { + ReferenceList list = new ReferenceList(); + list.setIdentifier("http://example.com/"); + list.add("http://example.com/a"); + + Representation representation = list.getTextRepresentation(); + assertEquals(MediaType.TEXT_URI_LIST, representation.getMediaType()); + String text = representation.getText(); + assertTrue(text.contains("# http://example.com/")); + assertTrue(text.contains("http://example.com/a")); + } + + @Test + void getWebRepresentation_producesHtmlListing() throws IOException { + ReferenceList list = new ReferenceList(); + list.setIdentifier("http://example.com/dir/"); + list.add("http://example.com/dir/a"); + + Representation representation = list.getWebRepresentation(); + assertEquals(MediaType.TEXT_HTML, representation.getMediaType()); + String text = representation.getText(); + assertTrue(text.contains("")); + assertTrue(text.contains("http://example.com/dir/a")); + } + + @Test + void getWebRepresentation_withoutIdentifier_usesGenericTitle() throws IOException { + ReferenceList list = new ReferenceList(); + list.add("http://example.com/a"); + + Representation representation = list.getWebRepresentation(); + String text = representation.getText(); + assertTrue(text.contains("List of references")); + } + + @Test + void subList_returnsReferenceListInstance() { + ReferenceList list = new ReferenceList(); + list.add("http://example.com/a"); + list.add("http://example.com/b"); + list.add("http://example.com/c"); + + ReferenceList sub = list.subList(1, 3); + assertEquals(2, sub.size()); + assertEquals("http://example.com/b", sub.get(0).toString()); + assertEquals("http://example.com/c", sub.get(1).toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/data/ServerInfoTestCase.java b/org.restlet/src/test/java/org/restlet/data/ServerInfoTestCase.java new file mode 100644 index 0000000000..9cbf8f85ac --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/data/ServerInfoTestCase.java @@ -0,0 +1,43 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** Test {@link org.restlet.data.ServerInfo}. */ +class ServerInfoTestCase { + + @Test + void constructor_setsDefaultValues() { + ServerInfo info = new ServerInfo(); + assertNull(info.getAddress()); + assertNull(info.getAgent()); + assertEquals(-1, info.getPort()); + assertFalse(info.isAcceptingRanges()); + } + + @Test + void settersUpdateState() { + ServerInfo info = new ServerInfo(); + info.setAddress("127.0.0.1"); + info.setAgent("Restlet-Framework/2.5"); + info.setPort(8080); + info.setAcceptingRanges(true); + + assertEquals("127.0.0.1", info.getAddress()); + assertEquals("Restlet-Framework/2.5", info.getAgent()); + assertEquals(8080, info.getPort()); + assertTrue(info.isAcceptingRanges()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/CompositeHelperTestCase.java b/org.restlet/src/test/java/org/restlet/engine/CompositeHelperTestCase.java new file mode 100644 index 0000000000..8077680963 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/CompositeHelperTestCase.java @@ -0,0 +1,137 @@ +/** + * 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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.Method; +import org.restlet.data.Status; +import org.restlet.routing.Filter; + +class CompositeHelperTestCase { + + private static class TestCompositeHelper extends CompositeHelper { + TestCompositeHelper(Restlet helped) { + super(helped); + } + + @Override + public void start() {} + + @Override + public void stop() {} + + @Override + public void update() {} + } + + private static Filter markerFilter(java.util.concurrent.atomic.AtomicBoolean invoked) { + return new Filter() { + @Override + protected int doHandle(Request request, Response response) { + invoked.set(true); + return CONTINUE; + } + }; + } + + @Test + void addInboundFilter_singleFilter_becomesFirstAndLast() { + Context context = new Context(); + TestCompositeHelper helper = new TestCompositeHelper(new Restlet(context) {}); + Filter filter = new Filter() {}; + + helper.addInboundFilter(filter); + + assertEquals(filter, helper.getFirstInboundFilter()); + } + + @Test + void addOutboundFilter_singleFilter_becomesFirstAndLast() { + Context context = new Context(); + TestCompositeHelper helper = new TestCompositeHelper(new Restlet(context) {}); + Filter filter = new Filter() {}; + + helper.addOutboundFilter(filter); + + assertEquals(filter, helper.getFirstOutboundFilter()); + } + + @Test + void addOutboundFilter_secondFilter_chainsAfterFirst() { + Context context = new Context(); + TestCompositeHelper helper = new TestCompositeHelper(new Restlet(context) {}); + Filter first = new Filter() {}; + Filter second = new Filter() {}; + + helper.addOutboundFilter(first); + helper.addOutboundFilter(second); + + assertEquals(first, helper.getFirstOutboundFilter()); + assertEquals(second, first.getNext()); + } + + @Test + void clear_resetsAllFiltersAndNextRestlets() { + Context context = new Context(); + TestCompositeHelper helper = new TestCompositeHelper(new Restlet(context) {}); + helper.addInboundFilter(new Filter() {}); + helper.addOutboundFilter(new Filter() {}); + + helper.clear(); + + assertNull(helper.getFirstInboundFilter()); + assertNull(helper.getFirstOutboundFilter()); + assertNull(helper.getOutboundNext()); + } + + @Test + void handle_withInboundFilter_delegatesToFilter() { + Context context = new Context(); + TestCompositeHelper helper = new TestCompositeHelper(new Restlet(context) {}); + java.util.concurrent.atomic.AtomicBoolean invoked = + new java.util.concurrent.atomic.AtomicBoolean(); + helper.addInboundFilter(markerFilter(invoked)); + + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + helper.handle(request, response); + + assertEquals(true, invoked.get()); + } + + @Test + void handle_withoutFilterOrNext_setsInternalServerError() { + Context context = new Context(); + TestCompositeHelper helper = new TestCompositeHelper(new Restlet(context) {}); + + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + helper.handle(request, response); + + assertEquals(Status.SERVER_ERROR_INTERNAL, response.getStatus()); + } + + @Test + void getOutboundNext_withoutFilters_returnsSetNext() { + Context context = new Context(); + TestCompositeHelper helper = new TestCompositeHelper(new Restlet(context) {}); + Restlet next = new Restlet(context) {}; + + helper.setOutboundNext(next); + + assertEquals(next, helper.getOutboundNext()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/EditionTestCase.java b/org.restlet/src/test/java/org/restlet/engine/EditionTestCase.java new file mode 100644 index 0000000000..919f1b7a58 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/EditionTestCase.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; + +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 org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class EditionTestCase { + + @AfterEach + void tearDownEach() { + Edition.CURRENT = Edition.JSE; + } + + @Test + void getFullName_returnsConfiguredName() { + assertEquals("Java Standard Edition", Edition.JSE.getFullName()); + } + + @Test + void getMediumName_returnsConfiguredName() { + assertEquals("Java SE", Edition.JSE.getMediumName()); + } + + @Test + void getShortName_returnsConfiguredName() { + assertEquals("JSE", Edition.JSE.getShortName()); + } + + @Test + void isCurrentEdition_matchesCurrentStaticField() { + Edition.CURRENT = Edition.JSE; + assertTrue(Edition.JSE.isCurrentEdition()); + assertFalse(Edition.ANDROID.isCurrentEdition()); + } + + @Test + void isNotCurrentEdition_isInverseOfIsCurrentEdition() { + Edition.CURRENT = Edition.JSE; + assertFalse(Edition.JSE.isNotCurrentEdition()); + assertTrue(Edition.ANDROID.isNotCurrentEdition()); + } + + @Test + void setCurrentEdition_updatesCurrentField() { + Edition.ANDROID.setCurrentEdition(); + assertEquals(Edition.ANDROID, Edition.CURRENT); + } + + @Test + void isCurrentEditionOneOf_containingCurrentEdition_returnsTrue() { + assertTrue(Edition.isCurrentEditionOneOf(Edition.ANDROID, Edition.JSE)); + } + + @Test + void isCurrentEditionOneOf_notContainingCurrentEdition_returnsFalse() { + Edition.CURRENT = Edition.JSE; + assertFalse(Edition.isCurrentEditionOneOf(Edition.ANDROID)); + } + + @Test + void isCurrentEditionOneOf_nullArray_returnsFalse() { + assertFalse(Edition.isCurrentEditionOneOf((Edition[]) null)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/RestletHelperTestCase.java b/org.restlet/src/test/java/org/restlet/engine/RestletHelperTestCase.java new file mode 100644 index 0000000000..ecc2da2659 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/RestletHelperTestCase.java @@ -0,0 +1,131 @@ +/** + * 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; + +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.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.Method; +import org.restlet.service.MetadataService; + +class RestletHelperTestCase { + + private static class TestRestletHelper extends RestletHelper { + TestRestletHelper(Restlet helped) { + super(helped); + } + + @Override + public void start() {} + + @Override + public void stop() {} + + @Override + public void update() {} + } + + @Test + void getHelpedAndSetHelped_roundTrip() { + Restlet restlet = new Restlet() {}; + TestRestletHelper helper = new TestRestletHelper(restlet); + assertEquals(restlet, helper.getHelped()); + + Restlet other = new Restlet() {}; + helper.setHelped(other); + assertEquals(other, helper.getHelped()); + } + + @Test + void getAttributes_returnsMutableMap() { + TestRestletHelper helper = new TestRestletHelper(new Restlet() {}); + helper.getAttributes().put("key", "value"); + assertEquals("value", helper.getAttributes().get("key")); + } + + @Test + void getContext_delegatesToHelpedRestlet() { + Context context = new Context(); + Restlet restlet = new Restlet(context) {}; + TestRestletHelper helper = new TestRestletHelper(restlet); + assertEquals(context, helper.getContext()); + } + + @Test + void getHelpedParameters_withContext_returnsContextParameters() { + Context context = new Context(); + Restlet restlet = new Restlet(context) {}; + TestRestletHelper helper = new TestRestletHelper(restlet); + assertEquals(context.getParameters(), helper.getHelpedParameters()); + } + + @Test + void getHelpedParameters_withoutContext_returnsEmptySeries() { + Restlet restlet = new Restlet() {}; + TestRestletHelper helper = new TestRestletHelper(restlet); + assertTrue(helper.getHelpedParameters().isEmpty()); + } + + @Test + void getLogger_withContext_returnsContextLogger() { + Context context = new Context(); + Restlet restlet = new Restlet(context) {}; + TestRestletHelper helper = new TestRestletHelper(restlet); + assertEquals(context.getLogger(), helper.getLogger()); + } + + @Test + void getLogger_withoutContext_returnsCurrentLogger() { + Restlet restlet = new Restlet() {}; + TestRestletHelper helper = new TestRestletHelper(restlet); + assertNotNull(helper.getLogger()); + } + + @Test + void getMetadataService_withoutApplication_returnsNewInstance() { + Restlet restlet = new Restlet() {}; + TestRestletHelper helper = new TestRestletHelper(restlet); + assertNotNull(helper.getMetadataService()); + } + + @Test + void getMetadataService_withApplication_returnsApplicationMetadataService() { + org.restlet.Application application = new org.restlet.Application(new Context()); + org.restlet.Application.setCurrent(application); + try { + TestRestletHelper helper = new TestRestletHelper(application); + MetadataService result = helper.getMetadataService(); + assertEquals(application.getMetadataService(), result); + } finally { + org.restlet.Application.setCurrent(null); + } + } + + @Test + void handle_setsCurrentResponseAndContext() { + Context context = new Context(); + Restlet restlet = new Restlet(context) {}; + TestRestletHelper helper = new TestRestletHelper(restlet); + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + helper.handle(request, response); + + assertEquals(response, Response.getCurrent()); + assertEquals(context, Context.getCurrent()); + + Response.setCurrent(null); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/application/DecoderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/application/DecoderTestCase.java new file mode 100644 index 0000000000..ee3c29dec4 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/application/DecoderTestCase.java @@ -0,0 +1,149 @@ +/** + * 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 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.Method; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; + +class DecoderTestCase { + + @Test + void constructor_singleArg_decodesRequestOnly() { + Decoder decoder = new Decoder(new Context()); + assertTrue(decoder.isDecodingRequest()); + assertFalse(decoder.isDecodingResponse()); + } + + @Test + void constructor_explicitFlags_setsBoth() { + Decoder decoder = new Decoder(new Context(), false, true); + assertFalse(decoder.isDecodingRequest()); + assertTrue(decoder.isDecodingResponse()); + } + + @Test + void canDecode_nullRepresentation_returnsFalse() { + Decoder decoder = new Decoder(new Context()); + assertFalse(decoder.canDecode(null)); + } + + @Test + void canDecode_noEncodings_returnsFalse() { + Decoder decoder = new Decoder(new Context()); + Representation representation = new StringRepresentation("body"); + assertFalse(decoder.canDecode(representation)); + } + + @Test + void canDecode_identityEncodingOnly_returnsFalse() { + Decoder decoder = new Decoder(new Context()); + Representation representation = new StringRepresentation("body"); + representation.getEncodings().add(Encoding.IDENTITY); + assertFalse(decoder.canDecode(representation)); + } + + @Test + void canDecode_gzipEncoding_returnsTrue() { + Decoder decoder = new Decoder(new Context()); + Representation representation = new StringRepresentation("body"); + representation.getEncodings().add(Encoding.GZIP); + assertTrue(decoder.canDecode(representation)); + } + + @Test + void decode_unsupportedEncoding_returnsOriginalRepresentation() { + Decoder decoder = new Decoder(new Context()); + Representation representation = new StringRepresentation("body"); + representation.getEncodings().add(Encoding.COMPRESS); + assertEquals(representation, decoder.decode(representation)); + } + + @Test + void decode_identityEncodingOnly_returnsOriginalRepresentation() { + Decoder decoder = new Decoder(new Context()); + Representation representation = new StringRepresentation("body"); + representation.getEncodings().add(Encoding.IDENTITY); + assertEquals(representation, decoder.decode(representation)); + } + + @Test + void decode_supportedEncoding_wrapsInDecodeRepresentation() { + Decoder decoder = new Decoder(new Context()); + Representation representation = new StringRepresentation("body"); + representation.getEncodings().add(Encoding.GZIP); + assertTrue(decoder.decode(representation) instanceof DecodeRepresentation); + } + + @Test + void beforeHandle_decodingRequestEnabled_decodesRequestEntity() { + Decoder decoder = new Decoder(new Context(), true, false); + Request request = new Request(Method.POST, "http://localhost/test"); + Representation entity = new StringRepresentation("body"); + entity.getEncodings().add(Encoding.GZIP); + request.setEntity(entity); + Response response = new Response(request); + + int result = decoder.beforeHandle(request, response); + + assertTrue(request.getEntity() instanceof DecodeRepresentation); + assertEquals(org.restlet.routing.Filter.CONTINUE, result); + } + + @Test + void beforeHandle_decodingRequestDisabled_leavesRequestEntityUnchanged() { + Decoder decoder = new Decoder(new Context(), false, false); + Request request = new Request(Method.POST, "http://localhost/test"); + Representation entity = new StringRepresentation("body"); + entity.getEncodings().add(Encoding.GZIP); + request.setEntity(entity); + Response response = new Response(request); + + decoder.beforeHandle(request, response); + + assertEquals(entity, request.getEntity()); + } + + @Test + void afterHandle_decodingResponseEnabled_decodesResponseEntity() { + Decoder decoder = new Decoder(new Context(), false, true); + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + Representation entity = new StringRepresentation("body"); + entity.getEncodings().add(Encoding.GZIP); + response.setEntity(entity); + + decoder.afterHandle(request, response); + + assertTrue(response.getEntity() instanceof DecodeRepresentation); + } + + @Test + void afterHandle_decodingResponseDisabled_leavesResponseEntityUnchanged() { + Decoder decoder = new Decoder(new Context(), false, false); + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + Representation entity = new StringRepresentation("body"); + entity.getEncodings().add(Encoding.GZIP); + response.setEntity(entity); + + decoder.afterHandle(request, response); + + assertEquals(entity, response.getEntity()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/application/MetadataExtensionTestCase.java b/org.restlet/src/test/java/org/restlet/engine/application/MetadataExtensionTestCase.java new file mode 100644 index 0000000000..964d718c98 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/application/MetadataExtensionTestCase.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.engine.application; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; +import org.restlet.data.Encoding; +import org.restlet.data.Language; +import org.restlet.data.MediaType; + +class MetadataExtensionTestCase { + + @Test + void getName_returnsConstructedName() { + MetadataExtension extension = new MetadataExtension("json", MediaType.APPLICATION_JSON); + assertEquals("json", extension.getName()); + } + + @Test + void getMetadata_returnsConstructedMetadata() { + MetadataExtension extension = new MetadataExtension("json", MediaType.APPLICATION_JSON); + assertEquals(MediaType.APPLICATION_JSON, extension.getMetadata()); + } + + @Test + void getMediaType_castsMetadataToMediaType() { + MetadataExtension extension = new MetadataExtension("json", MediaType.APPLICATION_JSON); + assertEquals(MediaType.APPLICATION_JSON, extension.getMediaType()); + } + + @Test + void getCharacterSet_castsMetadataToCharacterSet() { + MetadataExtension extension = new MetadataExtension("utf8", CharacterSet.UTF_8); + assertEquals(CharacterSet.UTF_8, extension.getCharacterSet()); + } + + @Test + void getEncoding_castsMetadataToEncoding() { + MetadataExtension extension = new MetadataExtension("gzip", Encoding.GZIP); + assertEquals(Encoding.GZIP, extension.getEncoding()); + } + + @Test + void getLanguage_castsMetadataToLanguage() { + MetadataExtension extension = new MetadataExtension("en", Language.ENGLISH); + assertEquals(Language.ENGLISH, extension.getLanguage()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/application/RangeFilterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/application/RangeFilterTestCase.java new file mode 100644 index 0000000000..e762b8e8c2 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/application/RangeFilterTestCase.java @@ -0,0 +1,130 @@ +/** + * 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 org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.restlet.Application; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.Method; +import org.restlet.data.Range; +import org.restlet.data.Status; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; + +class RangeFilterTestCase { + + private Application application; + + private RangeFilter filter; + + @BeforeEach + void setUpEach() { + application = new Application(new Context()); + Application.setCurrent(application); + filter = new RangeFilter(application.getContext()); + } + + @AfterEach + void tearDownEach() { + Application.setCurrent(null); + } + + private Response successResponse(Method method, Representation entity) { + Request request = new Request(method, "http://localhost/test"); + Response response = new Response(request); + response.setStatus(Status.SUCCESS_OK); + response.setEntity(entity); + return response; + } + + @Test + void afterHandle_rangeServiceDisabled_marksServerInfoNotAcceptingRanges() { + application.getRangeService().setEnabled(false); + Response response = successResponse(Method.GET, new StringRepresentation("body")); + + filter.afterHandle(response.getRequest(), response); + + assertFalse(response.getServerInfo().isAcceptingRanges()); + } + + @Test + void afterHandle_rangeServiceEnabled_marksServerInfoAcceptingRanges() { + application.getRangeService().setEnabled(true); + Response response = successResponse(Method.GET, new StringRepresentation("body")); + + filter.afterHandle(response.getRequest(), response); + + assertTrue(response.getServerInfo().isAcceptingRanges()); + } + + @Test + void afterHandle_unsafeMethod_leavesEntityUnchanged() { + Response response = successResponse(Method.POST, new StringRepresentation("body")); + Representation original = response.getEntity(); + + filter.afterHandle(response.getRequest(), response); + + assertEquals(original, response.getEntity()); + assertEquals(Status.SUCCESS_OK, response.getStatus()); + } + + @Test + void afterHandle_nonSuccessStatus_leavesStatusUnchanged() { + Response response = successResponse(Method.GET, new StringRepresentation("body")); + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + + filter.afterHandle(response.getRequest(), response); + + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, response.getStatus()); + } + + @Test + void afterHandle_multipleRequestedRanges_setsNotImplemented() { + Response response = successResponse(Method.GET, new StringRepresentation("0123456789")); + response.getRequest().getRanges().add(new Range(0, 2)); + response.getRequest().getRanges().add(new Range(3, 2)); + + filter.afterHandle(response.getRequest(), response); + + assertEquals(Status.SERVER_ERROR_NOT_IMPLEMENTED, response.getStatus()); + } + + @Test + void afterHandle_noRequestedRange_leavesResponseUnchanged() { + Response response = successResponse(Method.GET, new StringRepresentation("0123456789")); + + filter.afterHandle(response.getRequest(), response); + + assertEquals(Status.SUCCESS_OK, response.getStatus()); + } + + @Test + void afterHandle_singleMatchingRange_wrapsEntityAsPartialContent() { + Response response = successResponse(Method.GET, new StringRepresentation("0123456789")); + Range requested = new Range(0, 5); + response.getRequest().getRanges().add(requested); + + filter.afterHandle(response.getRequest(), response); + + assertEquals(Status.SUCCESS_PARTIAL_CONTENT, response.getStatus()); + } + + @Test + void getRangeService_returnsApplicationRangeService() { + assertEquals(application.getRangeService(), filter.getRangeService()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/application/StatusFilterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/application/StatusFilterTestCase.java new file mode 100644 index 0000000000..e30bf832bf --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/application/StatusFilterTestCase.java @@ -0,0 +1,130 @@ +/** + * 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 org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.Method; +import org.restlet.data.Status; +import org.restlet.representation.StringRepresentation; +import org.restlet.service.StatusService; + +class StatusFilterTestCase { + + private Response newResponse() { + Request request = new Request(Method.GET, "http://localhost/test"); + return new Response(request); + } + + @Test + void constructor_withOverwritingFlag_setsFlagAndNullStatusService() { + StatusFilter filter = new StatusFilter(new Context(), true); + assertTrue(filter.isOverwriting()); + assertEquals(null, filter.getStatusService()); + } + + @Test + void constructor_withStatusService_copiesOverwritingFlag() { + StatusService service = new StatusService(); + service.setOverwriting(true); + StatusFilter filter = new StatusFilter(new Context(), service); + assertTrue(filter.isOverwriting()); + assertEquals(service, filter.getStatusService()); + } + + @Test + void setOverwriting_updatesFlag() { + StatusFilter filter = new StatusFilter(new Context(), false); + filter.setOverwriting(true); + assertTrue(filter.isOverwriting()); + } + + @Test + void setStatusService_updatesService() { + StatusFilter filter = new StatusFilter(new Context(), false); + StatusService service = new StatusService(); + filter.setStatusService(service); + assertEquals(service, filter.getStatusService()); + } + + @Test + void afterHandle_nullStatus_defaultsToSuccessOk() { + StatusFilter filter = new StatusFilter(new Context(), new StatusService()); + Response response = newResponse(); + response.setStatus(null); + + filter.afterHandle(response.getRequest(), response); + + assertEquals(Status.SUCCESS_OK, response.getStatus()); + } + + @Test + void afterHandle_errorStatusWithoutEntity_setsRepresentationFromStatusService() { + StatusFilter filter = new StatusFilter(new Context(), new StatusService()); + Response response = newResponse(); + response.setStatus(Status.SERVER_ERROR_INTERNAL); + + filter.afterHandle(response.getRequest(), response); + + assertNotNull(response.getEntity()); + } + + @Test + void afterHandle_errorStatusWithEntityNotOverwriting_leavesEntityUnchanged() { + StatusFilter filter = new StatusFilter(new Context(), false); + filter.setStatusService(new StatusService()); + Response response = newResponse(); + response.setStatus(Status.SERVER_ERROR_INTERNAL); + response.setEntity(new StringRepresentation("original")); + + filter.afterHandle(response.getRequest(), response); + + assertEquals("original", getText(response)); + } + + @Test + void afterHandle_errorStatusWithEntityOverwriting_replacesEntity() { + StatusFilter filter = new StatusFilter(new Context(), true); + filter.setStatusService(new StatusService()); + Response response = newResponse(); + response.setStatus(Status.SERVER_ERROR_INTERNAL); + response.setEntity(new StringRepresentation("original")); + + filter.afterHandle(response.getRequest(), response); + + assertFalse("original".equals(getText(response))); + } + + @Test + void afterHandle_successStatus_leavesEntityUnchanged() { + StatusFilter filter = new StatusFilter(new Context(), new StatusService()); + Response response = newResponse(); + response.setStatus(Status.SUCCESS_OK); + response.setEntity(new StringRepresentation("original")); + + filter.afterHandle(response.getRequest(), response); + + assertEquals("original", getText(response)); + } + + private static String getText(Response response) { + try { + return response.getEntity() == null ? null : response.getEntity().getText(); + } catch (java.io.IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/application/StrictConnegTestCase.java b/org.restlet/src/test/java/org/restlet/engine/application/StrictConnegTestCase.java new file mode 100644 index 0000000000..c97deb49c0 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/application/StrictConnegTestCase.java @@ -0,0 +1,154 @@ +/** + * 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.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.Request; +import org.restlet.data.CharacterSet; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Preference; +import org.restlet.representation.Variant; + +class StrictConnegTestCase { + + private static StrictConneg newConneg(Request request) { + return new StrictConneg(request, null); + } + + @Test + void scoreMediaType_nullMediaType_returnsZero() { + StrictConneg conneg = newConneg(new Request(Method.GET, "http://localhost/test")); + assertEquals(0.0F, conneg.scoreMediaType(null)); + } + + @Test + void scoreMediaType_noMatchingPreference_returnsNegativeOne() { + Request request = new Request(Method.GET, "http://localhost/test"); + request.getClientInfo().getAcceptedMediaTypes().add(new Preference<>(MediaType.TEXT_PLAIN)); + StrictConneg conneg = newConneg(request); + + assertEquals(-1.0F, conneg.scoreMediaType(MediaType.APPLICATION_JSON)); + } + + @Test + void scoreMediaType_matchingPreference_returnsQuality() { + Request request = new Request(Method.GET, "http://localhost/test"); + request.getClientInfo() + .getAcceptedMediaTypes() + .add(new Preference<>(MediaType.TEXT_PLAIN, 0.8F)); + StrictConneg conneg = newConneg(request); + + assertEquals(0.8F, conneg.scoreMediaType(MediaType.TEXT_PLAIN)); + } + + @Test + void scoreCharacterSet_matchingPreference_returnsQuality() { + Request request = new Request(Method.GET, "http://localhost/test"); + request.getClientInfo() + .getAcceptedCharacterSets() + .add(new Preference<>(CharacterSet.UTF_8, 0.9F)); + StrictConneg conneg = newConneg(request); + + assertEquals(0.9F, conneg.scoreCharacterSet(CharacterSet.UTF_8)); + } + + @Test + void scoreEncodings_emptyList_returnsZero() { + StrictConneg conneg = newConneg(new Request(Method.GET, "http://localhost/test")); + assertEquals(0.0F, conneg.scoreEncodings(List.of())); + } + + @Test + void scoreLanguages_emptyList_returnsZero() { + StrictConneg conneg = newConneg(new Request(Method.GET, "http://localhost/test")); + assertEquals(0.0F, conneg.scoreLanguages(List.of())); + } + + @Test + void scoreVariant_noAcceptedMediaType_returnsNegativeOne() { + Request request = new Request(Method.GET, "http://localhost/test"); + request.getClientInfo().getAcceptedMediaTypes().clear(); + request.getClientInfo() + .getAcceptedMediaTypes() + .add(new Preference<>(MediaType.TEXT_PLAIN, 1.0F)); + StrictConneg conneg = newConneg(request); + + Variant variant = new Variant(MediaType.APPLICATION_JSON); + assertEquals(-1.0F, conneg.scoreVariant(variant)); + } + + @Test + void scoreVariant_fullyAcceptedVariant_returnsPositiveScore() { + Request request = new Request(Method.GET, "http://localhost/test"); + request.getClientInfo() + .getAcceptedMediaTypes() + .add(new Preference<>(MediaType.TEXT_PLAIN, 1.0F)); + request.getClientInfo() + .getAcceptedCharacterSets() + .add(new Preference<>(CharacterSet.UTF_8, 1.0F)); + request.getClientInfo() + .getAcceptedLanguages() + .add(new Preference<>(Language.ENGLISH, 1.0F)); + StrictConneg conneg = newConneg(request); + + Variant variant = new Variant(MediaType.TEXT_PLAIN); + variant.setCharacterSet(CharacterSet.UTF_8); + variant.setLanguages(Arrays.asList(Language.ENGLISH)); + + float score = conneg.scoreVariant(variant); + assertTrue(score >= 0.0F); + } + + @Test + void getPreferredVariant_choosesHighestScoringVariant() { + Request request = new Request(Method.GET, "http://localhost/test"); + request.getClientInfo().getAcceptedMediaTypes().clear(); + request.getClientInfo() + .getAcceptedMediaTypes() + .add(new Preference<>(MediaType.APPLICATION_JSON, 1.0F)); + request.getClientInfo() + .getAcceptedMediaTypes() + .add(new Preference<>(MediaType.TEXT_PLAIN, 0.5F)); + StrictConneg conneg = newConneg(request); + + Variant jsonVariant = new Variant(MediaType.APPLICATION_JSON); + Variant textVariant = new Variant(MediaType.TEXT_PLAIN); + + Variant preferred = conneg.getPreferredVariant(Arrays.asList(textVariant, jsonVariant)); + + assertEquals(jsonVariant, preferred); + } + + @Test + void getPreferredVariant_emptyList_returnsNull() { + StrictConneg conneg = newConneg(new Request(Method.GET, "http://localhost/test")); + assertNull(conneg.getPreferredVariant(List.of())); + } + + @Test + void getPreferredVariant_nullList_returnsNull() { + StrictConneg conneg = newConneg(new Request(Method.GET, "http://localhost/test")); + assertNull(conneg.getPreferredVariant(null)); + } + + @Test + void getRequest_returnsConstructorRequest() { + Request request = new Request(Method.GET, "http://localhost/test"); + StrictConneg conneg = newConneg(request); + assertEquals(request, conneg.getRequest()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/converter/ConverterUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/converter/ConverterUtilsTestCase.java new file mode 100644 index 0000000000..a28377d3eb --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/converter/ConverterUtilsTestCase.java @@ -0,0 +1,53 @@ +/** + * 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.converter; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +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.data.MediaType; +import org.restlet.data.Status; +import org.restlet.engine.application.StatusInfo; +import org.restlet.engine.resource.VariantInfo; +import org.restlet.representation.StringRepresentation; +import org.restlet.representation.Variant; + +class ConverterUtilsTestCase { + + @Test + void getBestHelper_objectSource_findsMatchingConverter() { + StatusInfo source = new StatusInfo(Status.SUCCESS_OK); + ConverterHelper helper = + ConverterUtils.getBestHelper(source, new Variant(MediaType.TEXT_HTML), null); + assertNotNull(helper); + assertTrue(helper instanceof StatusInfoHtmlConverter); + } + + @Test + void getBestHelper_representationSource_findsMatchingConverter() { + StringRepresentation source = new StringRepresentation("body", MediaType.TEXT_PLAIN); + ConverterHelper helper = ConverterUtils.getBestHelper(source, String.class, null); + assertNotNull(helper); + } + + @Test + void getVariants_statusInfoClass_returnsVariants() { + List variants = ConverterUtils.getVariants(StatusInfo.class, null); + assertNotNull(variants); + assertTrue(variants.size() >= 2); + } + + @Test + void getVariants_unsupportedClass_returnsNull() { + assertNull(ConverterUtils.getVariants(ConverterUtilsTestCase.class, null)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/converter/DefaultConverterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/converter/DefaultConverterTestCase.java new file mode 100644 index 0000000000..a3bfbce041 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/converter/DefaultConverterTestCase.java @@ -0,0 +1,249 @@ +/** + * 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.converter; + +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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Preference; +import org.restlet.representation.InputRepresentation; +import org.restlet.representation.ObjectRepresentation; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.representation.Variant; + +class DefaultConverterTestCase { + + private final DefaultConverter converter = new DefaultConverter(); + + @Test + void getObjectClasses_defaultVariant_includesBaseClasses() { + List> classes = converter.getObjectClasses(new Variant()); + assertTrue(classes.contains(String.class)); + assertTrue(classes.contains(InputStream.class)); + assertTrue(classes.contains(Reader.class)); + } + + @Test + void getObjectClasses_formVariant_includesFormClass() { + List> classes = + converter.getObjectClasses(new Variant(MediaType.APPLICATION_WWW_FORM)); + assertTrue(classes.contains(Form.class)); + } + + @Test + void getVariants_stringClass_returnsAllVariant() throws IOException { + assertEquals(1, converter.getVariants(String.class).size()); + } + + @Test + void getVariants_formClass_returnsFormVariant() throws IOException { + assertEquals(1, converter.getVariants(Form.class).size()); + } + + @Test + void getVariants_nullClass_returnsEmptyList() throws IOException { + assertTrue(converter.getVariants(null).isEmpty()); + } + + @Test + void getVariants_unrelatedClass_returnsEmptyList() throws IOException { + assertTrue(converter.getVariants(Object.class).isEmpty()); + } + + @Test + void score_string_returnsMaxScore() { + assertEquals(1.0F, converter.score("text", null, null)); + } + + @Test + void score_inputStream_returnsMaxScore() { + assertEquals(1.0F, converter.score(new ByteArrayInputStream(new byte[0]), null, null)); + } + + @Test + void score_form_compatibleTarget_returnsMaxScore() { + Form form = new Form(); + assertEquals( + 1.0F, converter.score(form, new Variant(MediaType.APPLICATION_WWW_FORM), null)); + } + + @Test + void score_form_incompatibleTarget_returnsLowerScore() { + Form form = new Form(); + assertEquals(0.6F, converter.score(form, new Variant(MediaType.TEXT_PLAIN), null)); + } + + @Test + void score_null_returnsNegative() { + assertEquals(-1.0F, converter.score((Object) null, (Variant) null, null)); + } + + @Test + void score_representationTarget_stringClass_returnsMaxScore() throws IOException { + Representation source = new StringRepresentation("body"); + assertEquals(1.0F, converter.score(source, String.class, null)); + } + + @Test + void score_representationTarget_nullTargetNonObjectRepresentation_returnsNegative() { + Representation source = new StringRepresentation("body"); + assertEquals(-1.0F, converter.score(source, (Class) null, null)); + } + + @Test + void score_representationTarget_fileClassWithNonFileRepresentation_returnsNegative() { + Representation source = new StringRepresentation("body"); + assertEquals(-1.0F, converter.score(source, java.io.File.class, null)); + } + + @Test + void score_representationTarget_formClassCompatible_returnsMaxScore() { + Representation source = new StringRepresentation("a=b", MediaType.APPLICATION_WWW_FORM); + assertEquals(1.0F, converter.score(source, Form.class, null)); + } + + @Test + void toObject_targetIsSourceType_returnsSourceUnchanged() throws IOException { + Representation source = new StringRepresentation("body"); + assertEquals(source, converter.toObject(source, Representation.class, null)); + } + + @Test + void toObject_targetIsString_returnsText() throws IOException { + Representation source = new StringRepresentation("hello"); + assertEquals("hello", converter.toObject(source, String.class, null)); + } + + @Test + void toObject_targetIsForm_parsesFormEntity() throws IOException { + Representation source = new StringRepresentation("a=b", MediaType.APPLICATION_WWW_FORM); + Form form = converter.toObject(source, Form.class, null); + assertEquals("b", form.getFirstValue("a")); + } + + @Test + void toObject_targetIsInputStream_returnsStream() throws IOException { + Representation source = new StringRepresentation("hello"); + assertTrue(converter.toObject(source, InputStream.class, null) instanceof InputStream); + } + + @Test + void toObject_targetIsInputRepresentation_wrapsStream() throws IOException { + Representation source = new StringRepresentation("hello"); + assertTrue( + converter.toObject(source, InputRepresentation.class, null) + instanceof InputRepresentation); + } + + @Test + void toObject_targetIsReader_returnsReader() throws IOException { + Representation source = new StringRepresentation("hello"); + assertTrue(converter.toObject(source, Reader.class, null) instanceof Reader); + } + + @Test + void toObject_unrelatedNullTarget_nonObjectRepresentation_returnsNull() throws IOException { + Representation source = new StringRepresentation("hello"); + assertNull(converter.toObject(source, null, null)); + } + + @Test + void toRepresentation_string_usesTextPlainByDefault() throws IOException { + Representation result = converter.toRepresentation("hello", new Variant(), null); + assertEquals("hello", result.getText()); + assertEquals(MediaType.TEXT_PLAIN, result.getMediaType()); + } + + @Test + void toRepresentation_inputStream_usesOctetStreamByDefault() throws IOException { + Representation result = + converter.toRepresentation( + new ByteArrayInputStream("hi".getBytes()), new Variant(), null); + assertEquals(MediaType.APPLICATION_OCTET_STREAM, result.getMediaType()); + } + + @Test + void toRepresentation_reader_usesTextPlainByDefault() throws IOException { + Representation result = + converter.toRepresentation(new StringReader("hi"), new Variant(), null); + assertEquals("hi", result.getText()); + } + + @Test + void toRepresentation_representation_returnsUnchanged() throws IOException { + Representation source = new StringRepresentation("hi"); + assertEquals(source, converter.toRepresentation(source, new Variant(), null)); + } + + @Test + void toRepresentation_form_returnsWebRepresentation() throws IOException { + Form form = new Form(); + form.add("a", "b"); + Representation result = converter.toRepresentation(form, new Variant(), null); + assertEquals(MediaType.APPLICATION_WWW_FORM, result.getMediaType()); + } + + @Test + void toRepresentation_serializable_returnsObjectRepresentation() throws IOException { + Representation result = + converter.toRepresentation( + "hello", new Variant(MediaType.APPLICATION_JAVA_OBJECT), null); + assertTrue( + result instanceof ObjectRepresentation + || result instanceof StringRepresentation); + } + + @Test + void toRepresentation_null_returnsNull() throws IOException { + assertNull(converter.toRepresentation(null, new Variant(), null)); + } + + @Test + void updatePreferences_form_addsFormPreference() { + List> preferences = new ArrayList<>(); + converter.updatePreferences(preferences, Form.class); + assertEquals(MediaType.APPLICATION_WWW_FORM, preferences.get(0).getMetadata()); + } + + @Test + void updatePreferences_string_addsTextPreferences() { + List> preferences = new ArrayList<>(); + converter.updatePreferences(preferences, String.class); + assertTrue( + preferences.stream().anyMatch(p -> p.getMetadata().equals(MediaType.TEXT_PLAIN))); + } + + @Test + void updatePreferences_inputStream_addsOctetStreamPreferences() { + List> preferences = new ArrayList<>(); + converter.updatePreferences(preferences, InputStream.class); + assertTrue( + preferences.stream() + .anyMatch(p -> p.getMetadata().equals(MediaType.APPLICATION_OCTET_STREAM))); + } + + @Test + void updatePreferences_unrelatedClass_leavesPreferencesEmpty() { + List> preferences = new ArrayList<>(); + converter.updatePreferences(preferences, Object.class); + assertTrue(preferences.isEmpty()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/converter/StatusInfoHtmlConverterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/converter/StatusInfoHtmlConverterTestCase.java new file mode 100644 index 0000000000..d41a4f816d --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/converter/StatusInfoHtmlConverterTestCase.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.converter; + +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.IOException; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.data.MediaType; +import org.restlet.data.Status; +import org.restlet.engine.application.StatusInfo; +import org.restlet.representation.Representation; +import org.restlet.representation.Variant; + +class StatusInfoHtmlConverterTestCase { + + private final StatusInfoHtmlConverter converter = new StatusInfoHtmlConverter(); + + @Test + void getObjectClasses_compatibleVariant_returnsStatusInfo() { + List> classes = converter.getObjectClasses(new Variant(MediaType.TEXT_HTML)); + assertTrue(classes.contains(StatusInfo.class)); + } + + @Test + void getObjectClasses_incompatibleVariant_returnsNull() { + assertNull(converter.getObjectClasses(new Variant(MediaType.APPLICATION_JSON))); + } + + @Test + void getVariants_statusInfoClass_returnsHtmlVariants() throws IOException { + List variants = converter.getVariants(StatusInfo.class); + assertEquals(2, variants.size()); + } + + @Test + void getVariants_otherClass_returnsEmptyList() throws IOException { + assertTrue(converter.getVariants(String.class).isEmpty()); + } + + @Test + void getVariants_nullClass_returnsEmptyList() throws IOException { + assertTrue(converter.getVariants(null).isEmpty()); + } + + @Test + void score_statusInfoSourceAndCompatibleTarget_returnsMaxScore() { + StatusInfo status = new StatusInfo(Status.SUCCESS_OK); + float score = converter.score(status, new Variant(MediaType.TEXT_HTML), null); + assertEquals(1.0F, score); + } + + @Test + void score_nonStatusInfoSource_returnsNegative() { + float score = converter.score("not status info", new Variant(MediaType.TEXT_HTML), null); + assertTrue(score < 0); + } + + @Test + void score_representationSourceOverload_alwaysReturnsNegative() throws IOException { + Representation rep = new org.restlet.representation.StringRepresentation("body"); + assertTrue(converter.score(rep, StatusInfo.class, null) < 0); + } + + @Test + void toObject_alwaysReturnsNull() throws IOException { + Representation rep = new org.restlet.representation.StringRepresentation("body"); + assertNull(converter.toObject(rep, StatusInfo.class, null)); + } + + @Test + void toRepresentation_statusInfoWithDescriptionAndContact_includesAllSections() + throws IOException { + StatusInfo status = new StatusInfo(Status.SERVER_ERROR_INTERNAL); + status.setDescription("Something broke"); + status.setContactEmail("admin@example.com"); + status.setHomeRef("http://example.com/home"); + + Representation result = + converter.toRepresentation(status, new Variant(MediaType.TEXT_HTML), null); + String html = result.getText(); + + assertTrue(html.contains("Something broke")); + assertTrue(html.contains("admin@example.com")); + assertTrue(html.contains("home page")); + assertEquals(MediaType.TEXT_HTML, result.getMediaType()); + } + + @Test + void toRepresentation_statusInfoWithoutOptionalFields_omitsThoseSections() throws IOException { + StatusInfo status = new StatusInfo(0, null, null); + Representation result = + converter.toRepresentation(status, new Variant(MediaType.TEXT_HTML), null); + String html = result.getText(); + assertTrue(html.contains("No information available")); + } + + @Test + void toRepresentation_nonStatusInfoSource_returnsNull() throws IOException { + assertNull( + converter.toRepresentation( + "not status info", new Variant(MediaType.TEXT_HTML), null)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/CacheDirectiveReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/CacheDirectiveReaderTestCase.java new file mode 100644 index 0000000000..7118ee664d --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/CacheDirectiveReaderTestCase.java @@ -0,0 +1,53 @@ +/** + * 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.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.data.CacheDirective; +import org.restlet.data.Header; + +class CacheDirectiveReaderTestCase { + + @Test + void readValue_nameOnlyDirective_parsesName() throws IOException { + CacheDirectiveReader reader = new CacheDirectiveReader("no-cache"); + CacheDirective directive = reader.readValue(); + assertEquals("no-cache", directive.getName()); + } + + @Test + void readValue_nameValueDirective_parsesNameAndValue() throws IOException { + CacheDirectiveReader reader = new CacheDirectiveReader("max-age=3600"); + CacheDirective directive = reader.readValue(); + assertEquals("max-age", directive.getName()); + assertEquals("3600", directive.getValue()); + } + + @Test + void addValues_multipleDirectives_populatesCollection() { + List directives = new ArrayList<>(); + new CacheDirectiveReader("no-cache, max-age=3600").addValues(directives); + assertEquals(2, directives.size()); + assertEquals("no-cache", directives.get(0).getName()); + assertEquals("max-age", directives.get(1).getName()); + } + + @Test + void addValuesStatic_fromHeader_populatesCollection() { + List directives = new ArrayList<>(); + Header header = new Header("Cache-Control", "no-cache"); + CacheDirectiveReader.addValues(header, directives); + assertEquals(1, directives.size()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/CacheDirectiveWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/CacheDirectiveWriterTestCase.java new file mode 100644 index 0000000000..9714e3c163 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/CacheDirectiveWriterTestCase.java @@ -0,0 +1,39 @@ +/** + * 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.assertTrue; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.restlet.data.CacheDirective; + +class CacheDirectiveWriterTestCase { + + @Test + void write_nameOnlyDirective_writesNameOnly() { + String result = CacheDirectiveWriter.write(Arrays.asList(CacheDirective.noCache())); + assertEquals("no-cache", result); + } + + @Test + void write_nameValueDirective_writesNameEqualsValue() { + String result = CacheDirectiveWriter.write(Arrays.asList(CacheDirective.maxAge(3600))); + assertTrue(result.startsWith("max-age=")); + } + + @Test + void write_multipleDirectives_joinsWithSeparator() { + String result = + CacheDirectiveWriter.write( + Arrays.asList(CacheDirective.noCache(), CacheDirective.noStore())); + assertEquals("no-cache, no-store", result); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/ChallengeRequestReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/ChallengeRequestReaderTestCase.java new file mode 100644 index 0000000000..6737670f73 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/ChallengeRequestReaderTestCase.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.engine.header; + +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.IOException; +import org.junit.jupiter.api.Test; +import org.restlet.data.ChallengeRequest; + +class ChallengeRequestReaderTestCase { + + @Test + void readValue_schemeOnly_parsesSchemeName() throws IOException { + ChallengeRequestReader reader = new ChallengeRequestReader("Basic"); + ChallengeRequest result = reader.readValue(); + assertEquals("Basic", result.getScheme().getTechnicalName()); + } + + @Test + void readValue_schemeWithParameters_capturesRawValue() throws IOException { + ChallengeRequestReader reader = new ChallengeRequestReader("Digest realm=\"testrealm\""); + ChallengeRequest result = reader.readValue(); + assertEquals("Digest", result.getScheme().getTechnicalName()); + assertTrue(result.getRawValue().contains("realm")); + } + + @Test + void readValue_emptyHeader_returnsNull() throws IOException { + ChallengeRequestReader reader = new ChallengeRequestReader(""); + assertNull(reader.readValue()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/ChallengeWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/ChallengeWriterTestCase.java new file mode 100644 index 0000000000..44c76513bd --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/ChallengeWriterTestCase.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.engine.header; + +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 org.junit.jupiter.api.Test; +import org.restlet.data.Parameter; + +class ChallengeWriterTestCase { + + @Test + void append_challengeRequest_isNoOp() { + ChallengeWriter writer = new ChallengeWriter(); + writer.append((org.restlet.data.ChallengeRequest) null); + assertEquals("", writer.toString()); + } + + @Test + void isFirstChallengeParameter_initiallyTrue() { + assertTrue(new ChallengeWriter().isFirstChallengeParameter()); + } + + @Test + void appendChallengeParameter_name_writesTokenWithoutLeadingComma() { + ChallengeWriter writer = new ChallengeWriter(); + writer.appendChallengeParameter("realm"); + assertEquals("realm", writer.toString()); + assertFalse(writer.isFirstChallengeParameter()); + } + + @Test + void appendChallengeParameter_nameAndValue_joinsWithEquals() { + ChallengeWriter writer = new ChallengeWriter(); + writer.appendChallengeParameter("realm", "test"); + assertEquals("realm=test", writer.toString()); + } + + @Test + void appendChallengeParameter_secondParameter_prefixedWithComma() { + ChallengeWriter writer = new ChallengeWriter(); + writer.appendChallengeParameter("a", "1"); + writer.appendChallengeParameter("b", "2"); + assertEquals("a=1, b=2", writer.toString()); + } + + @Test + void appendChallengeParameter_fromParameterObject_delegatesToNameValueOverload() { + ChallengeWriter writer = new ChallengeWriter(); + writer.appendChallengeParameter(new Parameter("realm", "test")); + assertEquals("realm=test", writer.toString()); + } + + @Test + void appendQuotedChallengeParameter_quotesValue() { + ChallengeWriter writer = new ChallengeWriter(); + writer.appendQuotedChallengeParameter("realm", "test realm"); + assertEquals("realm=\"test realm\"", writer.toString()); + } + + @Test + void appendQuotedChallengeParameter_fromParameterObject_delegatesToNameValueOverload() { + ChallengeWriter writer = new ChallengeWriter(); + writer.appendQuotedChallengeParameter(new Parameter("realm", "test realm")); + assertEquals("realm=\"test realm\"", writer.toString()); + } + + @Test + void setFirstChallengeParameter_updatesState() { + ChallengeWriter writer = new ChallengeWriter(); + writer.setFirstChallengeParameter(false); + assertFalse(writer.isFirstChallengeParameter()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/ContentTypeReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/ContentTypeReaderTestCase.java new file mode 100644 index 0000000000..820455af95 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/ContentTypeReaderTestCase.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.engine.header; + +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.io.IOException; +import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; +import org.restlet.data.MediaType; + +class ContentTypeReaderTestCase { + + @Test + void readValue_mediaTypeOnly_parsesMediaType() throws IOException { + ContentType result = new ContentTypeReader("text/plain").readValue(); + assertEquals(MediaType.TEXT_PLAIN, result.getMediaType()); + assertNull(result.getCharacterSet()); + } + + @Test + void readValue_withCharsetParameter_extractsCharacterSet() throws IOException { + ContentType result = new ContentTypeReader("text/plain; charset=UTF-8").readValue(); + assertEquals(MediaType.TEXT_PLAIN, result.getMediaType()); + assertEquals(CharacterSet.UTF_8, result.getCharacterSet()); + } + + @Test + void readValue_withOtherParameter_keepsItOnMediaType() throws IOException { + ContentType result = + new ContentTypeReader("multipart/form-data; boundary=abc123").readValue(); + assertEquals("abc123", result.getMediaType().getParameters().getFirstValue("boundary")); + } + + @Test + void readValue_withQuotedParameterValue_unquotesIt() throws IOException { + ContentType result = + new ContentTypeReader("multipart/form-data; boundary=\"abc 123\"").readValue(); + assertEquals("abc 123", result.getMediaType().getParameters().getFirstValue("boundary")); + } + + @Test + void readValue_emptyMediaTypeName_throws() { + ContentTypeReader reader = new ContentTypeReader("; charset=UTF-8"); + assertThrows(IOException.class, reader::readValue); + } + + @Test + void readValue_emptyParameterName_throws() { + ContentTypeReader reader = new ContentTypeReader("text/plain; =UTF-8"); + assertThrows(IOException.class, reader::readValue); + } + + @Test + void readValue_parameterWithoutValue_isRecordedWithNullValue() throws IOException { + ContentType result = new ContentTypeReader("text/plain; charset").readValue(); + assertNull(result.getMediaType().getParameters().getFirstValue("charset")); + } + + @Test + void readValue_emptyHeader_returnsNull() throws IOException { + assertNull(new ContentTypeReader("").readValue()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/CookieSettingReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/CookieSettingReaderTestCase.java new file mode 100644 index 0000000000..eed6357ed5 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/CookieSettingReaderTestCase.java @@ -0,0 +1,119 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.restlet.data.CookieSetting; + +class CookieSettingReaderTestCase { + + @Test + void read_nameAndValue_parsesBoth() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value"); + assertEquals("name", cookieSetting.getName()); + assertEquals("value", cookieSetting.getValue()); + } + + @Test + void read_emptyHeader_returnsNull() { + assertNull(CookieSettingReader.read("")); + } + + @Test + void read_dollarPrefixedAttribute_isSkipped() { + CookieSetting cookieSetting = CookieSettingReader.read("$Version=1; name=value"); + assertEquals("name", cookieSetting.getName()); + } + + @Test + void read_path_setsPath() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value; path=/app"); + assertEquals("/app", cookieSetting.getPath()); + } + + @Test + void read_domain_setsDomain() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value; domain=example.com"); + assertEquals("example.com", cookieSetting.getDomain()); + } + + @Test + void read_comment_setsComment() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value; comment=hello"); + assertEquals("hello", cookieSetting.getComment()); + } + + @Test + void read_discard_setsMaxAgeToNegativeOne() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value; discard"); + assertEquals(-1, cookieSetting.getMaxAge()); + } + + @Test + void read_validMaxAge_setsMaxAge() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value; max-age=3600"); + assertEquals(3600, cookieSetting.getMaxAge()); + } + + @Test + void read_invalidMaxAge_defaultsToIntegerMax() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value; max-age=notanumber"); + assertEquals(Integer.MAX_VALUE, cookieSetting.getMaxAge()); + } + + @Test + void read_secureFlag_setsSecureTrue() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value; secure"); + assertTrue(cookieSetting.isSecure()); + } + + @Test + void read_httpOnlyFlag_setsAccessRestrictedTrue() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value; httpOnly"); + assertTrue(cookieSetting.isAccessRestricted()); + } + + @Test + void read_version_setsVersion() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value; version=1"); + assertEquals(1, cookieSetting.getVersion()); + } + + @Test + void read_unknownAttribute_isIgnored() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value; unknown=foo"); + assertEquals("name", cookieSetting.getName()); + } + + @Test + void read_validExpiresDate_setsPositiveMaxAge() { + java.util.Date future = new java.util.Date(System.currentTimeMillis() + 3_600_000L); + String expires = + org.restlet.engine.util.DateUtils.format( + future, org.restlet.engine.util.DateUtils.FORMAT_RFC_1123.get(0)); + CookieSetting cookieSetting = CookieSettingReader.read("name=value; expires=" + expires); + assertTrue(cookieSetting.getMaxAge() > 0); + } + + @Test + void read_invalidExpiresDate_leavesMaxAgeAtDefault() { + CookieSetting cookieSetting = CookieSettingReader.read("name=value; expires=not-a-date"); + assertEquals(-1, cookieSetting.getMaxAge()); + } + + @Test + void read_quotedValue_isUnquoted() { + CookieSetting cookieSetting = CookieSettingReader.read("name=\"quoted value\""); + assertEquals("quoted value", cookieSetting.getValue()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/CookieSettingWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/CookieSettingWriterTestCase.java new file mode 100644 index 0000000000..e8eb73549e --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/CookieSettingWriterTestCase.java @@ -0,0 +1,117 @@ +/** + * 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 static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.data.CookieSetting; + +class CookieSettingWriterTestCase { + + @Test + void write_simpleCookie_writesNameEqualsValue() { + assertEquals("name=value", CookieSettingWriter.write(new CookieSetting("name", "value"))); + } + + @Test + void write_missingName_throwsIllegalArgumentException() { + CookieSetting cookieSetting = new CookieSetting(0, "", "value"); + assertThrows( + IllegalArgumentException.class, () -> CookieSettingWriter.write(cookieSetting)); + } + + @Test + void write_versionOne_appendsQuotedVersion() { + CookieSetting cookieSetting = new CookieSetting(1, "name", "value"); + assertTrue( + CookieSettingWriter.write(cookieSetting) + .startsWith("name=\"value\"; Version=\"1\"")); + } + + @Test + void write_pathVersionZero_appendsUnquotedPath() { + CookieSetting cookieSetting = new CookieSetting(0, "name", "value", "/path", null); + assertEquals("name=value; Path=/path", CookieSettingWriter.write(cookieSetting)); + } + + @Test + void write_pathVersionOne_appendsQuotedPath() { + CookieSetting cookieSetting = new CookieSetting(1, "name", "value", "/path", null); + assertTrue(CookieSettingWriter.write(cookieSetting).contains("; Path=\"/path\"")); + } + + @Test + void write_maxAgeVersionZero_appendsExpiresDate() { + CookieSetting cookieSetting = new CookieSetting("name", "value"); + cookieSetting.setMaxAge(3600); + assertTrue(CookieSettingWriter.write(cookieSetting).contains("; Expires=")); + } + + @Test + void write_maxAgeVersionOne_appendsMaxAge() { + CookieSetting cookieSetting = new CookieSetting(1, "name", "value"); + cookieSetting.setMaxAge(3600); + assertTrue(CookieSettingWriter.write(cookieSetting).contains("; Max-Age=\"3600\"")); + } + + @Test + void write_discardVersionOne_appendsDiscard() { + CookieSetting cookieSetting = new CookieSetting(1, "name", "value"); + cookieSetting.setMaxAge(-1); + assertTrue(CookieSettingWriter.write(cookieSetting).contains("; Discard")); + } + + @Test + void write_domain_isLowercased() { + CookieSetting cookieSetting = new CookieSetting(0, "name", "value", null, "EXAMPLE.COM"); + assertTrue(CookieSettingWriter.write(cookieSetting).contains("; Domain=example.com")); + } + + @Test + void write_secureFlag_appendsSecure() { + CookieSetting cookieSetting = new CookieSetting("name", "value"); + cookieSetting.setSecure(true); + assertTrue(CookieSettingWriter.write(cookieSetting).contains("; Secure")); + } + + @Test + void write_accessRestrictedFlag_appendsHttpOnly() { + CookieSetting cookieSetting = new CookieSetting("name", "value"); + cookieSetting.setAccessRestricted(true); + assertTrue(CookieSettingWriter.write(cookieSetting).contains("; HttpOnly")); + } + + @Test + void write_commentVersionOne_appendsQuotedComment() { + CookieSetting cookieSetting = new CookieSetting(1, "name", "value"); + cookieSetting.setComment("a comment"); + assertTrue(CookieSettingWriter.write(cookieSetting).contains("; Comment=\"a comment\"")); + } + + @Test + void write_commentVersionZero_isIgnored() { + CookieSetting cookieSetting = new CookieSetting("name", "value"); + cookieSetting.setComment("a comment"); + assertTrue(!CookieSettingWriter.write(cookieSetting).contains("Comment")); + } + + @Test + void write_listOfCookieSettings_joinsWithSemicolon() { + List settings = + Arrays.asList(new CookieSetting("a", "1"), new CookieSetting("b", "2")); + String result = CookieSettingWriter.write(settings); + assertTrue(result.contains("a=1")); + assertTrue(result.contains("b=2")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/CookieWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/CookieWriterTestCase.java new file mode 100644 index 0000000000..eeac3bfa06 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/CookieWriterTestCase.java @@ -0,0 +1,91 @@ +/** + * 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 static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.restlet.data.Cookie; + +class CookieWriterTestCase { + + @Test + void write_simpleCookie_writesNameEqualsValue() { + assertEquals("name=value", CookieWriter.write(new Cookie("name", "value"))); + } + + @Test + void write_versionedCookie_quotesValueAndAppendsPathAndDomain() { + Cookie cookie = new Cookie(1, "name", "value", "/path", "example.com"); + String result = CookieWriter.write(cookie); + assertEquals("name=\"value\"; $Path=\"/path\"; $Domain=\"example.com\"", result); + } + + @Test + void write_missingName_throwsIllegalArgumentException() { + Cookie cookie = new Cookie(0, "", "value"); + assertThrows(IllegalArgumentException.class, () -> CookieWriter.write(cookie)); + } + + @Test + void write_listOfCookies_prefixesVersionAndJoinsWithSemicolon() { + List cookies = Arrays.asList(new Cookie(1, "a", "1"), new Cookie(1, "b", "2")); + String result = CookieWriter.write(cookies); + assertEquals("$Version=\"1\"; a=\"1\"; b=\"2\"", result); + } + + @Test + void write_listWithVersionZero_omitsVersionPrefix() { + List cookies = Arrays.asList(new Cookie("a", "1"), new Cookie("b", "2")); + assertEquals("a=1; b=2", CookieWriter.write(cookies)); + } + + @Test + void write_emptyList_returnsEmptyString() { + assertEquals("", CookieWriter.write(List.of())); + } + + @Test + void appendValue_versionZero_appendsUnquoted() { + assertEquals("value", new CookieWriter().appendValue("value", 0).toString()); + } + + @Test + void appendValue_nonZeroVersion_appendsQuoted() { + assertEquals("\"value\"", new CookieWriter().appendValue("value", 1).toString()); + } + + @Test + void getCookies_matchingNameInDestinationMap_isUpdated() { + Cookie cookie = new Cookie("name", "value"); + Map destination = new HashMap<>(); + destination.put("name", null); + + CookieWriter.getCookies(List.of(cookie), destination); + + assertEquals(cookie, destination.get("name")); + } + + @Test + void getCookies_nonMatchingName_isIgnored() { + Cookie cookie = new Cookie("other", "value"); + Map destination = new HashMap<>(); + destination.put("name", null); + + CookieWriter.getCookies(List.of(cookie), destination); + + assertTrue(destination.containsKey("name")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/DateWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/DateWriterTestCase.java new file mode 100644 index 0000000000..a6f35cc4c6 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/DateWriterTestCase.java @@ -0,0 +1,48 @@ +/** + * 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.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import org.junit.jupiter.api.Test; +import org.restlet.engine.util.DateUtils; + +class DateWriterTestCase { + + private static Date date(int year, int month, int day, int hour, int minute, int second) { + Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + cal.clear(); + cal.set(year, month, day, hour, minute, second); + return cal.getTime(); + } + + @Test + void write_defaultFormat_matchesDateUtilsDefault() { + Date date = date(2024, Calendar.MARCH, 5, 13, 7, 9); + assertEquals(DateUtils.format(date), DateWriter.write(date)); + } + + @Test + void write_cookieFalse_matchesDefaultFormat() { + Date date = date(2024, Calendar.MARCH, 5, 13, 7, 9); + assertEquals(DateWriter.write(date), DateWriter.write(date, false)); + } + + @Test + void write_cookieTrue_matchesRfc1036Format() { + Date date = date(2024, Calendar.MARCH, 5, 13, 7, 9); + assertEquals( + DateUtils.format(date, DateUtils.FORMAT_RFC_1036.getFirst()), + DateWriter.write(date, true)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/DimensionReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/DimensionReaderTestCase.java new file mode 100644 index 0000000000..7a366a1229 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/DimensionReaderTestCase.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.engine.header; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.data.Dimension; +import org.restlet.data.Header; + +class DimensionReaderTestCase { + + @Test + void readValue_accept_returnsMediaType() throws IOException { + assertEquals( + Dimension.MEDIA_TYPE, + new DimensionReader(HeaderConstants.HEADER_ACCEPT).readValue()); + } + + @Test + void readValue_acceptCharset_returnsCharacterSet() throws IOException { + assertEquals( + Dimension.CHARACTER_SET, + new DimensionReader(HeaderConstants.HEADER_ACCEPT_CHARSET).readValue()); + } + + @Test + void readValue_acceptEncoding_returnsEncoding() throws IOException { + assertEquals( + Dimension.ENCODING, + new DimensionReader(HeaderConstants.HEADER_ACCEPT_ENCODING).readValue()); + } + + @Test + void readValue_acceptLanguage_returnsLanguage() throws IOException { + assertEquals( + Dimension.LANGUAGE, + new DimensionReader(HeaderConstants.HEADER_ACCEPT_LANGUAGE).readValue()); + } + + @Test + void readValue_authorization_returnsAuthorization() throws IOException { + assertEquals( + Dimension.AUTHORIZATION, + new DimensionReader(HeaderConstants.HEADER_AUTHORIZATION).readValue()); + } + + @Test + void readValue_userAgent_returnsClientAgent() throws IOException { + assertEquals( + Dimension.CLIENT_AGENT, + new DimensionReader(HeaderConstants.HEADER_USER_AGENT).readValue()); + } + + @Test + void readValue_origin_returnsOrigin() throws IOException { + assertEquals( + Dimension.ORIGIN, new DimensionReader(HeaderConstants.HEADER_ORIGIN).readValue()); + } + + @Test + void readValue_star_returnsUnspecified() throws IOException { + assertEquals(Dimension.UNSPECIFIED, new DimensionReader("*").readValue()); + } + + @Test + void readValue_unknownValue_returnsNull() throws IOException { + assertNull(new DimensionReader("something-else").readValue()); + } + + @Test + void addValuesStatic_fromHeader_populatesCollection() { + List dimensions = new ArrayList<>(); + Header header = new Header("Vary", HeaderConstants.HEADER_ACCEPT); + DimensionReader.addValues(header, dimensions); + assertEquals(1, dimensions.size()); + assertEquals(Dimension.MEDIA_TYPE, dimensions.get(0)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/DimensionWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/DimensionWriterTestCase.java new file mode 100644 index 0000000000..1ac1868264 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/DimensionWriterTestCase.java @@ -0,0 +1,100 @@ +/** + * 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.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import org.junit.jupiter.api.Test; +import org.restlet.data.Dimension; + +class DimensionWriterTestCase { + + @Test + void write_nullOrEmpty_returnsEmptyString() { + assertEquals("", DimensionWriter.write((Collection) null)); + assertEquals("", DimensionWriter.write(Collections.emptyList())); + } + + @Test + void write_clientAddress_returnsStar() { + assertEquals("*", DimensionWriter.write(Collections.singleton(Dimension.CLIENT_ADDRESS))); + } + + @Test + void write_time_returnsStar() { + assertEquals("*", DimensionWriter.write(Collections.singleton(Dimension.TIME))); + } + + @Test + void write_unspecified_returnsStar() { + assertEquals("*", DimensionWriter.write(Collections.singleton(Dimension.UNSPECIFIED))); + } + + @Test + void write_characterSet_writesAcceptCharsetHeaderName() { + assertEquals( + HeaderConstants.HEADER_ACCEPT_CHARSET, + DimensionWriter.write(Collections.singleton(Dimension.CHARACTER_SET))); + } + + @Test + void write_clientAgent_writesUserAgentHeaderName() { + assertEquals( + HeaderConstants.HEADER_USER_AGENT, + DimensionWriter.write(Collections.singleton(Dimension.CLIENT_AGENT))); + } + + @Test + void write_encoding_writesAcceptEncodingHeaderName() { + assertEquals( + HeaderConstants.HEADER_ACCEPT_ENCODING, + DimensionWriter.write(Collections.singleton(Dimension.ENCODING))); + } + + @Test + void write_language_writesAcceptLanguageHeaderName() { + assertEquals( + HeaderConstants.HEADER_ACCEPT_LANGUAGE, + DimensionWriter.write(Collections.singleton(Dimension.LANGUAGE))); + } + + @Test + void write_mediaType_writesAcceptHeaderName() { + assertEquals( + HeaderConstants.HEADER_ACCEPT, + DimensionWriter.write(Collections.singleton(Dimension.MEDIA_TYPE))); + } + + @Test + void write_authorization_writesAuthorizationHeaderName() { + assertEquals( + HeaderConstants.HEADER_AUTHORIZATION, + DimensionWriter.write(Collections.singleton(Dimension.AUTHORIZATION))); + } + + @Test + void write_origin_writesOriginHeaderName() { + assertEquals( + HeaderConstants.HEADER_ORIGIN, + DimensionWriter.write(Collections.singleton(Dimension.ORIGIN))); + } + + @Test + void write_multipleDimensions_joinsWithSeparator() { + LinkedHashSet dimensions = + new LinkedHashSet<>(Arrays.asList(Dimension.MEDIA_TYPE, Dimension.ENCODING)); + assertEquals( + HeaderConstants.HEADER_ACCEPT + ", " + HeaderConstants.HEADER_ACCEPT_ENCODING, + DimensionWriter.write(dimensions)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/EncodingReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/EncodingReaderTestCase.java new file mode 100644 index 0000000000..9d5017062c --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/EncodingReaderTestCase.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 static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.data.Encoding; + +class EncodingReaderTestCase { + + @Test + void readValue_gzip_parsesEncoding() throws IOException { + assertEquals(Encoding.GZIP, new EncodingReader("gzip").readValue()); + } + + @Test + void addValues_excludesIdentityEncoding() { + List encodings = new ArrayList<>(); + new EncodingReader("identity, gzip").addValues(encodings); + assertEquals(1, encodings.size()); + assertTrue(encodings.contains(Encoding.GZIP)); + assertFalse(encodings.contains(Encoding.IDENTITY)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/EncodingWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/EncodingWriterTestCase.java new file mode 100644 index 0000000000..a21c6be8c7 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/EncodingWriterTestCase.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 static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.restlet.data.Encoding; + +class EncodingWriterTestCase { + + @Test + void write_gzip_writesEncodingName() { + assertEquals(Encoding.GZIP.getName(), EncodingWriter.write(Arrays.asList(Encoding.GZIP))); + } + + @Test + void canWrite_identity_returnsFalse() { + EncodingWriter writer = new EncodingWriter(); + assertFalse(writer.canWrite(Encoding.IDENTITY)); + } + + @Test + void write_identityIsSkipped() { + String result = EncodingWriter.write(Arrays.asList(Encoding.IDENTITY, Encoding.GZIP)); + assertEquals(Encoding.GZIP.getName(), result); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/ExpectationReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/ExpectationReaderTestCase.java new file mode 100644 index 0000000000..ffef5c061b --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/ExpectationReaderTestCase.java @@ -0,0 +1,53 @@ +/** + * 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.assertTrue; + +import java.io.IOException; +import org.junit.jupiter.api.Test; +import org.restlet.data.ClientInfo; +import org.restlet.data.Expectation; + +class ExpectationReaderTestCase { + + @Test + void readValue_nameOnly_parsesName() throws IOException { + ExpectationReader reader = new ExpectationReader("100-continue"); + Expectation expectation = reader.readValue(); + assertEquals("100-continue", expectation.getName()); + assertTrue(expectation.getParameters().isEmpty()); + } + + @Test + void readValue_withParameter_addsParameter() throws IOException { + ExpectationReader reader = new ExpectationReader("foo;bar=baz"); + Expectation expectation = reader.readValue(); + assertEquals("foo", expectation.getName()); + assertEquals(1, expectation.getParameters().size()); + assertEquals("bar", expectation.getParameters().get(0).getName()); + assertEquals("baz", expectation.getParameters().get(0).getValue()); + } + + @Test + void addValues_populatesClientInfoExpectations() { + ClientInfo clientInfo = new ClientInfo(); + ExpectationReader.addValues("100-continue", clientInfo); + assertEquals(1, clientInfo.getExpectations().size()); + assertEquals("100-continue", clientInfo.getExpectations().get(0).getName()); + } + + @Test + void addValues_nullHeader_leavesExpectationsUnchanged() { + ClientInfo clientInfo = new ClientInfo(); + ExpectationReader.addValues(null, clientInfo); + assertTrue(clientInfo.getExpectations().isEmpty()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/HeaderReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/HeaderReaderTestCase.java new file mode 100644 index 0000000000..8e674db447 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/HeaderReaderTestCase.java @@ -0,0 +1,348 @@ +/** + * 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.data.Header; +import org.restlet.data.Parameter; + +class HeaderReaderTestCase { + + @Test + void readDate_notCookieFormat_parsesRfc1123() { + Date date = HeaderReader.readDate("Sun, 06 Nov 1994 08:49:37 GMT", false); + assertEquals(784111777000L, date.getTime()); + } + + @Test + void readDate_cookieFormat_parsesRfc1036() { + Date date = HeaderReader.readDate("Sunday, 06-Nov-94 08:49:37 GMT", true); + assertEquals(784111777000L, date.getTime()); + } + + @Test + void readHeaderFromCharSequence_endOfHeaders_returnsNull() throws IOException { + assertNull(HeaderReader.readHeader("")); + } + + @Test + void readHeaderFromCharSequence_crlfOnly_returnsNull() throws IOException { + assertNull(HeaderReader.readHeader("\r\n")); + } + + @Test + void readHeaderFromCharSequence_crWithoutLf_throws() { + assertThrows(IOException.class, () -> HeaderReader.readHeader("\rX")); + } + + @Test + void readHeaderFromCharSequence_missingColon_throws() { + assertThrows(IOException.class, () -> HeaderReader.readHeader("NameOnly")); + } + + @Test + void readHeaderFromCharSequence_nameOnly_returnsHeaderWithNullValue() throws IOException { + Header header = HeaderReader.readHeader("Name:"); + assertEquals("Name", header.getName()); + } + + @Test + void readHeaderFromCharSequence_nameAndValue_parsesBoth() throws IOException { + Header header = HeaderReader.readHeader("Content-Type: text/plain"); + assertEquals("Content-Type", header.getName()); + assertEquals("text/plain", header.getValue()); + } + + @Test + void readHeaderFromInputStream_nameAndValue_parsesBoth() throws IOException { + ByteArrayInputStream is = + new ByteArrayInputStream("Content-Type: text/plain\r\n".getBytes()); + Header header = HeaderReader.readHeader(is, new StringBuilder()); + assertEquals("Content-Type", header.getName()); + assertEquals("text/plain", header.getValue()); + } + + @Test + void readHeaderFromInputStream_endOfHeaders_returnsNull() throws IOException { + ByteArrayInputStream is = new ByteArrayInputStream("\r\n".getBytes()); + assertNull(HeaderReader.readHeader(is, new StringBuilder())); + } + + @Test + void readHeaderFromInputStream_crWithoutLf_throws() { + ByteArrayInputStream is = new ByteArrayInputStream("\rX".getBytes()); + assertThrows(IOException.class, () -> HeaderReader.readHeader(is, new StringBuilder())); + } + + @Test + void readHeaderFromInputStream_missingColon_throws() { + ByteArrayInputStream is = new ByteArrayInputStream("NameOnly".getBytes()); + assertThrows(IOException.class, () -> HeaderReader.readHeader(is, new StringBuilder())); + } + + @Test + void readHeaderFromInputStream_missingTrailingLineFeed_throws() { + ByteArrayInputStream is = new ByteArrayInputStream("Name: value\r".getBytes()); + assertThrows(IOException.class, () -> HeaderReader.readHeader(is, new StringBuilder())); + } + + @Test + void peek_atStart_returnsFirstCharacterWithoutAdvancing() { + HeaderReader reader = new HeaderReader<>("abc"); + assertEquals('a', reader.peek()); + assertEquals('a', reader.peek()); + } + + @Test + void peek_emptyHeader_returnsMinusOne() { + HeaderReader reader = new HeaderReader<>(""); + assertEquals(-1, reader.peek()); + } + + @Test + void read_advancesIndexUntilEnd() { + HeaderReader reader = new HeaderReader<>("ab"); + assertEquals('a', reader.read()); + assertEquals('b', reader.read()); + assertEquals(-1, reader.read()); + } + + @Test + void unread_movesIndexBackOneCharacter() { + HeaderReader reader = new HeaderReader<>("abc"); + reader.read(); + reader.read(); + reader.unread(); + assertEquals('b', reader.read()); + } + + @Test + void markAndReset_repositionsToMarkedIndex() { + HeaderReader reader = new HeaderReader<>("abc"); + reader.read(); + reader.mark(); + reader.read(); + reader.reset(); + assertEquals('b', reader.read()); + } + + @Test + void readToken_stopsAtNonTokenCharacter() { + HeaderReader reader = new HeaderReader<>("abc=def"); + assertEquals("abc", reader.readToken()); + assertEquals('=', reader.read()); + } + + @Test + void readDigits_stopsAtNonTokenCharacter() { + HeaderReader reader = new HeaderReader<>("123;abc"); + assertEquals("123", reader.readDigits()); + assertEquals(';', reader.read()); + } + + @Test + void readQuotedString_simpleValue_returnsUnquoted() throws IOException { + HeaderReader reader = new HeaderReader<>("\"hello\""); + assertEquals("hello", reader.readQuotedString()); + } + + @Test + void readQuotedString_withEscapedCharacter_unescapesIt() throws IOException { + HeaderReader reader = new HeaderReader<>("\"a\\\"b\""); + assertEquals("a\"b", reader.readQuotedString()); + } + + @Test + void readQuotedString_withoutLeadingQuote_throws() { + HeaderReader reader = new HeaderReader<>("hello\""); + assertThrows(IOException.class, reader::readQuotedString); + } + + @Test + void readQuotedString_unterminated_throws() { + HeaderReader reader = new HeaderReader<>("\"hello"); + assertThrows(IOException.class, reader::readQuotedString); + } + + @Test + void readActualNamedValue_quotedString_returnsUnquoted() throws IOException { + HeaderReader reader = new HeaderReader<>("\"quoted value\""); + assertEquals("quoted value", reader.readActualNamedValue()); + } + + @Test + void readActualNamedValue_token_returnsToken() throws IOException { + HeaderReader reader = new HeaderReader<>("token123"); + assertEquals("token123", reader.readActualNamedValue()); + } + + @Test + void readComment_simpleComment_returnsContent() throws IOException { + HeaderReader reader = new HeaderReader<>("(a comment)"); + assertEquals("a comment", reader.readComment()); + } + + @Test + void readComment_nestedComment_returnsFullContent() throws IOException { + HeaderReader reader = new HeaderReader<>("(outer (inner) end)"); + assertEquals("outer (inner) end", reader.readComment()); + } + + @Test + void readComment_withoutLeadingParenthesis_throws() { + HeaderReader reader = new HeaderReader<>("no parenthesis)"); + assertThrows(IOException.class, reader::readComment); + } + + @Test + void readComment_unterminated_throws() { + HeaderReader reader = new HeaderReader<>("(unterminated"); + assertThrows(IOException.class, reader::readComment); + } + + @Test + void readRawText_stopsAtSpace() { + HeaderReader reader = new HeaderReader<>("abc def"); + assertEquals("abc", reader.readRawText()); + } + + @Test + void readRawText_emptyInput_returnsNull() { + HeaderReader reader = new HeaderReader<>(""); + assertNull(reader.readRawText()); + } + + @Test + void readRawValue_trimsTrailingSpacesAndStopsAtComma() { + HeaderReader reader = new HeaderReader<>(" value with spaces , next"); + assertEquals("value with spaces", reader.readRawValue()); + } + + @Test + void readRawValue_emptyInput_returnsNull() { + HeaderReader reader = new HeaderReader<>(""); + assertNull(reader.readRawValue()); + } + + @Test + void readParameter_nameOnly_returnsNullValueParameter() throws IOException { + HeaderReader reader = new HeaderReader<>("flag"); + Parameter parameter = reader.readParameter(); + assertEquals("flag", parameter.getName()); + assertNull(parameter.getValue()); + } + + @Test + void readParameter_nameAndValue_parsesBoth() throws IOException { + HeaderReader reader = new HeaderReader<>("name=value"); + Parameter parameter = reader.readParameter(); + assertEquals("name", parameter.getName()); + assertEquals("value", parameter.getValue()); + } + + @Test + void readParameter_emptyName_throws() { + HeaderReader reader = new HeaderReader<>("=value"); + assertThrows(IOException.class, reader::readParameter); + } + + @Test + void skipParameterSeparator_semicolonPresent_returnsTrueAndSkipsSpaces() { + HeaderReader reader = new HeaderReader<>(" ; rest"); + assertTrue(reader.skipParameterSeparator()); + assertEquals('r', reader.read()); + } + + @Test + void skipParameterSeparator_noSeparator_returnsFalseAndDoesNotConsume() { + HeaderReader reader = new HeaderReader<>("rest"); + assertFalse(reader.skipParameterSeparator()); + assertEquals('r', reader.read()); + } + + @Test + void skipValueSeparator_commaPresent_returnsTrue() { + HeaderReader reader = new HeaderReader<>(" , rest"); + assertTrue(reader.skipValueSeparator()); + assertEquals('r', reader.read()); + } + + @Test + void skipValueSeparator_noSeparator_returnsFalse() { + HeaderReader reader = new HeaderReader<>("rest"); + assertFalse(reader.skipValueSeparator()); + } + + @Test + void skipSpaces_leadingSpaces_returnsTrueAndSkipsThem() { + HeaderReader reader = new HeaderReader<>(" rest"); + assertTrue(reader.skipSpaces()); + assertEquals('r', reader.read()); + } + + @Test + void skipSpaces_noSpaces_returnsFalse() { + HeaderReader reader = new HeaderReader<>("rest"); + assertFalse(reader.skipSpaces()); + } + + @Test + void readValue_defaultImplementation_returnsNull() throws IOException { + HeaderReader reader = new HeaderReader<>("anything"); + assertNull(reader.readValue()); + } + + @Test + void readValues_defaultImplementation_returnsEmptyList() { + HeaderReader reader = new HeaderReader<>("anything"); + List values = reader.readValues(); + assertTrue(values.isEmpty()); + } + + @Test + void addValues_customReaderWithTokenValues_addsEachNonNullValue() { + HeaderReader reader = + new HeaderReader<>("a, b, a") { + @Override + public String readValue() { + return readToken(); + } + }; + List values = new ArrayList<>(); + reader.addValues(values); + assertEquals(List.of("a", "b"), values); + } + + @Test + void canAdd_duplicateValue_isExcludedByDefault() { + HeaderReader reader = new HeaderReader<>(""); + List existing = new ArrayList<>(List.of("a")); + assertFalse(reader.canAdd("a", existing)); + assertTrue(reader.canAdd("b", existing)); + assertFalse(reader.canAdd(null, existing)); + } + + @Test + void constructor_nullHeader_behavesAsEmpty() { + HeaderReader reader = new HeaderReader<>(null); + assertEquals(-1, reader.peek()); + assertEquals(-1, reader.read()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/HeaderWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/HeaderWriterTestCase.java new file mode 100644 index 0000000000..3ff56e29eb --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/HeaderWriterTestCase.java @@ -0,0 +1,224 @@ +/** + * 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; + +class HeaderWriterTestCase { + + private static HeaderWriter newWriter() { + return new HeaderWriter<>() { + @Override + public HeaderWriter append(String value) { + return append((CharSequence) value); + } + }; + } + + @Test + void appendChar_appendsSingleCharacter() { + assertEquals("a", newWriter().append('a').toString()); + } + + @Test + void appendCharArray_appendsAllCharacters() { + assertEquals("abc", newWriter().append(new char[] {'a', 'b', 'c'}).toString()); + } + + @Test + void appendCharArray_null_appendsNothing() { + assertEquals("", newWriter().append((char[]) null).toString()); + } + + @Test + void appendCharSequence_appendsText() { + assertEquals("hello", newWriter().append((CharSequence) "hello").toString()); + } + + @Test + void appendCollection_nullOrEmpty_appendsNothing() { + assertEquals("", newWriter().append((java.util.Collection) null).toString()); + assertEquals("", newWriter().append(java.util.List.of()).toString()); + } + + @Test + void appendCollection_multipleValues_joinsWithValueSeparator() { + assertEquals("a, b", newWriter().append(Arrays.asList("a", "b")).toString()); + } + + @Test + void appendCollection_skipsValuesRejectedByCanWrite() { + HeaderWriter writer = + new HeaderWriter<>() { + @Override + public HeaderWriter append(String value) { + return append((CharSequence) value); + } + + @Override + protected boolean canWrite(String value) { + return !"skip".equals(value); + } + }; + writer.append(Arrays.asList("a", "skip", "b")); + assertEquals("a, b", writer.toString()); + } + + @Test + void appendInt_appendsDecimalRepresentation() { + assertEquals("42", newWriter().append(42).toString()); + } + + @Test + void appendLong_appendsDecimalRepresentation() { + assertEquals("123456789012", newWriter().append(123456789012L).toString()); + } + + @Test + void appendComment_plainText_isWrappedInParentheses() { + assertEquals("(hello)", newWriter().appendComment("hello").toString()); + } + + @Test + void appendComment_withSpecialCharacter_isQuotedPair() { + assertEquals("(a\\)b)", newWriter().appendComment("a)b").toString()); + } + + @Test + void appendExtension_namedValueObject_delegatesToNameValueOverload() { + org.restlet.data.Parameter parameter = new org.restlet.data.Parameter("name", "value"); + assertEquals("name=value", newWriter().appendExtension(parameter).toString()); + } + + @Test + void appendExtension_nullNamedValue_appendsNothing() { + assertEquals( + "", + newWriter().appendExtension((org.restlet.util.NamedValue) null).toString()); + } + + @Test + void appendExtension_nameOnly_appendsNameOnly() { + assertEquals("name", newWriter().appendExtension("name", null).toString()); + } + + @Test + void appendExtension_tokenValue_appendsUnquoted() { + assertEquals("name=value", newWriter().appendExtension("name", "value").toString()); + } + + @Test + void appendExtension_nonTokenValue_appendsQuoted() { + assertEquals( + "name=\"has space\"", newWriter().appendExtension("name", "has space").toString()); + } + + @Test + void appendExtension_emptyName_appendsNothing() { + assertEquals("", newWriter().appendExtension("", "value").toString()); + } + + @Test + void appendParameterSeparator_appendsSemicolon() { + assertEquals(";", newWriter().appendParameterSeparator().toString()); + } + + @Test + void appendProduct_nameOnly_appendsNameOnly() { + assertEquals("Restlet", newWriter().appendProduct("Restlet", null).toString()); + } + + @Test + void appendProduct_nameAndVersion_joinsWithSlash() { + assertEquals("Restlet/2.7", newWriter().appendProduct("Restlet", "2.7").toString()); + } + + @Test + void appendQuotedPair_prefixesWithBackslash() { + assertEquals("\\\"", newWriter().appendQuotedPair('"').toString()); + } + + @Test + void appendQuotedString_nullOrEmpty_appendsNothing() { + assertEquals("", newWriter().appendQuotedString(null).toString()); + assertEquals("", newWriter().appendQuotedString("").toString()); + } + + @Test + void appendQuotedString_plainText_isWrappedInQuotes() { + assertEquals("\"hello\"", newWriter().appendQuotedString("hello").toString()); + } + + @Test + void appendQuotedString_withEmbeddedQuote_isEscaped() { + assertEquals("\"a\\\"b\"", newWriter().appendQuotedString("a\"b").toString()); + } + + @Test + void appendSpace_appendsSingleSpace() { + assertEquals(" ", newWriter().appendSpace().toString()); + } + + @Test + void appendToken_validToken_isAppended() { + assertEquals("abc123", newWriter().appendToken("abc123").toString()); + } + + @Test + void appendToken_invalidToken_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> newWriter().appendToken("has space")); + } + + @Test + void appendUriEncoded_encodesReservedCharacters() { + String result = newWriter().appendUriEncoded("a b", CharacterSet.UTF_8).toString(); + assertEquals("a%20b", result); + } + + @Test + void appendValueSeparator_appendsCommaAndSpace() { + assertEquals(", ", newWriter().appendValueSeparator().toString()); + } + + @Test + void canWrite_nonNullValue_returnsTrueByDefault() { + assertTrue(newWriter().append(java.util.List.of("value")).toString().equals("value")); + } + + @Test + void append_chaining_returnsSameWriterInstance() { + HeaderWriter writer = newWriter(); + assertEquals(writer, writer.append('a')); + } + + @Test + void appendCollection_allValuesRejected_appendsNothing() { + HeaderWriter writer = + new HeaderWriter<>() { + @Override + public HeaderWriter append(String value) { + return append((CharSequence) value); + } + + @Override + protected boolean canWrite(String value) { + return false; + } + }; + writer.append(Arrays.asList("a", "b")); + assertFalse(writer.toString().length() > 0); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/LanguageReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/LanguageReaderTestCase.java new file mode 100644 index 0000000000..df28e231fe --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/LanguageReaderTestCase.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.header; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import org.junit.jupiter.api.Test; +import org.restlet.data.Language; + +class LanguageReaderTestCase { + + @Test + void readValue_english_parsesLanguage() throws IOException { + assertEquals(Language.ENGLISH, new LanguageReader("en").readValue()); + } + + @Test + void readValue_frenchFrance_parsesLanguage() throws IOException { + assertEquals(Language.FRENCH_FRANCE, new LanguageReader("fr-fr").readValue()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/LanguageWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/LanguageWriterTestCase.java new file mode 100644 index 0000000000..dab8c115a2 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/LanguageWriterTestCase.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.header; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.restlet.data.Language; + +class LanguageWriterTestCase { + + @Test + void write_singleLanguage_writesLanguageName() { + assertEquals( + Language.ENGLISH.getName(), LanguageWriter.write(Arrays.asList(Language.ENGLISH))); + } + + @Test + void write_multipleLanguages_joinsWithSeparator() { + String result = + LanguageWriter.write(Arrays.asList(Language.ENGLISH, Language.FRENCH_FRANCE)); + assertEquals(Language.ENGLISH.getName() + ", " + Language.FRENCH_FRANCE.getName(), result); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/MetadataWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/MetadataWriterTestCase.java new file mode 100644 index 0000000000..0e89726912 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/MetadataWriterTestCase.java @@ -0,0 +1,24 @@ +/** + * 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 org.junit.jupiter.api.Test; +import org.restlet.data.Language; + +class MetadataWriterTestCase { + + @Test + void append_metadata_writesItsName() { + MetadataWriter writer = new MetadataWriter<>(); + writer.append(Language.ENGLISH); + assertEquals(Language.ENGLISH.getName(), writer.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/MethodWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/MethodWriterTestCase.java new file mode 100644 index 0000000000..03d970bef6 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/MethodWriterTestCase.java @@ -0,0 +1,42 @@ +/** + * 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.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.restlet.data.Method; + +class MethodWriterTestCase { + + @Test + void write_singleMethod_writesMethodName() { + Set methods = new LinkedHashSet<>(); + methods.add(Method.GET); + assertEquals("GET", MethodWriter.write(methods)); + } + + @Test + void write_multipleMethods_joinsWithSeparator() { + Set methods = new LinkedHashSet<>(); + methods.add(Method.GET); + methods.add(Method.POST); + assertEquals("GET, POST", MethodWriter.write(methods)); + } + + @Test + void append_method_writesTokenizedName() { + MethodWriter writer = new MethodWriter(); + writer.append(Method.PUT); + assertTrue(writer.toString().contains("PUT")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/PreferenceWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/PreferenceWriterTestCase.java new file mode 100644 index 0000000000..ffa098e77c --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/PreferenceWriterTestCase.java @@ -0,0 +1,69 @@ +/** + * 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.restlet.data.MediaType; +import org.restlet.data.Parameter; +import org.restlet.data.Preference; +import org.restlet.util.Series; + +class PreferenceWriterTestCase { + + @Test + void isValidQuality_withinRange_returnsTrue() { + assertTrue(PreferenceWriter.isValidQuality(0F)); + assertTrue(PreferenceWriter.isValidQuality(1F)); + assertTrue(PreferenceWriter.isValidQuality(0.5F)); + } + + @Test + void isValidQuality_outsideRange_returnsFalse() { + assertFalse(PreferenceWriter.isValidQuality(-0.1F)); + assertFalse(PreferenceWriter.isValidQuality(1.1F)); + } + + @Test + void write_fullQuality_omitsQualityParameter() { + Preference pref = new Preference<>(MediaType.TEXT_PLAIN, 1F); + assertEquals("text/plain", PreferenceWriter.write(Arrays.asList(pref))); + } + + @Test + void write_partialQuality_appendsQParameter() { + Preference pref = new Preference<>(MediaType.TEXT_PLAIN, 0.5F); + assertEquals("text/plain;q=0.5", PreferenceWriter.write(Arrays.asList(pref))); + } + + @Test + void write_withParameters_appendsEachOne() { + Series parameters = new Series<>(Parameter.class); + parameters.add(new Parameter("level", "1")); + Preference pref = new Preference<>(MediaType.TEXT_PLAIN, 1F, parameters); + assertEquals("text/plain;level=1", PreferenceWriter.write(Arrays.asList(pref))); + } + + @Test + void appendQuality_invalidValue_throwsIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, () -> new PreferenceWriter().appendQuality(2F)); + } + + @Test + void appendQuality_formatsWithMaxTwoDecimals() { + String result = new PreferenceWriter().appendQuality(0.333F).toString(); + assertEquals("0.33", result); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/ProductReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/ProductReaderTestCase.java new file mode 100644 index 0000000000..147295af2b --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/ProductReaderTestCase.java @@ -0,0 +1,59 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.data.Product; + +class ProductReaderTestCase { + + @Test + void read_nullInput_returnsEmptyList() { + assertTrue(ProductReader.read(null).isEmpty()); + } + + @Test + void read_tokenOnly_parsesName() { + List products = ProductReader.read("Restlet"); + assertEquals(1, products.size()); + assertEquals("Restlet", products.get(0).getName()); + assertNull(products.get(0).getVersion()); + } + + @Test + void read_tokenAndVersion_parsesBoth() { + List products = ProductReader.read("Restlet/2.7"); + assertEquals(1, products.size()); + assertEquals("Restlet", products.get(0).getName()); + assertEquals("2.7", products.get(0).getVersion()); + } + + @Test + void read_tokenVersionAndComment_parsesAll() { + List products = ProductReader.read("Restlet/2.7 (Java)"); + assertEquals(1, products.size()); + assertEquals("Restlet", products.get(0).getName()); + assertEquals("2.7", products.get(0).getVersion()); + assertEquals("Java", products.get(0).getComment()); + } + + @Test + void read_multipleProducts_parsesEachOne() { + List products = ProductReader.read("Restlet/2.7 (Java) Mozilla/5.0"); + assertEquals(2, products.size()); + assertEquals("Restlet", products.get(0).getName()); + assertEquals("Mozilla", products.get(1).getName()); + assertEquals("5.0", products.get(1).getVersion()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/ProductWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/ProductWriterTestCase.java new file mode 100644 index 0000000000..49de548f1f --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/ProductWriterTestCase.java @@ -0,0 +1,59 @@ +/** + * 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.Arrays; +import org.junit.jupiter.api.Test; +import org.restlet.data.Product; + +class ProductWriterTestCase { + + @Test + void write_nameOnly_writesNameOnly() { + assertEquals( + "Restlet", ProductWriter.write(Arrays.asList(new Product("Restlet", null, null)))); + } + + @Test + void write_nameAndVersion_joinsWithSlash() { + assertEquals( + "Restlet/2.7", + ProductWriter.write(Arrays.asList(new Product("Restlet", "2.7", null)))); + } + + @Test + void write_nameVersionAndComment_appendsParenthesizedComment() { + assertEquals( + "Restlet/2.7 (Java)", + ProductWriter.write(Arrays.asList(new Product("Restlet", "2.7", "Java")))); + } + + @Test + void write_multipleProducts_joinsWithSpace() { + String result = + ProductWriter.write( + Arrays.asList( + new Product("Restlet", "2.7", null), + new Product("Mozilla", "5.0", null))); + assertEquals("Restlet/2.7 Mozilla/5.0", result); + } + + @Test + void write_nullOrEmptyName_throwsIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, + () -> ProductWriter.write(Arrays.asList(new Product(null, "2.7", null)))); + assertThrows( + IllegalArgumentException.class, + () -> ProductWriter.write(Arrays.asList(new Product("", "2.7", null)))); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/RecipientInfoReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/RecipientInfoReaderTestCase.java new file mode 100644 index 0000000000..ae9a2cdc97 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/RecipientInfoReaderTestCase.java @@ -0,0 +1,61 @@ +/** + * 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.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.RecipientInfo; + +class RecipientInfoReaderTestCase { + + @Test + void readValue_implicitHttpProtocol_defaultsProtocolNameToHttp() throws IOException { + RecipientInfoReader reader = new RecipientInfoReader("1.1 proxy.example.com"); + RecipientInfo info = reader.readValue(); + assertEquals("HTTP", info.getProtocol().getName()); + assertEquals("1.1", info.getProtocol().getVersion()); + assertEquals("proxy.example.com", info.getName()); + } + + @Test + void readValue_explicitProtocol_parsesNameAndVersion() throws IOException { + RecipientInfoReader reader = new RecipientInfoReader("FSTR/2.1 proxy.example.com"); + RecipientInfo info = reader.readValue(); + assertEquals("FSTR", info.getProtocol().getName()); + assertEquals("2.1", info.getProtocol().getVersion()); + } + + @Test + void readValue_withComment_parsesComment() throws IOException { + RecipientInfoReader reader = new RecipientInfoReader("1.1 proxy.example.com (Apache)"); + RecipientInfo info = reader.readValue(); + assertEquals("proxy.example.com", info.getName()); + assertEquals("Apache", info.getComment()); + } + + @Test + void readValue_emptyProtocolToken_throws() { + RecipientInfoReader reader = new RecipientInfoReader(""); + assertThrows(IOException.class, reader::readValue); + } + + @Test + void addValuesStatic_fromHeader_populatesCollection() { + List infos = new ArrayList<>(); + Header header = new Header("Via", "1.1 proxy.example.com"); + RecipientInfoReader.addValues(header, infos); + assertEquals(1, infos.size()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/RecipientInfoWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/RecipientInfoWriterTestCase.java new file mode 100644 index 0000000000..d74554b156 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/RecipientInfoWriterTestCase.java @@ -0,0 +1,48 @@ +/** + * 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.Arrays; +import org.junit.jupiter.api.Test; +import org.restlet.data.Protocol; +import org.restlet.data.RecipientInfo; + +class RecipientInfoWriterTestCase { + + @Test + void write_nameOnly_writesProtocolAndName() { + RecipientInfo info = + new RecipientInfo(new Protocol("HTTP", "HTTP", null, -1, "1.1"), "proxy", null); + assertEquals("HTTP/1.1 proxy", RecipientInfoWriter.write(Arrays.asList(info))); + } + + @Test + void write_withComment_appendsCommentInParentheses() { + RecipientInfo info = + new RecipientInfo(new Protocol("HTTP", "HTTP", null, -1, "1.1"), "proxy", null); + info.setComment("Apache"); + assertEquals("HTTP/1.1 proxy (Apache)", RecipientInfoWriter.write(Arrays.asList(info))); + } + + @Test + void append_nullProtocol_throwsIllegalArgumentException() { + RecipientInfo info = new RecipientInfo(); + assertThrows(IllegalArgumentException.class, () -> new RecipientInfoWriter().append(info)); + } + + @Test + void append_nullName_throwsIllegalArgumentException() { + RecipientInfo info = + new RecipientInfo(new Protocol("HTTP", "HTTP", null, -1, "1.1"), null, null); + assertThrows(IllegalArgumentException.class, () -> new RecipientInfoWriter().append(info)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/StringWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/StringWriterTestCase.java new file mode 100644 index 0000000000..0703453151 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/StringWriterTestCase.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.header; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.LinkedHashSet; +import java.util.Set; +import org.junit.jupiter.api.Test; + +class StringWriterTestCase { + + @Test + void write_singleValue_writesValue() { + Set values = new LinkedHashSet<>(); + values.add("a"); + assertEquals("a", StringWriter.write(values)); + } + + @Test + void write_multipleValues_joinsWithSeparator() { + Set values = new LinkedHashSet<>(); + values.add("a"); + values.add("b"); + assertEquals("a, b", StringWriter.write(values)); + } + + @Test + void append_value_writesTokenizedValue() { + StringWriter writer = new StringWriter(); + writer.append("token"); + assertEquals("token", writer.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/TagReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/TagReaderTestCase.java new file mode 100644 index 0000000000..4c925d2b81 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/TagReaderTestCase.java @@ -0,0 +1,54 @@ +/** + * 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.assertTrue; + +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.Tag; + +class TagReaderTestCase { + + @Test + void readValue_singleTag_parsesTag() throws IOException { + TagReader reader = new TagReader("\"abc\""); + Tag tag = reader.readValue(); + assertEquals(new Tag("abc"), tag); + } + + @Test + void readValue_weakTag_parsesWeakFlag() throws IOException { + TagReader reader = new TagReader("W/\"abc\""); + Tag tag = reader.readValue(); + assertTrue(tag.isWeak()); + } + + @Test + void addValues_multipleTags_populatesCollection() { + List tags = new ArrayList<>(); + new TagReader("\"abc\", \"def\"").addValues(tags); + assertEquals(2, tags.size()); + assertEquals(new Tag("abc"), tags.get(0)); + assertEquals(new Tag("def"), tags.get(1)); + } + + @Test + void addValuesStatic_fromHeader_populatesCollection() { + List tags = new ArrayList<>(); + Header header = new Header("ETag", "\"abc\""); + TagReader.addValues(header, tags); + assertEquals(1, tags.size()); + assertEquals(new Tag("abc"), tags.get(0)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/TagWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/TagWriterTestCase.java new file mode 100644 index 0000000000..876c67b0ac --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/TagWriterTestCase.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.header; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.restlet.data.Tag; + +class TagWriterTestCase { + + @Test + void write_singleTag_delegatesToTagFormat() { + Tag tag = new Tag("abc"); + assertEquals(tag.format(), TagWriter.write(tag)); + } + + @Test + void write_listOfTags_joinsWithSeparator() { + Tag tag1 = new Tag("abc"); + Tag tag2 = new Tag("def"); + String result = TagWriter.write(Arrays.asList(tag1, tag2)); + assertEquals(tag1.format() + ", " + tag2.format(), result); + } + + @Test + void append_singleTag_writesFormattedValue() { + Tag tag = new Tag("abc"); + TagWriter writer = new TagWriter(); + writer.append(tag); + assertEquals(tag.format(), writer.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/TokenReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/TokenReaderTestCase.java new file mode 100644 index 0000000000..4d7db3ce64 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/TokenReaderTestCase.java @@ -0,0 +1,32 @@ +/** + * 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.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class TokenReaderTestCase { + + @Test + void readValue_singleToken_returnsToken() throws IOException { + TokenReader reader = new TokenReader("gzip"); + assertEquals("gzip", reader.readValue()); + } + + @Test + void addValues_multipleTokens_populatesCollection() { + List tokens = new ArrayList<>(); + new TokenReader("gzip, deflate").addValues(tokens); + assertEquals(List.of("gzip", "deflate"), tokens); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/header/WarningWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/WarningWriterTestCase.java new file mode 100644 index 0000000000..2d9f9119ff --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/header/WarningWriterTestCase.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.engine.header; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.restlet.data.Status; +import org.restlet.data.Warning; + +class WarningWriterTestCase { + + private static Warning newWarning() { + Warning warning = new Warning(); + warning.setStatus(Status.valueOf(110)); + warning.setAgent("restlet/2.7"); + warning.setText("Response is stale"); + return warning; + } + + @Test + void write_validWarning_formatsCodeAgentAndQuotedText() { + String result = WarningWriter.write(Arrays.asList(newWarning())); + assertEquals("110 restlet/2.7 \"Response is stale\"", result); + } + + @Test + void write_withDate_appendsQuotedDate() { + Warning warning = newWarning(); + warning.setDate(new java.util.Date(0)); + String result = WarningWriter.write(Arrays.asList(warning)); + assertTrue(result.startsWith("110 restlet/2.7 \"Response is stale\" \"")); + } + + @Test + void append_nullStatus_throwsIllegalArgumentException() { + Warning warning = newWarning(); + warning.setStatus(null); + assertThrows(IllegalArgumentException.class, () -> new WarningWriter().append(warning)); + } + + @Test + void append_missingAgent_throwsIllegalArgumentException() { + Warning warning = newWarning(); + warning.setAgent(null); + assertThrows(IllegalArgumentException.class, () -> new WarningWriter().append(warning)); + } + + @Test + void append_missingText_throwsIllegalArgumentException() { + Warning warning = newWarning(); + warning.setText(""); + assertThrows(IllegalArgumentException.class, () -> new WarningWriter().append(warning)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/io/PipeStreamTestCase.java b/org.restlet/src/test/java/org/restlet/engine/io/PipeStreamTestCase.java new file mode 100644 index 0000000000..59e518daf1 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/io/PipeStreamTestCase.java @@ -0,0 +1,47 @@ +/** + * 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.io; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.junit.jupiter.api.Test; + +class PipeStreamTestCase { + + @Test + void writeThenReadThenClose_roundTripsBytesAndSignalsEndOfStream() throws IOException { + PipeStream pipe = new PipeStream(); + OutputStream out = pipe.getOutputStream(); + InputStream in = pipe.getInputStream(); + + out.write('a'); + out.write('b'); + out.close(); + + assertEquals('a', in.read()); + assertEquals('b', in.read()); + assertEquals(-1, in.read()); + assertEquals(-1, in.read()); + } + + @Test + void write_masksToUnsignedByte() throws IOException { + PipeStream pipe = new PipeStream(); + OutputStream out = pipe.getOutputStream(); + InputStream in = pipe.getInputStream(); + + out.write(0xFF); + out.close(); + + assertEquals(0xFF, in.read()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/io/RangeInputStreamTestCase.java b/org.restlet/src/test/java/org/restlet/engine/io/RangeInputStreamTestCase.java new file mode 100644 index 0000000000..9f1f20252e --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/io/RangeInputStreamTestCase.java @@ -0,0 +1,167 @@ +/** + * 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.io; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import org.restlet.data.Range; + +class RangeInputStreamTestCase { + + private static byte[] source() { + return "0123456789".getBytes(StandardCharsets.US_ASCII); + } + + @Test + void readByteArray_boundedRange_returnsOnlyThatSlice() throws IOException { + RangeInputStream in = + new RangeInputStream(new ByteArrayInputStream(source()), 10, new Range(2, 5)); + + byte[] buffer = new byte[10]; + int read = in.read(buffer, 0, buffer.length); + + assertEquals("23456", new String(buffer, 0, read, StandardCharsets.US_ASCII)); + } + + @Test + void readByteArray_unboundedRangeFromIndex_readsToEndOfStream() throws IOException { + RangeInputStream in = + new RangeInputStream( + new ByteArrayInputStream(source()), 10, new Range(5, Range.SIZE_MAX)); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[4]; + int read; + while ((read = in.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, read); + } + + assertEquals("56789", out.toString(StandardCharsets.US_ASCII)); + } + + @Test + void readByteArray_lastRangeWithKnownSize_readsFromComputedStart() throws IOException { + RangeInputStream in = + new RangeInputStream( + new ByteArrayInputStream(source()), 10, new Range(Range.INDEX_LAST, 3)); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[10]; + int read; + while ((read = in.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, read); + } + + assertEquals("789", out.toString(StandardCharsets.US_ASCII)); + } + + @Test + void readSingleByte_skipsBytesOutsideRange() throws IOException { + RangeInputStream in = + new RangeInputStream(new ByteArrayInputStream(source()), 10, new Range(2, 3)); + + assertEquals('2', (char) in.read()); + assertEquals('3', (char) in.read()); + assertEquals('4', (char) in.read()); + assertEquals(-1, in.read()); + } + + @Test + void available_returnsConfiguredRangeSize() throws IOException { + RangeInputStream in = + new RangeInputStream(new ByteArrayInputStream(source()), 10, new Range(2, 5)); + assertEquals(5, in.available()); + } + + @Test + void mark_withRegularIndex_doesNotThrow() { + RangeInputStream in = + new RangeInputStream(new ByteArrayInputStream(source()), 10, new Range(2, 5)); + in.mark(10); + } + + @Test + void mark_withLastIndex_doesNotThrow() { + RangeInputStream in = + new RangeInputStream( + new ByteArrayInputStream(source()), 10, new Range(Range.INDEX_LAST, 3)); + in.mark(10); + } + + @Test + void constructor_unknownSizeWithLastIndexAndNonMaxSize_throws() { + assertThrows( + IllegalArgumentException.class, + () -> + new RangeInputStream( + new ByteArrayInputStream(source()), + org.restlet.representation.Representation.UNKNOWN_SIZE, + new Range(Range.INDEX_LAST, 3))); + } + + @Test + void constructor_unknownSizeWithLastIndexAndMaxSize_readsWholeStream() throws IOException { + RangeInputStream in = + new RangeInputStream( + new ByteArrayInputStream(source()), + org.restlet.representation.Representation.UNKNOWN_SIZE, + new Range(Range.INDEX_LAST, Range.SIZE_MAX)); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[10]; + int read; + while ((read = in.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, read); + } + + assertEquals("0123456789", out.toString(StandardCharsets.US_ASCII)); + } + + @Test + void constructor_unknownSizeWithIndexAndMaxSize_readsFromIndexToEnd() throws IOException { + RangeInputStream in = + new RangeInputStream( + new ByteArrayInputStream(source()), + org.restlet.representation.Representation.UNKNOWN_SIZE, + new Range(3, Range.SIZE_MAX)); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[10]; + int read; + while ((read = in.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, read); + } + + assertEquals("3456789", out.toString(StandardCharsets.US_ASCII)); + } + + @Test + void constructor_unknownSizeWithIndexAndBoundedSize_readsExactSlice() throws IOException { + RangeInputStream in = + new RangeInputStream( + new ByteArrayInputStream(source()), + org.restlet.representation.Representation.UNKNOWN_SIZE, + new Range(3, 4)); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[10]; + int read; + while ((read = in.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, read); + } + + assertEquals("3456", out.toString(StandardCharsets.US_ASCII)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/io/WriterOutputStreamTestCase.java b/org.restlet/src/test/java/org/restlet/engine/io/WriterOutputStreamTestCase.java new file mode 100644 index 0000000000..15a085237e --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/io/WriterOutputStreamTestCase.java @@ -0,0 +1,100 @@ +/** + * 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.io; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; + +class WriterOutputStreamTestCase { + + @Test + void writeByteArray_decodesUsingGivenCharacterSet() throws IOException { + StringWriter writer = new StringWriter(); + WriterOutputStream out = new WriterOutputStream(writer, CharacterSet.UTF_8); + + out.write("hello".getBytes(StandardCharsets.UTF_8)); + + assertEquals("hello", writer.toString()); + } + + @Test + void writeByteArray_nullCharacterSet_defaultsToIso88591() throws IOException { + StringWriter writer = new StringWriter(); + WriterOutputStream out = new WriterOutputStream(writer, null); + + out.write("abc".getBytes(StandardCharsets.ISO_8859_1)); + + assertEquals("abc", writer.toString()); + } + + @Test + void writeSingleByte_delegatesToWriter() throws IOException { + StringWriter writer = new StringWriter(); + WriterOutputStream out = new WriterOutputStream(writer, CharacterSet.UTF_8); + + out.write('a'); + + assertEquals("a", writer.toString()); + } + + @Test + void writeByteArrayWithOffsetAndLength_writesOnlyThatSlice() throws IOException { + StringWriter writer = new StringWriter(); + WriterOutputStream out = new WriterOutputStream(writer, CharacterSet.UTF_8); + byte[] data = "xxhelloxx".getBytes(StandardCharsets.UTF_8); + + out.write(data, 2, 5); + + assertEquals("hello", writer.toString()); + } + + @Test + void flush_flushesUnderlyingWriter() throws IOException { + java.util.concurrent.atomic.AtomicBoolean flushed = + new java.util.concurrent.atomic.AtomicBoolean(); + java.io.Writer tracker = + new java.io.FilterWriter(new StringWriter()) { + @Override + public void flush() throws IOException { + flushed.set(true); + super.flush(); + } + }; + WriterOutputStream out = new WriterOutputStream(tracker, CharacterSet.UTF_8); + + out.flush(); + + assertTrue(flushed.get()); + } + + @Test + void close_closesUnderlyingWriter() throws IOException { + java.util.concurrent.atomic.AtomicBoolean closed = + new java.util.concurrent.atomic.AtomicBoolean(); + java.io.Writer tracker = + new java.io.FilterWriter(new StringWriter()) { + @Override + public void close() throws IOException { + closed.set(true); + super.close(); + } + }; + WriterOutputStream out = new WriterOutputStream(tracker, CharacterSet.UTF_8); + + out.close(); + + assertTrue(closed.get()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/resource/ThrowableAnnotationInfoTestCase.java b/org.restlet/src/test/java/org/restlet/engine/resource/ThrowableAnnotationInfoTestCase.java new file mode 100644 index 0000000000..464d7bbe52 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/resource/ThrowableAnnotationInfoTestCase.java @@ -0,0 +1,81 @@ +/** + * 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.resource; + +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 org.junit.jupiter.api.Test; +import org.restlet.data.Status; + +class ThrowableAnnotationInfoTestCase { + + @Test + void constructor_parsesStatusFromAnnotationValue() { + ThrowableAnnotationInfo info = + new ThrowableAnnotationInfo(RuntimeException.class, 404, true); + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, info.getStatus()); + assertEquals("404", info.getAnnotationValue()); + assertEquals(RuntimeException.class, info.getJavaClass()); + assertNull(info.getJavaMethod()); + assertTrue(info.isSerializable()); + } + + @Test + void equals_sameClassAndStatus_returnsTrue() { + ThrowableAnnotationInfo info1 = + new ThrowableAnnotationInfo(RuntimeException.class, 404, true); + ThrowableAnnotationInfo info2 = + new ThrowableAnnotationInfo(RuntimeException.class, 404, false); + assertTrue(info1.equals(info2)); + } + + @Test + void equals_differentStatus_returnsFalse() { + ThrowableAnnotationInfo info1 = + new ThrowableAnnotationInfo(RuntimeException.class, 404, true); + ThrowableAnnotationInfo info2 = + new ThrowableAnnotationInfo(RuntimeException.class, 500, true); + assertFalse(info1.equals(info2)); + } + + @Test + void equals_differentType_returnsFalse() { + ThrowableAnnotationInfo info = + new ThrowableAnnotationInfo(RuntimeException.class, 404, true); + assertFalse(info.equals("not an info")); + } + + @Test + void equals_sameInstance_returnsTrue() { + ThrowableAnnotationInfo info = + new ThrowableAnnotationInfo(RuntimeException.class, 404, true); + assertTrue(info.equals(info)); + } + + @Test + void hashCode_matchesForEqualInstances() { + ThrowableAnnotationInfo info1 = + new ThrowableAnnotationInfo(RuntimeException.class, 404, true); + ThrowableAnnotationInfo info2 = + new ThrowableAnnotationInfo(RuntimeException.class, 404, false); + assertEquals(info1.hashCode(), info2.hashCode()); + } + + @Test + void toString_containsStatusAndSerializable() { + ThrowableAnnotationInfo info = + new ThrowableAnnotationInfo(RuntimeException.class, 404, true); + String result = info.toString(); + assertTrue(result.contains("404")); + assertTrue(result.contains("serializable=true")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/resource/VariantInfoTestCase.java b/org.restlet/src/test/java/org/restlet/engine/resource/VariantInfoTestCase.java new file mode 100644 index 0000000000..4ee9ab0f73 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/resource/VariantInfoTestCase.java @@ -0,0 +1,78 @@ +/** + * 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.resource; + +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.Collections; +import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; +import org.restlet.data.MediaType; +import org.restlet.representation.Variant; + +class VariantInfoTestCase { + + @Test + void constructor_mediaTypeOnly_setsDefaultInputScore() { + VariantInfo variant = new VariantInfo(MediaType.APPLICATION_JSON); + assertEquals(MediaType.APPLICATION_JSON, variant.getMediaType()); + assertNull(variant.getAnnotationInfo()); + assertEquals(1.0f, variant.getInputScore()); + } + + @Test + void constructor_fromVariant_copiesCharacterSetEncodingsAndLanguages() { + Variant source = new Variant(MediaType.TEXT_PLAIN); + source.setCharacterSet(CharacterSet.UTF_8); + source.setLanguages(Collections.singletonList(org.restlet.data.Language.ENGLISH)); + + VariantInfo variant = new VariantInfo(source, null); + + assertEquals(MediaType.TEXT_PLAIN, variant.getMediaType()); + assertEquals(CharacterSet.UTF_8, variant.getCharacterSet()); + assertEquals(1, variant.getLanguages().size()); + } + + @Test + void setInputScore_updatesValue() { + VariantInfo variant = new VariantInfo(MediaType.APPLICATION_JSON); + variant.setInputScore(0.5f); + assertEquals(0.5f, variant.getInputScore()); + } + + @Test + void equals_sameMediaTypeAndAnnotationInfo_returnsTrue() { + VariantInfo variant1 = new VariantInfo(MediaType.APPLICATION_JSON, null); + VariantInfo variant2 = new VariantInfo(MediaType.APPLICATION_JSON, null); + assertTrue(variant1.equals(variant2)); + assertEquals(variant1.hashCode(), variant2.hashCode()); + } + + @Test + void equals_differentMediaType_returnsFalse() { + VariantInfo variant1 = new VariantInfo(MediaType.APPLICATION_JSON, null); + VariantInfo variant2 = new VariantInfo(MediaType.TEXT_PLAIN, null); + assertFalse(variant1.equals(variant2)); + } + + @Test + void equals_differentType_returnsFalse() { + VariantInfo variant = new VariantInfo(MediaType.APPLICATION_JSON, null); + assertFalse(variant.equals("not a variant")); + } + + @Test + void equals_sameInstance_returnsTrue() { + VariantInfo variant = new VariantInfo(MediaType.APPLICATION_JSON, null); + assertTrue(variant.equals(variant)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/security/AuthenticatorUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/security/AuthenticatorUtilsTestCase.java new file mode 100644 index 0000000000..99a005aac4 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/security/AuthenticatorUtilsTestCase.java @@ -0,0 +1,197 @@ +/** + * 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.security; + +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 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.ChallengeResponse; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.Method; + +class AuthenticatorUtilsTestCase { + + @Test + void anyNull_noNulls_returnsFalse() { + assertFalse(AuthenticatorUtils.anyNull("a", "b", 1)); + } + + @Test + void anyNull_someNull_returnsTrue() { + assertTrue(AuthenticatorUtils.anyNull("a", null, 1)); + } + + @Test + void formatNonceCount_padsToEightHexCharacters() { + assertEquals("00000001", AuthenticatorUtils.formatNonceCount(1)); + assertEquals("000000ff", AuthenticatorUtils.formatNonceCount(255)); + } + + @Test + void formatAuthenticationInfo_null_returnsEmptyString() { + assertEquals("", AuthenticatorUtils.formatAuthenticationInfo(null)); + } + + @Test + void formatAuthenticationInfo_allFieldsSet_formatsAllParameters() { + AuthenticationInfo info = new AuthenticationInfo("next", 5, "client", "auth", "digest"); + String result = AuthenticatorUtils.formatAuthenticationInfo(info); + assertTrue(result.contains("nextnonce=\"next\"")); + assertTrue(result.contains("qop=auth")); + assertTrue(result.contains("nc=00000005")); + assertTrue(result.contains("rspauth=\"digest\"")); + assertTrue(result.contains("cnonce=client")); + } + + @Test + void formatAuthenticationInfo_emptyFields_areOmitted() { + AuthenticationInfo info = new AuthenticationInfo("", 0, "", "", ""); + assertEquals("", AuthenticatorUtils.formatAuthenticationInfo(info)); + } + + @Test + void formatRequest_nullChallenge_returnsNull() { + assertNull(AuthenticatorUtils.formatRequest(null, new Response(new Request()), null)); + } + + @Test + void formatRequest_nullScheme_returnsNull() { + ChallengeRequest challenge = new ChallengeRequest((ChallengeScheme) null); + assertNull(AuthenticatorUtils.formatRequest(challenge, new Response(new Request()), null)); + } + + @Test + void formatRequest_rawValuePresent_isUsedDirectly() { + ChallengeRequest challenge = new ChallengeRequest(ChallengeScheme.HTTP_BASIC); + challenge.setRawValue("realm=\"test\""); + String result = + AuthenticatorUtils.formatRequest(challenge, new Response(new Request()), null); + assertEquals("Basic realm=\"test\"", result); + } + + @Test + void formatResponse_nullChallenge_returnsNull() { + assertNull(AuthenticatorUtils.formatResponse(null, new Request(), null)); + } + + @Test + void formatResponse_nullScheme_returnsNull() { + ChallengeResponse challenge = new ChallengeResponse((ChallengeScheme) null); + assertNull(AuthenticatorUtils.formatResponse(challenge, new Request(), null)); + } + + @Test + void formatResponse_rawValuePresent_isUsedDirectly() { + ChallengeResponse challenge = new ChallengeResponse(ChallengeScheme.HTTP_BASIC); + challenge.setRawValue("dXNlcjpwYXNz"); + String result = AuthenticatorUtils.formatResponse(challenge, new Request(), null); + assertEquals("Basic dXNlcjpwYXNz", result); + } + + @Test + void parseAuthenticationInfo_allFields_parsesEachParameter() { + AuthenticationInfo info = + AuthenticatorUtils.parseAuthenticationInfo( + "nextnonce=\"next\", qop=auth, nc=00000005, rspauth=\"digest\", cnonce=client"); + assertEquals("next", info.getNextServerNonce()); + assertEquals("auth", info.getQuality()); + assertEquals(5, info.getNonceCount()); + assertEquals("digest", info.getResponseDigest()); + assertEquals("client", info.getClientNonce()); + } + + @Test + void parseAuthenticationInfo_invalidNonceCount_defaultsToZero() { + AuthenticationInfo info = AuthenticatorUtils.parseAuthenticationInfo("nc=notahexnumber"); + assertEquals(0, info.getNonceCount()); + } + + @Test + void parseAuthenticationInfo_unparseableHeader_returnsNull() { + assertNull(AuthenticatorUtils.parseAuthenticationInfo("")); + } + + @Test + void parseRequest_nullHeader_returnsEmptyList() { + assertTrue( + AuthenticatorUtils.parseRequest(new Response(new Request()), null, null).isEmpty()); + } + + @Test + void parseResponse_nullHeader_returnsNull() { + assertNull(AuthenticatorUtils.parseResponse(new Request(), null, null)); + } + + @Test + void parseResponse_headerWithoutSpace_returnsNull() { + assertNull(AuthenticatorUtils.parseResponse(new Request(), "NoSpaceHeader", null)); + } + + @Test + void parseResponse_validHeader_parsesSchemeAndRawValue() { + ChallengeResponse result = + AuthenticatorUtils.parseResponse(new Request(), "Basic dXNlcjpwYXNz", null); + assertEquals("Basic", result.getScheme().getTechnicalName()); + assertEquals("dXNlcjpwYXNz", result.getRawValue()); + } + + @Test + void update_noMatchingChallengeRequest_leavesRealmAndNonceNull() { + Request request = new Request(Method.GET, "http://example.com/path"); + Response response = new Response(request); + ChallengeResponse challengeResponse = new ChallengeResponse(ChallengeScheme.HTTP_BASIC); + + AuthenticatorUtils.update(challengeResponse, request, response); + + assertNull(challengeResponse.getRealm()); + assertNull(challengeResponse.getServerNonce()); + } + + @Test + void update_matchingChallengeRequest_copiesRealmAndNonce() { + Request request = new Request(Method.GET, "http://example.com/path"); + Response response = new Response(request); + ChallengeRequest challengeRequest = new ChallengeRequest(ChallengeScheme.HTTP_DIGEST); + challengeRequest.setRealm("test-realm"); + challengeRequest.setServerNonce("test-nonce"); + response.getChallengeRequests().add(challengeRequest); + + ChallengeResponse challengeResponse = new ChallengeResponse(ChallengeScheme.HTTP_DIGEST); + AuthenticatorUtils.update(challengeResponse, request, response); + + assertEquals("test-realm", challengeResponse.getRealm()); + assertEquals("test-nonce", challengeResponse.getServerNonce()); + } + + @Test + void updateReference_noChallengeResponse_returnsUnchangedReference() { + Request request = new Request(Method.GET, "http://example.com/path"); + assertEquals( + request.getResourceRef(), + AuthenticatorUtils.updateReference(request.getResourceRef(), null, request)); + } + + @Test + void updateReference_rawValuePresent_returnsUnchangedReference() { + Request request = new Request(Method.GET, "http://example.com/path"); + ChallengeResponse challengeResponse = new ChallengeResponse(ChallengeScheme.HTTP_BASIC); + challengeResponse.setRawValue("dXNlcjpwYXNz"); + assertEquals( + request.getResourceRef(), + AuthenticatorUtils.updateReference( + request.getResourceRef(), challengeResponse, request)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/security/RoleMappingTestCase.java b/org.restlet/src/test/java/org/restlet/engine/security/RoleMappingTestCase.java new file mode 100644 index 0000000000..fe2758f430 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/security/RoleMappingTestCase.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.engine.security; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; +import org.restlet.security.Role; +import org.restlet.security.User; + +class RoleMappingTestCase { + + @Test + void defaultConstructor_hasNullSourceAndTarget() { + RoleMapping mapping = new RoleMapping(); + assertNull(mapping.getSource()); + assertNull(mapping.getTarget()); + } + + @Test + void constructor_setsSourceAndTarget() { + User user = new User("alice"); + Role role = new Role(null, "admin", "Administrator role"); + RoleMapping mapping = new RoleMapping(user, role); + assertEquals(user, mapping.getSource()); + assertEquals(role, mapping.getTarget()); + } + + @Test + void settersUpdateState() { + RoleMapping mapping = new RoleMapping(); + User user = new User("bob"); + Role role = new Role(null, "editor", "Editor role"); + mapping.setSource(user); + mapping.setTarget(role); + assertEquals(user, mapping.getSource()); + assertEquals(role, mapping.getTarget()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/AlphabeticalComparatorTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/AlphabeticalComparatorTestCase.java new file mode 100644 index 0000000000..664b563620 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/AlphabeticalComparatorTestCase.java @@ -0,0 +1,55 @@ +/** + * 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.assertTrue; + +import org.junit.jupiter.api.Test; +import org.restlet.data.Reference; + +class AlphabeticalComparatorTestCase { + + private final AlphabeticalComparator comparator = new AlphabeticalComparator(); + + @Test + void compare_bothNullIdentifiers_returnsZero() { + Reference ref0 = new Reference((String) null); + Reference ref1 = new Reference((String) null); + assertEquals(0, comparator.compare(ref0, ref1)); + } + + @Test + void compare_firstNullIdentifier_returnsNegative() { + Reference ref0 = new Reference((String) null); + Reference ref1 = new Reference("http://example.com/a"); + assertTrue(comparator.compare(ref0, ref1) < 0); + } + + @Test + void compare_secondNullIdentifier_returnsPositive() { + Reference ref0 = new Reference("http://example.com/a"); + Reference ref1 = new Reference((String) null); + assertTrue(comparator.compare(ref0, ref1) > 0); + } + + @Test + void compare_bothNonNull_delegatesToStringComparison() { + Reference ref0 = new Reference("http://example.com/a"); + Reference ref1 = new Reference("http://example.com/b"); + assertTrue(comparator.compare(ref0, ref1) < 0); + assertTrue(comparator.compare(ref1, ref0) > 0); + } + + @Test + void compareStrings_usesNaturalOrdering() { + assertTrue(comparator.compare("abc", "abd") < 0); + assertEquals(0, comparator.compare("abc", "abc")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/CaseInsensitiveHashSetTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/CaseInsensitiveHashSetTestCase.java new file mode 100644 index 0000000000..226b569ec9 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/CaseInsensitiveHashSetTestCase.java @@ -0,0 +1,49 @@ +/** + * 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +class CaseInsensitiveHashSetTestCase { + + @Test + void constructor_lowercasesInitialElements() { + CaseInsensitiveHashSet set = new CaseInsensitiveHashSet(Arrays.asList("Foo", "BAR")); + assertTrue(set.contains("foo")); + assertTrue(set.contains("FOO")); + assertTrue(set.contains("bar")); + } + + @Test + void add_lowercasesElement() { + CaseInsensitiveHashSet set = new CaseInsensitiveHashSet(Collections.emptyList()); + set.add("Hello"); + assertTrue(set.contains("hello")); + assertTrue(set.contains("HELLO")); + } + + @Test + void contains_stringOverload_isCaseInsensitive() { + CaseInsensitiveHashSet set = new CaseInsensitiveHashSet(Collections.singletonList("value")); + assertTrue(set.contains("VALUE")); + assertFalse(set.contains("other")); + } + + @Test + void contains_objectOverload_delegatesToStringOverload() { + CaseInsensitiveHashSet set = new CaseInsensitiveHashSet(Collections.singletonList("value")); + Object key = "VALUE"; + assertTrue(set.contains(key)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/ChildClientDispatcherTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/ChildClientDispatcherTestCase.java new file mode 100644 index 0000000000..d984e381d2 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/ChildClientDispatcherTestCase.java @@ -0,0 +1,150 @@ +/** + * 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.assertTrue; + +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Test; +import org.restlet.Application; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.LocalReference; +import org.restlet.data.Method; +import org.restlet.data.Status; +import org.restlet.routing.Filter; + +class ChildClientDispatcherTestCase { + + @Test + void doHandle_riapApplication_dispatchesToChildApplicationInboundRoot() throws Exception { + Context parentContext = new Context(); + ChildContext childContext = new ChildContext(parentContext); + AtomicBoolean handled = new AtomicBoolean(); + Application application = + new Application(childContext) { + @Override + public Restlet createInboundRoot() { + return new Restlet() { + @Override + public void handle(Request request, Response response) { + handled.set(true); + response.setStatus(Status.SUCCESS_OK); + } + }; + } + }; + application.start(); + childContext.setChild(application); + + ChildClientDispatcher dispatcher = new ChildClientDispatcher(childContext); + Request request = + new Request( + Method.GET, + LocalReference.createRiapReference(LocalReference.RIAP_APPLICATION, "/path") + .toString()); + Response response = new Response(request); + + int result = dispatcher.doHandle(request, response); + + assertTrue(handled.get()); + assertEquals(Filter.CONTINUE, result); + } + + @Test + void doHandle_riapComponent_delegatesToParentClientDispatcher() { + Context parentContext = new Context(); + AtomicBoolean handled = new AtomicBoolean(); + parentContext.setClientDispatcher( + new Restlet() { + @Override + public void handle(Request request, Response response) { + handled.set(true); + } + }); + ChildContext childContext = new ChildContext(parentContext); + ChildClientDispatcher dispatcher = new ChildClientDispatcher(childContext); + + Request request = + new Request( + Method.GET, + LocalReference.createRiapReference(LocalReference.RIAP_COMPONENT, "/path") + .toString()); + Response response = new Response(request); + + dispatcher.doHandle(request, response); + + assertTrue(handled.get()); + } + + @Test + void doHandle_riapHost_delegatesToParentClientDispatcher() { + Context parentContext = new Context(); + AtomicBoolean handled = new AtomicBoolean(); + parentContext.setClientDispatcher( + new Restlet() { + @Override + public void handle(Request request, Response response) { + handled.set(true); + } + }); + ChildContext childContext = new ChildContext(parentContext); + ChildClientDispatcher dispatcher = new ChildClientDispatcher(childContext); + + Request request = + new Request( + Method.GET, + LocalReference.createRiapReference(LocalReference.RIAP_HOST, "/path") + .toString()); + Response response = new Response(request); + + dispatcher.doHandle(request, response); + + assertTrue(handled.get()); + } + + @Test + void doHandle_nonRiapProtocol_delegatesToParentClientDispatcher() { + Context parentContext = new Context(); + AtomicBoolean handled = new AtomicBoolean(); + parentContext.setClientDispatcher( + new Restlet() { + @Override + public void handle(Request request, Response response) { + handled.set(true); + } + }); + ChildContext childContext = new ChildContext(parentContext); + ChildClientDispatcher dispatcher = new ChildClientDispatcher(childContext); + + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + int result = dispatcher.doHandle(request, response); + + assertTrue(handled.get()); + assertEquals(Filter.CONTINUE, result); + } + + @Test + void doHandle_noParentContext_logsWarningAndDoesNotThrow() { + ChildContext childContext = new ChildContext(null); + ChildClientDispatcher dispatcher = new ChildClientDispatcher(childContext); + + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + int result = dispatcher.doHandle(request, response); + + assertEquals(Filter.CONTINUE, result); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/ChildContextTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/ChildContextTestCase.java new file mode 100644 index 0000000000..974c2855f5 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/ChildContextTestCase.java @@ -0,0 +1,53 @@ +/** + * 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 static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Restlet; + +class ChildContextTestCase { + + @Test + void constructor_withParentContext_copiesServerDispatcher() { + Context parent = new Context(); + ChildContext child = new ChildContext(parent); + assertEquals(parent.getServerDispatcher(), child.getServerDispatcher()); + assertEquals(parent, child.getParentContext()); + } + + @Test + void constructor_setsChildClientDispatcher() { + Context parent = new Context(); + ChildContext child = new ChildContext(parent); + assertNotNull(child.getClientDispatcher()); + } + + @Test + void constructor_withNullParentContext_leavesServerDispatcherNull() { + ChildContext child = new ChildContext(null); + assertNull(child.getServerDispatcher()); + assertNull(child.getExecutorService()); + } + + @Test + void getChildAndSetChild_roundTrip() { + Context parent = new Context(); + ChildContext child = new ChildContext(parent); + Restlet restlet = new Restlet() {}; + + child.setChild(restlet); + + assertEquals(restlet, child.getChild()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/ContextualRunnableTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/ContextualRunnableTestCase.java new file mode 100644 index 0000000000..3c0fea0c25 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/ContextualRunnableTestCase.java @@ -0,0 +1,48 @@ +/** + * 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.assertTrue; + +import org.junit.jupiter.api.Test; + +class ContextualRunnableTestCase { + + private static class TestContextualRunnable extends ContextualRunnable { + volatile boolean ran; + + @Override + public void run() { + ran = true; + } + } + + @Test + void constructor_capturesCurrentThreadContextClassLoader() { + TestContextualRunnable runnable = new TestContextualRunnable(); + assertEquals( + Thread.currentThread().getContextClassLoader(), runnable.getContextClassLoader()); + } + + @Test + void setContextClassLoader_updatesValue() { + TestContextualRunnable runnable = new TestContextualRunnable(); + ClassLoader custom = ContextualRunnableTestCase.class.getClassLoader(); + runnable.setContextClassLoader(custom); + assertEquals(custom, runnable.getContextClassLoader()); + } + + @Test + void run_invokesSubclassImplementation() { + TestContextualRunnable runnable = new TestContextualRunnable(); + runnable.run(); + assertTrue(runnable.ran); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/EngineClassLoaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/EngineClassLoaderTestCase.java new file mode 100644 index 0000000000..a4d49f2882 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/EngineClassLoaderTestCase.java @@ -0,0 +1,93 @@ +/** + * 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 static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.URL; +import java.util.Enumeration; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.restlet.engine.Engine; + +class EngineClassLoaderTestCase { + + private Engine engine; + + private ClassLoader previousUserClassLoader; + + @BeforeEach + void setUpEach() { + engine = Engine.getInstance(); + previousUserClassLoader = engine.getUserClassLoader(); + } + + @AfterEach + void tearDownEach() { + engine.setUserClassLoader(previousUserClassLoader); + } + + @Test + void loadClass_existingClass_delegatesToUserClassLoader() throws ClassNotFoundException { + engine.setUserClassLoader(EngineClassLoaderTestCase.class.getClassLoader()); + EngineClassLoader loader = new EngineClassLoader(engine); + + Class result = loader.loadClass("java.lang.String"); + + assertEquals(String.class, result); + } + + @Test + void loadClass_withoutUserClassLoader_fallsBackToContextClassLoader() + throws ClassNotFoundException { + engine.setUserClassLoader(null); + EngineClassLoader loader = new EngineClassLoader(engine); + + Class result = loader.loadClass("java.lang.String"); + + assertEquals(String.class, result); + } + + @Test + void loadClass_unknownClass_throwsClassNotFoundException() { + EngineClassLoader loader = new EngineClassLoader(engine); + + assertThrows( + ClassNotFoundException.class, () -> loader.loadClass("com.example.NoSuchClass")); + } + + @Test + void getResource_delegatesToUserClassLoader() { + engine.setUserClassLoader(EngineClassLoaderTestCase.class.getClassLoader()); + EngineClassLoader loader = new EngineClassLoader(engine); + + URL result = loader.getResource("java/lang/String.class"); + + assertNotNull(result); + } + + @Test + void getResources_returnsDeduplicatedEnumeration() throws Exception { + engine.setUserClassLoader(EngineClassLoaderTestCase.class.getClassLoader()); + EngineClassLoader loader = new EngineClassLoader(engine); + + Enumeration resources = loader.getResources("java/lang/String.class"); + + assertNotNull(resources); + } + + @Test + void getEngine_returnsConstructorArgument() { + EngineClassLoader loader = new EngineClassLoader(engine); + assertEquals(engine, loader.getEngine()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/FormUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/FormUtilsTestCase.java new file mode 100644 index 0000000000..1f6946aa1d --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/FormUtilsTestCase.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.engine.util; + +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.IOException; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Parameter; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; + +class FormUtilsTestCase { + + @Test + void create_nullName_returnsNull() { + assertNull(FormUtils.create(null, "value", false, CharacterSet.UTF_8)); + } + + @Test + void create_notDecoded_usesRawValues() { + Parameter param = FormUtils.create("a%20b", "c%20d", false, CharacterSet.UTF_8); + assertEquals("a%20b", param.getName()); + assertEquals("c%20d", param.getValue()); + } + + @Test + void create_decoded_decodesNameAndValue() { + Parameter param = FormUtils.create("a%20b", "c%20d", true, CharacterSet.UTF_8); + assertEquals("a b", param.getName()); + assertEquals("c d", param.getValue()); + } + + @Test + void create_nullValue_producesNullValuedParameter() { + Parameter param = FormUtils.create("name", null, false, CharacterSet.UTF_8); + assertEquals("name", param.getName()); + assertNull(param.getValue()); + } + + @Test + void getFirstParameter_fromQueryString_returnsMatch() throws IOException { + Parameter param = + FormUtils.getFirstParameter("a=1&b=2", "b", CharacterSet.UTF_8, '&', true); + assertEquals("2", param.getValue()); + } + + @Test + void getFirstParameter_fromRepresentation_returnsMatch() throws IOException { + Representation post = new StringRepresentation("a=1&b=2", MediaType.APPLICATION_WWW_FORM); + Parameter param = FormUtils.getFirstParameter(post, "a"); + assertEquals("1", param.getValue()); + } + + @Test + void getParameter_singleMatch_returnsParameterValue() throws IOException { + Object result = FormUtils.getParameter("a=1&b=2", "a", CharacterSet.UTF_8, '&', true); + assertEquals("1", result); + } + + @Test + void getParameter_multipleMatches_returnsListOfValues() throws IOException { + Object result = FormUtils.getParameter("a=1&a=2", "a", CharacterSet.UTF_8, '&', true); + assertTrue(result instanceof java.util.List); + assertEquals(2, ((java.util.List) result).size()); + } + + @Test + void getParameters_fromQueryString_populatesMap() throws IOException { + Map params = new HashMap<>(); + params.put("a", null); + params.put("b", null); + FormUtils.getParameters("a=1&b=2", params, CharacterSet.UTF_8, '&', true); + assertEquals("1", params.get("a")); + assertEquals("2", params.get("b")); + } + + @Test + void getParameters_fromRepresentation_populatesMap() throws IOException { + Representation post = new StringRepresentation("a=1", MediaType.APPLICATION_WWW_FORM); + Map params = new HashMap<>(); + params.put("a", null); + FormUtils.getParameters(post, params); + assertEquals("1", params.get("a")); + } + + @Test + void isParameterFound_matchingParameter_returnsTrue() { + Parameter searched = new Parameter("charset", "UTF-8"); + MediaType typeWithParam = new MediaType("application/json;charset=UTF-8"); + assertTrue(FormUtils.isParameterFound(searched, typeWithParam)); + } + + @Test + void isParameterFound_nonMatchingParameter_returnsFalse() { + Parameter searched = new Parameter("charset", "ISO-8859-1"); + MediaType typeWithParam = new MediaType("application/json;charset=UTF-8"); + assertFalse(FormUtils.isParameterFound(searched, typeWithParam)); + } + + @Test + void parse_representationOverload_addsParametersToForm() { + Form form = new Form(); + Representation post = new StringRepresentation("a=1", MediaType.APPLICATION_WWW_FORM); + FormUtils.parse(form, post, true); + assertEquals("1", form.getFirstValue("a")); + } + + @Test + void parse_nullRepresentation_leavesFormUnchanged() { + Form form = new Form(); + FormUtils.parse(form, (Representation) null, true); + assertTrue(form.isEmpty()); + } + + @Test + void parse_stringOverload_addsParametersToForm() { + Form form = new Form(); + FormUtils.parse(form, "a=1&b=2", CharacterSet.UTF_8, true, '&'); + assertEquals("1", form.getFirstValue("a")); + assertEquals("2", form.getFirstValue("b")); + } + + @Test + void parse_emptyString_leavesFormUnchanged() { + Form form = new Form(); + FormUtils.parse(form, "", CharacterSet.UTF_8, true, '&'); + assertTrue(form.isEmpty()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/ImmutableDateTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/ImmutableDateTestCase.java new file mode 100644 index 0000000000..1147cc49a8 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/ImmutableDateTestCase.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.assertThrows; + +import java.util.Date; +import org.junit.jupiter.api.Test; + +class ImmutableDateTestCase { + + @Test + void constructor_copiesTimeFromSource() { + Date source = new Date(123456789L); + ImmutableDate immutable = new ImmutableDate(source); + assertEquals(source.getTime(), immutable.getTime()); + } + + @Test + void clone_throwsUnsupportedOperationException() { + ImmutableDate immutable = new ImmutableDate(new Date()); + assertThrows(UnsupportedOperationException.class, immutable::clone); + } + + @Test + void setDate_throwsUnsupportedOperationException() { + ImmutableDate immutable = new ImmutableDate(new Date()); + assertThrows(UnsupportedOperationException.class, () -> immutable.setDate(1)); + } + + @Test + void setHours_throwsUnsupportedOperationException() { + ImmutableDate immutable = new ImmutableDate(new Date()); + assertThrows(UnsupportedOperationException.class, () -> immutable.setHours(1)); + } + + @Test + void setMinutes_throwsUnsupportedOperationException() { + ImmutableDate immutable = new ImmutableDate(new Date()); + assertThrows(UnsupportedOperationException.class, () -> immutable.setMinutes(1)); + } + + @Test + void setMonth_throwsUnsupportedOperationException() { + ImmutableDate immutable = new ImmutableDate(new Date()); + assertThrows(UnsupportedOperationException.class, () -> immutable.setMonth(1)); + } + + @Test + void setSeconds_throwsUnsupportedOperationException() { + ImmutableDate immutable = new ImmutableDate(new Date()); + assertThrows(UnsupportedOperationException.class, () -> immutable.setSeconds(1)); + } + + @Test + void setTime_throwsUnsupportedOperationException() { + ImmutableDate immutable = new ImmutableDate(new Date()); + assertThrows(UnsupportedOperationException.class, () -> immutable.setTime(1)); + } + + @Test + void setYear_throwsUnsupportedOperationException() { + ImmutableDate immutable = new ImmutableDate(new Date()); + assertThrows(UnsupportedOperationException.class, () -> immutable.setYear(1)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/InternetDateFormatTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/InternetDateFormatTestCase.java new file mode 100644 index 0000000000..fd3a0e7743 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/InternetDateFormatTestCase.java @@ -0,0 +1,212 @@ +/** + * 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 static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.text.ParsePosition; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import org.junit.jupiter.api.Test; + +class InternetDateFormatTestCase { + + @Test + void toString_calendarUtc_formatsWithZSuffix() { + Calendar cal = new GregorianCalendar(InternetDateFormat.UTC); + cal.clear(); + cal.set(2024, Calendar.MARCH, 5, 13, 7, 9); + assertEquals("2024-03-05T13:07:09Z", InternetDateFormat.toString(cal)); + } + + @Test + void toString_calendarWithMilliseconds_includesFraction() { + Calendar cal = new GregorianCalendar(InternetDateFormat.UTC); + cal.clear(); + cal.set(2024, Calendar.MARCH, 5, 13, 7, 9); + cal.set(Calendar.MILLISECOND, 500); + assertEquals("2024-03-05T13:07:09.50Z", InternetDateFormat.toString(cal)); + } + + @Test + void toString_calendarWithPositiveOffset_formatsSignedOffset() { + TimeZone plusTwo = TimeZone.getTimeZone("GMT+02:00"); + Calendar cal = new GregorianCalendar(plusTwo); + cal.clear(); + cal.set(2024, Calendar.MARCH, 5, 13, 7, 9); + assertEquals("2024-03-05T13:07:09+02:00", InternetDateFormat.toString(cal)); + } + + @Test + void toString_calendarWithNegativeOffset_formatsSignedOffset() { + TimeZone minusFive = TimeZone.getTimeZone("GMT-05:00"); + Calendar cal = new GregorianCalendar(minusFive); + cal.clear(); + cal.set(2024, Calendar.MARCH, 5, 13, 7, 9); + assertEquals("2024-03-05T13:07:09-05:00", InternetDateFormat.toString(cal)); + } + + @Test + void toString_date_usesUtcByDefault() { + Date date = InternetDateFormat.parseDate("2024-03-05T13:07:09Z"); + assertEquals("2024-03-05T13:07:09Z", InternetDateFormat.toString(date)); + } + + @Test + void toString_time_usesUtcByDefault() { + long time = InternetDateFormat.parseTime("2024-03-05T13:07:09Z"); + assertEquals("2024-03-05T13:07:09Z", InternetDateFormat.toString(time)); + } + + @Test + void now_returnsParseableString() { + String now = InternetDateFormat.now(); + assertNotNull(InternetDateFormat.parseDate(now)); + } + + @Test + void now_withZone_returnsParseableString() { + String now = InternetDateFormat.now(TimeZone.getTimeZone("GMT+01:00")); + assertNotNull(InternetDateFormat.parseDate(now)); + } + + @Test + void parse_zuluTimeZone_parsesAsUtc() { + Calendar cal = InternetDateFormat.parseCalendar("2024-03-05T13:07:09z"); + assertEquals(2024, cal.get(Calendar.YEAR)); + assertEquals(Calendar.MARCH, cal.get(Calendar.MONTH)); + assertEquals(5, cal.get(Calendar.DAY_OF_MONTH)); + assertEquals(0, cal.getTimeZone().getRawOffset()); + } + + @Test + void parse_explicitOffset_parsesTimeZone() { + Calendar cal = InternetDateFormat.parseCalendar("2024-03-05T13:07:09+02:30"); + assertEquals(2 * 60 * 60000 + 30 * 60000, cal.getTimeZone().getRawOffset()); + } + + @Test + void parse_negativeOffset_parsesTimeZone() { + Calendar cal = InternetDateFormat.parseCalendar("2024-03-05T13:07:09-02:30"); + assertEquals(-(2 * 60 * 60000 + 30 * 60000), cal.getTimeZone().getRawOffset()); + } + + @Test + void parse_withFractionalSeconds_setsMilliseconds() { + Calendar cal = InternetDateFormat.parseCalendar("2024-03-05T13:07:09.5Z"); + assertEquals(500, cal.get(Calendar.MILLISECOND)); + } + + @Test + void parse_spaceSeparator_isAccepted() { + Calendar cal = InternetDateFormat.parseCalendar("2024-03-05 13:07:09Z"); + assertEquals(2024, cal.get(Calendar.YEAR)); + } + + @Test + void parse_invalidString_throwsIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, () -> InternetDateFormat.parseDate("not-a-date")); + } + + @Test + void valueOf_date_roundTripsToSameInstant() { + Date date = new Date(1_700_000_000_000L); + InternetDateFormat format = InternetDateFormat.valueOf(date); + assertEquals(date, format.getDate()); + assertEquals(date.getTime(), format.getTime()); + } + + @Test + void valueOf_dateWithZone_roundTripsToSameInstant() { + Date date = new Date(1_700_000_000_000L); + InternetDateFormat format = + InternetDateFormat.valueOf(date, TimeZone.getTimeZone("GMT+01:00")); + assertEquals(date, format.getDate()); + } + + @Test + void valueOf_time_roundTripsToSameInstant() { + long time = 1_700_000_000_000L; + InternetDateFormat format = InternetDateFormat.valueOf(time); + assertEquals(time, format.getTime()); + } + + @Test + void valueOf_timeWithZone_roundTripsToSameInstant() { + long time = 1_700_000_000_000L; + InternetDateFormat format = + InternetDateFormat.valueOf(time, TimeZone.getTimeZone("GMT+01:00")); + assertEquals(time, format.getTime()); + } + + @Test + void valueOf_string_parsesToMatchingCalendar() { + InternetDateFormat format = InternetDateFormat.valueOf("2024-03-05T13:07:09Z"); + assertEquals("2024-03-05T13:07:09Z", format.toString()); + } + + @Test + void constructor_fromCalendar_clonesInput() { + Calendar cal = new GregorianCalendar(InternetDateFormat.UTC); + cal.clear(); + cal.set(2024, Calendar.MARCH, 5, 13, 7, 9); + InternetDateFormat format = new InternetDateFormat(cal); + + cal.set(Calendar.YEAR, 1999); + + assertEquals(2024, format.getCalendar().get(Calendar.YEAR)); + } + + @Test + void constructor_defaultZone_usesUtc() { + InternetDateFormat format = new InternetDateFormat(); + assertEquals(0, format.getCalendar().getTimeZone().getRawOffset()); + } + + @Test + void constructor_withZoneOnly_setsCurrentTimeInThatZone() { + InternetDateFormat format = new InternetDateFormat(TimeZone.getTimeZone("GMT+01:00")); + assertEquals(60 * 60000, format.getCalendar().getTimeZone().getRawOffset()); + } + + @Test + void format_appendsRfc3339RepresentationToBuffer() { + Date date = InternetDateFormat.parseDate("2024-03-05T13:07:09Z"); + StringBuffer buffer = new StringBuffer("prefix-"); + InternetDateFormat format = new InternetDateFormat(); + format.format(date, buffer, null); + assertEquals("prefix-2024-03-05T13:07:09Z", buffer.toString()); + } + + @Test + void parse_instanceMethod_delegatesToStaticParseDate() throws Exception { + InternetDateFormat format = new InternetDateFormat(); + Date date = format.parse("2024-03-05T13:07:09Z"); + assertEquals(InternetDateFormat.parseDate("2024-03-05T13:07:09Z"), date); + } + + @Test + void parse_instanceMethodWithPosition_delegatesToStaticParseDate() { + InternetDateFormat format = new InternetDateFormat(); + Date date = format.parse("2024-03-05T13:07:09Z", new ParsePosition(0)); + assertEquals(InternetDateFormat.parseDate("2024-03-05T13:07:09Z"), date); + } + + @Test + void utc_hasZeroOffsetAndNoDst() { + assertTrue(InternetDateFormat.UTC.getRawOffset() == 0); + assertTrue(InternetDateFormat.UTC.getDSTSavings() == 0); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/ReferenceUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/ReferenceUtilsTestCase.java new file mode 100644 index 0000000000..8632aca638 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/ReferenceUtilsTestCase.java @@ -0,0 +1,87 @@ +/** + * 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 org.junit.jupiter.api.Test; +import org.restlet.Request; +import org.restlet.data.Header; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.util.Series; + +class ReferenceUtilsTestCase { + + @Test + void update_withoutChallengeResponse_returnsAbsoluteReferenceUnchanged() { + Request request = new Request(Method.GET, "http://example.com/path"); + Reference result = ReferenceUtils.update(request.getResourceRef(), request); + assertEquals("http://example.com/path", result.toString()); + } + + @Test + void format_notProxied_returnsPathAndQuery() { + Request request = new Request(Method.GET, "http://example.com/path?a=b"); + String result = ReferenceUtils.format(request.getResourceRef(), false, request); + assertEquals("/path?a=b", result); + } + + @Test + void format_notProxiedWithoutQuery_returnsPathOnly() { + Request request = new Request(Method.GET, "http://example.com/path"); + String result = ReferenceUtils.format(request.getResourceRef(), false, request); + assertEquals("/path", result); + } + + @Test + void format_notProxiedWithEmptyPath_returnsRootSlash() { + Request request = new Request(Method.GET, "http://example.com"); + String result = ReferenceUtils.format(request.getResourceRef(), false, request); + assertEquals("/", result); + } + + @Test + void format_proxied_returnsFullIdentifier() { + Request request = new Request(Method.GET, "http://example.com/path?a=b"); + String result = ReferenceUtils.format(request.getResourceRef(), true, request); + assertEquals("http://example.com/path?a=b", result); + } + + @Test + void getOriginalRef_withNullHeaders_returnsTargetRef() { + Reference resourceRef = new Reference("http://example.com/path"); + Reference result = ReferenceUtils.getOriginalRef(resourceRef, null); + assertEquals("http://example.com/path", result.toString()); + } + + @Test + void getOriginalRef_withForwardedHeaders_updatesPortAndScheme() { + Reference resourceRef = new Reference("http://example.com/path"); + Series

headers = new Series<>(Header.class); + headers.add(HeaderConstants.HEADER_X_FORWARDED_PORT, "8443"); + headers.add(HeaderConstants.HEADER_X_FORWARDED_PROTO, "https"); + + Reference result = ReferenceUtils.getOriginalRef(resourceRef, headers); + + assertEquals(8443, result.getHostPort()); + assertEquals("https", result.getScheme()); + } + + @Test + void getOriginalRef_withoutForwardedHeaders_leavesReferenceUnchanged() { + Reference resourceRef = new Reference("http://example.com/path"); + Series
headers = new Series<>(Header.class); + + Reference result = ReferenceUtils.getOriginalRef(resourceRef, headers); + + assertEquals("http", result.getScheme()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/SetUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/SetUtilsTestCase.java new file mode 100644 index 0000000000..dd491262ea --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/SetUtilsTestCase.java @@ -0,0 +1,33 @@ +/** + * 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.assertTrue; + +import java.util.Set; +import org.junit.jupiter.api.Test; + +class SetUtilsTestCase { + + @Test + void newHashSet_withElements_containsAllElements() { + Set set = SetUtils.newHashSet("a", "b", "c"); + assertEquals(3, set.size()); + assertTrue(set.contains("a")); + assertTrue(set.contains("b")); + assertTrue(set.contains("c")); + } + + @Test + void newHashSet_withNoElements_isEmpty() { + Set set = SetUtils.newHashSet(); + assertTrue(set.isEmpty()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/StringUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/StringUtilsTestCase.java new file mode 100644 index 0000000000..fa056f28a2 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/StringUtilsTestCase.java @@ -0,0 +1,162 @@ +/** + * 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.assertArrayEquals; +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.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +class StringUtilsTestCase { + + @Test + void firstLower_lowercasesFirstCharacterOnly() { + assertEquals("hELLO", StringUtils.firstLower("HELLO")); + } + + @Test + void firstLower_nullOrEmpty_returnsUnchanged() { + assertNull(StringUtils.firstLower(null)); + assertEquals("", StringUtils.firstLower("")); + } + + @Test + void firstUpper_uppercasesFirstCharacterOnly() { + assertEquals("Hello", StringUtils.firstUpper("hello")); + } + + @Test + void firstUpper_nullOrEmpty_returnsUnchanged() { + assertNull(StringUtils.firstUpper(null)); + assertEquals("", StringUtils.firstUpper("")); + } + + @Test + void getAsciiBytes_encodesString() { + assertArrayEquals( + "abc".getBytes(StandardCharsets.US_ASCII), StringUtils.getAsciiBytes("abc")); + } + + @Test + void getAsciiBytes_null_returnsEmptyArray() { + assertArrayEquals(new byte[0], StringUtils.getAsciiBytes(null)); + } + + @Test + void getLatin1Bytes_encodesString() { + assertArrayEquals( + "abc".getBytes(StandardCharsets.ISO_8859_1), StringUtils.getLatin1Bytes("abc")); + } + + @Test + void getLatin1Bytes_null_returnsEmptyArray() { + assertArrayEquals(new byte[0], StringUtils.getLatin1Bytes(null)); + } + + @Test + void htmlEscape_nullOrEmpty_returnsUnchanged() { + assertNull(StringUtils.htmlEscape(null)); + assertEquals("", StringUtils.htmlEscape("")); + } + + @Test + void htmlEscape_asciiWithoutEntities_isUnchanged() { + assertEquals("hello world", StringUtils.htmlEscape("hello world")); + } + + @Test + void htmlEscape_knownEntities_areEscapedByName() { + assertEquals(""&<>", StringUtils.htmlEscape("\"&<>")); + } + + @Test + void htmlEscape_nonAsciiKnownEntity_isEscapedByName() { + // U+00FF (yuml) and U+20AC (euro) are both known HTML 4.0 entities. + assertEquals("ÿ", StringUtils.htmlEscape("ÿ")); + assertEquals("€", StringUtils.htmlEscape("€")); + } + + @Test + void htmlEscape_nonAsciiWithoutEntity_isEscapedNumerically() { + // U+00FE (thorn) is a known entity, but codepoints beyond the 10000-entry table + // (e.g. U+2603 SNOWMAN) fall back to numeric escaping. + assertEquals("☃", StringUtils.htmlEscape("☃")); + } + + @Test + void htmlUnescape_nullOrEmpty_returnsUnchanged() { + assertNull(StringUtils.htmlUnescape(null)); + assertEquals("", StringUtils.htmlUnescape("")); + } + + @Test + void htmlUnescape_namedEntity_isDecoded() { + assertEquals("\"&<>", StringUtils.htmlUnescape(""&<>")); + } + + @Test + void htmlUnescape_decimalNumericEntity_isDecoded() { + assertEquals("€", StringUtils.htmlUnescape("€")); + } + + @Test + void htmlUnescape_hexNumericEntityLowerX_isDecoded() { + assertEquals("€", StringUtils.htmlUnescape("€")); + } + + @Test + void htmlUnescape_hexNumericEntityUpperX_isDecoded() { + assertEquals("€", StringUtils.htmlUnescape("€")); + } + + @Test + void htmlUnescape_invalidNumericEntity_isLeftAsIs() { + assertEquals("&#notanumber;", StringUtils.htmlUnescape("&#notanumber;")); + } + + @Test + void htmlUnescape_unknownEntityName_isLeftAsIs() { + assertEquals("&unknownentity;", StringUtils.htmlUnescape("&unknownentity;")); + } + + @Test + void htmlUnescape_emptyEntity_isReplacedWithAmpersandSemicolon() { + assertEquals("&;", StringUtils.htmlUnescape("&;")); + } + + @Test + void htmlUnescape_ampersandWithoutSemicolon_isLeftAsIs() { + assertEquals("a & b", StringUtils.htmlUnescape("a & b")); + } + + @Test + void htmlUnescape_consecutiveAmpersands_areHandled() { + assertEquals("&&", StringUtils.htmlUnescape("&&")); + } + + @Test + void htmlUnescape_trailingAmpersand_isLeftAsIs() { + assertEquals("value&", StringUtils.htmlUnescape("value&")); + } + + @Test + void isNullOrEmpty_nullOrEmptyString_returnsTrue() { + assertTrue(StringUtils.isNullOrEmpty(null)); + assertTrue(StringUtils.isNullOrEmpty("")); + } + + @Test + void isNullOrEmpty_nonEmptyString_returnsFalse() { + assertFalse(StringUtils.isNullOrEmpty("x")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/SystemUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/SystemUtilsTestCase.java new file mode 100644 index 0000000000..a2d9cdc80e --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/SystemUtilsTestCase.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.junit.jupiter.api.Test; + +class SystemUtilsTestCase { + + @Test + void getJavaMajorVersion_parsesJavaVersionSystemProperty() { + String javaVersion = System.getProperty("java.version"); + int dotIndex = javaVersion.indexOf('.'); + int expected = dotIndex < 0 ? 0 : Integer.parseInt(javaVersion.substring(0, dotIndex)); + assertEquals(expected, SystemUtils.getJavaMajorVersion()); + } + + @Test + void getJavaMinorVersion_returnsZero_whenVersionHasNoMinorComponent() { + // Modern JDKs (e.g. "21") have no "major.minor" form, exercising the catch branch. + assertEquals(0, SystemUtils.getJavaMinorVersion()); + } + + @Test + void getJavaUpdateVersion_returnsZero_whenVersionHasNoUpdateComponent() { + // Modern JDKs have no "_update" suffix, exercising the catch branch. + assertEquals(0, SystemUtils.getJavaUpdateVersion()); + } + + @Test + void hashCode_nullVarargs_returnsBaseValue() { + assertEquals(17, SystemUtils.hashCode((Object[]) null)); + } + + @Test + void hashCode_noArguments_returnsBaseValue() { + assertEquals(17, SystemUtils.hashCode(new Object[0])); + } + + @Test + void hashCode_withNullElement_treatsAsZero() { + assertEquals(17 * 31, SystemUtils.hashCode((Object) null)); + } + + @Test + void hashCode_isConsistentForEqualInputs() { + assertEquals(SystemUtils.hashCode("a", "b"), SystemUtils.hashCode("a", "b")); + } + + @Test + void hashCode_differsForDifferentInputs() { + assertFalse(SystemUtils.hashCode("a", "b") == SystemUtils.hashCode("a", "c")); + } + + @Test + void isWindows_matchesOsNameProperty() { + boolean expected = System.getProperty("os.name").toLowerCase().contains("win"); + assertEquals(expected, SystemUtils.isWindows()); + } + + @Test + void shouldApplyBug6331920Patch_returnsFalseOnModernNonSunJvm() { + // This JVM is neither an old Sun JVM nor an old Java version, so no patch is needed. + assertFalse(SystemUtils.shouldApplyBug6331920Patch()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/TemplateDispatcherTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/TemplateDispatcherTestCase.java new file mode 100644 index 0000000000..0d65c264e0 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/TemplateDispatcherTestCase.java @@ -0,0 +1,104 @@ +/** + * 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 static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.representation.StringRepresentation; + +class TemplateDispatcherTestCase { + + private final TemplateDispatcher dispatcher = new TemplateDispatcher(); + + @Test + void afterHandle_entityWithoutLocationRef_setsItFromResourceRef() { + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + response.setEntity(new StringRepresentation("body")); + + dispatcher.afterHandle(request, response); + + assertEquals( + request.getResourceRef().getTargetRef().toString(), + response.getEntity().getLocationRef().toString()); + } + + @Test + void afterHandle_entityWithExistingLocationRef_leavesItUnchanged() { + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + StringRepresentation entity = new StringRepresentation("body"); + entity.setLocationRef("http://localhost/other"); + response.setEntity(entity); + + dispatcher.afterHandle(request, response); + + assertEquals("http://localhost/other", response.getEntity().getLocationRef().toString()); + } + + @Test + void afterHandle_nullEntity_doesNothing() { + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + dispatcher.afterHandle(request, response); + + assertNull(response.getEntity()); + } + + @Test + void beforeHandle_nullProtocol_throwsUnsupportedOperationException() { + Request request = new Request(Method.GET, "relative/path"); + Response response = new Response(request); + + assertThrows( + UnsupportedOperationException.class, + () -> dispatcher.beforeHandle(request, response)); + } + + @Test + void beforeHandle_uriWithoutTemplate_leavesResourceRefUnchanged() { + Request request = new Request(Method.GET, "http://localhost/test"); + Reference original = request.getResourceRef(); + Response response = new Response(request); + + int result = dispatcher.beforeHandle(request, response); + + assertEquals(original.toString(), request.getResourceRef().toString()); + assertEquals(org.restlet.routing.Filter.CONTINUE, result); + } + + @Test + void beforeHandle_uriWithTemplate_resolvesPlaceholder() { + Request request = new Request(Method.GET, "http://localhost/{name}"); + request.getAttributes().put("name", "world"); + Response response = new Response(request); + + dispatcher.beforeHandle(request, response); + + assertEquals("http://localhost/world", request.getResourceRef().toString()); + } + + @Test + void beforeHandle_setsOriginalRef() { + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + dispatcher.beforeHandle(request, response); + + assertEquals(request.getResourceRef().getTargetRef(), request.getOriginalRef()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/WrapperScheduledExecutorServiceTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/WrapperScheduledExecutorServiceTestCase.java new file mode 100644 index 0000000000..0e81b8ea4c --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/WrapperScheduledExecutorServiceTestCase.java @@ -0,0 +1,136 @@ +/** + * 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class WrapperScheduledExecutorServiceTestCase { + + private ScheduledExecutorService delegate; + + private WrapperScheduledExecutorService wrapper; + + @BeforeEach + void setUpEach() { + delegate = Executors.newSingleThreadScheduledExecutor(); + wrapper = new WrapperScheduledExecutorService(delegate); + } + + @AfterEach + void tearDownEach() { + delegate.shutdownNow(); + } + + @Test + void getWrapped_returnsConstructorArgument() { + assertEquals(delegate, wrapper.getWrapped()); + } + + @Test + void submitCallable_executesAndReturnsResult() throws Exception { + Future future = wrapper.submit(() -> "done"); + assertEquals("done", future.get()); + } + + @Test + void submitRunnable_executesTask() throws Exception { + java.util.concurrent.atomic.AtomicBoolean ran = + new java.util.concurrent.atomic.AtomicBoolean(); + Future future = wrapper.submit(() -> ran.set(true)); + future.get(); + assertTrue(ran.get()); + } + + @Test + void submitRunnableWithResult_returnsGivenResult() throws Exception { + Future future = wrapper.submit(() -> {}, "result"); + assertEquals("result", future.get()); + } + + @Test + void execute_runsCommand() throws Exception { + java.util.concurrent.CountDownLatch latch = new java.util.concurrent.CountDownLatch(1); + wrapper.execute(latch::countDown); + assertTrue(latch.await(2, TimeUnit.SECONDS)); + } + + @Test + void schedule_callable_executesAfterDelay() throws Exception { + ScheduledFuture future = + wrapper.schedule(() -> "scheduled", 1, TimeUnit.MILLISECONDS); + assertEquals("scheduled", future.get()); + } + + @Test + void schedule_runnable_executesAfterDelay() throws Exception { + ScheduledFuture future = wrapper.schedule(() -> {}, 1, TimeUnit.MILLISECONDS); + future.get(); + assertNotNull(future); + } + + @Test + void invokeAll_executesAllTasks() throws Exception { + List> tasks = List.of(() -> "a", () -> "b"); + List> results = wrapper.invokeAll(tasks); + assertEquals(2, results.size()); + } + + @Test + void invokeAllWithTimeout_executesAllTasks() throws Exception { + List> tasks = List.of(() -> "a"); + List> results = wrapper.invokeAll(tasks, 2, TimeUnit.SECONDS); + assertEquals(1, results.size()); + } + + @Test + void invokeAny_returnsResultOfOneTask() throws Exception { + List> tasks = List.of(() -> "a"); + assertEquals("a", wrapper.invokeAny(tasks)); + } + + @Test + void invokeAnyWithTimeout_returnsResultOfOneTask() throws Exception { + List> tasks = List.of(() -> "a"); + assertEquals("a", wrapper.invokeAny(tasks, 2, TimeUnit.SECONDS)); + } + + @Test + void isShutdownAndIsTerminated_reflectDelegateState() { + assertFalse(wrapper.isShutdown()); + assertFalse(wrapper.isTerminated()); + } + + @Test + void shutdownAndAwaitTermination_stopsAcceptingTasks() throws Exception { + wrapper.shutdown(); + boolean terminated = wrapper.awaitTermination(2, TimeUnit.SECONDS); + assertTrue(terminated); + assertTrue(wrapper.isShutdown()); + } + + @Test + void shutdownNow_returnsPendingTasks() { + List pending = wrapper.shutdownNow(); + assertNotNull(pending); + } +} diff --git a/org.restlet/src/test/java/org/restlet/representation/CharacterRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/CharacterRepresentationTestCase.java new file mode 100644 index 0000000000..a0d0aab924 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/representation/CharacterRepresentationTestCase.java @@ -0,0 +1,83 @@ +/** + * 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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; + +/** + * Unit tests for the {@link CharacterRepresentation} abstract class, exercised through a minimal + * local subclass. + * + * @author Jerome Louvel + */ +class CharacterRepresentationTestCase { + + /** Minimal concrete subclass backed by an in-memory string. */ + private static class SimpleCharacterRepresentation extends CharacterRepresentation { + + private final String content; + + SimpleCharacterRepresentation(MediaType mediaType, String content) { + super(mediaType); + this.content = content; + } + + @Override + public Reader getReader() { + return new StringReader(content); + } + + @Override + public void write(Writer writer) throws IOException { + writer.write(content); + } + } + + @Test + void constructor_setsMediaTypeAndDefaultsToUtf8CharacterSet() { + SimpleCharacterRepresentation representation = + new SimpleCharacterRepresentation(MediaType.TEXT_PLAIN, "hello"); + + assertEquals(MediaType.TEXT_PLAIN, representation.getMediaType()); + assertEquals(CharacterSet.UTF_8, representation.getCharacterSet()); + } + + @Test + void getStream_producesBytesMatchingReaderContent() throws IOException { + SimpleCharacterRepresentation representation = + new SimpleCharacterRepresentation(MediaType.TEXT_PLAIN, "some text"); + + try (InputStream stream = representation.getStream()) { + assertEquals("some text", IoUtils.toString(stream, representation.getCharacterSet())); + } + } + + @Test + void writeToOutputStream_encodesUsingConfiguredCharacterSet() throws IOException { + SimpleCharacterRepresentation representation = + new SimpleCharacterRepresentation(MediaType.TEXT_PLAIN, "café"); + representation.setCharacterSet(CharacterSet.UTF_8); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + representation.write(out); + + assertEquals("café", new String(out.toByteArray(), CharacterSet.UTF_8.getName())); + } +} diff --git a/org.restlet/src/test/java/org/restlet/representation/EmptyRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/EmptyRepresentationTestCase.java new file mode 100644 index 0000000000..dd03c0ccd1 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/representation/EmptyRepresentationTestCase.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.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.ByteArrayOutputStream; +import java.io.StringWriter; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the {@link EmptyRepresentation} class. + * + * @author Jerome Louvel + */ +class EmptyRepresentationTestCase { + + @Test + void constructor_setsUnavailableTransientAndZeroSize() { + EmptyRepresentation representation = new EmptyRepresentation(); + + assertFalse(representation.isAvailable()); + assertTrue(representation.isTransient()); + assertEquals(0, representation.getSize()); + assertTrue(representation.isEmpty()); + } + + @Test + void getReaderGetStreamAndGetText_returnNull() throws Exception { + EmptyRepresentation representation = new EmptyRepresentation(); + + assertNull(representation.getReader()); + assertNull(representation.getStream()); + assertNull(representation.getText()); + } + + @Test + void writeToWriter_doesNothingAndDoesNotThrow() throws Exception { + EmptyRepresentation representation = new EmptyRepresentation(); + StringWriter writer = new StringWriter(); + + representation.write(writer); + + assertEquals("", writer.toString()); + } + + @Test + void writeToOutputStream_doesNothingAndDoesNotThrow() throws Exception { + EmptyRepresentation representation = new EmptyRepresentation(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + representation.write(out); + + assertEquals(0, out.size()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/representation/InputRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/InputRepresentationTestCase.java new file mode 100644 index 0000000000..ac41ea36ba --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/representation/InputRepresentationTestCase.java @@ -0,0 +1,114 @@ +/** + * 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import org.restlet.data.MediaType; + +/** + * Unit tests for the {@link InputRepresentation} class. + * + * @author Jerome Louvel + */ +class InputRepresentationTestCase { + + private static InputStream stream(String content) { + return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)); + } + + @Test + void constructor_withStreamOnly_isTransientAndAvailable() { + InputRepresentation representation = new InputRepresentation(stream("content")); + + assertTrue(representation.isTransient()); + assertTrue(representation.isAvailable()); + assertEquals(Representation.UNKNOWN_SIZE, representation.getSize()); + } + + @Test + void constructor_withMediaType_setsMediaType() { + InputRepresentation representation = + new InputRepresentation(stream("content"), MediaType.TEXT_PLAIN); + + assertEquals(MediaType.TEXT_PLAIN, representation.getMediaType()); + } + + @Test + void constructor_withExpectedSize_setsSize() { + InputRepresentation representation = + new InputRepresentation(stream("content"), MediaType.TEXT_PLAIN, 7L); + + assertEquals(7L, representation.getSize()); + } + + @Test + void getStream_returnsStreamOnceThenNull() throws IOException { + InputRepresentation representation = new InputRepresentation(stream("content")); + + InputStream first = representation.getStream(); + assertTrue(first != null); + + InputStream second = representation.getStream(); + assertNull(second); + } + + @Test + void getStream_marksRepresentationUnavailableAfterConsumption() throws IOException { + InputRepresentation representation = new InputRepresentation(stream("content")); + + representation.getStream(); + + assertFalse(representation.isAvailable()); + } + + @Test + void getText_readsFullStreamContent() throws IOException { + InputRepresentation representation = new InputRepresentation(stream("hello world")); + + assertEquals("hello world", representation.getText()); + } + + @Test + void write_copiesStreamContentToOutputStream() throws IOException { + InputRepresentation representation = new InputRepresentation(stream("copied content")); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + representation.write(out); + + assertEquals("copied content", out.toString(StandardCharsets.UTF_8.name())); + } + + @Test + void release_closesStreamAndMarksUnavailable() { + InputRepresentation representation = new InputRepresentation(stream("content")); + + representation.release(); + + assertFalse(representation.isAvailable()); + } + + @Test + void setStream_withNull_marksRepresentationUnavailable() { + InputRepresentation representation = new InputRepresentation(stream("content")); + + representation.setStream(null); + + assertFalse(representation.isAvailable()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/representation/ObjectRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/ObjectRepresentationTestCase.java new file mode 100644 index 0000000000..fc40542abc --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/representation/ObjectRepresentationTestCase.java @@ -0,0 +1,128 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.Serializable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.restlet.data.MediaType; + +/** + * Unit tests for the {@link ObjectRepresentation} class. + * + * @author Jerome Louvel + */ +class ObjectRepresentationTestCase { + + private boolean initialBinarySupported; + + private boolean initialXmlSupported; + + @BeforeEach + void setUpEach() { + initialBinarySupported = ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED; + initialXmlSupported = ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED; + } + + @AfterEach + void tearDownEach() { + ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED = initialBinarySupported; + ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED = initialXmlSupported; + } + + @Test + void constructor_withObjectOnly_usesJavaObjectMediaType() throws IOException { + ObjectRepresentation representation = new ObjectRepresentation<>("payload"); + + assertEquals(MediaType.APPLICATION_JAVA_OBJECT, representation.getMediaType()); + assertEquals("payload", representation.getObject()); + } + + @Test + void constructor_withNullMediaType_defaultsToJavaObject() { + ObjectRepresentation representation = new ObjectRepresentation<>("payload", null); + + assertEquals(MediaType.APPLICATION_JAVA_OBJECT, representation.getMediaType()); + } + + @Test + void setObjectThenGetObject_roundTrips() throws IOException { + ObjectRepresentation representation = new ObjectRepresentation<>("initial"); + + representation.setObject("updated"); + + assertEquals("updated", representation.getObject()); + } + + @Test + void release_clearsObject() throws IOException { + ObjectRepresentation representation = new ObjectRepresentation<>("payload"); + + representation.release(); + + assertNull(representation.getObject()); + } + + @Test + void writeThenReadBack_roundTripsBinarySerializedObject() throws Exception { + ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED = true; + ObjectRepresentation original = new ObjectRepresentation<>("round-trip-value"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + original.write(out); + + Representation serialized = + new ByteArrayRepresentation(out.toByteArray(), MediaType.APPLICATION_JAVA_OBJECT); + ObjectRepresentation deserialized = new ObjectRepresentation<>(serialized); + + assertEquals("round-trip-value", deserialized.getObject()); + } + + @Test + void constructor_fromBinaryRepresentation_throwsWhenBinaryNotSupported() throws Exception { + ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED = true; + ObjectRepresentation original = new ObjectRepresentation<>("value"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + original.write(out); + Representation serialized = + new ByteArrayRepresentation(out.toByteArray(), MediaType.APPLICATION_JAVA_OBJECT); + + ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED = false; + + assertThrows(IllegalArgumentException.class, () -> new ObjectRepresentation<>(serialized)); + } + + @Test + void constructor_fromRepresentationWithUnsupportedMediaType_throwsIllegalArgumentException() { + Representation notSerialized = new StringRepresentation("plain text"); + + assertThrows( + IllegalArgumentException.class, () -> new ObjectRepresentation<>(notSerialized)); + } + + @Test + void write_withXmlMediaType_producesXmlEncodedOutput() throws IOException { + ObjectRepresentation representation = + new ObjectRepresentation<>("xml-value", MediaType.APPLICATION_JAVA_OBJECT_XML); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + representation.write(out); + + String xml = out.toString("UTF-8"); + assertTrue(xml.contains("xml-value")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/representation/OutputRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/OutputRepresentationTestCase.java new file mode 100644 index 0000000000..1b1115d96e --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/representation/OutputRepresentationTestCase.java @@ -0,0 +1,77 @@ +/** + * 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.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; + +/** + * Unit tests for the {@link OutputRepresentation} abstract class, exercised through a minimal local + * subclass. + * + * @author Jerome Louvel + */ +class OutputRepresentationTestCase { + + /** Minimal concrete subclass writing a fixed piece of content. */ + private static class SimpleOutputRepresentation extends OutputRepresentation { + + private final String content; + + SimpleOutputRepresentation(MediaType mediaType, String content) { + super(mediaType); + this.content = content; + } + + SimpleOutputRepresentation(MediaType mediaType, long expectedSize, String content) { + super(mediaType, expectedSize); + this.content = content; + } + + @Override + public void write(OutputStream outputStream) throws IOException { + outputStream.write(content.getBytes(StandardCharsets.UTF_8)); + } + } + + @Test + void constructor_withMediaTypeOnly_hasUnknownSize() { + SimpleOutputRepresentation representation = + new SimpleOutputRepresentation(MediaType.TEXT_PLAIN, "data"); + + assertEquals(MediaType.TEXT_PLAIN, representation.getMediaType()); + assertEquals(Representation.UNKNOWN_SIZE, representation.getSize()); + } + + @Test + void constructor_withExpectedSize_setsSize() { + SimpleOutputRepresentation representation = + new SimpleOutputRepresentation(MediaType.TEXT_PLAIN, 4L, "data"); + + assertEquals(4L, representation.getSize()); + } + + @Test + void getStream_producesContentWrittenByWrite() throws IOException { + SimpleOutputRepresentation representation = + new SimpleOutputRepresentation(MediaType.TEXT_PLAIN, "streamed content"); + + try (InputStream stream = representation.getStream()) { + assertEquals( + "streamed content", IoUtils.toString(stream, representation.getCharacterSet())); + } + } +} diff --git a/org.restlet/src/test/java/org/restlet/representation/RepresentationInfoTestCase.java b/org.restlet/src/test/java/org/restlet/representation/RepresentationInfoTestCase.java new file mode 100644 index 0000000000..ca501315b8 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/representation/RepresentationInfoTestCase.java @@ -0,0 +1,119 @@ +/** + * 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.assertNull; + +import java.util.Date; +import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; +import org.restlet.data.MediaType; +import org.restlet.data.Tag; + +/** + * Unit tests for the {@link RepresentationInfo} class. + * + * @author Jerome Louvel + */ +class RepresentationInfoTestCase { + + @Test + void defaultConstructor_hasNoMediaTypeOrTag() { + RepresentationInfo info = new RepresentationInfo(); + + assertNull(info.getMediaType()); + assertNull(info.getTag()); + assertNull(info.getModificationDate()); + } + + @Test + void constructor_withMediaType_setsMediaType() { + RepresentationInfo info = new RepresentationInfo(MediaType.APPLICATION_JSON); + + assertEquals(MediaType.APPLICATION_JSON, info.getMediaType()); + } + + @Test + void constructor_withMediaTypeAndModificationDate_setsBoth() { + Date date = new Date(0); + RepresentationInfo info = new RepresentationInfo(MediaType.TEXT_PLAIN, date); + + assertEquals(MediaType.TEXT_PLAIN, info.getMediaType()); + assertEquals(date, info.getModificationDate()); + } + + @Test + void constructor_withMediaTypeModificationDateAndTag_setsAllFields() { + Date date = new Date(0); + Tag tag = new Tag("etag-value"); + RepresentationInfo info = new RepresentationInfo(MediaType.TEXT_PLAIN, date, tag); + + assertEquals(MediaType.TEXT_PLAIN, info.getMediaType()); + assertEquals(date, info.getModificationDate()); + assertEquals(tag, info.getTag()); + } + + @Test + void constructor_withMediaTypeAndTag_setsBoth() { + Tag tag = new Tag("etag-value"); + RepresentationInfo info = new RepresentationInfo(MediaType.TEXT_PLAIN, tag); + + assertEquals(MediaType.TEXT_PLAIN, info.getMediaType()); + assertEquals(tag, info.getTag()); + assertNull(info.getModificationDate()); + } + + @Test + void constructor_fromVariant_copiesVariantMetadata() { + Variant variant = new Variant(MediaType.APPLICATION_XML); + variant.setCharacterSet(CharacterSet.UTF_8); + Date date = new Date(0); + Tag tag = new Tag("copied-tag"); + + RepresentationInfo info = new RepresentationInfo(variant, date, tag); + + assertEquals(MediaType.APPLICATION_XML, info.getMediaType()); + assertEquals(CharacterSet.UTF_8, info.getCharacterSet()); + assertEquals(date, info.getModificationDate()); + assertEquals(tag, info.getTag()); + } + + @Test + void constructor_fromVariantAndTag_copiesVariantMetadataWithoutDate() { + Variant variant = new Variant(MediaType.APPLICATION_XML); + Tag tag = new Tag("copied-tag"); + + RepresentationInfo info = new RepresentationInfo(variant, tag); + + assertEquals(MediaType.APPLICATION_XML, info.getMediaType()); + assertEquals(tag, info.getTag()); + assertNull(info.getModificationDate()); + } + + @Test + void setModificationDateThenGetModificationDate_roundTrips() { + RepresentationInfo info = new RepresentationInfo(); + Date date = new Date(123456789L); + + info.setModificationDate(date); + + assertEquals(date, info.getModificationDate()); + } + + @Test + void setTagThenGetTag_roundTrips() { + RepresentationInfo info = new RepresentationInfo(); + Tag tag = new Tag("my-tag"); + + info.setTag(tag); + + assertEquals(tag, info.getTag()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/representation/RepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/RepresentationTestCase.java new file mode 100644 index 0000000000..393ebed89f --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/representation/RepresentationTestCase.java @@ -0,0 +1,235 @@ +/** + * 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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import org.junit.jupiter.api.Test; +import org.restlet.data.Digest; +import org.restlet.data.Disposition; +import org.restlet.data.MediaType; +import org.restlet.data.Range; +import org.restlet.data.Tag; + +/** + * Unit tests for the {@link Representation} abstract class, exercised through a minimal local + * subclass. + * + * @author Jerome Louvel + */ +class RepresentationTestCase { + + /** Minimal concrete subclass backed by an in-memory string. */ + private static class SimpleRepresentation extends Representation { + + private final String content; + + SimpleRepresentation(String content) { + super(MediaType.TEXT_PLAIN); + this.content = content; + setSize(content.getBytes(StandardCharsets.UTF_8).length); + } + + @Override + public Reader getReader() { + return new StringReader(content); + } + + @Override + public InputStream getStream() { + return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public void write(Writer writer) throws IOException { + writer.write(content); + } + + @Override + public void write(OutputStream outputStream) throws IOException { + outputStream.write(content.getBytes(StandardCharsets.UTF_8)); + } + } + + @Test + void constructor_withMediaTypeOnly_setsSensibleDefaults() { + SimpleRepresentation representation = new SimpleRepresentation(""); + representation.setSize(Representation.UNKNOWN_SIZE); + + assertTrue(representation.isAvailable()); + assertFalse(representation.isTransient()); + assertNull(representation.getDisposition()); + assertNull(representation.getDigest()); + assertNull(representation.getExpirationDate()); + assertNull(representation.getRange()); + } + + @Test + void isAvailable_isFalseWhenSizeIsZero() { + SimpleRepresentation representation = new SimpleRepresentation(""); + + assertEquals(0, representation.getSize()); + assertTrue(representation.isEmpty()); + assertFalse(representation.isAvailable()); + } + + @Test + void hasKnownSize_isTrueWhenSizeIsNonNegative() { + SimpleRepresentation representation = new SimpleRepresentation("content"); + + assertTrue(representation.hasKnownSize()); + + representation.setSize(Representation.UNKNOWN_SIZE); + assertFalse(representation.hasKnownSize()); + } + + @Test + void exhaust_consumesStreamAndReturnsByteCount() throws IOException { + SimpleRepresentation representation = new SimpleRepresentation("0123456789"); + + long consumed = representation.exhaust(); + + assertEquals(10L, consumed); + } + + @Test + void getText_usesWriteWhenAvailable() throws IOException { + SimpleRepresentation representation = new SimpleRepresentation("some text"); + + assertEquals("some text", representation.getText()); + } + + @Test + void getText_returnsEmptyStringWhenEmpty() throws IOException { + SimpleRepresentation representation = new SimpleRepresentation(""); + + assertEquals("", representation.getText()); + } + + @Test + void append_writesTextToAppendable() throws IOException { + SimpleRepresentation representation = new SimpleRepresentation("appended"); + StringBuilder builder = new StringBuilder(); + + representation.append(builder); + + assertEquals("appended", builder.toString()); + } + + @Test + void setDigestThenGetDigest_roundTrips() { + SimpleRepresentation representation = new SimpleRepresentation("data"); + Digest digest = new Digest(Digest.ALGORITHM_MD5, new byte[] {1, 2, 3}); + + representation.setDigest(digest); + + assertEquals(digest, representation.getDigest()); + } + + @Test + void setDispositionThenGetDisposition_roundTrips() { + SimpleRepresentation representation = new SimpleRepresentation("data"); + Disposition disposition = new Disposition(Disposition.TYPE_ATTACHMENT); + + representation.setDisposition(disposition); + + assertEquals(disposition, representation.getDisposition()); + } + + @Test + void setExpirationDateThenGetExpirationDate_roundTrips() { + SimpleRepresentation representation = new SimpleRepresentation("data"); + Date date = new Date(0); + + representation.setExpirationDate(date); + + assertEquals(date, representation.getExpirationDate()); + } + + @Test + void setRangeThenGetRange_roundTrips() { + SimpleRepresentation representation = new SimpleRepresentation("data"); + Range range = new Range(0, 10); + + representation.setRange(range); + + assertEquals(range, representation.getRange()); + } + + @Test + void setTransientThenIsTransient_roundTrips() { + SimpleRepresentation representation = new SimpleRepresentation("data"); + + representation.setTransient(true); + + assertTrue(representation.isTransient()); + } + + @Test + void release_marksRepresentationUnavailable() { + SimpleRepresentation representation = new SimpleRepresentation("data"); + + representation.release(); + + assertFalse(representation.isAvailable()); + } + + @Test + void constructorFromVariant_copiesMetadata() { + Variant variant = new Variant(MediaType.APPLICATION_JSON); + variant.setCharacterSet(org.restlet.data.CharacterSet.UTF_8); + + // Use a dedicated subclass constructed from the variant to verify copy semantics. + Representation fromVariant = new StreamRepresentationFromVariant(variant, new Tag("abc")); + + assertEquals(MediaType.APPLICATION_JSON, fromVariant.getMediaType()); + assertEquals(org.restlet.data.CharacterSet.UTF_8, fromVariant.getCharacterSet()); + assertEquals(new Tag("abc"), fromVariant.getTag()); + } + + /** Helper subclass used solely to exercise the Representation(Variant, Tag) constructor. */ + private static class StreamRepresentationFromVariant extends Representation { + + StreamRepresentationFromVariant(Variant variant, Tag tag) { + super(variant, tag); + } + + @Override + public Reader getReader() { + return new StringReader(""); + } + + @Override + public InputStream getStream() { + return new ByteArrayInputStream(new byte[0]); + } + + @Override + public void write(Writer writer) { + // no-op + } + + @Override + public void write(OutputStream outputStream) { + // no-op + } + } +} diff --git a/org.restlet/src/test/java/org/restlet/representation/StreamRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/StreamRepresentationTestCase.java new file mode 100644 index 0000000000..88f0bc40e1 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/representation/StreamRepresentationTestCase.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.representation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; +import org.restlet.data.MediaType; + +/** + * Unit tests for the {@link StreamRepresentation} abstract class, exercised through a minimal local + * subclass. + * + * @author Jerome Louvel + */ +class StreamRepresentationTestCase { + + /** Minimal concrete subclass backed by an in-memory byte array. */ + private static class SimpleStreamRepresentation extends StreamRepresentation { + + private final String content; + + SimpleStreamRepresentation(MediaType mediaType, String content) { + super(mediaType); + this.content = content; + } + + @Override + public InputStream getStream() { + return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public void write(OutputStream outputStream) throws IOException { + outputStream.write(content.getBytes(StandardCharsets.UTF_8)); + } + } + + @Test + void constructor_setsMediaType() { + SimpleStreamRepresentation representation = + new SimpleStreamRepresentation(MediaType.TEXT_PLAIN, "content"); + + assertEquals(MediaType.TEXT_PLAIN, representation.getMediaType()); + } + + @Test + void getReader_delegatesToStreamUsingConfiguredCharacterSet() throws IOException { + SimpleStreamRepresentation representation = + new SimpleStreamRepresentation(MediaType.TEXT_PLAIN, "héllo"); + representation.setCharacterSet(CharacterSet.UTF_8); + + try (Reader reader = representation.getReader()) { + char[] buffer = new char[5]; + int read = reader.read(buffer); + assertEquals("héllo", new String(buffer, 0, read)); + } + } + + @Test + void writeToWriter_delegatesToWriteOutputStream() throws IOException { + SimpleStreamRepresentation representation = + new SimpleStreamRepresentation(MediaType.TEXT_PLAIN, "writer content"); + representation.setCharacterSet(CharacterSet.UTF_8); + StringWriter writer = new StringWriter(); + + representation.write(writer); + + assertEquals("writer content", writer.toString()); + } + + @Test + void writeToOutputStream_writesRawContent() throws IOException { + SimpleStreamRepresentation representation = + new SimpleStreamRepresentation(MediaType.TEXT_PLAIN, "raw"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + representation.write(out); + + assertEquals("raw", out.toString(StandardCharsets.UTF_8.name())); + } +} diff --git a/org.restlet/src/test/java/org/restlet/representation/StringRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/StringRepresentationTestCase.java new file mode 100644 index 0000000000..5b1413d6f3 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/representation/StringRepresentationTestCase.java @@ -0,0 +1,171 @@ +/** + * 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.IOException; +import java.io.Reader; +import java.io.StringWriter; +import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; +import org.restlet.data.Language; +import org.restlet.data.MediaType; + +/** + * Unit tests for the {@link StringRepresentation} class. + * + * @author Jerome Louvel + */ +class StringRepresentationTestCase { + + @Test + void constructor_withCharSequence_usesTextPlainAndUtf8() { + StringRepresentation representation = new StringRepresentation("hello"); + + assertEquals("hello", representation.getText()); + assertEquals(MediaType.TEXT_PLAIN, representation.getMediaType()); + assertEquals(CharacterSet.UTF_8, representation.getCharacterSet()); + } + + @Test + void constructor_withCharArray_convertsToString() { + StringRepresentation representation = new StringRepresentation("hello".toCharArray()); + + assertEquals("hello", representation.getText()); + } + + @Test + void constructor_withLanguage_addsLanguageToList() { + StringRepresentation representation = new StringRepresentation("hello", Language.ENGLISH); + + assertTrue(representation.getLanguages().contains(Language.ENGLISH)); + } + + @Test + void constructor_withMediaType_setsMediaType() { + StringRepresentation representation = + new StringRepresentation("{}", MediaType.APPLICATION_JSON); + + assertEquals(MediaType.APPLICATION_JSON, representation.getMediaType()); + } + + @Test + void constructor_withFullSignature_setsAllMetadata() { + StringRepresentation representation = + new StringRepresentation( + "text", MediaType.TEXT_PLAIN, Language.FRENCH, CharacterSet.ISO_8859_1); + + assertEquals(MediaType.TEXT_PLAIN, representation.getMediaType()); + assertTrue(representation.getLanguages().contains(Language.FRENCH)); + assertEquals(CharacterSet.ISO_8859_1, representation.getCharacterSet()); + } + + @Test + void getSize_reflectsUtf8ByteLength() { + StringRepresentation representation = new StringRepresentation("café"); + + assertEquals( + "café".getBytes(CharacterSet.UTF_8.toCharset()).length, representation.getSize()); + } + + @Test + void setText_updatesSize() { + StringRepresentation representation = new StringRepresentation("abc"); + + representation.setText("abcdef"); + + assertEquals(6, representation.getSize()); + assertEquals("abcdef", representation.getText()); + } + + @Test + void setText_withNull_setsUnknownSize() { + StringRepresentation representation = new StringRepresentation("abc"); + + representation.setText((String) null); + + assertNull(representation.getText()); + assertEquals(Representation.UNKNOWN_SIZE, representation.getSize()); + } + + @Test + void getReader_returnsFreshReaderWithFullContent() throws IOException { + StringRepresentation representation = new StringRepresentation("reader content"); + + try (Reader reader = representation.getReader()) { + char[] buffer = new char[32]; + int read = reader.read(buffer); + assertEquals("reader content", new String(buffer, 0, read)); + } + } + + @Test + void getStream_encodesTextUsingCharacterSet() throws IOException { + StringRepresentation representation = new StringRepresentation("stream content"); + + String result = + new String( + representation.getStream().readAllBytes(), CharacterSet.UTF_8.toCharset()); + + assertEquals("stream content", result); + } + + @Test + void write_writesTextToWriter() throws IOException { + StringRepresentation representation = new StringRepresentation("written content"); + StringWriter writer = new StringWriter(); + + representation.write(writer); + + assertEquals("written content", writer.toString()); + } + + @Test + void toString_returnsText() { + StringRepresentation representation = new StringRepresentation("as string"); + + assertEquals("as string", representation.toString()); + } + + @Test + void release_clearsTextAndMarksUnavailable() { + StringRepresentation representation = new StringRepresentation("to release"); + + representation.release(); + + assertNull(representation.getText()); + assertFalse(representation.isAvailable()); + } + + @Test + void setCharacterSet_updatesSize() { + StringRepresentation representation = new StringRepresentation("café"); + + representation.setCharacterSet(CharacterSet.ISO_8859_1); + + assertEquals( + "café".getBytes(CharacterSet.ISO_8859_1.toCharset()).length, + representation.getSize()); + } + + @Test + void write_withNullText_writesNothing() throws IOException { + StringRepresentation representation = new StringRepresentation("abc"); + representation.setText((String) null); + StringWriter writer = new StringWriter(); + + representation.write(writer); + + assertEquals("", writer.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/representation/VariantTestCase.java b/org.restlet/src/test/java/org/restlet/representation/VariantTestCase.java new file mode 100644 index 0000000000..8b10638b10 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/representation/VariantTestCase.java @@ -0,0 +1,199 @@ +/** + * 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.assertNotEquals; +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.data.CharacterSet; +import org.restlet.data.ClientInfo; +import org.restlet.data.Encoding; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Reference; + +/** + * Unit tests for the {@link Variant} class. + * + * @author Jerome Louvel + */ +class VariantTestCase { + + @Test + void defaultConstructor_hasNoMediaType() { + Variant variant = new Variant(); + + assertEquals(null, variant.getMediaType()); + assertTrue(variant.getLanguages().isEmpty()); + assertTrue(variant.getEncodings().isEmpty()); + } + + @Test + void constructor_withMediaType_setsMediaType() { + Variant variant = new Variant(MediaType.TEXT_PLAIN); + + assertEquals(MediaType.TEXT_PLAIN, variant.getMediaType()); + } + + @Test + void constructor_withMediaTypeAndLanguage_addsLanguage() { + Variant variant = new Variant(MediaType.TEXT_PLAIN, Language.ENGLISH); + + assertTrue(variant.getLanguages().contains(Language.ENGLISH)); + } + + @Test + void settersAndGetters_roundTrip() { + Variant variant = new Variant(); + + variant.setCharacterSet(CharacterSet.UTF_8); + variant.setMediaType(MediaType.APPLICATION_JSON); + variant.setLocationRef(new Reference("http://localhost/resource")); + + assertEquals(CharacterSet.UTF_8, variant.getCharacterSet()); + assertEquals(MediaType.APPLICATION_JSON, variant.getMediaType()); + assertEquals(new Reference("http://localhost/resource"), variant.getLocationRef()); + } + + @Test + void setLocationRefFromString_parsesUri() { + Variant variant = new Variant(); + + variant.setLocationRef("http://localhost/other"); + + assertEquals(new Reference("http://localhost/other"), variant.getLocationRef()); + } + + @Test + void getEncodings_rejectsNullElement() { + Variant variant = new Variant(); + + assertThrows(IllegalArgumentException.class, () -> variant.getEncodings().add(null)); + } + + @Test + void getLanguages_rejectsNullElement() { + Variant variant = new Variant(); + + assertThrows(IllegalArgumentException.class, () -> variant.getLanguages().add(null)); + } + + @Test + void equals_isTrueForEquivalentVariants() { + Variant v1 = new Variant(MediaType.TEXT_PLAIN); + v1.setCharacterSet(CharacterSet.UTF_8); + Variant v2 = new Variant(MediaType.TEXT_PLAIN); + v2.setCharacterSet(CharacterSet.UTF_8); + + assertEquals(v1, v2); + assertEquals(v1.hashCode(), v2.hashCode()); + } + + @Test + void equals_isFalseForDifferentMediaType() { + Variant v1 = new Variant(MediaType.TEXT_PLAIN); + Variant v2 = new Variant(MediaType.APPLICATION_JSON); + + assertNotEquals(v1, v2); + } + + @Test + void equals_isFalseForNonVariantObject() { + Variant variant = new Variant(MediaType.TEXT_PLAIN); + + assertNotEquals(variant, "not a variant"); + } + + @Test + void equals_isTrueForSameInstance() { + Variant variant = new Variant(MediaType.TEXT_PLAIN); + + assertEquals(variant, variant); + } + + @Test + void includes_isTrueWhenMediaTypeIsBroader() { + Variant textAll = new Variant(MediaType.TEXT_ALL); + Variant textPlain = new Variant(MediaType.TEXT_PLAIN); + + assertTrue(textAll.includes(textPlain)); + assertFalse(textPlain.includes(textAll)); + } + + @Test + void includes_isFalseForNullOther() { + Variant variant = new Variant(MediaType.TEXT_PLAIN); + + assertFalse(variant.includes(null)); + } + + @Test + void isCompatible_isTrueWhenEitherIncludesTheOther() { + Variant textAll = new Variant(MediaType.TEXT_ALL); + Variant textPlain = new Variant(MediaType.TEXT_PLAIN); + + assertTrue(textAll.isCompatible(textPlain)); + assertTrue(textPlain.isCompatible(textAll)); + } + + @Test + void isCompatible_isFalseForNullOther() { + Variant variant = new Variant(MediaType.TEXT_PLAIN); + + assertFalse(variant.isCompatible(null)); + } + + @Test + void createClientInfo_reflectsVariantPreferences() { + Variant variant = new Variant(MediaType.APPLICATION_JSON, Language.ENGLISH); + variant.setCharacterSet(CharacterSet.UTF_8); + variant.getEncodings().add(Encoding.GZIP); + + ClientInfo clientInfo = variant.createClientInfo(); + + assertTrue( + clientInfo.getAcceptedMediaTypes().stream() + .anyMatch( + preference -> + MediaType.APPLICATION_JSON.equals( + preference.getMetadata()))); + assertTrue( + clientInfo.getAcceptedCharacterSets().stream() + .anyMatch( + preference -> CharacterSet.UTF_8.equals(preference.getMetadata()))); + assertTrue( + clientInfo.getAcceptedLanguages().stream() + .anyMatch(preference -> Language.ENGLISH.equals(preference.getMetadata()))); + assertTrue( + clientInfo.getAcceptedEncodings().stream() + .anyMatch(preference -> Encoding.GZIP.equals(preference.getMetadata()))); + } + + @Test + void toString_includesMediaTypeAndCharacterSet() { + Variant variant = new Variant(MediaType.TEXT_PLAIN); + variant.setCharacterSet(CharacterSet.UTF_8); + + String result = variant.toString(); + + assertTrue(result.contains("text/plain")); + assertTrue(result.contains("UTF-8")); + } + + @Test + void toString_isEmptyBracketsWhenNoMetadataSet() { + Variant variant = new Variant(); + + assertEquals("[]", variant.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/resource/ClientResourceTestCase.java b/org.restlet/src/test/java/org/restlet/resource/ClientResourceTestCase.java new file mode 100644 index 0000000000..8798a24215 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/resource/ClientResourceTestCase.java @@ -0,0 +1,472 @@ +/** + * 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.resource; + +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.data.Status; +import org.restlet.engine.Engine; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; + +/** + * Unit tests for the {@link ClientResource} class. + * + * @author Jerome Louvel + */ +class ClientResourceTestCase { + + @BeforeEach + void setUpEach() { + Engine.clearThreadLocalVariables(); + Engine.register(false); + Engine.getInstance().registerDefaultConverters(); + } + + @AfterEach + void tearDownEach() { + Engine.clearThreadLocalVariables(); + } + + @Test + void constructorWithUri_setsReferenceAndDefaultGetMethod() { + ClientResource cr = new ClientResource("http://localhost/test"); + + assertEquals(new Reference("http://localhost/test"), cr.getReference()); + assertEquals(Method.GET, cr.getMethod()); + } + + @Test + void constructorWithMethodAndUri_setsMethod() { + ClientResource cr = new ClientResource(Method.POST, "http://localhost/test"); + + assertEquals(Method.POST, cr.getMethod()); + } + + @Test + void defaultSettings_matchDocumentedDefaults() { + ClientResource cr = new ClientResource("http://localhost/test"); + + assertEquals(10, cr.getMaxRedirects()); + assertTrue(cr.isFollowingRedirects()); + assertTrue(cr.isRetryOnError()); + assertEquals(2000L, cr.getRetryDelay()); + assertEquals(2, cr.getRetryAttempts()); + assertFalse(cr.isRequestEntityBuffering()); + assertFalse(cr.isResponseEntityBuffering()); + } + + @Test + void copyConstructor_copiesConfiguration() { + ClientResource original = new ClientResource("http://localhost/test"); + original.setMaxRedirects(5); + original.setRetryAttempts(1); + + ClientResource copy = new ClientResource(original); + + assertEquals(5, copy.getMaxRedirects()); + assertEquals(1, copy.getRetryAttempts()); + } + + @Test + void setNextThenGetNext_roundTrips() { + ClientResource cr = new ClientResource("http://localhost/test"); + Restlet next = + new Restlet() { + @Override + public void handle(Request request, Response response) { + response.setStatus(Status.SUCCESS_OK); + } + }; + + cr.setNext(next); + + assertEquals(next, cr.getNext()); + assertTrue(cr.hasNext()); + } + + @Test + void get_returnsEntityFromNextRestlet() { + ClientResource cr = new ClientResource("http://localhost/test"); + cr.setNext( + new Restlet() { + @Override + public void handle(Request request, Response response) { + response.setStatus(Status.SUCCESS_OK); + response.setEntity(new StringRepresentation("hello")); + } + }); + + Representation result = cr.get(); + + assertEquals("hello", getText(result)); + } + + @Test + void put_sendsPutMethodToNextRestlet() { + ClientResource cr = new ClientResource("http://localhost/test"); + final AtomicInteger methodSeen = new AtomicInteger(); + cr.setNext( + new Restlet() { + @Override + public void handle(Request request, Response response) { + if (Method.PUT.equals(request.getMethod())) { + methodSeen.incrementAndGet(); + } + response.setStatus(Status.SUCCESS_OK); + response.setEntity(new StringRepresentation("put-ack")); + } + }); + + Representation result = cr.put(new StringRepresentation("payload")); + + assertEquals(1, methodSeen.get()); + assertEquals("put-ack", getText(result)); + } + + @Test + void post_sendsPostMethodToNextRestlet() { + ClientResource cr = new ClientResource("http://localhost/test"); + cr.setNext( + new Restlet() { + @Override + public void handle(Request request, Response response) { + assertEquals(Method.POST, request.getMethod()); + response.setStatus(Status.SUCCESS_OK); + response.setEntity(new StringRepresentation("post-ack")); + } + }); + + Representation result = cr.post(new StringRepresentation("payload")); + + assertEquals("post-ack", getText(result)); + } + + @Test + void delete_sendsDeleteMethodToNextRestlet() { + ClientResource cr = new ClientResource("http://localhost/test"); + cr.setNext( + new Restlet() { + @Override + public void handle(Request request, Response response) { + assertEquals(Method.DELETE, request.getMethod()); + response.setStatus(Status.SUCCESS_OK); + } + }); + + cr.delete(); + + assertEquals(Status.SUCCESS_OK, cr.getStatus()); + } + + @Test + void handle_withErrorStatus_throwsResourceException() { + ClientResource cr = new ClientResource("http://localhost/test"); + cr.setNext( + new Restlet() { + @Override + public void handle(Request request, Response response) { + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + } + }); + + ResourceException exception = assertThrows(ResourceException.class, cr::get); + + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, exception.getStatus()); + } + + @Test + void handle_retriesRecoverableErrorsUntilSuccess() { + ClientResource cr = new ClientResource("http://localhost/test"); + cr.setRetryDelay(0); + final AtomicInteger callCount = new AtomicInteger(); + cr.setNext( + new Restlet() { + @Override + public void handle(Request request, Response response) { + if (callCount.incrementAndGet() <= 2) { + response.setStatus(Status.SERVER_ERROR_SERVICE_UNAVAILABLE); + } else { + response.setStatus(Status.SUCCESS_OK); + response.setEntity(new StringRepresentation("retried-result")); + } + } + }); + + Representation result = cr.get(); + + assertEquals(3, callCount.get()); + assertEquals("retried-result", getText(result)); + } + + @Test + void handle_followsSeeOtherRedirectForSafeMethod() { + ClientResource cr = new ClientResource("http://localhost/test"); + final AtomicInteger callCount = new AtomicInteger(); + cr.setNext( + new Restlet() { + @Override + public void handle(Request request, Response response) { + if (callCount.incrementAndGet() == 1) { + response.setStatus(Status.REDIRECTION_SEE_OTHER); + response.setLocationRef(new Reference("http://localhost/other")); + } else { + response.setStatus(Status.SUCCESS_OK); + response.setEntity(new StringRepresentation("redirected-result")); + } + } + }); + + Representation result = cr.get(); + + assertEquals(2, callCount.get()); + assertEquals("redirected-result", getText(result)); + } + + @Test + void getChild_withRelativeReference_returnsChildResource() { + ClientResource cr = new ClientResource("http://localhost/parent/"); + + ClientResource child = cr.getChild("child"); + + assertEquals(new Reference("http://localhost/parent/child"), child.getReference()); + } + + @Test + void getChild_withAbsoluteReference_throwsResourceException() { + ClientResource cr = new ClientResource("http://localhost/parent/"); + + ResourceException exception = + assertThrows( + ResourceException.class, + () -> cr.getChild(new Reference("http://otherhost/child"))); + + assertEquals(Status.CLIENT_ERROR_BAD_REQUEST, exception.getStatus()); + } + + @Test + void getParent_withHierarchicalReference_returnsParentResource() { + ClientResource cr = new ClientResource("http://localhost/parent/child"); + + ClientResource parent = cr.getParent(); + + assertEquals(new Reference("http://localhost/parent/"), parent.getReference()); + } + + @Test + void getParent_withNonHierarchicalReference_throwsResourceException() { + ClientResource cr = new ClientResource("mailto:someone@example.com"); + + ResourceException exception = assertThrows(ResourceException.class, cr::getParent); + + assertEquals(Status.CLIENT_ERROR_BAD_REQUEST, exception.getStatus()); + } + + @Test + void wrap_proxiesAnnotatedInterfaceThroughFinder() { + boolean previous = + org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED; + org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED = true; + try { + ClientResource cr = new ClientResource("http://localhost/test"); + Finder finder = new Finder(new Context()); + finder.setTargetClass(MyServerResource01.class); + cr.setNext(finder); + + MyResource01 proxy = cr.wrap(MyResource01.class); + + assertNotNull(proxy.represent()); + assertEquals("myName", proxy.represent().getName()); + } finally { + org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED = + previous; + } + } + + @Test + void setEntityBuffering_setsBothRequestAndResponseBuffering() { + ClientResource cr = new ClientResource("http://localhost/test"); + + cr.setEntityBuffering(true); + + assertTrue(cr.isRequestEntityBuffering()); + assertTrue(cr.isResponseEntityBuffering()); + } + + private static ClientResource echoResource() { + ClientResource cr = new ClientResource("http://localhost/test"); + cr.setNext( + new Restlet() { + @Override + public void handle(Request request, Response response) { + response.setStatus(Status.SUCCESS_OK); + response.setEntity( + new StringRepresentation("echo-" + request.getMethod().getName())); + } + }); + return cr; + } + + @Test + void get_withResultClass_convertsEntity() { + ClientResource cr = echoResource(); + String result = cr.get(String.class); + assertEquals("echo-GET", result); + } + + @Test + void get_withMediaType_returnsRepresentation() { + ClientResource cr = echoResource(); + Representation result = cr.get(org.restlet.data.MediaType.TEXT_PLAIN); + assertEquals("echo-GET", getText(result)); + } + + @Test + void head_returnsRepresentation() { + ClientResource cr = echoResource(); + Representation result = cr.head(); + assertEquals("echo-HEAD", getText(result)); + assertNotNull(result); + } + + @Test + void head_withMediaType_setsAcceptedMediaType() { + ClientResource cr = echoResource(); + Representation result = cr.head(org.restlet.data.MediaType.TEXT_PLAIN); + assertEquals("echo-HEAD", getText(result)); + } + + @Test + void options_returnsRepresentation() { + ClientResource cr = echoResource(); + Representation result = cr.options(); + assertEquals("echo-OPTIONS", getText(result)); + } + + @Test + void options_withResultClass_convertsEntity() { + ClientResource cr = echoResource(); + String result = cr.options(String.class); + assertEquals("echo-OPTIONS", result); + } + + @Test + void options_withMediaType_returnsRepresentation() { + ClientResource cr = echoResource(); + Representation result = cr.options(org.restlet.data.MediaType.TEXT_PLAIN); + assertEquals("echo-OPTIONS", getText(result)); + } + + @Test + void patch_withObjectEntity_sendsPatchMethod() { + ClientResource cr = echoResource(); + Representation result = cr.patch("payload"); + assertEquals("echo-PATCH", getText(result)); + } + + @Test + void patch_withObjectEntityAndResultClass_convertsEntity() { + ClientResource cr = echoResource(); + String result = cr.patch("payload", String.class); + assertEquals("echo-PATCH", result); + } + + @Test + void patch_withObjectEntityAndMediaType_returnsRepresentation() { + ClientResource cr = echoResource(); + Representation result = cr.patch("payload", org.restlet.data.MediaType.TEXT_PLAIN); + assertEquals("echo-PATCH", getText(result)); + } + + @Test + void patch_withRepresentationEntity_sendsPatchMethod() { + ClientResource cr = echoResource(); + Representation result = cr.patch(new StringRepresentation("payload")); + assertEquals("echo-PATCH", getText(result)); + } + + @Test + void post_withObjectEntityAndResultClass_convertsEntity() { + ClientResource cr = echoResource(); + String result = cr.post("payload", String.class); + assertEquals("echo-POST", result); + } + + @Test + void post_withObjectEntityAndMediaType_returnsRepresentation() { + ClientResource cr = echoResource(); + Representation result = cr.post("payload", org.restlet.data.MediaType.TEXT_PLAIN); + assertEquals("echo-POST", getText(result)); + } + + @Test + void post_withObjectEntity_sendsPostMethod() { + ClientResource cr = echoResource(); + Representation result = cr.post("payload"); + assertEquals("echo-POST", getText(result)); + } + + @Test + void put_withObjectEntityAndResultClass_convertsEntity() { + ClientResource cr = echoResource(); + String result = cr.put("payload", String.class); + assertEquals("echo-PUT", result); + } + + @Test + void put_withObjectEntityAndMediaType_returnsRepresentation() { + ClientResource cr = echoResource(); + Representation result = cr.put("payload", org.restlet.data.MediaType.TEXT_PLAIN); + assertEquals("echo-PUT", getText(result)); + } + + @Test + void put_withObjectEntity_sendsPutMethod() { + ClientResource cr = echoResource(); + Representation result = cr.put("payload"); + assertEquals("echo-PUT", getText(result)); + } + + @Test + void delete_withResultClass_convertsEntity() { + ClientResource cr = echoResource(); + String result = cr.delete(String.class); + assertEquals("echo-DELETE", result); + } + + @Test + void delete_withMediaType_returnsRepresentation() { + ClientResource cr = echoResource(); + Representation result = cr.delete(org.restlet.data.MediaType.TEXT_PLAIN); + assertEquals("echo-DELETE", getText(result)); + } + + private static String getText(Representation representation) { + try { + return representation == null ? null : representation.getText(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/org.restlet/src/test/java/org/restlet/resource/FinderTestCase.java b/org.restlet/src/test/java/org/restlet/resource/FinderTestCase.java new file mode 100644 index 0000000000..100c38c5ba --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/resource/FinderTestCase.java @@ -0,0 +1,170 @@ +/** + * 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.resource; + +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.assertTrue; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.Method; +import org.restlet.data.Status; +import org.restlet.engine.Engine; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; + +/** + * Unit tests for the {@link Finder} class. + * + * @author Jerome Louvel + */ +class FinderTestCase { + + /** Simple server resource used as a target for the finder. */ + public static class SampleServerResource extends ServerResource { + + @Override + protected Representation get() { + return new StringRepresentation("sample-content"); + } + } + + @BeforeEach + void setUpEach() { + Engine.clearThreadLocalVariables(); + } + + @AfterEach + void tearDownEach() { + Engine.clearThreadLocalVariables(); + } + + @Test + void defaultConstructor_hasNoTargetClass() { + Finder finder = new Finder(); + + assertNull(finder.getTargetClass()); + } + + @Test + void constructorWithTargetClass_setsTargetClass() { + Finder finder = new Finder(new Context(), SampleServerResource.class); + + assertEquals(SampleServerResource.class, finder.getTargetClass()); + } + + @Test + void setTargetClassThenGetTargetClass_roundTrips() { + Finder finder = new Finder(); + + finder.setTargetClass(SampleServerResource.class); + + assertEquals(SampleServerResource.class, finder.getTargetClass()); + } + + @Test + void create_withoutTargetClass_returnsNull() { + Finder finder = new Finder(); + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + assertNull(finder.create(request, response)); + } + + @Test + void create_withTargetClass_instantiatesDefaultConstructor() { + Finder finder = new Finder(null, SampleServerResource.class); + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + ServerResource result = finder.create(request, response); + + assertNotNull(result); + assertTrue(result instanceof SampleServerResource); + } + + @Test + void find_delegatesToCreate() { + Finder finder = new Finder(null, SampleServerResource.class); + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + assertNotNull(finder.find(request, response)); + } + + @Test + void handle_withoutTargetClass_setsNotFoundStatus() { + Finder finder = new Finder(); + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + finder.handle(request, response); + + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, response.getStatus()); + } + + @Test + void handle_withTargetClass_dispatchesToServerResource() { + Finder finder = new Finder(new Context(), SampleServerResource.class); + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + finder.handle(request, response); + + assertEquals(Status.SUCCESS_OK, response.getStatus()); + assertNotNull(response.getEntity()); + } + + @Test + void toString_withoutTargetClass_indicatesNoTarget() { + Finder finder = new Finder(); + + assertEquals("Finder with no target class", finder.toString()); + } + + @Test + void toString_withTargetClass_includesSimpleName() { + Finder finder = new Finder(null, SampleServerResource.class); + + assertTrue(finder.toString().contains("SampleServerResource")); + } + + @Test + void createFinder_withoutFinderClass_createsPlainFinder() { + Finder finder = Finder.createFinder(SampleServerResource.class, null, new Context(), null); + + assertNotNull(finder); + assertEquals(Finder.class, finder.getClass()); + assertEquals(SampleServerResource.class, finder.getTargetClass()); + } + + /** Custom finder subclass exposing the expected (Context, Class) constructor. */ + public static class CustomFinder extends Finder { + public CustomFinder(Context context, Class targetClass) { + super(context, targetClass); + } + } + + @Test + void createFinder_withFinderClass_instantiatesCustomFinder() { + Finder finder = + Finder.createFinder( + SampleServerResource.class, CustomFinder.class, new Context(), null); + + assertNotNull(finder); + assertTrue(finder instanceof CustomFinder); + assertEquals(SampleServerResource.class, finder.getTargetClass()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/resource/ResourceExceptionTestCase.java b/org.restlet/src/test/java/org/restlet/resource/ResourceExceptionTestCase.java new file mode 100644 index 0000000000..6db5ac072b --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/resource/ResourceExceptionTestCase.java @@ -0,0 +1,120 @@ +/** + * 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.resource; + +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.assertSame; + +import org.junit.jupiter.api.Test; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.Method; +import org.restlet.data.Status; + +/** + * Unit tests for the {@link ResourceException} class. + * + * @author Jerome Louvel + */ +class ResourceExceptionTestCase { + + @Test + void constructor_withCodeOnly_setsMatchingStatus() { + ResourceException re = new ResourceException(404); + + assertEquals(404, re.getStatus().getCode()); + assertNull(re.getRequest()); + assertNull(re.getResponse()); + } + + @Test + void constructor_withCodeAndReasonPhrase_setsReasonPhrase() { + ResourceException re = new ResourceException(500, "Custom Reason"); + + assertEquals(500, re.getStatus().getCode()); + assertEquals("Custom Reason", re.getStatus().getReasonPhrase()); + } + + @Test + void constructor_withCodeReasonPhraseAndDescription_setsDescription() { + ResourceException re = new ResourceException(400, "Bad Request", "Missing field"); + + assertEquals(400, re.getStatus().getCode()); + assertEquals("Bad Request", re.getStatus().getReasonPhrase()); + assertEquals("Missing field", re.getStatus().getDescription()); + } + + @Test + void constructor_withStatus_copiesStatus() { + ResourceException re = new ResourceException(Status.CLIENT_ERROR_FORBIDDEN); + + assertEquals(Status.CLIENT_ERROR_FORBIDDEN, re.getStatus()); + } + + @Test + void constructor_withStatusAndCause_setsCauseAndStatus() { + Exception cause = new IllegalStateException("boom"); + ResourceException re = new ResourceException(Status.SERVER_ERROR_INTERNAL, cause); + + assertEquals(Status.SERVER_ERROR_INTERNAL, re.getStatus()); + assertSame(cause, re.getCause()); + } + + @Test + void constructor_withThrowableOnly_defaultsToInternalServerError() { + Exception cause = new RuntimeException("failure"); + ResourceException re = new ResourceException(cause); + + assertEquals(Status.SERVER_ERROR_INTERNAL.getCode(), re.getStatus().getCode()); + assertSame(cause, re.getCause()); + } + + @Test + void constructor_withStatusRequestAndResponse_exposesRequestAndResponse() { + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + ResourceException re = + new ResourceException(Status.CLIENT_ERROR_NOT_FOUND, request, response); + + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, re.getStatus()); + assertSame(request, re.getRequest()); + assertSame(response, re.getResponse()); + } + + @Test + void constructor_withStatusAndDescription_appliesDescriptionToCopiedStatus() { + ResourceException re = + new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Invalid input"); + + assertEquals(Status.CLIENT_ERROR_BAD_REQUEST.getCode(), re.getStatus().getCode()); + assertEquals("Invalid input", re.getStatus().getDescription()); + } + + @Test + void getMessage_returnsStatusToString() { + ResourceException re = new ResourceException(Status.CLIENT_ERROR_NOT_FOUND); + + assertEquals(Status.CLIENT_ERROR_NOT_FOUND.toString(), re.getMessage()); + } + + @Test + void constructor_withCodeNameDescriptionAndUri_setsAllStatusFields() { + ResourceException re = + new ResourceException( + 422, "Unprocessable", "Bad payload", "http://example.com/spec"); + + assertEquals(422, re.getStatus().getCode()); + assertEquals("Unprocessable", re.getStatus().getReasonPhrase()); + assertEquals("Bad payload", re.getStatus().getDescription()); + assertNotNull(re.getStatus().getUri()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/resource/ResourceTestCase.java b/org.restlet/src/test/java/org/restlet/resource/ResourceTestCase.java new file mode 100644 index 0000000000..99da92f37b --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/resource/ResourceTestCase.java @@ -0,0 +1,241 @@ +/** + * 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.resource; + +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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.data.Status; +import org.restlet.engine.Engine; +import org.restlet.representation.Representation; + +/** + * Unit tests for the {@link Resource} abstract class, exercised through a minimal local subclass. + * + * @author Jerome Louvel + */ +class ResourceTestCase { + + /** Minimal concrete subclass used to exercise the abstract base class. */ + private static class ConcreteResource extends Resource { + + private final Map attributes = new HashMap<>(); + + private volatile boolean released; + + private volatile boolean failInit; + + @Override + public String getAttribute(String name) { + Object value = attributes.get(name); + return (value == null) ? null : value.toString(); + } + + @Override + public void setAttribute(String name, Object value) { + attributes.put(name, value); + } + + @Override + public Representation handle() { + return null; + } + + @Override + protected void doInit() throws ResourceException { + if (failInit) { + throw new ResourceException(Status.SERVER_ERROR_INTERNAL); + } + } + + @Override + protected void doRelease() { + released = true; + } + } + + private ConcreteResource resource; + + @BeforeEach + void setUpEach() { + Engine.clearThreadLocalVariables(); + resource = new ConcreteResource(); + } + + @AfterEach + void tearDownEach() { + Engine.clearThreadLocalVariables(); + resource = null; + } + + @Test + void toBoolean_convertsValidStringAndReturnsNullForNull() { + assertEquals(Boolean.TRUE, Resource.toBoolean("true")); + assertNull(Resource.toBoolean(null)); + } + + @Test + void toByte_convertsValidStringAndReturnsNullForNull() { + assertEquals(Byte.valueOf((byte) 5), Resource.toByte("5")); + assertNull(Resource.toByte(null)); + } + + @Test + void toDouble_convertsValidStringAndReturnsNullForNull() { + assertEquals(Double.valueOf(3.14), Resource.toDouble("3.14")); + assertNull(Resource.toDouble(null)); + } + + @Test + void toFloat_convertsValidStringAndReturnsNullForNull() { + assertEquals(Float.valueOf(1.5f), Resource.toFloat("1.5")); + assertNull(Resource.toFloat(null)); + } + + @Test + void toInteger_convertsValidStringAndReturnsNullForNull() { + assertEquals(Integer.valueOf(42), Resource.toInteger("42")); + assertNull(Resource.toInteger(null)); + } + + @Test + void toLong_convertsValidStringAndReturnsNullForNull() { + assertEquals(Long.valueOf(42L), Resource.toLong("42")); + assertNull(Resource.toLong(null)); + } + + @Test + void toShort_convertsValidStringAndReturnsNullForNull() { + assertEquals(Short.valueOf((short) 7), Resource.toShort("7")); + assertNull(Resource.toShort(null)); + } + + @Test + void getRequestAndGetResponse_returnNullBeforeInit() { + assertNull(resource.getRequest()); + assertNull(resource.getResponse()); + assertNull(resource.getContext()); + } + + @Test + void init_setsContextRequestAndResponse() { + Context context = new Context(); + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + + resource.init(context, request, response); + + assertEquals(context, resource.getContext()); + assertEquals(request, resource.getRequest()); + assertEquals(response, resource.getResponse()); + } + + @Test + void init_catchesExceptionThrownByDoInit() { + Context context = new Context(); + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + resource.failInit = true; + + // Should not propagate the ResourceException thrown by doInit(). + resource.init(context, request, response); + + assertEquals(request, resource.getRequest()); + } + + @Test + void getMethod_delegatesToRequest() { + Request request = new Request(Method.PUT, "http://localhost/test"); + resource.setRequest(request); + + assertEquals(Method.PUT, resource.getMethod()); + } + + @Test + void getStatus_delegatesToResponse() { + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + resource.setResponse(response); + + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, resource.getStatus()); + } + + @Test + void getReference_delegatesToRequest() { + Request request = new Request(Method.GET, "http://localhost/test"); + resource.setRequest(request); + + assertEquals(new Reference("http://localhost/test"), resource.getReference()); + } + + @Test + void getApplication_createsNewInstanceWhenNoneSet() { + assertNotNull(resource.getApplication()); + } + + @Test + void setApplication_returnsConfiguredInstance() { + org.restlet.Application application = new org.restlet.Application(); + resource.setApplication(application); + + assertEquals(application, resource.getApplication()); + } + + @Test + void setQueryValueThenGetQueryValue_roundTrips() { + Request request = new Request(Method.GET, "http://localhost/test"); + resource.setRequest(request); + + resource.setQueryValue("name", "value"); + + assertEquals("value", resource.getQueryValue("name")); + } + + @Test + void getQueryValue_returnsNullWhenNoRequest() { + assertNull(resource.getQueryValue("name")); + } + + @Test + void release_invokesDoRelease() { + assertFalse(resource.released); + resource.release(); + assertTrue(resource.released); + } + + @Test + void toString_combinesRequestAndResponse() { + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + resource.init(new Context(), request, response); + + String result = resource.toString(); + + assertTrue(result.contains(request.toString())); + } + + @Test + void toString_isEmptyWhenNoRequestOrResponse() { + assertEquals("", resource.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/resource/ServerResourceTestCase.java b/org.restlet/src/test/java/org/restlet/resource/ServerResourceTestCase.java new file mode 100644 index 0000000000..f425267f75 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/resource/ServerResourceTestCase.java @@ -0,0 +1,470 @@ +/** + * 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.resource; + +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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.Method; +import org.restlet.data.Status; +import org.restlet.engine.Engine; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; + +/** + * Unit tests for the {@link ServerResource} abstract class, exercised through minimal local + * subclasses. + * + * @author Jerome Louvel + */ +class ServerResourceTestCase { + + /** Server resource with no overridden methods: exercises default behaviors. */ + private static class PlainServerResource extends ServerResource {} + + /** Server resource overriding the standard uniform interface methods. */ + private static class EchoServerResource extends ServerResource { + + volatile boolean putInvoked; + + volatile boolean deleteInvoked; + + @Override + protected Representation get() { + return new StringRepresentation("get-result"); + } + + @Override + protected Representation put(Representation entity) { + putInvoked = true; + return new StringRepresentation("put-result"); + } + + @Override + protected Representation delete() { + deleteInvoked = true; + return new StringRepresentation("delete-result"); + } + } + + @BeforeEach + void setUpEach() { + Engine.clearThreadLocalVariables(); + Engine.register(false); + } + + @AfterEach + void tearDownEach() { + Engine.clearThreadLocalVariables(); + } + + private static EchoServerResource createInitializedResource(Method method) { + EchoServerResource resource = new EchoServerResource(); + Context context = new Context(); + Request request = new Request(method, "http://localhost/test"); + Response response = new Response(request); + resource.init(context, request, response); + return resource; + } + + @Test + void defaultFlags_areTrueByDefault() { + PlainServerResource resource = new PlainServerResource(); + + assertTrue(resource.isAnnotated()); + assertTrue(resource.isConditional()); + assertTrue(resource.isExisting()); + assertTrue(resource.isNegotiated()); + } + + @Test + void nameAndDescription_roundTrip() { + PlainServerResource resource = new PlainServerResource(); + + resource.setName("myResource"); + resource.setDescription("myDescription"); + + assertEquals("myResource", resource.getName()); + assertEquals("myDescription", resource.getDescription()); + } + + @Test + void setAnnotated_updatesFlag() { + PlainServerResource resource = new PlainServerResource(); + + resource.setAnnotated(false); + + assertFalse(resource.isAnnotated()); + } + + @Test + void setConditional_updatesFlag() { + PlainServerResource resource = new PlainServerResource(); + + resource.setConditional(false); + + assertFalse(resource.isConditional()); + } + + @Test + void setExisting_updatesFlag() { + PlainServerResource resource = new PlainServerResource(); + + resource.setExisting(false); + + assertFalse(resource.isExisting()); + } + + @Test + void setNegotiated_updatesFlag() { + PlainServerResource resource = new PlainServerResource(); + + resource.setNegotiated(false); + + assertFalse(resource.isNegotiated()); + } + + @Test + void getAttribute_readsFromRequestAttributes() { + EchoServerResource resource = createInitializedResource(Method.GET); + resource.getRequestAttributes().put("key", "value"); + + assertEquals("value", resource.getAttribute("key")); + } + + @Test + void getAttribute_returnsNullWhenMissing() { + EchoServerResource resource = createInitializedResource(Method.GET); + + assertNull(resource.getAttribute("missing")); + } + + @Test + void setAttribute_writesToResponseAttributes() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.setAttribute("key", "value"); + + assertEquals("value", resource.getResponseAttributes().get("key")); + } + + @Test + void handle_withGetMethod_dispatchesToGetAndSetsEntity() throws Exception { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.handle(); + + assertEquals(Status.SUCCESS_OK, resource.getStatus()); + assertEquals("get-result", resource.getResponseEntity().getText()); + } + + @Test + void handle_withPutMethod_dispatchesToPutRegardlessOfExisting() throws Exception { + EchoServerResource resource = createInitializedResource(Method.PUT); + resource.setExisting(false); + + resource.handle(); + + assertTrue(resource.putInvoked); + assertEquals("put-result", resource.getResponseEntity().getText()); + } + + @Test + void handle_withDeleteMethod_dispatchesToDelete() throws Exception { + EchoServerResource resource = createInitializedResource(Method.DELETE); + + resource.handle(); + + assertTrue(resource.deleteInvoked); + assertEquals("delete-result", resource.getResponseEntity().getText()); + } + + @Test + void handle_withUnsupportedMethodOnPlainResource_setsMethodNotAllowed() { + PlainServerResource resource = new PlainServerResource(); + Context context = new Context(); + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + resource.init(context, request, response); + + resource.handle(); + + assertEquals(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED, resource.getStatus()); + } + + @Test + void handle_whenNotExistingAndMethodSafe_setsNotFound() { + EchoServerResource resource = createInitializedResource(Method.GET); + resource.setExisting(false); + + resource.handle(); + + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, resource.getStatus()); + } + + @Test + void doError_setsResponseStatus() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.doError(Status.CLIENT_ERROR_CONFLICT); + + assertEquals(Status.CLIENT_ERROR_CONFLICT, resource.getStatus()); + } + + @Test + void isInRole_returnsFalseWhenClientHasNoRoles() { + EchoServerResource resource = createInitializedResource(Method.GET); + + assertFalse(resource.isInRole("admin")); + } + + @Test + void getRole_createsRoleWithGivenName() { + EchoServerResource resource = createInitializedResource(Method.GET); + + assertEquals("admin", resource.getRole("admin").getName()); + } + + @Test + void redirectPermanent_withStringUri_setsLocationAndStatus() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.redirectPermanent("http://localhost/target"); + + assertEquals(Status.REDIRECTION_PERMANENT, resource.getStatus()); + assertEquals("http://localhost/target", resource.getLocationRef().toString()); + } + + @Test + void redirectPermanent_withReference_setsLocationAndStatus() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.redirectPermanent(new org.restlet.data.Reference("http://localhost/target")); + + assertEquals(Status.REDIRECTION_PERMANENT, resource.getStatus()); + } + + @Test + void redirectSeeOther_withStringUri_setsLocationAndStatus() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.redirectSeeOther("http://localhost/target"); + + assertEquals(Status.REDIRECTION_SEE_OTHER, resource.getStatus()); + } + + @Test + void redirectSeeOther_withReference_setsLocationAndStatus() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.redirectSeeOther(new org.restlet.data.Reference("http://localhost/target")); + + assertEquals(Status.REDIRECTION_SEE_OTHER, resource.getStatus()); + } + + @Test + void redirectTemporary_withStringUri_setsLocationAndStatus() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.redirectTemporary("http://localhost/target"); + + assertEquals(Status.REDIRECTION_TEMPORARY, resource.getStatus()); + } + + @Test + void redirectTemporary_withReference_setsLocationAndStatus() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.redirectTemporary(new org.restlet.data.Reference("http://localhost/target")); + + assertEquals(Status.REDIRECTION_TEMPORARY, resource.getStatus()); + } + + @Test + void setLocationRef_withString_setsLocation() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.setLocationRef("http://localhost/other"); + + assertEquals("http://localhost/other", resource.getLocationRef().toString()); + } + + @Test + void setStatus_statusOnly_updatesResponseStatus() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.setStatus(Status.CLIENT_ERROR_FORBIDDEN); + + assertEquals(Status.CLIENT_ERROR_FORBIDDEN, resource.getStatus()); + } + + @Test + void setStatus_statusAndMessage_setsReasonPhrase() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.setStatus(Status.CLIENT_ERROR_FORBIDDEN, "custom message"); + + assertEquals("custom message", resource.getStatus().getDescription()); + } + + @Test + void setStatus_statusAndThrowable_attachesThrowable() { + EchoServerResource resource = createInitializedResource(Method.GET); + Exception cause = new IllegalStateException("boom"); + + resource.setStatus(Status.SERVER_ERROR_INTERNAL, cause); + + assertEquals(cause, resource.getStatus().getThrowable()); + } + + @Test + void setStatus_statusThrowableAndMessage_setsBoth() { + EchoServerResource resource = createInitializedResource(Method.GET); + Exception cause = new IllegalStateException("boom"); + + resource.setStatus(Status.SERVER_ERROR_INTERNAL, cause, "custom message"); + + assertEquals(cause, resource.getStatus().getThrowable()); + assertEquals("custom message", resource.getStatus().getReasonPhrase()); + } + + @Test + void commit_delegatesToResponseWithoutThrowing() { + EchoServerResource resource = createInitializedResource(Method.GET); + assertFalse(resource.isCommitted()); + + resource.commit(); + + assertFalse(resource.isCommitted()); + } + + @Test + void setCommitted_updatesFlag() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.setCommitted(true); + + assertTrue(resource.isCommitted()); + } + + @Test + void isAutoCommitting_defaultsToTrue() { + EchoServerResource resource = createInitializedResource(Method.GET); + + assertTrue(resource.isAutoCommitting()); + } + + @Test + void setAutoCommitting_updatesFlag() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.setAutoCommitting(false); + + assertFalse(resource.isAutoCommitting()); + } + + @Test + void abort_delegatesToResponseWithoutThrowing() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.abort(); + + assertNotNull(resource.getResponse()); + } + + @Test + void setAllowedMethods_updatesAllowedMethodsSet() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.setAllowedMethods(java.util.Set.of(Method.GET, Method.POST)); + + assertTrue(resource.getResponse().getAllowedMethods().contains(Method.GET)); + assertTrue(resource.getResponse().getAllowedMethods().contains(Method.POST)); + } + + @Test + void setServerInfo_updatesResponseServerInfo() { + EchoServerResource resource = createInitializedResource(Method.GET); + org.restlet.data.ServerInfo serverInfo = new org.restlet.data.ServerInfo(); + serverInfo.setAgent("test-agent"); + + resource.setServerInfo(serverInfo); + + assertEquals("test-agent", resource.getResponse().getServerInfo().getAgent()); + } + + @Test + void setCookieSettings_updatesResponseCookieSettings() { + EchoServerResource resource = createInitializedResource(Method.GET); + org.restlet.util.Series cookieSettings = + new org.restlet.util.Series<>(org.restlet.data.CookieSetting.class); + cookieSettings.add(new org.restlet.data.CookieSetting(0, "name", "value")); + + resource.setCookieSettings(cookieSettings); + + assertEquals(1, resource.getResponse().getCookieSettings().size()); + } + + @Test + void setDimensions_updatesResponseDimensions() { + EchoServerResource resource = createInitializedResource(Method.GET); + + resource.setDimensions(java.util.Set.of(org.restlet.data.Dimension.CHARACTER_SET)); + + assertTrue( + resource.getResponse() + .getDimensions() + .contains(org.restlet.data.Dimension.CHARACTER_SET)); + } + + @Test + void setChallengeRequests_updatesResponseChallengeRequests() { + EchoServerResource resource = createInitializedResource(Method.GET); + java.util.List requests = + java.util.List.of( + new org.restlet.data.ChallengeRequest( + org.restlet.data.ChallengeScheme.HTTP_BASIC)); + + resource.setChallengeRequests(requests); + + assertEquals(1, resource.getResponse().getChallengeRequests().size()); + } + + @Test + void setProxyChallengeRequests_updatesResponseProxyChallengeRequests() { + EchoServerResource resource = createInitializedResource(Method.GET); + java.util.List requests = + java.util.List.of( + new org.restlet.data.ChallengeRequest( + org.restlet.data.ChallengeScheme.HTTP_BASIC)); + + resource.setProxyChallengeRequests(requests); + + assertEquals(1, resource.getResponse().getProxyChallengeRequests().size()); + } + + @Test + void getOnSentAndSetOnSent_roundTrip() { + EchoServerResource resource = createInitializedResource(Method.GET); + org.restlet.Uniform callback = (request, response) -> {}; + + resource.setOnSent(callback); + + assertEquals(callback, resource.getOnSent()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/routing/RedirectorTestCase.java b/org.restlet/src/test/java/org/restlet/routing/RedirectorTestCase.java new file mode 100644 index 0000000000..0e774d2877 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/routing/RedirectorTestCase.java @@ -0,0 +1,279 @@ +/** + * 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +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; +import org.restlet.data.Header; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.data.Status; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.representation.StringRepresentation; + +/** Unit tests for {@link Redirector}. */ +class RedirectorTestCase { + + @Test + void twoArgConstructor_defaultsToServerOutboundMode() { + Redirector redirector = new Redirector(new Context(), "/target"); + + assertEquals(Redirector.MODE_SERVER_OUTBOUND, redirector.getMode()); + assertEquals("/target", redirector.getTargetTemplate()); + assertTrue(redirector.isHeadersCleaning()); + } + + @Test + void threeArgConstructor_setsGivenMode() { + Redirector redirector = + new Redirector(new Context(), "/target", Redirector.MODE_CLIENT_FOUND); + + assertEquals(Redirector.MODE_CLIENT_FOUND, redirector.getMode()); + } + + @Test + void settersRoundTrip() { + Redirector redirector = new Redirector(new Context(), "/target"); + + redirector.setMode(Redirector.MODE_CLIENT_TEMPORARY); + assertEquals(Redirector.MODE_CLIENT_TEMPORARY, redirector.getMode()); + + redirector.setTargetTemplate("/other"); + assertEquals("/other", redirector.getTargetTemplate()); + + redirector.setHeadersCleaning(false); + assertFalse(redirector.isHeadersCleaning()); + } + + @Test + void getTargetRef_withRelativeTemplate_isResolvedAgainstResourceRef() { + Redirector redirector = new Redirector(new Context(), "/target"); + Request request = new Request(Method.GET, "http://localhost/foo/bar"); + Response response = new Response(request); + + Reference targetRef = redirector.getTargetRef(request, response); + + // getTargetRef() returns a reference that is relative to the request's + // resource reference; resolving it yields the absolute target URI. + assertEquals("/target", targetRef.toString()); + assertEquals("http://localhost/target", targetRef.getTargetRef().toString()); + } + + @Test + void getTargetRef_withAbsoluteTemplate_isUsedAsIs() { + Redirector redirector = new Redirector(new Context(), "http://example.com/target"); + Request request = new Request(Method.GET, "http://localhost/foo"); + Response response = new Response(request); + + Reference targetRef = redirector.getTargetRef(request, response); + + assertEquals("http://example.com/target", targetRef.toString()); + } + + @Test + void getTargetRef_withTemplateVariable_isResolvedFromRequest() { + Redirector redirector = + new Redirector( + new Context(), "http://example.com/{a}", Redirector.MODE_CLIENT_FOUND); + Request request = new Request(Method.GET, "http://localhost/foo"); + request.getAttributes().put("a", "resolved"); + Response response = new Response(request); + + Reference targetRef = redirector.getTargetRef(request, response); + + assertEquals("http://example.com/resolved", targetRef.toString()); + } + + @Test + void handle_clientPermanentMode_redirectsPermanently() { + Redirector redirector = + new Redirector(new Context(), "/new", Redirector.MODE_CLIENT_PERMANENT); + Request request = new Request(Method.GET, "http://localhost/old"); + Response response = new Response(request); + + redirector.handle(request, response); + + assertEquals(Status.REDIRECTION_PERMANENT, response.getStatus()); + assertEquals("http://localhost/new", response.getLocationRef().getTargetRef().toString()); + } + + @Test + void handle_clientFoundMode_redirectsWithFoundStatus() { + Redirector redirector = new Redirector(new Context(), "/new", Redirector.MODE_CLIENT_FOUND); + Request request = new Request(Method.GET, "http://localhost/old"); + Response response = new Response(request); + + redirector.handle(request, response); + + assertEquals(Status.REDIRECTION_FOUND, response.getStatus()); + assertEquals("http://localhost/new", response.getLocationRef().getTargetRef().toString()); + } + + @Test + void handle_clientSeeOtherMode_redirectsWithSeeOtherStatus() { + Redirector redirector = + new Redirector(new Context(), "/new", Redirector.MODE_CLIENT_SEE_OTHER); + Request request = new Request(Method.GET, "http://localhost/old"); + Response response = new Response(request); + + redirector.handle(request, response); + + assertEquals(Status.REDIRECTION_SEE_OTHER, response.getStatus()); + } + + @Test + void handle_clientTemporaryMode_redirectsWithTemporaryStatus() { + Redirector redirector = + new Redirector(new Context(), "/new", Redirector.MODE_CLIENT_TEMPORARY); + Request request = new Request(Method.GET, "http://localhost/old"); + Response response = new Response(request); + + redirector.handle(request, response); + + assertEquals(Status.REDIRECTION_TEMPORARY, response.getStatus()); + } + + @Test + void handle_serverOutboundModeWithoutDispatcher_logsWarningAndDoesNotThrow() { + Redirector redirector = + new Redirector( + new Context(), "http://backend/target", Redirector.MODE_SERVER_OUTBOUND); + Request request = new Request(Method.GET, "http://localhost/foo"); + Response response = new Response(request); + + redirector.handle(request, response); + } + + @Test + void handle_serverInboundModeWithoutDispatcher_logsWarningAndDoesNotThrow() { + Redirector redirector = + new Redirector( + new Context(), "http://backend/target", Redirector.MODE_SERVER_INBOUND); + Request request = new Request(Method.GET, "http://localhost/foo"); + Response response = new Response(request); + + redirector.handle(request, response); + } + + @Test + void serverRedirect_withNext_dispatchesRequestAndRestoresResourceRef() { + Redirector redirector = new Redirector(new Context(), "http://backend/target"); + Reference originalResourceRef = new Reference("http://localhost/foo"); + Request request = new Request(Method.GET, originalResourceRef); + Response response = new Response(request); + Reference targetRef = new Reference("http://backend/target"); + + Restlet next = + new Restlet(new Context()) { + @Override + public void handle(Request req, Response resp) { + super.handle(req, resp); + assertEquals(targetRef.toString(), req.getResourceRef().toString()); + resp.setEntity(new StringRepresentation("payload")); + } + }; + + redirector.serverRedirect(next, targetRef, request, response); + + assertEquals(originalResourceRef.toString(), request.getResourceRef().toString()); + assertNotNull(response.getEntity()); + } + + @Test + void serverRedirect_withNullNext_logsWarningAndDoesNotThrow() { + Redirector redirector = new Redirector(new Context(), "/target"); + Request request = new Request(Method.GET, "http://localhost/foo"); + Response response = new Response(request); + + redirector.serverRedirect(null, new Reference("http://backend/target"), request, response); + } + + @Test + void rewriteRequest_withHeadersCleaningEnabled_removesHeadersAttribute() { + Redirector redirector = new Redirector(new Context(), "/target"); + Request request = new Request(Method.GET, "http://localhost/foo"); + request.getHeaders().add(new Header("X-Custom", "value")); + + redirector.rewrite(request); + + assertNull(request.getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS)); + } + + @Test + void rewriteRequest_withHeadersCleaningDisabled_keepsOnlyExtensionHeaders() { + Redirector redirector = new Redirector(new Context(), "/target"); + redirector.setHeadersCleaning(false); + Request request = new Request(Method.GET, "http://localhost/foo"); + request.getHeaders().add(new Header("X-Custom", "value")); + request.getHeaders().add(new Header(HeaderConstants.HEADER_CONTENT_TYPE, "text/plain")); + + redirector.rewrite(request); + + org.restlet.util.Series

remaining = request.getHeaders(); + assertEquals(1, remaining.size()); + assertEquals("X-Custom", remaining.get(0).getName()); + } + + @Test + void rewriteResponse_withHeadersCleaningEnabled_removesHeadersAttribute() { + Redirector redirector = new Redirector(new Context(), "/target"); + Request request = new Request(Method.GET, "http://localhost/foo"); + Response response = new Response(request); + response.getHeaders().add(new Header("X-Custom", "value")); + + redirector.rewrite(response); + + assertNull(response.getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS)); + } + + @Test + void rewriteRepresentation_returnsSameInstanceByDefault() { + Redirector redirector = new Redirector(new Context(), "/target"); + StringRepresentation representation = new StringRepresentation("body"); + + assertEquals( + representation, + redirector.rewrite((org.restlet.representation.Representation) representation)); + } + + @Test + void rewriteLocation_rewritesResponseLocationUsingTemplate() { + Redirector redirector = + new Redirector( + new Context(), "http://backend{rr}", Redirector.MODE_SERVER_OUTBOUND); + Request request = new Request(Method.GET, "http://myproxy/foo"); + request.getResourceRef().setBaseRef(new Reference("http://myproxy")); + Response response = new Response(request); + response.setLocationRef("http://backend/foo"); + + redirector.rewriteLocation(request, response); + + assertEquals("http://myproxy/foo", response.getLocationRef().toString()); + } + + @Test + void rewriteLocation_withoutLocationRef_isNoOp() { + Redirector redirector = new Redirector(new Context(), "http://backend{rr}"); + Request request = new Request(Method.GET, "http://myproxy/foo"); + Response response = new Response(request); + + redirector.rewriteLocation(request, response); + + assertNull(response.getLocationRef()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/routing/RouteTestCase.java b/org.restlet/src/test/java/org/restlet/routing/RouteTestCase.java new file mode 100644 index 0000000000..d35bf7b662 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/routing/RouteTestCase.java @@ -0,0 +1,78 @@ +/** + * 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.assertSame; + +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 Route}. */ +class RouteTestCase { + + /** Minimal concrete fixture, since {@link Route} is abstract. */ + private static class FixedScoreRoute extends Route { + private final float score; + + FixedScoreRoute(Router router, Restlet next, float score) { + super(router, next); + this.score = score; + } + + @Override + public float score(Request request, Response response) { + return score; + } + } + + @Test + void constructorWithNext_hasNoRouterAndUsesNextContext() { + Restlet next = new MockRestlet(new Context()); + + Route route = new FixedScoreRoute(null, next, 1.0F); + + assertNull(route.getRouter()); + assertSame(next.getContext(), route.getContext()); + } + + @Test + void constructorWithRouter_usesRouterContext() { + Router router = new Router(new Context()); + Restlet next = new MockRestlet(null); + + Route route = new FixedScoreRoute(router, next, 1.0F); + + assertSame(router, route.getRouter()); + assertSame(router.getContext(), route.getContext()); + } + + @Test + void setRouter_roundTrip() { + Router router = new Router(); + Route route = new FixedScoreRoute(null, new MockRestlet(null), 1.0F); + + route.setRouter(router); + + assertSame(router, route.getRouter()); + } + + @Test + void score_returnsValueFromSubclass() { + Route route = new FixedScoreRoute(null, new MockRestlet(null), 0.75F); + + float score = route.score(new Request(), new Response(new Request())); + + assertEquals(0.75F, score); + } +} diff --git a/org.restlet/src/test/java/org/restlet/routing/RouterTestCase.java b/org.restlet/src/test/java/org/restlet/routing/RouterTestCase.java new file mode 100644 index 0000000000..b93718f1be --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/routing/RouterTestCase.java @@ -0,0 +1,366 @@ +/** + * 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +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.Restlet; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.data.Status; +import org.restlet.resource.Directory; +import org.restlet.resource.ServerResource; + +/** Unit tests for {@link Router}. */ +class RouterTestCase { + + /** Minimal concrete fixture. */ + static class MyServerResource extends ServerResource {} + + private Restlet targetRestlet() { + return new Restlet(new Context()) { + @Override + public void handle(Request request, Response response) { + super.handle(request, response); + response.setStatus(Status.SUCCESS_OK); + } + }; + } + + @Test + void defaultConstructor_hasExpectedDefaults() { + Router router = new Router(); + + assertEquals(Template.MODE_EQUALS, router.getDefaultMatchingMode()); + assertFalse(router.getDefaultMatchingQuery()); + assertNull(router.getDefaultRoute()); + assertEquals(Router.MODE_FIRST_MATCH, router.getRoutingMode()); + assertEquals(0.5F, router.getRequiredScore()); + assertEquals(1, router.getMaxAttempts()); + assertEquals(500L, router.getRetryDelay()); + assertNotNull(router.getRoutes()); + assertTrue(router.getRoutes().isEmpty()); + } + + @Test + void attach_withPlainRestlet_usesDefaultMatchingMode() { + Router router = new Router(new Context()); + + TemplateRoute route = router.attach(targetRestlet()); + + assertEquals(1, router.getRoutes().size()); + assertEquals(Template.MODE_EQUALS, route.getMatchingMode()); + } + + @Test + void attach_withDirectoryTarget_usesStartsWithMatchingMode() { + Router router = new Router(new Context()); + Directory directory = new Directory(router.getContext(), "clap://class/"); + + TemplateRoute route = router.attach(directory); + + assertEquals(Template.MODE_STARTS_WITH, route.getMatchingMode()); + } + + @Test + void attach_withRouterTarget_usesStartsWithMatchingMode() { + Router router = new Router(new Context()); + Router subRouter = new Router(router.getContext()); + + TemplateRoute route = router.attach(subRouter); + + assertEquals(Template.MODE_STARTS_WITH, route.getMatchingMode()); + } + + @Test + void attach_withFilterTarget_delegatesMatchingModeToNext() { + Router router = new Router(new Context()); + Directory directory = new Directory(router.getContext(), "clap://class/"); + Extractor filter = new Extractor(router.getContext(), directory); + + TemplateRoute route = router.attach(filter); + + assertEquals(Template.MODE_STARTS_WITH, route.getMatchingMode()); + } + + @Test + void attach_withPathTemplateAndExplicitMode_setsGivenMode() { + Router router = new Router(new Context()); + + TemplateRoute route = router.attach("/foo", targetRestlet(), Template.MODE_STARTS_WITH); + + assertEquals(Template.MODE_STARTS_WITH, route.getMatchingMode()); + assertEquals(1, router.getRoutes().size()); + } + + @Test + void attach_withResourceClass_createsFinderRoute() { + Router router = new Router(new Context()); + + TemplateRoute route = router.attach("/foo", MyServerResource.class); + + assertNotNull(route); + assertEquals(1, router.getRoutes().size()); + } + + @Test + void attachDefault_setsDefaultRouteWithStartsWithMode() { + Router router = new Router(new Context()); + Restlet target = targetRestlet(); + + TemplateRoute route = router.attachDefault(target); + + assertSame(route, router.getDefaultRoute()); + assertEquals(Template.MODE_STARTS_WITH, route.getMatchingMode()); + } + + @Test + void attachDefault_withResourceClass_setsDefaultRoute() { + Router router = new Router(new Context()); + + TemplateRoute route = router.attachDefault(MyServerResource.class); + + assertSame(route, router.getDefaultRoute()); + } + + @Test + void detach_byRestlet_removesMatchingRoutesAndDefaultRoute() { + Router router = new Router(new Context()); + Restlet target = targetRestlet(); + router.attach("/foo", target); + router.attachDefault(target); + + router.detach(target); + + assertTrue(router.getRoutes().isEmpty()); + assertNull(router.getDefaultRoute()); + } + + @Test + void detach_byClass_removesFinderRoutesAndDefaultRoute() { + Router router = new Router(new Context()); + router.attach("/foo", MyServerResource.class); + router.attachDefault(MyServerResource.class); + + router.detach(MyServerResource.class); + + assertTrue(router.getRoutes().isEmpty()); + assertNull(router.getDefaultRoute()); + } + + @Test + void getNext_firstMatch_returnsMatchingRoute() { + Router router = new Router(new Context()); + Route route = router.attach("/foo", targetRestlet()); + Request request = new Request(Method.GET, "http://localhost/foo"); + request.getResourceRef().setBaseRef(new Reference("http://localhost")); + Response response = new Response(request); + + Restlet next = router.getNext(request, response); + + assertNotNull(next); + assertSame(route, next); + } + + @Test + void getNext_noMatchAndNoDefaultRoute_setsNotFoundStatus() { + Router router = new Router(new Context()); + router.attach("/foo", targetRestlet(), Template.MODE_EQUALS); + Request request = new Request(Method.GET, "http://localhost/bar"); + Response response = new Response(request); + + Restlet next = router.getNext(request, response); + + assertNull(next); + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, response.getStatus()); + } + + @Test + void getNext_noMatch_fallsBackToDefaultRoute() { + Router router = new Router(new Context()); + router.attach("/foo", targetRestlet(), Template.MODE_EQUALS); + Restlet defaultTarget = targetRestlet(); + TemplateRoute defaultRoute = router.attachDefault(defaultTarget); + Request request = new Request(Method.GET, "http://localhost/bar"); + Response response = new Response(request); + + Restlet next = router.getNext(request, response); + + assertSame(defaultRoute, next); + } + + @Test + void getNext_bestMatchMode_selectsHighestScoringRoute() { + Router router = new Router(new Context()); + router.setRoutingMode(Router.MODE_BEST_MATCH); + Restlet shortTarget = targetRestlet(); + Restlet longTarget = targetRestlet(); + router.attach("/foo", shortTarget, Template.MODE_STARTS_WITH); + Route longRoute = router.attach("/foo/bar", longTarget, Template.MODE_STARTS_WITH); + Request request = new Request(Method.GET, "http://localhost/foo/bar"); + request.getResourceRef().setBaseRef(new Reference("http://localhost")); + Response response = new Response(request); + + Restlet next = router.getNext(request, response); + + assertSame(longRoute, next); + } + + @Test + void getNext_customMode_usesGetCustomOverride() { + Restlet target = targetRestlet(); + Router router = + new Router(new Context()) { + @Override + protected Route getCustom(Request request, Response response) { + return getRoutes().isEmpty() ? null : getRoutes().get(0); + } + }; + router.setRoutingMode(Router.MODE_CUSTOM); + Route route = router.attach("/foo", target); + Request request = new Request(Method.GET, "http://localhost/foo"); + Response response = new Response(request); + + Restlet next = router.getNext(request, response); + + assertSame(route, next); + } + + @Test + void getNext_retriesUpToMaxAttempts() { + java.util.concurrent.atomic.AtomicInteger attempts = + new java.util.concurrent.atomic.AtomicInteger(); + Restlet target = targetRestlet(); + Router router = + new Router(new Context()) { + @Override + protected Route getCustom(Request request, Response response) { + return attempts.incrementAndGet() < 2 ? null : getRoutes().get(0); + } + }; + router.setRoutingMode(Router.MODE_CUSTOM); + router.setMaxAttempts(3); + router.setRetryDelay(1L); + Route route = router.attach("/foo", target); + Request request = new Request(Method.GET, "http://localhost/foo"); + Response response = new Response(request); + + Restlet next = router.getNext(request, response); + + assertSame(route, next); + assertEquals(2, attempts.get()); + } + + @Test + void handle_dispatchesToMatchingRoute() { + Router router = new Router(new Context()); + router.attach("/foo", targetRestlet()); + Request request = new Request(Method.GET, "http://localhost/foo"); + request.getResourceRef().setBaseRef(new Reference("http://localhost")); + Response response = new Response(request); + + router.handle(request, response); + + assertEquals(Status.SUCCESS_OK, response.getStatus()); + } + + @Test + void handle_withNoMatch_setsNotFoundStatus() { + Router router = new Router(new Context()); + Request request = new Request(Method.GET, "http://localhost/foo"); + Response response = new Response(request); + + router.handle(request, response); + + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, response.getStatus()); + } + + @Test + void redirectPermanent_attachesRedirectorWithPermanentMode() { + Router router = new Router(new Context()); + + TemplateRoute route = router.redirectPermanent("/old", "/new"); + + assertTrue(route.getNext() instanceof Redirector); + assertEquals(Redirector.MODE_CLIENT_PERMANENT, ((Redirector) route.getNext()).getMode()); + } + + @Test + void redirectSeeOther_attachesRedirectorWithSeeOtherMode() { + Router router = new Router(new Context()); + + TemplateRoute route = router.redirectSeeOther("/old", "/new"); + + assertEquals(Redirector.MODE_CLIENT_SEE_OTHER, ((Redirector) route.getNext()).getMode()); + } + + @Test + void redirectTemporary_attachesRedirectorWithTemporaryMode() { + Router router = new Router(new Context()); + + TemplateRoute route = router.redirectTemporary("/old", "/new"); + + assertEquals(Redirector.MODE_CLIENT_TEMPORARY, ((Redirector) route.getNext()).getMode()); + } + + @Test + void startAndStop_propagateToRoutesAndDefaultRoute() throws Exception { + Router router = new Router(new Context()); + TemplateRoute route = router.attach("/foo", targetRestlet()); + TemplateRoute defaultRoute = router.attachDefault(targetRestlet()); + + router.start(); + + assertTrue(router.isStarted()); + assertTrue(route.isStarted()); + assertTrue(defaultRoute.isStarted()); + + router.stop(); + + assertFalse(router.isStarted()); + assertFalse(route.isStarted()); + assertFalse(defaultRoute.isStarted()); + } + + @Test + void settersRoundTrip() { + Router router = new Router(); + + router.setDefaultMatchingMode(Template.MODE_STARTS_WITH); + assertEquals(Template.MODE_STARTS_WITH, router.getDefaultMatchingMode()); + + router.setDefaultMatchingQuery(true); + assertTrue(router.getDefaultMatchingQuery()); + + router.setMaxAttempts(5); + assertEquals(5, router.getMaxAttempts()); + + router.setRequiredScore(0.9F); + assertEquals(0.9F, router.getRequiredScore()); + + router.setRetryDelay(100L); + assertEquals(100L, router.getRetryDelay()); + + router.setRoutingMode(Router.MODE_LAST_MATCH); + assertEquals(Router.MODE_LAST_MATCH, router.getRoutingMode()); + + org.restlet.util.RouteList routes = new org.restlet.util.RouteList(); + router.setRoutes(routes); + assertSame(routes, router.getRoutes()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/routing/TemplateRouteTestCase.java b/org.restlet/src/test/java/org/restlet/routing/TemplateRouteTestCase.java new file mode 100644 index 0000000000..d1792da258 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/routing/TemplateRouteTestCase.java @@ -0,0 +1,179 @@ +/** + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +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; +import org.restlet.data.Method; +import org.restlet.data.Status; + +/** Unit tests for {@link TemplateRoute}. */ +class TemplateRouteTestCase { + + @Test + void constructorWithNext_hasNoTemplate() { + Restlet next = new MockRestlet(null); + + TemplateRoute route = new TemplateRoute(next); + + assertNull(route.getTemplate()); + assertNull(route.getRouter()); + } + + @Test + void constructorWithStringPattern_createsStartsWithTemplate() { + Router router = new Router(new Context()); + Restlet next = new MockRestlet(null); + + TemplateRoute route = new TemplateRoute(router, "/foo", next); + + assertNotNull(route.getTemplate()); + assertEquals(Template.MODE_STARTS_WITH, route.getMatchingMode()); + } + + @Test + void constructorWithTemplate_defaultsMatchingQueryFromRouter() { + Router router = new Router(); + router.setDefaultMatchingQuery(true); + Template template = new Template("/foo"); + + TemplateRoute route = new TemplateRoute(router, template, new MockRestlet(null)); + + assertTrue(route.isMatchingQuery()); + } + + @Test + void constructorWithoutRouter_matchingQueryDefaultsTrue() { + TemplateRoute route = new TemplateRoute(null, new Template("/foo"), new MockRestlet(null)); + + assertTrue(route.isMatchingQuery()); + } + + @Test + void score_withoutRouter_returnsZero() { + TemplateRoute route = new TemplateRoute(null, new Template("/foo"), new MockRestlet(null)); + Request request = new Request(Method.GET, "http://localhost/foo"); + Response response = new Response(request); + + assertEquals(0F, route.score(request, response)); + } + + @Test + void score_withMatchingUri_returnsPositiveScore() { + Router router = new Router(new Context()); + TemplateRoute route = new TemplateRoute(router, "/foo", new MockRestlet(null)); + Request request = new Request(Method.GET, "http://localhost/foo"); + request.getResourceRef().setBaseRef(new org.restlet.data.Reference("http://localhost")); + Response response = new Response(request); + + float score = route.score(request, response); + + assertTrue(score > 0F); + } + + @Test + void score_withNonMatchingUri_returnsZero() { + Router router = new Router(new Context()); + TemplateRoute route = new TemplateRoute(router, "/foo", new MockRestlet(null)); + Request request = new Request(Method.GET, "http://localhost/bar"); + Response response = new Response(request); + + assertEquals(0F, route.score(request, response)); + } + + @Test + void beforeHandle_onMatch_updatesBaseReferenceAndContinues() { + Router router = new Router(new Context()); + Restlet next = + new Restlet(new Context()) { + @Override + public void handle(Request request, Response response) { + response.setStatus(Status.SUCCESS_OK); + } + }; + TemplateRoute route = new TemplateRoute(router, "/foo", next); + Request request = new Request(Method.GET, "http://localhost/foo/bar"); + request.getResourceRef().setBaseRef(new org.restlet.data.Reference("http://localhost")); + Response response = new Response(request); + + route.handle(request, response); + + assertEquals(Status.SUCCESS_OK, response.getStatus()); + assertEquals("/foo", request.getResourceRef().getBaseRef().getPath()); + } + + @Test + void beforeHandle_onNoMatch_setsNotFoundStatus() { + Router router = new Router(new Context()); + Restlet next = new MockRestlet(new Context()); + TemplateRoute route = + new TemplateRoute(router, new Template("/foo", Template.MODE_EQUALS), next); + Request request = new Request(Method.GET, "http://localhost/other"); + Response response = new Response(request); + + route.handle(request, response); + + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, response.getStatus()); + } + + @Test + void setMatchingMode_updatesTemplate() { + TemplateRoute route = new TemplateRoute(null, new Template("/foo"), new MockRestlet(null)); + + route.setMatchingMode(Template.MODE_STARTS_WITH); + + assertEquals(Template.MODE_STARTS_WITH, route.getMatchingMode()); + } + + @Test + void setMatchingQuery_roundTrip() { + TemplateRoute route = new TemplateRoute(null, new Template("/foo"), new MockRestlet(null)); + + route.setMatchingQuery(false); + + assertEquals(false, route.isMatchingQuery()); + } + + @Test + void setTemplate_roundTrip() { + TemplateRoute route = new TemplateRoute(new MockRestlet(null)); + Template template = new Template("/bar"); + + route.setTemplate(template); + + assertEquals(template, route.getTemplate()); + } + + @Test + void toString_withTemplate_includesPatternAndNext() { + Restlet next = new MockRestlet(null); + TemplateRoute route = new TemplateRoute(null, new Template("/foo"), next); + + String result = route.toString(); + + assertTrue(result.contains("/foo")); + } + + @Test + void toString_withoutTemplate_fallsBackToSuper() { + TemplateRoute route = new TemplateRoute(new MockRestlet(null)); + + String result = route.toString(); + + assertNotNull(result); + } +} diff --git a/org.restlet/src/test/java/org/restlet/routing/VariableTestCase.java b/org.restlet/src/test/java/org/restlet/routing/VariableTestCase.java new file mode 100644 index 0000000000..d77cbf5095 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/routing/VariableTestCase.java @@ -0,0 +1,108 @@ +/** + * 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link Variable}. */ +class VariableTestCase { + + @Test + void defaultConstructor_hasExpectedDefaults() { + Variable variable = new Variable(); + + assertEquals(Variable.TYPE_ALL, variable.getType()); + assertEquals("", variable.getDefaultValue()); + assertTrue(variable.isRequired()); + assertFalse(variable.isFixed()); + assertFalse(variable.isDecodingOnParse()); + assertFalse(variable.isEncodingOnFormat()); + } + + @Test + void typeConstructor_setsTypeAndDefaults() { + Variable variable = new Variable(Variable.TYPE_ALPHA); + + assertEquals(Variable.TYPE_ALPHA, variable.getType()); + assertEquals("", variable.getDefaultValue()); + assertTrue(variable.isRequired()); + assertFalse(variable.isFixed()); + } + + @Test + void fourArgConstructor_setsAllFields() { + Variable variable = new Variable(Variable.TYPE_DIGIT, "42", false, true); + + assertEquals(Variable.TYPE_DIGIT, variable.getType()); + assertEquals("42", variable.getDefaultValue()); + assertFalse(variable.isRequired()); + assertTrue(variable.isFixed()); + } + + @Test + void sixArgConstructor_setsEncodingFlags() { + Variable variable = new Variable(Variable.TYPE_URI_ALL, "x", true, false, true, true); + + assertTrue(variable.isDecodingOnParse()); + assertTrue(variable.isEncodingOnFormat()); + } + + @Test + void gettersAndSetters_roundTrip() { + Variable variable = new Variable(); + + variable.setType(Variable.TYPE_WORD); + assertEquals(Variable.TYPE_WORD, variable.getType()); + + variable.setDefaultValue("abc"); + assertEquals("abc", variable.getDefaultValue()); + + variable.setRequired(false); + assertFalse(variable.isRequired()); + + variable.setFixed(true); + assertTrue(variable.isFixed()); + + variable.setDecodingOnParse(true); + assertTrue(variable.isDecodingOnParse()); + + variable.setEncodingOnFormat(true); + assertTrue(variable.isEncodingOnFormat()); + } + + @Test + void encode_withUriPathType_encodesReservedCharacters() { + Variable variable = new Variable(Variable.TYPE_URI_PATH); + + String encoded = variable.encode("a b/c"); + + assertEquals(org.restlet.data.Reference.encode("a b/c"), encoded); + } + + @Test + void encode_withNonUriType_returnsValueUnchanged() { + Variable variable = new Variable(Variable.TYPE_ALPHA); + + String encoded = variable.encode("a b/c"); + + assertEquals("a b/c", encoded); + } + + @Test + void encode_withUriAllType_encodesValue() { + Variable variable = new Variable(Variable.TYPE_URI_ALL); + + assertEquals( + org.restlet.data.Reference.encode("hello world"), variable.encode("hello world")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/routing/VirtualHostTestCase.java b/org.restlet/src/test/java/org/restlet/routing/VirtualHostTestCase.java new file mode 100644 index 0000000000..f4a4d1c551 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/routing/VirtualHostTestCase.java @@ -0,0 +1,213 @@ +/** + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.data.Status; + +/** Unit tests for {@link VirtualHost}. */ +class VirtualHostTestCase { + + @Test + void defaultConstructor_matchesEverything() { + VirtualHost host = new VirtualHost(); + + assertEquals(".*", host.getHostDomain()); + assertEquals(".*", host.getHostPort()); + assertEquals(".*", host.getHostScheme()); + assertEquals(".*", host.getResourceDomain()); + assertEquals(".*", host.getResourcePort()); + assertEquals(".*", host.getResourceScheme()); + assertEquals(".*", host.getServerAddress()); + assertEquals(".*", host.getServerPort()); + assertEquals(Template.MODE_STARTS_WITH, host.getDefaultMatchingMode()); + assertEquals(Router.MODE_BEST_MATCH, host.getRoutingMode()); + } + + @Test + void constructorWithContext_createsChildContext() { + Context parent = new Context(); + + VirtualHost host = new VirtualHost(parent); + + assertNotNull(host.getContext()); + } + + @Test + void fullConstructor_setsAllPatterns() { + Context parent = new Context(); + + VirtualHost host = + new VirtualHost( + parent, + "hostDomain", + "hostPort", + "hostScheme", + "resourceDomain", + "resourcePort", + "resourceScheme", + "serverAddress", + "serverPort"); + + assertEquals("hostDomain", host.getHostDomain()); + assertEquals("hostPort", host.getHostPort()); + assertEquals("hostScheme", host.getHostScheme()); + assertEquals("resourceDomain", host.getResourceDomain()); + assertEquals("resourcePort", host.getResourcePort()); + assertEquals("resourceScheme", host.getResourceScheme()); + assertEquals("serverAddress", host.getServerAddress()); + assertEquals("serverPort", host.getServerPort()); + } + + @Test + void settersRoundTrip() { + VirtualHost host = new VirtualHost(); + + host.setHostDomain("a"); + assertEquals("a", host.getHostDomain()); + + host.setHostPort("b"); + assertEquals("b", host.getHostPort()); + + host.setHostScheme("c"); + assertEquals("c", host.getHostScheme()); + + host.setResourceDomain("d"); + assertEquals("d", host.getResourceDomain()); + + host.setResourcePort("e"); + assertEquals("e", host.getResourcePort()); + + host.setResourceScheme("f"); + assertEquals("f", host.getResourceScheme()); + + host.setServerAddress("g"); + assertEquals("g", host.getServerAddress()); + + host.setServerPort("h"); + assertEquals("h", host.getServerPort()); + } + + @Test + void attach_withoutContext_createsChildContextForTarget() { + Context parent = new Context(); + VirtualHost host = new VirtualHost(parent); + Restlet target = new MockRestlet(null); + + host.attach(target); + + assertNotNull(target.getContext()); + } + + @Test + void attach_withPathAndTargetHavingContext_keepsExistingContext() { + VirtualHost host = new VirtualHost(new Context()); + Context existing = new Context(); + Restlet target = new MockRestlet(existing); + + host.attach("/foo", target); + + assertSame(existing, target.getContext()); + } + + @Test + void attachDefault_withoutContext_createsChildContextForTarget() { + Context parent = new Context(); + VirtualHost host = new VirtualHost(parent); + Restlet target = new MockRestlet(null); + + host.attachDefault(target); + + assertNotNull(target.getContext()); + assertSame(host.getDefaultRoute().getNext(), target); + } + + @Test + void handle_dispatchesAndSetsCurrentHost() { + VirtualHost host = new VirtualHost(new Context()); + Restlet target = + new Restlet(new Context()) { + @Override + public void handle(Request request, Response response) { + super.handle(request, response); + response.setStatus(Status.SUCCESS_OK); + } + }; + host.attach("/foo", target); + Request request = new Request(Method.GET, "http://localhost/foo"); + request.getResourceRef().setBaseRef(new Reference("http://localhost")); + Response response = new Response(request); + + host.handle(request, response); + + assertEquals(Status.SUCCESS_OK, response.getStatus()); + assertEquals(Integer.valueOf(host.hashCode()), VirtualHost.getCurrent()); + assertNotNull(request.getRootRef()); + } + + @Test + void setContext_recreatesChildContext() { + VirtualHost host = new VirtualHost(); + Context parent = new Context(); + + host.setContext(parent); + + assertNotNull(host.getContext()); + } + + @Test + void setContext_withNull_clearsContext() { + VirtualHost host = new VirtualHost(new Context()); + + host.setContext(null); + + assertNull(host.getContext()); + } + + @Test + void staticCurrentThreadLocal_roundTrip() { + VirtualHost.setCurrent(42); + + assertEquals(Integer.valueOf(42), VirtualHost.getCurrent()); + + VirtualHost.setCurrent(null); + } + + @Test + void getLocalHostNameAndAddress_returnNonNullValues() { + assertNotNull(VirtualHost.getLocalHostName()); + assertNotNull(VirtualHost.getLocalHostAddress()); + } + + @Test + void getIpAddress_forLocalhost_returnsLoopbackAddress() { + String ip = VirtualHost.getIpAddress("localhost"); + + assertNotNull(ip); + } + + @Test + void getIpAddress_forUnknownHost_doesNotThrow() { + // Depending on the local DNS/network configuration, this may resolve to + // null (UnknownHostException) or to some address (e.g. search domain + // wildcards), so we only assert that no exception propagates. + VirtualHost.getIpAddress("this.host.does.not.exist.invalid"); + } +} diff --git a/org.restlet/src/test/java/org/restlet/security/AuthenticatorTestCase.java b/org.restlet/src/test/java/org/restlet/security/AuthenticatorTestCase.java new file mode 100644 index 0000000000..c11076a520 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/security/AuthenticatorTestCase.java @@ -0,0 +1,212 @@ +/** + * 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.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.Status; +import org.restlet.routing.Filter; + +/** Unit tests for {@link Authenticator}. */ +class AuthenticatorTestCase { + + /** Minimal concrete fixture, since {@link Authenticator} is abstract. */ + private static class TestAuthenticator extends Authenticator { + + private boolean authenticateResult; + + private int authenticateCallCount; + + TestAuthenticator(Context context) { + super(context); + } + + TestAuthenticator(Context context, boolean optional) { + super(context, optional); + } + + TestAuthenticator( + Context context, boolean multiAuthenticating, boolean optional, Enroler enroler) { + super(context, multiAuthenticating, optional, enroler); + } + + void setAuthenticateResult(boolean authenticateResult) { + this.authenticateResult = authenticateResult; + } + + int getAuthenticateCallCount() { + return authenticateCallCount; + } + + @Override + protected boolean authenticate(Request request, Response response) { + authenticateCallCount++; + return authenticateResult; + } + } + + @Test + void constructorWithContext_defaultsToRequiredAndMultiAuthenticating() { + TestAuthenticator authenticator = new TestAuthenticator(new Context()); + assertFalse(authenticator.isOptional()); + assertTrue(authenticator.isMultiAuthenticating()); + } + + @Test + void constructorWithOptional_setsOptionalFlag() { + TestAuthenticator authenticator = new TestAuthenticator(new Context(), true); + assertTrue(authenticator.isOptional()); + assertTrue(authenticator.isMultiAuthenticating()); + } + + @Test + void constructorWithContext_usesContextDefaultEnroler() { + Enroler enroler = clientInfo -> {}; + Context context = new Context(); + context.setDefaultEnroler(enroler); + + TestAuthenticator authenticator = new TestAuthenticator(context); + + assertSame(enroler, authenticator.getEnroler()); + } + + @Test + void getEnrolerSetEnroler_roundTrip() { + TestAuthenticator authenticator = new TestAuthenticator(null, false, false, null); + assertNull(authenticator.getEnroler()); + + Enroler enroler = clientInfo -> {}; + authenticator.setEnroler(enroler); + assertSame(enroler, authenticator.getEnroler()); + } + + @Test + void isOptionalSetOptional_roundTrip() { + TestAuthenticator authenticator = new TestAuthenticator(null, false, false, null); + assertFalse(authenticator.isOptional()); + authenticator.setOptional(true); + assertTrue(authenticator.isOptional()); + } + + @Test + void isMultiAuthenticatingSetMultiAuthenticating_roundTrip() { + TestAuthenticator authenticator = new TestAuthenticator(null, false, false, null); + assertFalse(authenticator.isMultiAuthenticating()); + authenticator.setMultiAuthenticating(true); + assertTrue(authenticator.isMultiAuthenticating()); + } + + @Test + void beforeHandle_whenAuthenticateSucceeds_returnsContinueAndMarksAuthenticated() { + TestAuthenticator authenticator = new TestAuthenticator(null, true, false, null); + authenticator.setAuthenticateResult(true); + Request request = new Request(); + Response response = new Response(request); + + int result = authenticator.beforeHandle(request, response); + + assertEquals(Filter.CONTINUE, result); + assertTrue(request.getClientInfo().isAuthenticated()); + } + + @Test + void beforeHandle_whenAuthenticateFailsAndNotOptional_returnsStopAndMarksUnauthenticated() { + TestAuthenticator authenticator = new TestAuthenticator(null, true, false, null); + authenticator.setAuthenticateResult(false); + Request request = new Request(); + Response response = new Response(request); + + int result = authenticator.beforeHandle(request, response); + + assertEquals(Filter.STOP, result); + assertFalse(request.getClientInfo().isAuthenticated()); + } + + @Test + void beforeHandle_whenAuthenticateFailsAndOptional_returnsContinueAndSetsOkStatus() { + TestAuthenticator authenticator = new TestAuthenticator(null, true, true, null); + authenticator.setAuthenticateResult(false); + Request request = new Request(); + Response response = new Response(request); + + int result = authenticator.beforeHandle(request, response); + + assertEquals(Filter.CONTINUE, result); + assertEquals(Status.SUCCESS_OK, response.getStatus()); + } + + @Test + void beforeHandle_whenAlreadyAuthenticatedAndNotMultiAuthenticating_skipsAuthenticate() { + TestAuthenticator authenticator = new TestAuthenticator(null, false, false, null); + authenticator.setAuthenticateResult(true); + Request request = new Request(); + request.getClientInfo().setAuthenticated(true); + Response response = new Response(request); + + int result = authenticator.beforeHandle(request, response); + + assertEquals(Filter.CONTINUE, result); + assertEquals(0, authenticator.getAuthenticateCallCount()); + } + + @Test + void beforeHandle_whenAlreadyAuthenticatedAndMultiAuthenticating_reAuthenticates() { + TestAuthenticator authenticator = new TestAuthenticator(null, true, false, null); + authenticator.setAuthenticateResult(true); + Request request = new Request(); + request.getClientInfo().setAuthenticated(true); + Response response = new Response(request); + + authenticator.beforeHandle(request, response); + + assertEquals(1, authenticator.getAuthenticateCallCount()); + } + + @Test + void authenticated_clearsChallengeRequestsAndInvokesEnroler() { + java.util.concurrent.atomic.AtomicBoolean enroled = + new java.util.concurrent.atomic.AtomicBoolean(false); + Enroler enroler = clientInfo -> enroled.set(true); + TestAuthenticator authenticator = new TestAuthenticator(null, true, false, enroler); + Request request = new Request(); + Response response = new Response(request); + response.getChallengeRequests() + .add( + new org.restlet.data.ChallengeRequest( + org.restlet.data.ChallengeScheme.HTTP_BASIC, "realm")); + + int result = authenticator.authenticated(request, response); + + assertEquals(Filter.CONTINUE, result); + assertTrue(request.getClientInfo().isAuthenticated()); + assertTrue(response.getChallengeRequests().isEmpty()); + assertTrue(enroled.get()); + } + + @Test + void unauthenticated_marksUnauthenticatedAndReturnsStop() { + TestAuthenticator authenticator = new TestAuthenticator(null, true, false, null); + Request request = new Request(); + request.getClientInfo().setAuthenticated(true); + Response response = new Response(request); + + int result = authenticator.unauthenticated(request, response); + + assertEquals(Filter.STOP, result); + assertFalse(request.getClientInfo().isAuthenticated()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/security/AuthorizerTestCase.java b/org.restlet/src/test/java/org/restlet/security/AuthorizerTestCase.java new file mode 100644 index 0000000000..8805fe78aa --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/security/AuthorizerTestCase.java @@ -0,0 +1,158 @@ +/** + * 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.assertNull; +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.Status; +import org.restlet.routing.Filter; + +/** Unit tests for {@link Authorizer}. */ +class AuthorizerTestCase { + + /** Minimal concrete fixture, since {@link Authorizer} is abstract. */ + private static class TestAuthorizer extends Authorizer { + + private boolean authorizeResult; + + TestAuthorizer() { + super(); + } + + TestAuthorizer(String identifier) { + super(identifier); + } + + void setAuthorizeResult(boolean authorizeResult) { + this.authorizeResult = authorizeResult; + } + + @Override + protected boolean authorize(Request request, Response response) { + return authorizeResult; + } + } + + @Test + void defaultConstructor_hasNullIdentifier() { + TestAuthorizer authorizer = new TestAuthorizer(); + assertNull(authorizer.getIdentifier()); + } + + @Test + void constructorWithIdentifier_setsIdentifier() { + TestAuthorizer authorizer = new TestAuthorizer("my-id"); + assertEquals("my-id", authorizer.getIdentifier()); + } + + @Test + void getIdentifierSetIdentifier_roundTrip() { + TestAuthorizer authorizer = new TestAuthorizer(); + authorizer.setIdentifier("another-id"); + assertEquals("another-id", authorizer.getIdentifier()); + } + + @Test + void authorized_default_returnsContinue() { + TestAuthorizer authorizer = new TestAuthorizer(); + Request request = new Request(); + Response response = new Response(request); + + assertEquals(Filter.CONTINUE, authorizer.authorized(request, response)); + } + + @Test + void unauthorized_default_setsForbiddenStatusAndReturnsStop() { + TestAuthorizer authorizer = new TestAuthorizer(); + Request request = new Request(); + Response response = new Response(request); + + int result = authorizer.unauthorized(request, response); + + assertEquals(Filter.STOP, result); + assertEquals(Status.CLIENT_ERROR_FORBIDDEN, response.getStatus()); + } + + @Test + void beforeHandle_whenAuthorizeSucceeds_returnsContinue() { + TestAuthorizer authorizer = new TestAuthorizer(); + authorizer.setAuthorizeResult(true); + Request request = new Request(); + Response response = new Response(request); + + assertEquals(Filter.CONTINUE, authorizer.beforeHandle(request, response)); + } + + @Test + void beforeHandle_whenAuthorizeFails_setsForbiddenAndReturnsStop() { + TestAuthorizer authorizer = new TestAuthorizer(); + authorizer.setAuthorizeResult(false); + Request request = new Request(); + Response response = new Response(request); + + int result = authorizer.beforeHandle(request, response); + + assertEquals(Filter.STOP, result); + assertEquals(Status.CLIENT_ERROR_FORBIDDEN, response.getStatus()); + } + + @Test + void always_authorize_returnsTrue() { + Request request = new Request(); + Response response = new Response(request); + + assertTrue(Authorizer.ALWAYS.authorize(request, response)); + } + + @Test + void never_authorize_returnsFalse() { + Request request = new Request(); + Response response = new Response(request); + + assertFalse(Authorizer.NEVER.authorize(request, response)); + } + + @Test + void authenticated_authorize_returnsTrueOnlyWhenClientAuthenticated() { + Request request = new Request(); + Response response = new Response(request); + assertFalse(Authorizer.AUTHENTICATED.authorize(request, response)); + + request.getClientInfo().setAuthenticated(true); + assertTrue(Authorizer.AUTHENTICATED.authorize(request, response)); + } + + @Test + void authenticated_beforeHandle_whenNotAuthenticated_setsUnauthorizedStatus() { + Request request = new Request(); + Response response = new Response(request); + + int result = Authorizer.AUTHENTICATED.beforeHandle(request, response); + + assertEquals(Filter.STOP, result); + assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus()); + } + + @Test + void authenticated_beforeHandle_whenAuthenticated_returnsContinue() { + Request request = new Request(); + request.getClientInfo().setAuthenticated(true); + Response response = new Response(request); + + int result = Authorizer.AUTHENTICATED.beforeHandle(request, response); + + assertEquals(Filter.CONTINUE, result); + } +} diff --git a/org.restlet/src/test/java/org/restlet/security/ChallengeAuthenticatorTestCase.java b/org.restlet/src/test/java/org/restlet/security/ChallengeAuthenticatorTestCase.java new file mode 100644 index 0000000000..e5fe96323e --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/security/ChallengeAuthenticatorTestCase.java @@ -0,0 +1,308 @@ +/** + * 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.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.ChallengeRequest; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.Status; + +/** Unit tests for {@link ChallengeAuthenticator}. */ +class ChallengeAuthenticatorTestCase { + + /** Verifier returning a fixed result, for exercising each authenticate() branch. */ + private static class FixedResultVerifier implements Verifier { + private final int result; + + FixedResultVerifier(int result) { + this.result = result; + } + + @Override + public int verify(Request request, Response response) { + return result; + } + } + + @Test + void constructorWithRealmAndVerifier_setsFields() { + Verifier verifier = new FixedResultVerifier(Verifier.RESULT_VALID); + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator( + null, true, ChallengeScheme.HTTP_BASIC, "realm", verifier); + + assertTrue(authenticator.isOptional()); + assertEquals(ChallengeScheme.HTTP_BASIC, authenticator.getScheme()); + assertEquals("realm", authenticator.getRealm()); + assertSame(verifier, authenticator.getVerifier()); + assertTrue(authenticator.isRechallenging()); + } + + @Test + void constructorWithoutOptional_defaultsToRequired() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "realm"); + assertFalse(authenticator.isOptional()); + } + + @Test + void constructorWithContext_usesContextDefaultVerifier() { + Verifier verifier = new FixedResultVerifier(Verifier.RESULT_VALID); + Context context = new Context(); + context.setDefaultVerifier(verifier); + + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator(context, true, ChallengeScheme.HTTP_BASIC, "realm"); + + assertSame(verifier, authenticator.getVerifier()); + } + + @Test + void authenticate_validCredentials_returnsTrue() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator( + null, + false, + ChallengeScheme.HTTP_BASIC, + "realm", + new FixedResultVerifier(Verifier.RESULT_VALID)); + Request request = new Request(); + Response response = new Response(request); + + assertTrue(authenticator.authenticate(request, response)); + } + + @Test + void authenticate_missingCredentialsNotOptional_challengesAndReturnsFalse() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator( + null, + false, + ChallengeScheme.HTTP_BASIC, + "realm", + new FixedResultVerifier(Verifier.RESULT_MISSING)); + Request request = new Request(); + Response response = new Response(request); + + boolean result = authenticator.authenticate(request, response); + + assertFalse(result); + assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus()); + assertEquals(1, response.getChallengeRequests().size()); + } + + @Test + void authenticate_missingCredentialsOptional_doesNotChallenge() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator( + null, + true, + ChallengeScheme.HTTP_BASIC, + "realm", + new FixedResultVerifier(Verifier.RESULT_MISSING)); + Request request = new Request(); + Response response = new Response(request); + + boolean result = authenticator.authenticate(request, response); + + assertFalse(result); + assertTrue(response.getChallengeRequests().isEmpty()); + } + + @Test + void authenticate_invalidCredentialsRechallenging_callsChallenge() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator( + null, + false, + ChallengeScheme.HTTP_BASIC, + "realm", + new FixedResultVerifier(Verifier.RESULT_INVALID)); + Request request = new Request(); + Response response = new Response(request); + + boolean result = authenticator.authenticate(request, response); + + assertFalse(result); + assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus()); + assertEquals(1, response.getChallengeRequests().size()); + } + + @Test + void authenticate_invalidCredentialsNotRechallenging_callsForbid() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator( + null, + false, + ChallengeScheme.HTTP_BASIC, + "realm", + new FixedResultVerifier(Verifier.RESULT_INVALID)); + authenticator.setRechallenging(false); + Request request = new Request(); + Response response = new Response(request); + + boolean result = authenticator.authenticate(request, response); + + assertFalse(result); + assertEquals(Status.CLIENT_ERROR_FORBIDDEN, response.getStatus()); + assertTrue(response.getChallengeRequests().isEmpty()); + } + + @Test + void authenticate_staleCredentials_challengesWithStaleFlag() { + final boolean[] staleFlag = new boolean[1]; + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator( + null, + false, + ChallengeScheme.HTTP_BASIC, + "realm", + new FixedResultVerifier(Verifier.RESULT_STALE)) { + @Override + protected ChallengeRequest createChallengeRequest(boolean stale) { + staleFlag[0] = stale; + return super.createChallengeRequest(stale); + } + }; + Request request = new Request(); + Response response = new Response(request); + + boolean result = authenticator.authenticate(request, response); + + assertFalse(result); + assertTrue(staleFlag[0]); + assertEquals(1, response.getChallengeRequests().size()); + } + + @Test + void authenticate_unknownIdentifierRechallenging_callsChallenge() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator( + null, + false, + ChallengeScheme.HTTP_BASIC, + "realm", + new FixedResultVerifier(Verifier.RESULT_UNKNOWN)); + Request request = new Request(); + Response response = new Response(request); + + boolean result = authenticator.authenticate(request, response); + + assertFalse(result); + assertEquals(1, response.getChallengeRequests().size()); + } + + @Test + void authenticate_unknownIdentifierNotRechallenging_callsForbid() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator( + null, + false, + ChallengeScheme.HTTP_BASIC, + "realm", + new FixedResultVerifier(Verifier.RESULT_UNKNOWN)); + authenticator.setRechallenging(false); + Request request = new Request(); + Response response = new Response(request); + + boolean result = authenticator.authenticate(request, response); + + assertFalse(result); + assertEquals(Status.CLIENT_ERROR_FORBIDDEN, response.getStatus()); + } + + @Test + void authenticate_withoutVerifier_setsInternalServerError() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator(null, false, ChallengeScheme.HTTP_BASIC, "realm", null); + Request request = new Request(); + Response response = new Response(request); + + boolean result = authenticator.authenticate(request, response); + + assertFalse(result); + assertEquals(Status.SERVER_ERROR_INTERNAL, response.getStatus()); + } + + @Test + void challenge_addsChallengeRequestAndSetsUnauthorizedStatus() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "realm"); + Request request = new Request(); + Response response = new Response(request); + + authenticator.challenge(response, false); + + assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus()); + assertEquals(1, response.getChallengeRequests().size()); + assertEquals( + ChallengeScheme.HTTP_BASIC, response.getChallengeRequests().get(0).getScheme()); + assertEquals("realm", response.getChallengeRequests().get(0).getRealm()); + } + + @Test + void forbid_setsForbiddenStatus() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "realm"); + Request request = new Request(); + Response response = new Response(request); + + authenticator.forbid(response); + + assertEquals(Status.CLIENT_ERROR_FORBIDDEN, response.getStatus()); + } + + @Test + void createChallengeRequest_returnsRequestWithSchemeAndRealm() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "realm"); + + ChallengeRequest challengeRequest = authenticator.createChallengeRequest(true); + + assertEquals(ChallengeScheme.HTTP_BASIC, challengeRequest.getScheme()); + assertEquals("realm", challengeRequest.getRealm()); + } + + @Test + void getRealmSetRealm_roundTrip() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "realm"); + authenticator.setRealm("other-realm"); + assertEquals("other-realm", authenticator.getRealm()); + } + + @Test + void getVerifierSetVerifier_roundTrip() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "realm"); + assertNull(authenticator.getVerifier()); + + Verifier verifier = new FixedResultVerifier(Verifier.RESULT_VALID); + authenticator.setVerifier(verifier); + assertSame(verifier, authenticator.getVerifier()); + } + + @Test + void isRechallengingSetRechallenging_roundTrip() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "realm"); + assertTrue(authenticator.isRechallenging()); + authenticator.setRechallenging(false); + assertFalse(authenticator.isRechallenging()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/security/GroupTestCase.java b/org.restlet/src/test/java/org/restlet/security/GroupTestCase.java new file mode 100644 index 0000000000..2eb9c1e5c8 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/security/GroupTestCase.java @@ -0,0 +1,161 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link Group}. */ +class GroupTestCase { + + @Test + void defaultConstructor_hasNullNameAndInheritsRoles() { + Group group = new Group(); + + assertNull(group.getName()); + assertNull(group.getDescription()); + assertTrue(group.isInheritingRoles()); + assertTrue(group.getMemberGroups().isEmpty()); + assertTrue(group.getMemberUsers().isEmpty()); + } + + @Test + void constructorWithNameAndDescription_setsFieldsAndInheritsRoles() { + Group group = new Group("admins", "Administrators group"); + + assertEquals("admins", group.getName()); + assertEquals("Administrators group", group.getDescription()); + assertTrue(group.isInheritingRoles()); + } + + @Test + void constructorWithInheritingRolesFalse_setsFieldFalse() { + Group group = new Group("admins", "Administrators group", false); + assertFalse(group.isInheritingRoles()); + } + + @Test + void getNameSetName_roundTrip() { + Group group = new Group(); + group.setName("users"); + assertEquals("users", group.getName()); + } + + @Test + void getDescriptionSetDescription_roundTrip() { + Group group = new Group(); + group.setDescription("desc"); + assertEquals("desc", group.getDescription()); + } + + @Test + void isInheritingRolesSetInheritingRoles_roundTrip() { + Group group = new Group(); + group.setInheritingRoles(false); + assertFalse(group.isInheritingRoles()); + } + + @Test + void getMemberGroups_isModifiableAndTracksMembership() { + Group group = new Group(); + Group child = new Group("child", null); + + group.getMemberGroups().add(child); + + assertEquals(1, group.getMemberGroups().size()); + assertSame(child, group.getMemberGroups().get(0)); + } + + @Test + void getMemberUsers_isModifiableAndTracksMembership() { + Group group = new Group(); + User user = new User("bob"); + + group.getMemberUsers().add(user); + + assertEquals(1, group.getMemberUsers().size()); + assertSame(user, group.getMemberUsers().get(0)); + } + + @Test + void setMemberGroups_withNull_clearsList() { + Group group = new Group(); + group.getMemberGroups().add(new Group("child", null)); + + group.setMemberGroups(null); + + assertTrue(group.getMemberGroups().isEmpty()); + } + + @Test + void setMemberGroups_withNewList_replacesContents() { + Group group = new Group(); + group.getMemberGroups().add(new Group("old", null)); + Group newChild = new Group("new", null); + + group.setMemberGroups(List.of(newChild)); + + assertEquals(1, group.getMemberGroups().size()); + assertSame(newChild, group.getMemberGroups().get(0)); + } + + @Test + void setMemberGroups_withSameListInstance_isNoOp() { + Group group = new Group(); + group.getMemberGroups().add(new Group("child", null)); + + group.setMemberGroups(group.getMemberGroups()); + + assertEquals(1, group.getMemberGroups().size()); + } + + @Test + void setMemberUsers_withNull_clearsList() { + Group group = new Group(); + group.getMemberUsers().add(new User("bob")); + + group.setMemberUsers(null); + + assertTrue(group.getMemberUsers().isEmpty()); + } + + @Test + void setMemberUsers_withNewList_replacesContents() { + Group group = new Group(); + group.getMemberUsers().add(new User("old")); + User newUser = new User("new"); + + group.setMemberUsers(List.of(newUser)); + + assertEquals(1, group.getMemberUsers().size()); + assertSame(newUser, group.getMemberUsers().get(0)); + } + + @Test + void setMemberUsers_withSameListInstance_isNoOp() { + Group group = new Group(); + group.getMemberUsers().add(new User("bob")); + + group.setMemberUsers(group.getMemberUsers()); + + assertEquals(1, group.getMemberUsers().size()); + } + + @Test + void toString_returnsName() { + Group group = new Group("admins", null); + assertEquals("admins", group.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/security/LocalVerifierTestCase.java b/org.restlet/src/test/java/org/restlet/security/LocalVerifierTestCase.java new file mode 100644 index 0000000000..cd608d10d6 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/security/LocalVerifierTestCase.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.security; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link LocalVerifier}. */ +class LocalVerifierTestCase { + + /** Minimal concrete fixture, since {@link LocalVerifier} is abstract. */ + private static class MapBackedLocalVerifier extends LocalVerifier { + + private final Map secrets = new HashMap<>(); + + void putSecret(String identifier, char[] secret) { + secrets.put(identifier, secret); + } + + @Override + public char[] getLocalSecret(String identifier) { + return secrets.get(identifier); + } + } + + @Test + void verify_withMatchingSecret_returnsValid() { + MapBackedLocalVerifier verifier = new MapBackedLocalVerifier(); + verifier.putSecret("alice", "secret".toCharArray()); + + assertEquals(Verifier.RESULT_VALID, verifier.verify("alice", "secret".toCharArray())); + } + + @Test + void verify_withNonMatchingSecret_returnsInvalid() { + MapBackedLocalVerifier verifier = new MapBackedLocalVerifier(); + verifier.putSecret("alice", "secret".toCharArray()); + + assertEquals(Verifier.RESULT_INVALID, verifier.verify("alice", "wrong".toCharArray())); + } + + @Test + void verify_withUnknownIdentifier_returnsInvalid() { + MapBackedLocalVerifier verifier = new MapBackedLocalVerifier(); + + assertEquals(Verifier.RESULT_INVALID, verifier.verify("unknown", "secret".toCharArray())); + } + + @Test + void getLocalSecret_returnsConfiguredValue() { + MapBackedLocalVerifier verifier = new MapBackedLocalVerifier(); + char[] secret = "secret".toCharArray(); + verifier.putSecret("alice", secret); + + assertSame(secret, verifier.getLocalSecret("alice")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/security/MapVerifierTestCase.java b/org.restlet/src/test/java/org/restlet/security/MapVerifierTestCase.java new file mode 100644 index 0000000000..ac4c2f55ca --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/security/MapVerifierTestCase.java @@ -0,0 +1,149 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.junit.jupiter.api.Test; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; + +/** Unit tests for {@link MapVerifier}. */ +class MapVerifierTestCase { + + @Test + void defaultConstructor_hasEmptyLocalSecrets() { + MapVerifier verifier = new MapVerifier(); + assertTrue(verifier.getLocalSecrets().isEmpty()); + } + + @Test + void constructorWithMap_usesProvidedMap() { + ConcurrentMap secrets = new ConcurrentHashMap<>(); + secrets.put("alice", "secret".toCharArray()); + + MapVerifier verifier = new MapVerifier(secrets); + + assertSame(secrets, verifier.getLocalSecrets()); + } + + @Test + void getLocalSecret_withNullIdentifier_returnsNull() { + MapVerifier verifier = new MapVerifier(); + assertNull(verifier.getLocalSecret(null)); + } + + @Test + void getLocalSecret_withKnownIdentifier_returnsSecret() { + MapVerifier verifier = new MapVerifier(); + verifier.getLocalSecrets().put("alice", "secret".toCharArray()); + + assertEquals("secret", new String(verifier.getLocalSecret("alice"))); + } + + @Test + void getLocalSecret_withUnknownIdentifier_returnsNull() { + MapVerifier verifier = new MapVerifier(); + assertNull(verifier.getLocalSecret("unknown")); + } + + @Test + void setLocalSecrets_withNull_clearsMap() { + MapVerifier verifier = new MapVerifier(); + verifier.getLocalSecrets().put("alice", "secret".toCharArray()); + + verifier.setLocalSecrets(null); + + assertTrue(verifier.getLocalSecrets().isEmpty()); + } + + @Test + void setLocalSecrets_withNewMap_replacesContents() { + MapVerifier verifier = new MapVerifier(); + verifier.getLocalSecrets().put("old", "secret".toCharArray()); + + verifier.setLocalSecrets(Map.of("new", "other".toCharArray())); + + assertEquals(1, verifier.getLocalSecrets().size()); + assertTrue(verifier.getLocalSecrets().containsKey("new")); + } + + @Test + void setLocalSecrets_withSameMapInstance_isNoOp() { + MapVerifier verifier = new MapVerifier(); + verifier.getLocalSecrets().put("alice", "secret".toCharArray()); + + verifier.setLocalSecrets(verifier.getLocalSecrets()); + + assertEquals(1, verifier.getLocalSecrets().size()); + } + + @Test + void verifyIdentifierSecret_withMatchingSecret_returnsValid() { + MapVerifier verifier = new MapVerifier(); + verifier.getLocalSecrets().put("alice", "secret".toCharArray()); + + assertEquals(Verifier.RESULT_VALID, verifier.verify("alice", "secret".toCharArray())); + } + + @Test + void verifyIdentifierSecret_withWrongSecret_returnsInvalid() { + MapVerifier verifier = new MapVerifier(); + verifier.getLocalSecrets().put("alice", "secret".toCharArray()); + + assertEquals(Verifier.RESULT_INVALID, verifier.verify("alice", "wrong".toCharArray())); + } + + @Test + void verifyRequestResponse_withMissingChallengeResponse_returnsMissing() { + MapVerifier verifier = new MapVerifier(); + Request request = new Request(); + Response response = new Response(request); + + assertEquals(Verifier.RESULT_MISSING, verifier.verify(request, response)); + } + + @Test + void verifyRequestResponse_withValidCredentials_setsUserOnClientInfo() { + MapVerifier verifier = new MapVerifier(); + verifier.getLocalSecrets().put("alice", "secret".toCharArray()); + Request request = new Request(); + request.setChallengeResponse( + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "alice", "secret")); + Response response = new Response(request); + + int result = verifier.verify(request, response); + + assertEquals(Verifier.RESULT_VALID, result); + assertEquals("alice", request.getClientInfo().getUser().getIdentifier()); + } + + @Test + void verifyRequestResponse_withInvalidCredentials_doesNotSetUser() { + MapVerifier verifier = new MapVerifier(); + verifier.getLocalSecrets().put("alice", "secret".toCharArray()); + Request request = new Request(); + request.setChallengeResponse( + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "alice", "wrong")); + Response response = new Response(request); + + int result = verifier.verify(request, response); + + assertEquals(Verifier.RESULT_INVALID, result); + assertNull(request.getClientInfo().getUser()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/security/RealmTestCase.java b/org.restlet/src/test/java/org/restlet/security/RealmTestCase.java new file mode 100644 index 0000000000..b9ddf49b77 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/security/RealmTestCase.java @@ -0,0 +1,142 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.CopyOnWriteArrayList; +import org.junit.jupiter.api.Test; +import org.restlet.data.Parameter; +import org.restlet.util.Series; + +/** Unit tests for {@link Realm}. */ +class RealmTestCase { + + /** Minimal concrete fixture, since {@link Realm} is abstract. */ + private static class TestRealm extends Realm { + + TestRealm() { + super(); + } + + TestRealm(Verifier verifier, Enroler enroler) { + super(verifier, enroler); + } + } + + private static final Verifier NO_OP_VERIFIER = (request, response) -> Verifier.RESULT_VALID; + + private static final Enroler NO_OP_ENROLER = clientInfo -> {}; + + @Test + void defaultConstructor_hasNullNameVerifierAndEnroler() { + TestRealm realm = new TestRealm(); + + assertNull(realm.getName()); + assertNull(realm.getVerifier()); + assertNull(realm.getEnroler()); + assertTrue(realm.getParameters().isEmpty()); + assertFalse(realm.isStarted()); + assertTrue(realm.isStopped()); + } + + @Test + void constructorWithVerifierAndEnroler_setsFields() { + TestRealm realm = new TestRealm(NO_OP_VERIFIER, NO_OP_ENROLER); + + assertSame(NO_OP_VERIFIER, realm.getVerifier()); + assertSame(NO_OP_ENROLER, realm.getEnroler()); + } + + @Test + void getNameSetName_roundTrip() { + TestRealm realm = new TestRealm(); + realm.setName("my-realm"); + assertEquals("my-realm", realm.getName()); + } + + @Test + void getVerifierSetVerifier_roundTrip() { + TestRealm realm = new TestRealm(); + realm.setVerifier(NO_OP_VERIFIER); + assertSame(NO_OP_VERIFIER, realm.getVerifier()); + } + + @Test + void getEnrolerSetEnroler_roundTrip() { + TestRealm realm = new TestRealm(); + realm.setEnroler(NO_OP_ENROLER); + assertSame(NO_OP_ENROLER, realm.getEnroler()); + } + + @Test + void getParameters_isModifiable() { + TestRealm realm = new TestRealm(); + realm.getParameters().add("name", "value"); + assertEquals(1, realm.getParameters().size()); + } + + @Test + void setParameters_withNull_clearsSeries() { + TestRealm realm = new TestRealm(); + realm.getParameters().add("name", "value"); + + realm.setParameters(null); + + assertTrue(realm.getParameters().isEmpty()); + } + + @Test + void setParameters_withNewSeries_replacesContents() { + TestRealm realm = new TestRealm(); + realm.getParameters().add("old", "value"); + + Series newParameters = + new Series<>(Parameter.class, new CopyOnWriteArrayList()); + newParameters.add("new", "value"); + realm.setParameters(newParameters); + + assertEquals(1, realm.getParameters().size()); + assertEquals("new", realm.getParameters().get(0).getName()); + } + + @Test + void setParameters_withSameSeriesInstance_isNoOp() { + TestRealm realm = new TestRealm(); + realm.getParameters().add("name", "value"); + + realm.setParameters(realm.getParameters()); + + assertEquals(1, realm.getParameters().size()); + } + + @Test + void startStop_updateStartedAndStoppedState() throws Exception { + TestRealm realm = new TestRealm(); + + realm.start(); + assertTrue(realm.isStarted()); + assertFalse(realm.isStopped()); + + realm.stop(); + assertFalse(realm.isStarted()); + assertTrue(realm.isStopped()); + } + + @Test + void toString_returnsName() { + TestRealm realm = new TestRealm(); + realm.setName("my-realm"); + assertEquals("my-realm", realm.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/security/RoleAuthorizerTestCase.java b/org.restlet/src/test/java/org/restlet/security/RoleAuthorizerTestCase.java new file mode 100644 index 0000000000..b8d8e2336c --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/security/RoleAuthorizerTestCase.java @@ -0,0 +1,142 @@ +/** + * 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.assertNull; +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; + +/** Unit tests for {@link RoleAuthorizer}. */ +class RoleAuthorizerTestCase { + + private Request requestWithRoles(Role... roles) { + Request request = new Request(); + request.getClientInfo().getRoles().addAll(List.of(roles)); + return request; + } + + @Test + void defaultConstructor_hasEmptyRoleListsAndNullIdentifier() { + RoleAuthorizer authorizer = new RoleAuthorizer(); + + assertNull(authorizer.getIdentifier()); + assertTrue(authorizer.getAuthorizedRoles().isEmpty()); + assertTrue(authorizer.getForbiddenRoles().isEmpty()); + } + + @Test + void constructorWithIdentifier_setsIdentifierAndEmptyLists() { + RoleAuthorizer authorizer = new RoleAuthorizer("my-id"); + + assertEquals("my-id", authorizer.getIdentifier()); + assertTrue(authorizer.getAuthorizedRoles().isEmpty()); + assertTrue(authorizer.getForbiddenRoles().isEmpty()); + } + + @Test + void authorize_withNoAuthorizedRolesConfigured_returnsTrue() { + RoleAuthorizer authorizer = new RoleAuthorizer(); + Request request = requestWithRoles(); + + assertTrue(authorizer.authorize(request, new Response(request))); + } + + @Test + void authorize_withMatchingAuthorizedRole_returnsTrue() { + Role adminRole = new Role(null, "admin", null); + RoleAuthorizer authorizer = new RoleAuthorizer(); + authorizer.getAuthorizedRoles().add(adminRole); + + Request request = requestWithRoles(adminRole); + + assertTrue(authorizer.authorize(request, new Response(request))); + } + + @Test + void authorize_withoutMatchingAuthorizedRole_returnsFalse() { + Role adminRole = new Role(null, "admin", null); + Role userRole = new Role(null, "user", null); + RoleAuthorizer authorizer = new RoleAuthorizer(); + authorizer.getAuthorizedRoles().add(adminRole); + + Request request = requestWithRoles(userRole); + + assertFalse(authorizer.authorize(request, new Response(request))); + } + + @Test + void authorize_withMatchingForbiddenRole_returnsFalseEvenWithoutAuthorizedRoles() { + Role bannedRole = new Role(null, "banned", null); + RoleAuthorizer authorizer = new RoleAuthorizer(); + authorizer.getForbiddenRoles().add(bannedRole); + + Request request = requestWithRoles(bannedRole); + + assertFalse(authorizer.authorize(request, new Response(request))); + } + + @Test + void authorize_withAuthorizedAndForbiddenRole_returnsFalse() { + Role adminRole = new Role(null, "admin", null); + Role bannedRole = new Role(null, "banned", null); + RoleAuthorizer authorizer = new RoleAuthorizer(); + authorizer.getAuthorizedRoles().add(adminRole); + authorizer.getForbiddenRoles().add(bannedRole); + + Request request = requestWithRoles(adminRole, bannedRole); + + assertFalse(authorizer.authorize(request, new Response(request))); + } + + @Test + void setAuthorizedRoles_withNull_clearsList() { + RoleAuthorizer authorizer = new RoleAuthorizer(); + authorizer.getAuthorizedRoles().add(new Role(null, "admin", null)); + + authorizer.setAuthorizedRoles(null); + + assertTrue(authorizer.getAuthorizedRoles().isEmpty()); + } + + @Test + void setAuthorizedRoles_withSameListInstance_isNoOp() { + RoleAuthorizer authorizer = new RoleAuthorizer(); + authorizer.getAuthorizedRoles().add(new Role(null, "admin", null)); + + authorizer.setAuthorizedRoles(authorizer.getAuthorizedRoles()); + + assertEquals(1, authorizer.getAuthorizedRoles().size()); + } + + @Test + void setForbiddenRoles_withNull_clearsList() { + RoleAuthorizer authorizer = new RoleAuthorizer(); + authorizer.getForbiddenRoles().add(new Role(null, "banned", null)); + + authorizer.setForbiddenRoles(null); + + assertTrue(authorizer.getForbiddenRoles().isEmpty()); + } + + @Test + void setForbiddenRoles_withSameListInstance_isNoOp() { + RoleAuthorizer authorizer = new RoleAuthorizer(); + authorizer.getForbiddenRoles().add(new Role(null, "banned", null)); + + authorizer.setForbiddenRoles(authorizer.getForbiddenRoles()); + + assertEquals(1, authorizer.getForbiddenRoles().size()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/security/SecretVerifierTestCase.java b/org.restlet/src/test/java/org/restlet/security/SecretVerifierTestCase.java new file mode 100644 index 0000000000..c4b6f1c03b --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/security/SecretVerifierTestCase.java @@ -0,0 +1,131 @@ +/** + * 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.assertNull; +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.ChallengeResponse; +import org.restlet.data.ChallengeScheme; + +/** Unit tests for {@link SecretVerifier}. */ +class SecretVerifierTestCase { + + /** Minimal concrete fixture, since {@link SecretVerifier} is abstract. */ + private static class TestSecretVerifier extends SecretVerifier { + + @Override + public int verify(String identifier, char[] secret) { + return "alice".equals(identifier) && compare(secret, "secret".toCharArray()) + ? RESULT_VALID + : RESULT_INVALID; + } + } + + @Test + void compare_withEqualSecrets_returnsTrue() { + assertTrue(SecretVerifier.compare("secret".toCharArray(), "secret".toCharArray())); + } + + @Test + void compare_withDifferentSecrets_returnsFalse() { + assertFalse(SecretVerifier.compare("secret".toCharArray(), "other!".toCharArray())); + } + + @Test + void compare_withDifferentLengths_returnsFalse() { + assertFalse(SecretVerifier.compare("secret".toCharArray(), "short".toCharArray())); + } + + @Test + void compare_withOneNullSecret_returnsFalse() { + assertFalse(SecretVerifier.compare(null, "secret".toCharArray())); + assertFalse(SecretVerifier.compare("secret".toCharArray(), null)); + } + + @Test + void compare_withBothNullSecrets_returnsFalse() { + assertFalse(SecretVerifier.compare(null, null)); + } + + @Test + void createUser_returnsUserWithGivenIdentifier() { + TestSecretVerifier verifier = new TestSecretVerifier(); + Request request = new Request(); + Response response = new Response(request); + + User user = verifier.createUser("alice", request, response); + + assertEquals("alice", user.getIdentifier()); + } + + @Test + void getIdentifier_extractsFromChallengeResponse() { + TestSecretVerifier verifier = new TestSecretVerifier(); + Request request = new Request(); + request.setChallengeResponse( + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "alice", "secret")); + Response response = new Response(request); + + assertEquals("alice", verifier.getIdentifier(request, response)); + } + + @Test + void getSecret_extractsFromChallengeResponse() { + TestSecretVerifier verifier = new TestSecretVerifier(); + Request request = new Request(); + request.setChallengeResponse( + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "alice", "secret")); + Response response = new Response(request); + + assertEquals("secret", new String(verifier.getSecret(request, response))); + } + + @Test + void verify_withoutChallengeResponse_returnsMissing() { + TestSecretVerifier verifier = new TestSecretVerifier(); + Request request = new Request(); + Response response = new Response(request); + + assertEquals(Verifier.RESULT_MISSING, verifier.verify(request, response)); + } + + @Test + void verify_withValidCredentials_setsUserOnClientInfo() { + TestSecretVerifier verifier = new TestSecretVerifier(); + Request request = new Request(); + request.setChallengeResponse( + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "alice", "secret")); + Response response = new Response(request); + + int result = verifier.verify(request, response); + + assertEquals(Verifier.RESULT_VALID, result); + assertEquals("alice", request.getClientInfo().getUser().getIdentifier()); + } + + @Test + void verify_withInvalidCredentials_doesNotSetUser() { + TestSecretVerifier verifier = new TestSecretVerifier(); + Request request = new Request(); + request.setChallengeResponse( + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "alice", "wrong")); + Response response = new Response(request); + + int result = verifier.verify(request, response); + + assertEquals(Verifier.RESULT_INVALID, result); + assertNull(request.getClientInfo().getUser()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/security/UserTestCase.java b/org.restlet/src/test/java/org/restlet/security/UserTestCase.java new file mode 100644 index 0000000000..4cf6164610 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/security/UserTestCase.java @@ -0,0 +1,126 @@ +/** + * 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.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link User}. */ +class UserTestCase { + + @Test + void defaultConstructor_hasNullFields() { + User user = new User(); + + assertNull(user.getIdentifier()); + assertNull(user.getSecret()); + assertNull(user.getFirstName()); + assertNull(user.getLastName()); + assertNull(user.getEmail()); + } + + @Test + void constructorWithIdentifier_setsIdentifierOnly() { + User user = new User("alice"); + + assertEquals("alice", user.getIdentifier()); + assertNull(user.getSecret()); + } + + @Test + void constructorWithIdentifierAndCharSecret_setsBoth() { + char[] secret = "secret".toCharArray(); + User user = new User("alice", secret); + + assertEquals("alice", user.getIdentifier()); + assertArrayEquals(secret, user.getSecret()); + } + + @Test + void constructorWithIdentifierAndStringSecret_convertsToCharArray() { + User user = new User("alice", "secret"); + + assertEquals("alice", user.getIdentifier()); + assertArrayEquals("secret".toCharArray(), user.getSecret()); + } + + @Test + void constructorWithAllFieldsAndCharSecret_setsAllFields() { + char[] secret = "secret".toCharArray(); + User user = new User("alice", secret, "Alice", "Wonderland", "alice@example.com"); + + assertEquals("alice", user.getIdentifier()); + assertArrayEquals(secret, user.getSecret()); + assertEquals("Alice", user.getFirstName()); + assertEquals("Wonderland", user.getLastName()); + assertEquals("alice@example.com", user.getEmail()); + } + + @Test + void constructorWithAllFieldsAndStringSecret_setsAllFields() { + User user = new User("alice", "secret", "Alice", "Wonderland", "alice@example.com"); + + assertEquals("alice", user.getIdentifier()); + assertArrayEquals("secret".toCharArray(), user.getSecret()); + assertEquals("Alice", user.getFirstName()); + assertEquals("Wonderland", user.getLastName()); + assertEquals("alice@example.com", user.getEmail()); + } + + @Test + void getIdentifierSetIdentifier_roundTrip() { + User user = new User(); + user.setIdentifier("bob"); + assertEquals("bob", user.getIdentifier()); + } + + @Test + void getSecretSetSecret_roundTrip() { + User user = new User(); + char[] secret = "secret".toCharArray(); + user.setSecret(secret); + assertArrayEquals(secret, user.getSecret()); + } + + @Test + void getFirstNameSetFirstName_roundTrip() { + User user = new User(); + user.setFirstName("Bob"); + assertEquals("Bob", user.getFirstName()); + } + + @Test + void getLastNameSetLastName_roundTrip() { + User user = new User(); + user.setLastName("Builder"); + assertEquals("Builder", user.getLastName()); + } + + @Test + void getEmailSetEmail_roundTrip() { + User user = new User(); + user.setEmail("bob@example.com"); + assertEquals("bob@example.com", user.getEmail()); + } + + @Test + void getName_returnsIdentifier() { + User user = new User("bob"); + assertEquals("bob", user.getName()); + } + + @Test + void toString_returnsIdentifier() { + User user = new User("bob"); + assertEquals("bob", user.toString()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/service/ConnectorServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/ConnectorServiceTestCase.java new file mode 100644 index 0000000000..e08094046f --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/service/ConnectorServiceTestCase.java @@ -0,0 +1,104 @@ +/** + * 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.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.data.Protocol; +import org.restlet.representation.StringRepresentation; + +/** Unit tests for {@link ConnectorService}. */ +class ConnectorServiceTestCase { + + @Test + void defaultConstructor_hasEmptyProtocolLists() { + ConnectorService service = new ConnectorService(); + + assertTrue(service.getClientProtocols().isEmpty()); + assertTrue(service.getServerProtocols().isEmpty()); + } + + @Test + void setClientProtocols_replacesListContents() { + ConnectorService service = new ConnectorService(); + service.getClientProtocols().add(Protocol.FTP); + + service.setClientProtocols(Arrays.asList(Protocol.HTTP, Protocol.HTTPS)); + + assertEquals(2, service.getClientProtocols().size()); + assertTrue(service.getClientProtocols().contains(Protocol.HTTP)); + assertTrue(service.getClientProtocols().contains(Protocol.HTTPS)); + } + + @Test + void setClientProtocols_withNull_clearsList() { + ConnectorService service = new ConnectorService(); + service.getClientProtocols().add(Protocol.HTTP); + + service.setClientProtocols(null); + + assertTrue(service.getClientProtocols().isEmpty()); + } + + @Test + void setServerProtocols_replacesListContents() { + ConnectorService service = new ConnectorService(); + service.getServerProtocols().add(Protocol.FTP); + + service.setServerProtocols(Arrays.asList(Protocol.HTTP)); + + assertEquals(1, service.getServerProtocols().size()); + assertTrue(service.getServerProtocols().contains(Protocol.HTTP)); + } + + @Test + void setServerProtocols_withNull_clearsList() { + ConnectorService service = new ConnectorService(); + service.getServerProtocols().add(Protocol.HTTP); + + service.setServerProtocols(null); + + assertTrue(service.getServerProtocols().isEmpty()); + } + + @Test + void setServerProtocols_withSameListReference_isNoOp() { + ConnectorService service = new ConnectorService(); + service.getServerProtocols().add(Protocol.HTTP); + List same = service.getServerProtocols(); + + service.setServerProtocols(same); + + assertEquals(1, service.getServerProtocols().size()); + } + + @Test + void beforeSendAndAfterSend_doNotThrow() { + ConnectorService service = new ConnectorService(); + + service.beforeSend(new StringRepresentation("test")); + service.afterSend(new StringRepresentation("test")); + service.beforeSend(null); + service.afterSend(null); + } + + @Test + void getClientProtocols_returnsModifiableList() { + ConnectorService service = new ConnectorService(); + + service.getClientProtocols().addAll(new ArrayList<>(List.of(Protocol.HTTP))); + + assertEquals(1, service.getClientProtocols().size()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/service/ConverterServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/ConverterServiceTestCase.java new file mode 100644 index 0000000000..62308c87ca --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/service/ConverterServiceTestCase.java @@ -0,0 +1,133 @@ +/** + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.List; +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.Engine; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.representation.Variant; + +/** Unit tests for {@link ConverterService}. */ +class ConverterServiceTestCase { + + @BeforeEach + void setUp() { + Engine.register(); + } + + @AfterEach + void tearDown() { + Engine.clearThreadLocalVariables(); + } + + @Test + void constructors_createUsableInstances() { + assertNotNull(new ConverterService()); + ConverterService disabled = new ConverterService(false); + assertEquals(false, disabled.isEnabled()); + } + + @Test + void toRepresentation_convertsStringToStringRepresentation() throws Exception { + ConverterService service = new ConverterService(); + + Representation representation = service.toRepresentation("hello"); + + assertNotNull(representation); + assertEquals("hello", representation.getText()); + assertEquals(MediaType.TEXT_PLAIN, representation.getMediaType()); + } + + @Test + void toRepresentation_withTargetMediaType_usesRequestedType() throws Exception { + ConverterService service = new ConverterService(); + + Representation representation = service.toRepresentation("hello", MediaType.TEXT_HTML); + + assertNotNull(representation); + assertEquals(MediaType.TEXT_HTML, representation.getMediaType()); + } + + @Test + void toObject_convertsRepresentationToString() throws Exception { + ConverterService service = new ConverterService(); + Representation source = new StringRepresentation("test content", MediaType.TEXT_PLAIN); + + Object result = service.toObject(source, String.class, null); + + assertEquals("test content", result); + } + + @Test + void toObject_withEmptyRepresentation_returnsNull() throws Exception { + ConverterService service = new ConverterService(); + + Object result = service.toObject(null); + + assertNull(result); + } + + @Test + void getObjectClasses_forTextVariant_includesString() { + ConverterService service = new ConverterService(); + Variant variant = new Variant(MediaType.TEXT_PLAIN); + + List> classes = service.getObjectClasses(variant); + + assertNotNull(classes); + assertEquals(true, classes.contains(String.class)); + } + + @Test + void getVariants_forStringSource_returnsNonEmptyList() throws Exception { + ConverterService service = new ConverterService(); + + List variants = + service.getVariants(String.class, new Variant(MediaType.TEXT_PLAIN)); + + assertNotNull(variants); + } + + @Test + void applyPatchCreatePatchRevertPatch_returnNullByDefault() throws Exception { + ConverterService service = new ConverterService(); + Representation initial = new StringRepresentation("initial"); + Representation modified = new StringRepresentation("modified"); + + assertNull(service.applyPatch(initial, modified)); + assertNull(service.createPatch(initial, modified)); + assertNull(service.revertPatch(modified, initial)); + } + + @Test + void getPatchTypes_returnsNullByDefault() { + ConverterService service = new ConverterService(); + + assertNull(service.getPatchTypes(MediaType.TEXT_PLAIN)); + } + + @Test + void updatePreferences_doesNotThrowForStringEntity() { + ConverterService service = new ConverterService(); + List> preferences = new ArrayList<>(); + + service.updatePreferences(preferences, String.class); + } +} diff --git a/org.restlet/src/test/java/org/restlet/service/DecoderServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/DecoderServiceTestCase.java new file mode 100644 index 0000000000..87119835dd --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/service/DecoderServiceTestCase.java @@ -0,0 +1,53 @@ +/** + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.engine.application.Decoder; +import org.restlet.routing.Filter; + +/** Unit tests for {@link DecoderService}. */ +class DecoderServiceTestCase { + + @Test + void defaultConstructor_isEnabled() { + DecoderService service = new DecoderService(); + assertTrue(service.isEnabled()); + } + + @Test + void constructorWithFlag_setsEnabled() { + DecoderService service = new DecoderService(false); + assertTrue(!service.isEnabled()); + } + + @Test + void createInboundFilter_returnsDecoder() { + DecoderService service = new DecoderService(); + + Filter filter = service.createInboundFilter(new Context()); + + assertNotNull(filter); + assertTrue(filter instanceof Decoder); + } + + @Test + void createOutboundFilter_returnsDecoder() { + DecoderService service = new DecoderService(); + + Filter filter = service.createOutboundFilter(new Context()); + + assertNotNull(filter); + assertTrue(filter instanceof Decoder); + } +} diff --git a/org.restlet/src/test/java/org/restlet/service/EncoderServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/EncoderServiceTestCase.java new file mode 100644 index 0000000000..51a40f1252 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/service/EncoderServiceTestCase.java @@ -0,0 +1,116 @@ +/** + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.data.MediaType; +import org.restlet.engine.application.Encoder; +import org.restlet.representation.StringRepresentation; +import org.restlet.routing.Filter; + +/** Unit tests for {@link EncoderService}. */ +class EncoderServiceTestCase { + + @Test + void defaultConstructor_isEnabledWithDefaultMinimumSize() { + EncoderService service = new EncoderService(); + + assertTrue(service.isEnabled()); + assertEquals(EncoderService.DEFAULT_MINIMUM_SIZE, service.getMinimumSize()); + assertFalse(service.getAcceptedMediaTypes().isEmpty()); + assertFalse(service.getIgnoredMediaTypes().isEmpty()); + } + + @Test + void constructorWithFlag_setsEnabled() { + EncoderService service = new EncoderService(false); + assertFalse(service.isEnabled()); + } + + @Test + void canEncode_withLargeAcceptedRepresentation_returnsTrue() { + EncoderService service = new EncoderService(); + service.setMinimumSize(EncoderService.ANY_SIZE); + StringRepresentation representation = + new StringRepresentation("some text", MediaType.TEXT_PLAIN); + + assertTrue(service.canEncode(representation)); + } + + @Test + void canEncode_withTooSmallRepresentation_returnsFalse() { + EncoderService service = new EncoderService(); + service.setMinimumSize(1_000_000); + StringRepresentation representation = + new StringRepresentation("small", MediaType.TEXT_PLAIN); + + assertFalse(service.canEncode(representation)); + } + + @Test + void canEncode_withIgnoredMediaType_returnsFalse() { + EncoderService service = new EncoderService(); + service.setMinimumSize(EncoderService.ANY_SIZE); + StringRepresentation representation = new StringRepresentation("data", MediaType.IMAGE_PNG); + + assertFalse(service.canEncode(representation)); + } + + @Test + void canEncode_withNullRepresentation_returnsFalse() { + EncoderService service = new EncoderService(); + + assertFalse(service.canEncode(null)); + } + + @Test + void getMinimumSizeAndSetMinimumSize_roundTrip() { + EncoderService service = new EncoderService(); + + service.setMinimumSize(2048); + + assertEquals(2048, service.getMinimumSize()); + } + + @Test + void createInboundFilter_returnsEncoder() { + EncoderService service = new EncoderService(); + + Filter filter = service.createInboundFilter(new Context()); + + assertNotNull(filter); + assertTrue(filter instanceof Encoder); + } + + @Test + void createOutboundFilter_returnsEncoder() { + EncoderService service = new EncoderService(); + + Filter filter = service.createOutboundFilter(new Context()); + + assertNotNull(filter); + assertTrue(filter instanceof Encoder); + } + + @Test + void getDefaultAcceptedMediaTypes_containsAll() { + assertTrue(EncoderService.getDefaultAcceptedMediaTypes().contains(MediaType.ALL)); + } + + @Test + void getDefaultIgnoredMediaTypes_containsImages() { + assertTrue(EncoderService.getDefaultIgnoredMediaTypes().contains(MediaType.IMAGE_ALL)); + } +} diff --git a/org.restlet/src/test/java/org/restlet/service/LogServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/LogServiceTestCase.java new file mode 100644 index 0000000000..a5df7f85e8 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/service/LogServiceTestCase.java @@ -0,0 +1,159 @@ +/** + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +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.Method; +import org.restlet.data.Status; +import org.restlet.engine.log.LogFilter; +import org.restlet.routing.Filter; +import org.restlet.routing.Template; + +/** Unit tests for {@link LogService}. */ +class LogServiceTestCase { + + @Test + void defaultConstructor_isEnabledWithNullDefaults() { + LogService service = new LogService(); + + assertTrue(service.isEnabled()); + assertNull(service.getLoggableTemplate()); + assertNull(service.getLoggerName()); + assertNull(service.getResponseLogFormat()); + assertNull(service.getLogPropertiesRef()); + assertFalse(service.isIdentityCheck()); + } + + @Test + void constructorWithFlag_setsEnabled() { + LogService service = new LogService(false); + assertFalse(service.isEnabled()); + } + + @Test + void createInboundFilter_returnsLogFilter() { + LogService service = new LogService(); + + Filter filter = service.createInboundFilter(new Context()); + + assertNotNull(filter); + assertTrue(filter instanceof LogFilter); + } + + @Test + void isLoggable_withoutTemplate_returnsTrue() { + LogService service = new LogService(); + Request request = new Request(Method.GET, "http://localhost/foo"); + + assertTrue(service.isLoggable(request)); + } + + @Test + void isLoggable_withMatchingTemplate_returnsTrue() { + LogService service = new LogService(); + service.setLoggableTemplate("http://localhost/foo"); + Request request = new Request(Method.GET, "http://localhost/foo"); + + assertTrue(service.isLoggable(request)); + } + + @Test + void isLoggable_withNonMatchingTemplate_returnsFalse() { + LogService service = new LogService(); + service.setLoggableTemplate("/bar"); + Request request = new Request(Method.GET, "http://localhost/foo"); + + assertFalse(service.isLoggable(request)); + } + + @Test + void setLoggableTemplate_withNullString_clearsTemplate() { + LogService service = new LogService(); + service.setLoggableTemplate("/foo"); + + service.setLoggableTemplate((String) null); + + assertNull(service.getLoggableTemplate()); + } + + @Test + void setLoggableTemplate_withTemplateInstance_roundTrips() { + LogService service = new LogService(); + Template template = new Template("/foo"); + + service.setLoggableTemplate(template); + + assertEquals(template, service.getLoggableTemplate()); + } + + @Test + void gettersAndSetters_roundTrip() { + LogService service = new LogService(); + + service.setIdentityCheck(true); + assertTrue(service.isIdentityCheck()); + + service.setLoggerName("my.logger"); + assertEquals("my.logger", service.getLoggerName()); + + service.setLogPropertiesRef("file:///tmp/log.properties"); + assertEquals("file:///tmp/log.properties", service.getLogPropertiesRef().toString()); + + service.setResponseLogFormat("{m}"); + assertEquals("{m}", service.getResponseLogFormat()); + } + + @Test + void getResponseLogMessage_withoutFormat_usesDefaultFormat() { + LogService service = new LogService(); + Request request = new Request(Method.GET, "http://localhost/foo?bar=baz"); + Response response = new Response(request); + response.setStatus(Status.SUCCESS_OK); + + String message = service.getResponseLogMessage(response, 42); + + assertNotNull(message); + assertTrue(message.contains("GET")); + assertTrue(message.contains("200")); + assertTrue(message.contains("42")); + } + + @Test + void getResponseLogMessage_afterStartWithCustomFormat_usesTemplate() throws Exception { + LogService service = new LogService(); + service.setResponseLogFormat("{m}"); + service.start(); + + Request request = new Request(Method.GET, "http://localhost/foo"); + Response response = new Response(request); + response.setStatus(Status.SUCCESS_OK); + + String message = service.getResponseLogMessage(response, 10); + + assertEquals("GET", message); + } + + @Test + void start_withoutLogPropertiesRef_startsNormally() throws Exception { + LogService service = new LogService(); + + service.start(); + + assertTrue(service.isStarted()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/service/RangeServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/RangeServiceTestCase.java new file mode 100644 index 0000000000..1631bc8157 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/service/RangeServiceTestCase.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.service; + +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 org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.engine.application.RangeFilter; +import org.restlet.routing.Filter; + +/** Unit tests for {@link RangeService}. */ +class RangeServiceTestCase { + + @Test + void defaultConstructor_isEnabled() { + RangeService service = new RangeService(); + assertTrue(service.isEnabled()); + } + + @Test + void constructorWithFlag_setsEnabled() { + RangeService service = new RangeService(false); + assertFalse(service.isEnabled()); + } + + @Test + void createInboundFilter_returnsRangeFilter() { + RangeService service = new RangeService(); + + Filter filter = service.createInboundFilter(new Context()); + + assertNotNull(filter); + assertTrue(filter instanceof RangeFilter); + } +} diff --git a/org.restlet/src/test/java/org/restlet/service/ServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/ServiceTestCase.java new file mode 100644 index 0000000000..506bad5f71 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/service/ServiceTestCase.java @@ -0,0 +1,124 @@ +/** + * 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.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.restlet.Context; + +/** Unit tests for {@link Service}. */ +class ServiceTestCase { + + /** Minimal concrete fixture, since {@link Service} is abstract. */ + private static class TestService extends Service { + TestService() { + super(); + } + + TestService(boolean enabled) { + super(enabled); + } + } + + @Test + void defaultConstructor_isEnabledAndNotStarted() { + TestService service = new TestService(); + + assertTrue(service.isEnabled()); + assertFalse(service.isStarted()); + assertTrue(service.isStopped()); + assertNull(service.getContext()); + } + + @Test + void constructorWithFlag_setsEnabled() { + TestService disabled = new TestService(false); + assertFalse(disabled.isEnabled()); + } + + @Test + void setContext_roundTrip() { + TestService service = new TestService(); + Context context = new Context(); + + service.setContext(context); + + assertSame(context, service.getContext()); + } + + @Test + void setEnabled_roundTrip() { + TestService service = new TestService(); + + service.setEnabled(false); + + assertFalse(service.isEnabled()); + } + + @Test + void start_whenEnabled_setsStarted() throws Exception { + TestService service = new TestService(); + + service.start(); + + assertTrue(service.isStarted()); + assertFalse(service.isStopped()); + } + + @Test + void start_whenDisabled_staysStopped() throws Exception { + TestService service = new TestService(false); + + service.start(); + + assertFalse(service.isStarted()); + assertTrue(service.isStopped()); + } + + @Test + void stop_afterStart_setsStopped() throws Exception { + TestService service = new TestService(); + service.start(); + + service.stop(); + + assertFalse(service.isStarted()); + assertTrue(service.isStopped()); + } + + @Test + void createInboundFilter_returnsNullByDefault() { + TestService service = new TestService(); + + assertNull(service.createInboundFilter(new Context())); + } + + @Test + void createOutboundFilter_returnsNullByDefault() { + TestService service = new TestService(); + + assertNull(service.createOutboundFilter(new Context())); + } + + @Test + void concreteSubclass_logService_isAlsoAService() { + LogService logService = new LogService(); + assertTrue(logService.isEnabled()); + } + + @Test + void concreteSubclass_encoderService_isAlsoAService() { + EncoderService encoderService = new EncoderService(); + assertTrue(encoderService.isEnabled()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/service/TaskServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/TaskServiceTestCase.java new file mode 100644 index 0000000000..5d00021ca4 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/service/TaskServiceTestCase.java @@ -0,0 +1,281 @@ +/** + * 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.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link TaskService}. */ +class TaskServiceTestCase { + + private TaskService taskService; + + @AfterEach + void tearDown() throws Exception { + if (taskService != null) { + taskService.setShutdownAllowed(true); + taskService.shutdownNow(); + taskService.awaitTermination(2, TimeUnit.SECONDS); + } + } + + @Test + void defaultConstructor_isEnabledWithFourThreads() { + taskService = new TaskService(); + + assertTrue(taskService.isEnabled()); + assertEquals(4, taskService.getCorePoolSize()); + assertTrue(taskService.isDaemon()); + assertFalse(taskService.isShutdownAllowed()); + } + + @Test + void constructorWithCorePoolSize_setsSize() { + taskService = new TaskService(2); + + assertEquals(2, taskService.getCorePoolSize()); + } + + @Test + void constructorWithEnabledAndDaemon_setsDaemonFlag() { + taskService = new TaskService(true, false); + + assertFalse(taskService.isDaemon()); + } + + @Test + void setCorePoolSizeAndSetDaemon_roundTrip() { + taskService = new TaskService(); + + taskService.setCorePoolSize(8); + taskService.setDaemon(false); + + assertEquals(8, taskService.getCorePoolSize()); + assertFalse(taskService.isDaemon()); + } + + @Test + void submitRunnable_executesTheTask() throws Exception { + taskService = new TaskService(); + AtomicBoolean ran = new AtomicBoolean(false); + + Future future = taskService.submit(() -> ran.set(true)); + future.get(5, TimeUnit.SECONDS); + + assertTrue(ran.get()); + } + + @Test + void submitCallable_returnsComputedResult() throws Exception { + taskService = new TaskService(); + + Future future = taskService.submit(() -> "done"); + + assertEquals("done", future.get(5, TimeUnit.SECONDS)); + } + + @Test + void submitRunnableWithResult_returnsGivenResult() throws Exception { + taskService = new TaskService(); + AtomicBoolean ran = new AtomicBoolean(false); + + Future future = taskService.submit(() -> ran.set(true), "the-result"); + + assertEquals("the-result", future.get(5, TimeUnit.SECONDS)); + assertTrue(ran.get()); + } + + @Test + void execute_runsCommandAsynchronously() throws Exception { + taskService = new TaskService(); + CountDownLatch latch = new CountDownLatch(1); + + taskService.execute(latch::countDown); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @Test + void schedule_runsCallableAfterDelay() throws Exception { + taskService = new TaskService(); + + ScheduledFuture future = + taskService.schedule( + (Callable) () -> "scheduled", 10, TimeUnit.MILLISECONDS); + + assertEquals("scheduled", future.get(5, TimeUnit.SECONDS)); + } + + @Test + void schedule_runsRunnableAfterDelay() throws Exception { + taskService = new TaskService(); + CountDownLatch latch = new CountDownLatch(1); + + ScheduledFuture future = + taskService.schedule((Runnable) latch::countDown, 10, TimeUnit.MILLISECONDS); + future.get(5, TimeUnit.SECONDS); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @Test + void scheduleAtFixedRate_runsMultipleTimes() throws Exception { + taskService = new TaskService(); + CountDownLatch latch = new CountDownLatch(3); + + ScheduledFuture future = + taskService.scheduleAtFixedRate(latch::countDown, 0, 10, TimeUnit.MILLISECONDS); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + future.cancel(true); + } + + @Test + void scheduleWithFixedDelay_runsMultipleTimes() throws Exception { + taskService = new TaskService(); + CountDownLatch latch = new CountDownLatch(3); + + ScheduledFuture future = + taskService.scheduleWithFixedDelay(latch::countDown, 0, 10, TimeUnit.MILLISECONDS); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + future.cancel(true); + } + + @Test + void invokeAll_executesAllTasks() throws Exception { + taskService = new TaskService(); + + @SuppressWarnings({"rawtypes", "unchecked"}) + List> futures = + taskService.invokeAll( + List.of((Callable) () -> "a", (Callable) () -> "b")); + + assertEquals(2, futures.size()); + assertEquals("a", futures.get(0).get()); + assertEquals("b", futures.get(1).get()); + } + + @Test + void invokeAny_returnsOneResult() throws Exception { + taskService = new TaskService(); + + @SuppressWarnings("unchecked") + Object result = taskService.invokeAny(List.of((Callable) () -> "only")); + + assertEquals("only", result); + } + + @Test + void isShutdown_beforeStart_returnsTrue() { + taskService = new TaskService(); + + assertTrue(taskService.isShutdown()); + } + + @Test + void isShutdown_afterStart_returnsFalse() throws Exception { + taskService = new TaskService(); + + taskService.start(); + + assertFalse(taskService.isShutdown()); + } + + @Test + void shutdown_withoutAllowedFlag_isNoOp() throws Exception { + taskService = new TaskService(); + taskService.start(); + + taskService.shutdown(); + + assertFalse(taskService.isShutdown()); + } + + @Test + void shutdown_withAllowedFlag_shutsDownExecutor() throws Exception { + taskService = new TaskService(); + taskService.start(); + taskService.setShutdownAllowed(true); + + taskService.shutdown(); + + assertTrue(taskService.awaitTermination(5, TimeUnit.SECONDS)); + assertTrue(taskService.isShutdown()); + } + + @Test + void shutdownNow_withoutAllowedFlag_returnsEmptyList() throws Exception { + taskService = new TaskService(); + taskService.start(); + + List pending = taskService.shutdownNow(); + + assertTrue(pending.isEmpty()); + assertFalse(taskService.isShutdown()); + } + + @Test + void shutdownNow_withAllowedFlag_stopsExecutor() throws Exception { + taskService = new TaskService(); + taskService.start(); + taskService.setShutdownAllowed(true); + + taskService.shutdownNow(); + + assertTrue(taskService.awaitTermination(5, TimeUnit.SECONDS)); + assertTrue(taskService.isTerminated()); + } + + @Test + void stop_shutsDownWrappedExecutorRegardlessOfAllowedFlag() throws Exception { + taskService = new TaskService(); + taskService.start(); + + taskService.stop(); + + // Note: isShutdown() is checked directly (rather than awaitTermination(), + // which calls startIfNeeded() and would transparently restart the + // service since stop() resets the started flag). + assertTrue(taskService.isShutdown()); + assertFalse(taskService.isStarted()); + } + + @Test + void wrap_copiesThreadLocalsAndDelegatesToWrappedExecutor() throws Exception { + java.util.concurrent.ScheduledExecutorService delegate = + java.util.concurrent.Executors.newSingleThreadScheduledExecutor(); + try { + ScheduledExecutorService wrapped = TaskService.wrap(delegate); + AtomicInteger counter = new AtomicInteger(); + + Future future = wrapped.submit(counter::incrementAndGet); + future.get(5, TimeUnit.SECONDS); + + assertEquals(1, counter.get()); + assertFalse(wrapped.isShutdown()); + assertFalse(wrapped.isTerminated()); + } finally { + delegate.shutdownNow(); + } + } +} diff --git a/org.restlet/src/test/java/org/restlet/service/TunnelServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/TunnelServiceTestCase.java new file mode 100644 index 0000000000..f5e4d565c8 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/service/TunnelServiceTestCase.java @@ -0,0 +1,142 @@ +/** + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.data.ClientInfo; +import org.restlet.engine.application.TunnelFilter; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.routing.Filter; + +/** Unit tests for {@link TunnelService}. */ +class TunnelServiceTestCase { + + @Test + void twoArgConstructor_enablesQueryTunnelOnly() { + TunnelService service = new TunnelService(true, true); + + assertTrue(service.isEnabled()); + assertTrue(service.isMethodTunnel()); + assertTrue(service.isPreferencesTunnel()); + assertTrue(service.isQueryTunnel()); + assertFalse(service.isExtensionsTunnel()); + assertFalse(service.isUserAgentTunnel()); + assertTrue(service.isHeadersTunnel()); + } + + @Test + void threeArgConstructor_setsEnabledFlag() { + TunnelService service = new TunnelService(false, true, false); + + assertFalse(service.isEnabled()); + assertTrue(service.isMethodTunnel()); + assertFalse(service.isPreferencesTunnel()); + } + + @Test + void fiveArgConstructor_setsQueryAndExtensionsTunnel() { + TunnelService service = new TunnelService(true, true, true, false, true); + + assertFalse(service.isQueryTunnel()); + assertTrue(service.isExtensionsTunnel()); + assertFalse(service.isUserAgentTunnel()); + } + + @Test + void sixArgConstructor_setsUserAgentTunnel() { + TunnelService service = new TunnelService(true, true, true, true, false, true); + + assertTrue(service.isUserAgentTunnel()); + assertTrue(service.isHeadersTunnel()); + } + + @Test + void sevenArgConstructor_setsHeadersTunnel() { + TunnelService service = new TunnelService(true, true, true, true, false, true, false); + + assertFalse(service.isHeadersTunnel()); + } + + @Test + void defaultParameterNames_areSet() { + TunnelService service = new TunnelService(true, true); + + assertEquals("charset", service.getCharacterSetParameter()); + assertEquals("encoding", service.getEncodingParameter()); + assertEquals("language", service.getLanguageParameter()); + assertEquals("media", service.getMediaTypeParameter()); + assertEquals("method", service.getMethodParameter()); + assertEquals(HeaderConstants.HEADER_X_HTTP_METHOD_OVERRIDE, service.getMethodHeader()); + } + + @Test + void gettersAndSetters_roundTrip() { + TunnelService service = new TunnelService(true, true); + + service.setCharacterSetParameter("cs"); + assertEquals("cs", service.getCharacterSetParameter()); + + service.setEncodingParameter("enc"); + assertEquals("enc", service.getEncodingParameter()); + + service.setLanguageParameter("lang"); + assertEquals("lang", service.getLanguageParameter()); + + service.setMediaTypeParameter("mt"); + assertEquals("mt", service.getMediaTypeParameter()); + + service.setMethodParameter("mth"); + assertEquals("mth", service.getMethodParameter()); + + service.setMethodHeader("X-Method"); + assertEquals("X-Method", service.getMethodHeader()); + + service.setMethodTunnel(false); + assertFalse(service.isMethodTunnel()); + + service.setPreferencesTunnel(false); + assertFalse(service.isPreferencesTunnel()); + + service.setQueryTunnel(false); + assertFalse(service.isQueryTunnel()); + + service.setExtensionsTunnel(true); + assertTrue(service.isExtensionsTunnel()); + + service.setUserAgentTunnel(true); + assertTrue(service.isUserAgentTunnel()); + + service.setHeadersTunnel(false); + assertFalse(service.isHeadersTunnel()); + } + + @Test + void allowClient_alwaysReturnsTrue() { + TunnelService service = new TunnelService(true, true); + + assertTrue(service.allowClient(new ClientInfo())); + assertTrue(service.allowClient(null)); + } + + @Test + void createInboundFilter_returnsTunnelFilter() { + TunnelService service = new TunnelService(true, true); + + Filter filter = service.createInboundFilter(new Context()); + + assertNotNull(filter); + assertTrue(filter instanceof TunnelFilter); + } +} diff --git a/org.restlet/src/test/java/org/restlet/util/ClientListTestCase.java b/org.restlet/src/test/java/org/restlet/util/ClientListTestCase.java new file mode 100644 index 0000000000..4bf9a84ff2 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/util/ClientListTestCase.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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; +import org.restlet.Client; +import org.restlet.Context; +import org.restlet.data.Protocol; + +class ClientListTestCase { + + @Test + void add_clientWithoutContext_assignsChildContext() { + Context context = new Context(); + ClientList list = new ClientList(context); + Client client = new Client(Protocol.HTTP); + + list.add(client); + + assertNotNull(client.getContext()); + } + + @Test + void add_clientWithExistingContext_leavesItUnchanged() { + Context context = new Context(); + ClientList list = new ClientList(context); + Client client = new Client(Protocol.HTTP); + Context existing = new Context(); + client.setContext(existing); + + list.add(client); + + assertEquals(existing, client.getContext()); + } + + @Test + void addProtocol_createsAndRegistersClient() { + Context context = new Context(); + ClientList list = new ClientList(context); + + Client client = list.add(Protocol.HTTP); + + assertEquals(1, list.size()); + assertNotNull(client.getContext()); + assertEquals(client, list.get(0)); + } + + @Test + void getContextAndSetContext_roundTrip() { + Context context = new Context(); + ClientList list = new ClientList(context); + assertEquals(context, list.getContext()); + + Context other = new Context(); + list.setContext(other); + assertEquals(other, list.getContext()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/util/NamedValuesCollectorTestCase.java b/org.restlet/src/test/java/org/restlet/util/NamedValuesCollectorTestCase.java new file mode 100644 index 0000000000..21578c0cc0 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/util/NamedValuesCollectorTestCase.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.util; + +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.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.restlet.data.Parameter; + +class NamedValuesCollectorTestCase { + + @Test + void constructorWithNames_prePopulatesKeysWithNullValues() { + NamedValuesCollector collector = new NamedValuesCollector("a", "b"); + assertTrue(collector.getCollectedValues().containsKey("a")); + assertNull(collector.getCollectedValues().get("a")); + } + + @Test + void constructorWithMap_usesProvidedMap() { + Map map = new HashMap<>(); + map.put("a", null); + NamedValuesCollector collector = new NamedValuesCollector(map); + assertEquals(map, collector.getCollectedValues()); + } + + @Test + void collect_unknownName_isIgnored() { + NamedValuesCollector collector = new NamedValuesCollector("a"); + collector.collect(new Parameter("unknown", "value")); + assertFalse(collector.getCollectedValues().containsKey("unknown")); + } + + @Test + void collect_firstValueForKnownName_setsValue() { + NamedValuesCollector collector = new NamedValuesCollector("a"); + collector.collect(new Parameter("a", "1")); + assertEquals("1", collector.getCollectedValues().get("a")); + } + + @Test + void collect_nullValueForKnownName_usesEmptyValueMarker() { + NamedValuesCollector collector = new NamedValuesCollector("a"); + collector.collect(new Parameter("a", null)); + assertEquals(Series.EMPTY_VALUE, collector.getCollectedValues().get("a")); + } + + @Test + void collect_secondValueForSameName_createsListOfValues() { + NamedValuesCollector collector = new NamedValuesCollector("a"); + collector.collect(new Parameter("a", "1")); + collector.collect(new Parameter("a", "2")); + + Object result = collector.getCollectedValues().get("a"); + assertTrue(result instanceof List); + assertEquals(List.of("1", "2"), result); + } + + @Test + void collect_thirdValueForSameName_appendsToExistingList() { + NamedValuesCollector collector = new NamedValuesCollector("a"); + collector.collect(new Parameter("a", "1")); + collector.collect(new Parameter("a", "2")); + collector.collect(new Parameter("a", "3")); + + assertEquals(List.of("1", "2", "3"), collector.getCollectedValues().get("a")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/util/NonNullItemsListTestCase.java b/org.restlet/src/test/java/org/restlet/util/NonNullItemsListTestCase.java new file mode 100644 index 0000000000..13ff0093c5 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/util/NonNullItemsListTestCase.java @@ -0,0 +1,140 @@ +/** + * 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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +class NonNullItemsListTestCase { + + @Test + void add_nonNullElement_succeeds() { + NonNullItemsList list = new NonNullItemsList<>("no nulls allowed"); + assertTrue(list.add("a")); + assertEquals("a", list.get(0)); + } + + @Test + void add_nullElement_throwsWithConfiguredMessage() { + NonNullItemsList list = new NonNullItemsList<>("no nulls allowed"); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> list.add(null)); + assertEquals("no nulls allowed", exception.getMessage()); + } + + @Test + void addAtIndex_nullElement_throws() { + NonNullItemsList list = new NonNullItemsList<>("msg"); + assertThrows(IllegalArgumentException.class, () -> list.add(0, null)); + } + + @Test + void addFirst_nullElement_throws() { + NonNullItemsList list = new NonNullItemsList<>("msg"); + assertThrows(IllegalArgumentException.class, () -> list.addFirst(null)); + } + + @Test + void addFirst_nonNullElement_succeeds() { + NonNullItemsList list = new NonNullItemsList<>("msg"); + list.add("b"); + list.addFirst("a"); + assertEquals("a", list.get(0)); + } + + @Test + void addLast_nullElement_throws() { + NonNullItemsList list = new NonNullItemsList<>("msg"); + assertThrows(IllegalArgumentException.class, () -> list.addLast(null)); + } + + @Test + void addLast_nonNullElement_succeeds() { + NonNullItemsList list = new NonNullItemsList<>("msg"); + list.add("a"); + list.addLast("b"); + assertEquals("b", list.get(1)); + } + + @Test + void addAll_collectionWithNull_throws() { + NonNullItemsList list = new NonNullItemsList<>("msg"); + assertThrows( + IllegalArgumentException.class, () -> list.addAll(Arrays.asList("a", null, "b"))); + } + + @Test + void addAll_nullCollection_throws() { + NonNullItemsList list = new NonNullItemsList<>("msg"); + assertThrows(IllegalArgumentException.class, () -> list.addAll(null)); + } + + @Test + void addAll_validCollection_succeeds() { + NonNullItemsList list = new NonNullItemsList<>("msg"); + assertTrue(list.addAll(Arrays.asList("a", "b"))); + assertEquals(2, list.size()); + } + + @Test + void addAllAtIndex_collectionWithNull_throws() { + NonNullItemsList list = new NonNullItemsList<>("msg"); + assertThrows( + IllegalArgumentException.class, + () -> list.addAll(0, Collections.singletonList(null))); + } + + @Test + void addAllAtIndex_validCollection_succeeds() { + NonNullItemsList list = new NonNullItemsList<>("msg"); + list.add("c"); + assertTrue(list.addAll(0, Arrays.asList("a", "b"))); + assertEquals(Arrays.asList("a", "b", "c"), list); + } + + @Test + void equals_sameElementsAndMessage_returnsTrue() { + NonNullItemsList list1 = new NonNullItemsList<>("msg"); + list1.add("a"); + NonNullItemsList list2 = new NonNullItemsList<>("msg"); + list2.add("a"); + assertTrue(list1.equals(list2)); + } + + @Test + void equals_differentMessage_returnsFalse() { + NonNullItemsList list1 = new NonNullItemsList<>("msg1"); + list1.add("a"); + NonNullItemsList list2 = new NonNullItemsList<>("msg2"); + list2.add("a"); + assertFalse(list1.equals(list2)); + } + + @Test + void equals_plainListWithSameElements_returnsFalse() { + NonNullItemsList list1 = new NonNullItemsList<>("msg"); + list1.add("a"); + ArrayList plainList = new ArrayList<>(java.util.List.of("a")); + assertFalse(list1.equals(plainList)); + } + + @Test + void hashCode_matchesDelegateHashCode() { + NonNullItemsList list = new NonNullItemsList<>("msg"); + list.add("a"); + assertEquals(list.getDelegate().hashCode(), list.hashCode()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/util/ResolverTestCase.java b/org.restlet/src/test/java/org/restlet/util/ResolverTestCase.java new file mode 100644 index 0000000000..0e31f1b299 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/util/ResolverTestCase.java @@ -0,0 +1,49 @@ +/** + * 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.assertNotNull; + +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.Method; + +class ResolverTestCase { + + private static class UppercaseResolver extends Resolver { + @Override + public String resolve(String name) { + return name.toUpperCase(); + } + } + + @Test + void resolve_customImplementation_returnsExpectedValue() { + Resolver resolver = new UppercaseResolver(); + assertEquals("HELLO", resolver.resolve("hello")); + } + + @Test + void createResolver_fromMap_resolvesEntries() { + Resolver resolver = Resolver.createResolver(Map.of("key", "value")); + assertEquals("value", resolver.resolve("key")); + } + + @Test + void createResolver_fromRequestAndResponse_isNotNull() { + Request request = new Request(Method.GET, "http://localhost/test"); + Response response = new Response(request); + Resolver resolver = Resolver.createResolver(request, response); + assertNotNull(resolver); + assertEquals("GET", resolver.resolve("m")); + } +} diff --git a/org.restlet/src/test/java/org/restlet/util/RouteListTestCase.java b/org.restlet/src/test/java/org/restlet/util/RouteListTestCase.java new file mode 100644 index 0000000000..d82ba395f3 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/util/RouteListTestCase.java @@ -0,0 +1,172 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import org.junit.jupiter.api.Test; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.Method; +import org.restlet.routing.Route; + +class RouteListTestCase { + + private static class ScoredRoute extends Route { + private final float scoreValue; + + ScoredRoute(Restlet next, float scoreValue) { + super(next); + this.scoreValue = scoreValue; + } + + @Override + public float score(Request request, Response response) { + return scoreValue; + } + } + + private static Request newRequest() { + return new Request(Method.GET, "http://localhost/test"); + } + + @Test + void constructor_withDelegate_copiesElements() { + Restlet target = new Restlet() {}; + ArrayList delegate = new ArrayList<>(); + delegate.add(new ScoredRoute(target, 1.0F)); + RouteList list = new RouteList(delegate); + assertEquals(1, list.size()); + } + + @Test + void getBest_returnsHighestScoringRouteAboveThreshold() { + RouteList list = new RouteList(); + Route low = new ScoredRoute(new Restlet() {}, 0.3F); + Route high = new ScoredRoute(new Restlet() {}, 0.9F); + list.add(low); + list.add(high); + + Route best = list.getBest(newRequest(), new Response(newRequest()), 0.1F); + + assertEquals(high, best); + } + + @Test + void getBest_noRouteMeetsThreshold_returnsNull() { + RouteList list = new RouteList(); + list.add(new ScoredRoute(new Restlet() {}, 0.1F)); + + assertNull(list.getBest(newRequest(), new Response(newRequest()), 0.5F)); + } + + @Test + void getFirst_returnsFirstMatchingRoute() { + RouteList list = new RouteList(); + Route first = new ScoredRoute(new Restlet() {}, 0.5F); + Route second = new ScoredRoute(new Restlet() {}, 0.9F); + list.add(first); + list.add(second); + + assertEquals(first, list.getFirst(newRequest(), new Response(newRequest()), 0.1F)); + } + + @Test + void getFirst_noMatch_returnsNull() { + RouteList list = new RouteList(); + list.add(new ScoredRoute(new Restlet() {}, 0.1F)); + + assertNull(list.getFirst(newRequest(), new Response(newRequest()), 0.5F)); + } + + @Test + void getLast_returnsLastMatchingRoute() { + RouteList list = new RouteList(); + Route first = new ScoredRoute(new Restlet() {}, 0.5F); + Route second = new ScoredRoute(new Restlet() {}, 0.9F); + list.add(first); + list.add(second); + + assertEquals(second, list.getLast(newRequest(), new Response(newRequest()), 0.1F)); + } + + @Test + void getLast_noMatch_returnsNull() { + RouteList list = new RouteList(); + list.add(new ScoredRoute(new Restlet() {}, 0.1F)); + + assertNull(list.getLast(newRequest(), new Response(newRequest()), 0.5F)); + } + + @Test + void getNext_cyclesThroughRoutesRoundRobin() { + RouteList list = new RouteList(); + Route first = new ScoredRoute(new Restlet() {}, 1.0F); + Route second = new ScoredRoute(new Restlet() {}, 1.0F); + list.add(first); + list.add(second); + + Route firstCall = list.getNext(newRequest(), new Response(newRequest()), 0.1F); + Route secondCall = list.getNext(newRequest(), new Response(newRequest()), 0.1F); + + assertTrue(firstCall == first || firstCall == second); + assertTrue(secondCall == first || secondCall == second); + } + + @Test + void getNext_emptyList_returnsNull() { + RouteList list = new RouteList(); + assertNull(list.getNext(newRequest(), new Response(newRequest()), 0.1F)); + } + + @Test + void getRandom_singleRouteMeetingThreshold_returnsIt() { + RouteList list = new RouteList(); + Route only = new ScoredRoute(new Restlet() {}, 1.0F); + list.add(only); + + assertEquals(only, list.getRandom(newRequest(), new Response(newRequest()), 0.1F)); + } + + @Test + void getRandom_emptyList_returnsNull() { + RouteList list = new RouteList(); + assertNull(list.getRandom(newRequest(), new Response(newRequest()), 0.1F)); + } + + @Test + void removeAll_removesRoutesTargetingGivenRestlet() { + RouteList list = new RouteList(); + Restlet target = new Restlet() {}; + Restlet other = new Restlet() {}; + list.add(new ScoredRoute(target, 1.0F)); + list.add(new ScoredRoute(other, 1.0F)); + + list.removeAll(target); + + assertEquals(1, list.size()); + assertEquals(other, list.get(0).getNext()); + } + + @Test + void subList_returnsRouteListInstance() { + RouteList list = new RouteList(); + list.add(new ScoredRoute(new Restlet() {}, 1.0F)); + list.add(new ScoredRoute(new Restlet() {}, 1.0F)); + + java.util.List sub = list.subList(0, 1); + + assertTrue(sub instanceof RouteList); + assertEquals(1, sub.size()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/util/ServerListTestCase.java b/org.restlet/src/test/java/org/restlet/util/ServerListTestCase.java new file mode 100644 index 0000000000..27b7f083fd --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/util/ServerListTestCase.java @@ -0,0 +1,114 @@ +/** + * 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.assertNotNull; + +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.Restlet; +import org.restlet.Server; +import org.restlet.data.Protocol; + +class ServerListTestCase { + + @Test + void add_protocolOnly_createsServerWithDefaultPort() { + Context context = new Context(); + Restlet next = new Restlet() {}; + ServerList list = new ServerList(context, next); + + Server server = list.add(Protocol.HTTP); + + assertEquals(Protocol.HTTP.getDefaultPort(), server.getPort()); + assertEquals(next, server.getNext()); + assertEquals(1, list.size()); + } + + @Test + void add_protocolAndPort_usesGivenPort() { + Context context = new Context(); + ServerList list = new ServerList(context, new Restlet() {}); + + Server server = list.add(Protocol.HTTP, 8080); + + assertEquals(8080, server.getPort()); + } + + @Test + void add_protocolAddressAndPort_usesGivenAddress() { + Context context = new Context(); + ServerList list = new ServerList(context, new Restlet() {}); + + Server server = list.add(Protocol.HTTP, "127.0.0.1", 8080); + + assertEquals("127.0.0.1", server.getAddress()); + assertEquals(8080, server.getPort()); + } + + @Test + void add_serverWithoutContext_assignsChildContext() { + Context context = new Context(); + ServerList list = new ServerList(context, new Restlet() {}); + Server server = new Server(Protocol.HTTP, null, 8080, null); + + list.add(server); + + assertNotNull(server.getContext()); + } + + @Test + void add_serverWithExistingContext_leavesItUnchanged() { + Context context = new Context(); + ServerList list = new ServerList(context, new Restlet() {}); + Server server = new Server(Protocol.HTTP, null, 8080, null); + Context existing = new Context(); + server.setContext(existing); + + list.add(server); + + assertEquals(existing, server.getContext()); + } + + @Test + void add_server_setsNextRestlet() { + Context context = new Context(); + Restlet next = new Restlet() {}; + ServerList list = new ServerList(context, next); + Server server = new Server(Protocol.HTTP, null, 8080, null); + + list.add(server); + + assertEquals(next, server.getNext()); + } + + @Test + void getContextAndSetContext_roundTrip() { + Context context = new Context(); + ServerList list = new ServerList(context, new Restlet() {}); + assertEquals(context, list.getContext()); + + Context other = new Context(); + list.setContext(other); + assertEquals(other, list.getContext()); + } + + @Test + void getNextAndSetNext_roundTrip() { + Context context = new Context(); + Restlet next = new Restlet() {}; + ServerList list = new ServerList(context, next); + assertEquals(next, list.getNext()); + + Restlet other = new Restlet() {}; + list.setNext(other); + assertEquals(other, list.getNext()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/util/ServiceListTestCase.java b/org.restlet/src/test/java/org/restlet/util/ServiceListTestCase.java new file mode 100644 index 0000000000..0a332be608 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/util/ServiceListTestCase.java @@ -0,0 +1,164 @@ +/** + * 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.restlet.Context; +import org.restlet.service.LogService; +import org.restlet.service.RangeService; +import org.restlet.service.Service; + +class ServiceListTestCase { + + @Test + void add_setsServiceContext() { + Context context = new Context(); + ServiceList list = new ServiceList(context); + RangeService service = new RangeService(); + + list.add(service); + + assertEquals(context, service.getContext()); + } + + @Test + void addAtIndex_setsServiceContext() { + Context context = new Context(); + ServiceList list = new ServiceList(context); + RangeService service = new RangeService(); + + list.add(0, service); + + assertEquals(context, service.getContext()); + } + + @Test + void addAll_setsContextOnEachService() { + Context context = new Context(); + ServiceList list = new ServiceList(context); + RangeService rangeService = new RangeService(); + LogService logService = new LogService(); + + list.addAll(Arrays.asList(rangeService, logService)); + + assertEquals(context, rangeService.getContext()); + assertEquals(context, logService.getContext()); + } + + @Test + void addAllAtIndex_setsContextOnEachService() { + Context context = new Context(); + ServiceList list = new ServiceList(context); + RangeService rangeService = new RangeService(); + + list.addAll(0, Arrays.asList(rangeService)); + + assertEquals(context, rangeService.getContext()); + } + + @Test + void getByClass_matchingService_returnsIt() { + ServiceList list = new ServiceList(new Context()); + RangeService rangeService = new RangeService(); + list.add(rangeService); + + assertEquals(rangeService, list.get(RangeService.class)); + } + + @Test + void getByClass_noMatch_returnsNull() { + ServiceList list = new ServiceList(new Context()); + assertNull(list.get(RangeService.class)); + } + + @Test + void getContext_returnsConstructorContext() { + Context context = new Context(); + ServiceList list = new ServiceList(context); + assertEquals(context, list.getContext()); + } + + @Test + void setList_replacesAllServices() { + ServiceList list = new ServiceList(new Context()); + list.add(new RangeService()); + + list.set(List.of(new LogService())); + + assertEquals(1, list.size()); + assertTrue(list.get(0) instanceof LogService); + } + + @Test + void setList_nullList_clearsServices() { + ServiceList list = new ServiceList(new Context()); + list.add(new RangeService()); + + list.set((List) null); + + assertTrue(list.isEmpty()); + } + + @Test + void setService_replacesExistingServiceOfSameType() { + ServiceList list = new ServiceList(new Context()); + RangeService original = new RangeService(); + list.add(original); + + RangeService replacement = new RangeService(); + list.set(replacement); + + assertEquals(1, list.size()); + assertEquals(replacement, list.get(0)); + } + + @Test + void setService_noExistingServiceOfType_appendsIt() { + ServiceList list = new ServiceList(new Context()); + list.add(new LogService()); + + RangeService rangeService = new RangeService(); + list.set(rangeService); + + assertEquals(2, list.size()); + assertTrue(list.get(RangeService.class) != null); + } + + @Test + void setContext_updatesContextOfRegisteredServices() { + ServiceList list = new ServiceList(new Context()); + RangeService service = new RangeService(); + list.add(service); + + Context newContext = new Context(); + list.setContext(newContext); + + assertEquals(newContext, list.getContext()); + assertEquals(newContext, service.getContext()); + } + + @Test + void startAndStop_delegateToEachService() throws Exception { + ServiceList list = new ServiceList(new Context()); + RangeService service = new RangeService(); + list.add(service); + + list.start(); + assertTrue(service.isStarted()); + + list.stop(); + assertTrue(service.isStopped()); + } +} diff --git a/org.restlet/src/test/java/org/restlet/util/WrapperListTestCase.java b/org.restlet/src/test/java/org/restlet/util/WrapperListTestCase.java new file mode 100644 index 0000000000..f6ee5ff268 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/util/WrapperListTestCase.java @@ -0,0 +1,246 @@ +/** + * 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.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import org.junit.jupiter.api.Test; + +class WrapperListTestCase { + + @Test + void defaultConstructor_startsEmpty() { + assertTrue(new WrapperList().isEmpty()); + } + + @Test + void constructorWithCapacity_startsEmpty() { + assertTrue(new WrapperList(5).isEmpty()); + } + + @Test + void constructorWithDelegate_wrapsGivenList() { + List delegate = new ArrayList<>(List.of("a")); + WrapperList list = new WrapperList<>(delegate); + assertEquals(1, list.size()); + assertEquals(delegate, list.getDelegate()); + } + + @Test + void add_appendsElement() { + WrapperList list = new WrapperList<>(); + assertTrue(list.add("a")); + assertEquals("a", list.get(0)); + } + + @Test + void addAtIndex_insertsAtPosition() { + WrapperList list = new WrapperList<>(); + list.add("a"); + list.add("c"); + list.add(1, "b"); + assertEquals(List.of("a", "b", "c"), list); + } + + @Test + void addFirstAndAddLast_insertAtBoundaries() { + WrapperList list = new WrapperList<>(); + list.add("b"); + list.addFirst("a"); + list.addLast("c"); + assertEquals(List.of("a", "b", "c"), list); + } + + @Test + void addAll_appendsAllElements() { + WrapperList list = new WrapperList<>(); + assertTrue(list.addAll(Arrays.asList("a", "b"))); + assertEquals(2, list.size()); + } + + @Test + void addAllAtIndex_insertsAllAtPosition() { + WrapperList list = new WrapperList<>(); + list.add("c"); + list.addAll(0, Arrays.asList("a", "b")); + assertEquals(List.of("a", "b", "c"), list); + } + + @Test + void clear_removesAllElements() { + WrapperList list = new WrapperList<>(); + list.add("a"); + list.clear(); + assertTrue(list.isEmpty()); + } + + @Test + void contains_findsElement() { + WrapperList list = new WrapperList<>(); + list.add("a"); + assertTrue(list.contains("a")); + assertFalse(list.contains("b")); + } + + @Test + void containsAll_findsAllElements() { + WrapperList list = new WrapperList<>(); + list.addAll(Arrays.asList("a", "b")); + assertTrue(list.containsAll(Arrays.asList("a", "b"))); + assertFalse(list.containsAll(Arrays.asList("a", "c"))); + } + + @Test + void equals_sameElementsInOrder_returnsTrue() { + WrapperList list1 = new WrapperList<>(); + list1.addAll(Arrays.asList("a", "b")); + WrapperList list2 = new WrapperList<>(); + list2.addAll(Arrays.asList("a", "b")); + assertTrue(list1.equals(list2)); + } + + @Test + void equals_differentElements_returnsFalse() { + WrapperList list1 = new WrapperList<>(); + list1.add("a"); + WrapperList list2 = new WrapperList<>(); + list2.add("b"); + assertFalse(list1.equals(list2)); + } + + @Test + void equals_sameInstance_returnsTrue() { + WrapperList list = new WrapperList<>(); + assertTrue(list.equals(list)); + } + + @Test + void equals_notAList_returnsFalse() { + WrapperList list = new WrapperList<>(); + assertFalse(list.equals("not a list")); + } + + @Test + void hashCode_matchesDelegateHashCode() { + List delegate = new ArrayList<>(List.of("a")); + WrapperList list = new WrapperList<>(delegate); + assertEquals(delegate.hashCode(), list.hashCode()); + } + + @Test + void indexOfAndLastIndexOf_locateElements() { + WrapperList list = new WrapperList<>(); + list.addAll(Arrays.asList("a", "b", "a")); + assertEquals(0, list.indexOf("a")); + assertEquals(2, list.lastIndexOf("a")); + assertEquals(-1, list.indexOf("z")); + } + + @Test + void iterator_iteratesOverElements() { + WrapperList list = new WrapperList<>(); + list.addAll(Arrays.asList("a", "b")); + Iterator iterator = list.iterator(); + assertTrue(iterator.hasNext()); + assertEquals("a", iterator.next()); + } + + @Test + void listIterator_startsAtBeginning() { + WrapperList list = new WrapperList<>(); + list.add("a"); + ListIterator iterator = list.listIterator(); + assertEquals("a", iterator.next()); + } + + @Test + void listIteratorWithIndex_startsAtGivenPosition() { + WrapperList list = new WrapperList<>(); + list.addAll(Arrays.asList("a", "b")); + ListIterator iterator = list.listIterator(1); + assertEquals("b", iterator.next()); + } + + @Test + void removeByIndex_returnsRemovedElement() { + WrapperList list = new WrapperList<>(); + list.add("a"); + assertEquals("a", list.remove(0)); + assertTrue(list.isEmpty()); + } + + @Test + void removeByObject_removesFirstOccurrence() { + WrapperList list = new WrapperList<>(); + list.add("a"); + assertTrue(list.remove("a")); + assertFalse(list.remove("a")); + } + + @Test + void removeAll_removesEachMatchingElement() { + WrapperList list = new WrapperList<>(); + list.addAll(Arrays.asList("a", "b", "c")); + assertTrue(list.removeAll(Arrays.asList("a", "c"))); + assertEquals(List.of("b"), list); + } + + @Test + void retainAll_keepsOnlySpecifiedElements() { + WrapperList list = new WrapperList<>(); + list.addAll(Arrays.asList("a", "b", "c")); + assertTrue(list.retainAll(Arrays.asList("b"))); + assertEquals(List.of("b"), list); + } + + @Test + void set_replacesElementAtIndexAndReturnsPrevious() { + WrapperList list = new WrapperList<>(); + list.add("a"); + assertEquals("a", list.set(0, "b")); + assertEquals("b", list.get(0)); + } + + @Test + void subList_returnsPortionAsWrapperList() { + WrapperList list = new WrapperList<>(); + list.addAll(Arrays.asList("a", "b", "c")); + List sub = list.subList(1, 3); + assertEquals(List.of("b", "c"), sub); + } + + @Test + void toArray_returnsAllElements() { + WrapperList list = new WrapperList<>(); + list.addAll(Arrays.asList("a", "b")); + assertEquals(2, list.toArray().length); + } + + @Test + void toArrayWithType_returnsTypedArray() { + WrapperList list = new WrapperList<>(); + list.add("a"); + String[] result = list.toArray(new String[0]); + assertEquals("a", result[0]); + } + + @Test + void toString_matchesDelegateToString() { + List delegate = new ArrayList<>(List.of("a")); + WrapperList list = new WrapperList<>(delegate); + assertEquals(delegate.toString(), list.toString()); + } +} diff --git a/pom.xml b/pom.xml index 953ed0ceee..de597636e1 100644 --- a/pom.xml +++ b/pom.xml @@ -307,7 +307,7 @@ INSTRUCTION COVEREDRATIO - 0.70 + 0.80