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

apelluru pushed a commit to branch feature/improve-test-coverage
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-core.git


The following commit(s) were added to refs/heads/feature/improve-test-coverage 
by this push:
     new 290c8a8  SLING-13257: Improve Test Coverage
290c8a8 is described below

commit 290c8a8f72fc94a8f59c9a35f5fc68670b8165f7
Author: Ashok Pelluru <[email protected]>
AuthorDate: Mon Jul 6 17:24:27 2026 +0200

    SLING-13257: Improve Test Coverage
---
 .../org/apache/sling/auth/core/AuthUtilTest.java   | 535 +++++++++++++++++++++
 .../core/impl/AuthenticationHandlerHolderTest.java | 242 ++++++++++
 .../impl/AuthenticationHandlerWrapperTest.java     | 113 +++++
 .../impl/AuthenticationRequirementHolderTest.java  |  71 +++
 .../impl/AuthenticatorWebConsolePluginTest.java    | 155 ++++++
 .../impl/HttpBasicAuthenticationHandlerTest.java   | 177 +++++++
 .../sling/auth/core/impl/LoginServletTest.java     | 107 +++++
 .../sling/auth/core/impl/LogoutServletTest.java    |  68 +++
 .../sling/auth/core/impl/PathBasedHolderTest.java  | 101 ++++
 .../auth/core/impl/SlingAuthenticatorTest.java     | 367 ++++++++++++++
 .../EngineAuthenticationHandlerHolderTest.java     | 211 ++++++++
 .../impl/engine/EngineSlingAuthenticatorTest.java  |  78 +++
 .../spi/AbstractAuthenticationFormServletTest.java | 147 ++++++
 .../spi/AbstractAuthenticationHandlerTest.java     |  92 ++++
 ...stractJakartaAuthenticationFormServletTest.java | 153 ++++++
 .../DefaultAuthenticationFeedbackHandlerTest.java  | 100 ++++
 ...ltJakartaAuthenticationFeedbackHandlerTest.java | 121 +++++
 .../sling/auth/core/spi/test-login-form.html       |   1 +
 18 files changed, 2839 insertions(+)

diff --git a/src/test/java/org/apache/sling/auth/core/AuthUtilTest.java 
b/src/test/java/org/apache/sling/auth/core/AuthUtilTest.java
index 6d9de6a..9eadf81 100644
--- a/src/test/java/org/apache/sling/auth/core/AuthUtilTest.java
+++ b/src/test/java/org/apache/sling/auth/core/AuthUtilTest.java
@@ -18,14 +18,24 @@
  */
 package org.apache.sling.auth.core;
 
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+
 import jakarta.servlet.http.HttpServletRequest;
+import org.apache.sling.api.auth.Authenticator;
 import org.apache.sling.api.resource.NonExistingResource;
+import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.SyntheticResource;
+import org.apache.sling.auth.core.spi.JakartaAuthenticationHandler;
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+@SuppressWarnings("deprecation")
 public class AuthUtilTest {
 
     final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
@@ -162,4 +172,529 @@ public class AuthUtilTest {
         Mockito.when(request.getHeader("User-Agent")).thenReturn("WebDAV 
Client");
         Assert.assertFalse(AuthUtil.isBrowserRequest(request));
     }
+
+    // ---- Jakarta variants ----
+
+    @Test
+    public void test_getAttributeOrParameter_jakarta() {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getAttribute("a")).thenReturn("attrValue");
+        Assert.assertEquals("attrValue", 
AuthUtil.getAttributeOrParameter(request, "a", "def"));
+
+        Mockito.when(request.getAttribute("b")).thenReturn(null);
+        Mockito.when(request.getParameter("b")).thenReturn("paramValue");
+        Assert.assertEquals("paramValue", 
AuthUtil.getAttributeOrParameter(request, "b", "def"));
+
+        Mockito.when(request.getAttribute("c")).thenReturn("");
+        Mockito.when(request.getParameter("c")).thenReturn("");
+        Assert.assertEquals("def", AuthUtil.getAttributeOrParameter(request, 
"c", "def"));
+
+        // non-string attribute is ignored
+        Mockito.when(request.getAttribute("d")).thenReturn(Integer.valueOf(1));
+        Mockito.when(request.getParameter("d")).thenReturn(null);
+        Assert.assertEquals("def", AuthUtil.getAttributeOrParameter(request, 
"d", "def"));
+    }
+
+    @Test
+    public void test_getLoginResource_jakarta() {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        
Mockito.when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/res");
+        Assert.assertEquals("/res", AuthUtil.getLoginResource(request, 
"/def"));
+
+        jakarta.servlet.http.HttpServletRequest request2 = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        Assert.assertEquals("/def", AuthUtil.getLoginResource(request2, 
"/def"));
+    }
+
+    @Test
+    public void test_getMappedLoginResourcePath_jakarta() {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        
Mockito.when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER))
+                .thenReturn(resolver);
+        
Mockito.when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/res");
+        Mockito.when(resolver.map(request, "/res")).thenReturn("/mapped/res");
+        Assert.assertEquals("/mapped/res", 
AuthUtil.getMappedLoginResourcePath(request, "/def"));
+    }
+
+    @Test
+    public void test_getMappedLoginResourcePath_null_jakarta() {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        // no resolver -> getResourceResolver returns null -> NPE guarded by 
null path only
+        // here defaultLoginResource is null and no param -> resourcePath null 
-> returns null
+        Assert.assertNull(AuthUtil.getMappedLoginResourcePath(request, null));
+    }
+
+    @Test
+    public void test_setLoginResourceAttribute_jakarta() {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        // attribute already set
+        
Mockito.when(request.getAttribute(Authenticator.LOGIN_RESOURCE)).thenReturn("/existing");
+        Assert.assertEquals("/existing", 
AuthUtil.setLoginResourceAttribute(request, "/def"));
+
+        // parameter set
+        jakarta.servlet.http.HttpServletRequest request2 = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        
Mockito.when(request2.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/param");
+        Assert.assertEquals("/param", 
AuthUtil.setLoginResourceAttribute(request2, "/def"));
+
+        // default used
+        jakarta.servlet.http.HttpServletRequest request3 = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        Assert.assertEquals("/def", 
AuthUtil.setLoginResourceAttribute(request3, "/def"));
+
+        // fallback to "/"
+        jakarta.servlet.http.HttpServletRequest request4 = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        Assert.assertEquals("/", AuthUtil.setLoginResourceAttribute(request4, 
null));
+    }
+
+    @Test
+    public void test_sendRedirect_jakarta() throws Exception {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        jakarta.servlet.http.HttpServletResponse response =
+                Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Mockito.when(request.getRequestURI()).thenReturn("/current");
+        Mockito.when(request.getQueryString()).thenReturn("x=1");
+
+        Map<String, String> params = new HashMap<>();
+        params.put("k", "v v");
+        AuthUtil.sendRedirect(request, response, "/valid/target", params);
+
+        
Mockito.verify(response).sendRedirect(Mockito.contains("/valid/target?"));
+    }
+
+    @Test
+    public void test_sendRedirect_invalidTarget_jakarta() throws Exception {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        jakarta.servlet.http.HttpServletResponse response =
+                Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
+        Mockito.when(request.getContextPath()).thenReturn("/ctx");
+        Mockito.when(request.getRequestURI()).thenReturn("/current");
+
+        AuthUtil.sendRedirect(request, response, "relative", null);
+        Mockito.verify(response).sendRedirect(Mockito.startsWith("/ctx?"));
+    }
+
+    @Test
+    public void test_sendRedirect_invalidTarget_rootContext_jakarta() throws 
Exception {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        jakarta.servlet.http.HttpServletResponse response =
+                Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Mockito.when(request.getRequestURI()).thenReturn("/current");
+
+        AuthUtil.sendRedirect(request, response, "relative", null);
+        Mockito.verify(response).sendRedirect(Mockito.startsWith("/?"));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void test_sendRedirect_committed_jakarta() throws Exception {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        jakarta.servlet.http.HttpServletResponse response =
+                Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
+        Mockito.when(response.isCommitted()).thenReturn(true);
+        AuthUtil.sendRedirect(request, response, "/target", null);
+    }
+
+    @Test
+    public void test_isValidateRequest_jakarta() {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        
Mockito.when(request.getParameter(AuthConstants.PAR_J_VALIDATE)).thenReturn("TRUE");
+        Assert.assertTrue(AuthUtil.isValidateRequest(request));
+        
Mockito.when(request.getParameter(AuthConstants.PAR_J_VALIDATE)).thenReturn("no");
+        Assert.assertFalse(AuthUtil.isValidateRequest(request));
+    }
+
+    @Test
+    public void test_sendValid_jakarta() {
+        jakarta.servlet.http.HttpServletResponse response =
+                Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
+        AuthUtil.sendValid(response);
+        
Mockito.verify(response).setStatus(jakarta.servlet.http.HttpServletResponse.SC_OK);
+        Mockito.verify(response).setContentLength(0);
+    }
+
+    @Test
+    public void test_sendInvalid_withReason_jakarta() throws Exception {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        jakarta.servlet.http.HttpServletResponse response =
+                Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
+        
Mockito.when(request.getAttribute(JakartaAuthenticationHandler.FAILURE_REASON))
+                .thenReturn("bad");
+        
Mockito.when(request.getAttribute(JakartaAuthenticationHandler.FAILURE_REASON_CODE))
+                .thenReturn("code1");
+        StringWriter sw = new StringWriter();
+        Mockito.when(response.getWriter()).thenReturn(new PrintWriter(sw));
+
+        AuthUtil.sendInvalid(request, response);
+        
Mockito.verify(response).setStatus(jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN);
+        Mockito.verify(response).setHeader(AuthConstants.X_REASON, "bad");
+        Mockito.verify(response).setHeader(AuthConstants.X_REASON_CODE, 
"code1");
+        Assert.assertTrue(sw.toString().contains("bad"));
+    }
+
+    @Test
+    public void test_sendInvalid_noReason_jakarta() {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        jakarta.servlet.http.HttpServletResponse response =
+                Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
+        AuthUtil.sendInvalid(request, response);
+        
Mockito.verify(response).setStatus(jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN);
+    }
+
+    @Test
+    public void test_checkReferer_jakarta() {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        // not a POST -> true
+        Mockito.when(request.getMethod()).thenReturn("GET");
+        Assert.assertTrue(AuthUtil.checkReferer(request, "/login"));
+
+        // POST with no referer -> true
+        Mockito.when(request.getMethod()).thenReturn("POST");
+        Assert.assertTrue(AuthUtil.checkReferer(request, "/login"));
+
+        // POST with matching referer -> true
+        Mockito.when(request.getContextPath()).thenReturn("");
+        
Mockito.when(request.getHeader("Referer")).thenReturn("http://host/login";);
+        Assert.assertTrue(AuthUtil.checkReferer(request, "/login"));
+
+        // POST with non-matching referer -> false
+        
Mockito.when(request.getHeader("Referer")).thenReturn("http://host/other";);
+        Assert.assertFalse(AuthUtil.checkReferer(request, "/login"));
+
+        // POST with malformed referer -> true (parse fails, falls through)
+        Mockito.when(request.getHeader("Referer")).thenReturn("::not a url::");
+        Assert.assertTrue(AuthUtil.checkReferer(request, "/login"));
+    }
+
+    @Test
+    public void test_isAjaxRequest_jakarta() {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        
Mockito.when(request.getHeader("X-Requested-With")).thenReturn("XMLHttpRequest");
+        Assert.assertTrue(AuthUtil.isAjaxRequest(request));
+        
Mockito.when(request.getHeader("X-Requested-With")).thenReturn("other");
+        Assert.assertFalse(AuthUtil.isAjaxRequest(request));
+    }
+
+    @Test
+    public void test_isRedirectValid_url_jakarta() {
+        Assert.assertFalse(
+                
AuthUtil.isRedirectValid((jakarta.servlet.http.HttpServletRequest) null, 
"http://www.google.com";));
+    }
+
+    @Test
+    public void test_isBrowserRequest_jakarta() {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        
Mockito.when(request.getHeader("User-Agent")).thenReturn("Mozilla/5.0");
+        Assert.assertTrue(AuthUtil.isBrowserRequest(request));
+        Mockito.when(request.getHeader("User-Agent")).thenReturn("curl");
+        Assert.assertFalse(AuthUtil.isBrowserRequest(request));
+    }
+
+    // ---- Deprecated javax variants ----
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_getAttributeOrParameter_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getAttribute("a")).thenReturn("attrValue");
+        Assert.assertEquals("attrValue", 
AuthUtil.getAttributeOrParameter(request, "a", "def"));
+
+        Mockito.when(request.getAttribute("b")).thenReturn(null);
+        Mockito.when(request.getParameter("b")).thenReturn("paramValue");
+        Assert.assertEquals("paramValue", 
AuthUtil.getAttributeOrParameter(request, "b", "def"));
+
+        Mockito.when(request.getAttribute("c")).thenReturn(null);
+        Mockito.when(request.getParameter("c")).thenReturn(null);
+        Assert.assertEquals("def", AuthUtil.getAttributeOrParameter(request, 
"c", "def"));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_getLoginResource_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        
Mockito.when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/res");
+        Assert.assertEquals("/res", AuthUtil.getLoginResource(request, 
"/def"));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_getMappedLoginResourcePath_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        
Mockito.when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER))
+                .thenReturn(resolver);
+        
Mockito.when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/res");
+        Mockito.when(resolver.map(request, "/res")).thenReturn("/mapped/res");
+        Assert.assertEquals("/mapped/res", 
AuthUtil.getMappedLoginResourcePath(request, "/def"));
+
+        javax.servlet.http.HttpServletRequest request2 = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Assert.assertNull(AuthUtil.getMappedLoginResourcePath(request2, null));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_setLoginResourceAttribute_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        
Mockito.when(request.getAttribute(Authenticator.LOGIN_RESOURCE)).thenReturn("/existing");
+        Assert.assertEquals("/existing", 
AuthUtil.setLoginResourceAttribute(request, "/def"));
+
+        javax.servlet.http.HttpServletRequest request2 = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        
Mockito.when(request2.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/param");
+        Assert.assertEquals("/param", 
AuthUtil.setLoginResourceAttribute(request2, "/def"));
+
+        javax.servlet.http.HttpServletRequest request3 = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Assert.assertEquals("/def", 
AuthUtil.setLoginResourceAttribute(request3, "/def"));
+
+        javax.servlet.http.HttpServletRequest request4 = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Assert.assertEquals("/", AuthUtil.setLoginResourceAttribute(request4, 
null));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_sendRedirect_javax() throws Exception {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        javax.servlet.http.HttpServletResponse response = 
Mockito.mock(javax.servlet.http.HttpServletResponse.class);
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Mockito.when(request.getRequestURI()).thenReturn("/current");
+        Mockito.when(request.getQueryString()).thenReturn(null);
+
+        Map<String, String> params = new HashMap<>();
+        params.put("k", "v");
+        AuthUtil.sendRedirect(request, response, "/valid/target", params);
+        
Mockito.verify(response).sendRedirect(Mockito.contains("/valid/target?"));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_isValidateRequest_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        
Mockito.when(request.getParameter(AuthConstants.PAR_J_VALIDATE)).thenReturn("true");
+        Assert.assertTrue(AuthUtil.isValidateRequest(request));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_sendValid_javax() {
+        javax.servlet.http.HttpServletResponse response = 
Mockito.mock(javax.servlet.http.HttpServletResponse.class);
+        AuthUtil.sendValid(response);
+        
Mockito.verify(response).setStatus(javax.servlet.http.HttpServletResponse.SC_OK);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_sendInvalid_javax() throws Exception {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        javax.servlet.http.HttpServletResponse response = 
Mockito.mock(javax.servlet.http.HttpServletResponse.class);
+        
Mockito.when(request.getAttribute(JakartaAuthenticationHandler.FAILURE_REASON))
+                .thenReturn("bad");
+        StringWriter sw = new StringWriter();
+        Mockito.when(response.getWriter()).thenReturn(new PrintWriter(sw));
+        AuthUtil.sendInvalid(request, response);
+        
Mockito.verify(response).setStatus(javax.servlet.http.HttpServletResponse.SC_FORBIDDEN);
+        Assert.assertTrue(sw.toString().contains("bad"));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_checkReferer_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getMethod()).thenReturn("POST");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        
Mockito.when(request.getHeader("Referer")).thenReturn("http://host/login";);
+        Assert.assertTrue(AuthUtil.checkReferer(request, "/login"));
+        
Mockito.when(request.getHeader("Referer")).thenReturn("http://host/other";);
+        Assert.assertFalse(AuthUtil.checkReferer(request, "/login"));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_isAjaxRequest_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        
Mockito.when(request.getHeader("X-Requested-With")).thenReturn("XMLHttpRequest");
+        Assert.assertTrue(AuthUtil.isAjaxRequest(request));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_isBrowserRequest_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getHeader("User-Agent")).thenReturn("Opera/9");
+        Assert.assertTrue(AuthUtil.isBrowserRequest(request));
+        Mockito.when(request.getHeader("User-Agent")).thenReturn(null);
+        Assert.assertFalse(AuthUtil.isBrowserRequest(request));
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_isRedirectValid_javax() {
+        
Assert.assertFalse(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest)
 null, "http://host";));
+        
Assert.assertTrue(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest)
 null, "/absolute/path"));
+        
Assert.assertFalse(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest)
 null, "/unnormalized//x"));
+    }
+
+    // ---- jakarta isRedirectValid, resolver resolves the target ----
+
+    @Test
+    public void test_isRedirectValid_resolvesToResource_jakarta() {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getContextPath()).thenReturn("");
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        final Resource resource = Mockito.mock(Resource.class);
+        Mockito.when(resource.getResourceType()).thenReturn("some/type");
+        Mockito.when(resolver.resolve(Mockito.eq(request), 
Mockito.anyString())).thenReturn(resource);
+        
Mockito.when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER))
+                .thenReturn(resolver);
+
+        Assert.assertTrue(AuthUtil.isRedirectValid(request, "/valid/path"));
+    }
+
+    // ---- javax isRedirectValid full branch coverage ----
+
+    @Test
+    public void test_isRedirectValid_empty_javax() {
+        
Assert.assertFalse(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest)
 null, ""));
+        
Assert.assertFalse(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest)
 null, null));
+    }
+
+    @Test
+    public void test_isRedirectValid_illegalCharacters_javax() {
+        // a space triggers a URISyntaxException
+        
Assert.assertFalse(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest)
 null, "/a b"));
+    }
+
+    @Test
+    public void test_isRedirectValid_url_javax() {
+        
Assert.assertFalse(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest)
 null, "http://host/x";));
+    }
+
+    @Test
+    public void test_isRedirectValid_notNormalized_javax() {
+        
Assert.assertFalse(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest)
 null, "/a//b"));
+    }
+
+    @Test
+    public void test_isRedirectValid_contextPathMismatch_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getContextPath()).thenReturn("/ctx");
+        Assert.assertFalse(AuthUtil.isRedirectValid(request, "/other/path"));
+    }
+
+    @Test
+    public void test_isRedirectValid_contextRoot_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getContextPath()).thenReturn("/ctx");
+        Assert.assertTrue(AuthUtil.isRedirectValid(request, "/ctx"));
+    }
+
+    @Test
+    public void test_isRedirectValid_notAbsoluteAfterContext_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getContextPath()).thenReturn("/ctx");
+        Assert.assertFalse(AuthUtil.isRedirectValid(request, "/ctxrelative"));
+    }
+
+    @Test
+    public void test_isRedirectValid_illegalChars_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Assert.assertFalse(AuthUtil.isRedirectValid(request, "/path'quote"));
+    }
+
+    @Test
+    public void test_isRedirectValid_resolvesToResource_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getContextPath()).thenReturn("");
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        final Resource resource = Mockito.mock(Resource.class);
+        Mockito.when(resource.getResourceType()).thenReturn("some/type");
+        Mockito.when(resolver.resolve(Mockito.eq(request), 
Mockito.anyString())).thenReturn(resource);
+        
Mockito.when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER))
+                .thenReturn(resolver);
+
+        Assert.assertTrue(AuthUtil.isRedirectValid(request, "/valid/path"));
+    }
+
+    // ---- javax sendRedirect branch coverage ----
+
+    @Test
+    public void test_sendRedirect_invalidTarget_rootContext_javax() throws 
Exception {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        javax.servlet.http.HttpServletResponse response = 
Mockito.mock(javax.servlet.http.HttpServletResponse.class);
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Mockito.when(request.getRequestURI()).thenReturn("/current");
+        AuthUtil.sendRedirect(request, response, "relative", null);
+        Mockito.verify(response).sendRedirect(Mockito.startsWith("/?"));
+    }
+
+    @Test
+    public void test_sendRedirect_invalidTarget_withContext_javax() throws 
Exception {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        javax.servlet.http.HttpServletResponse response = 
Mockito.mock(javax.servlet.http.HttpServletResponse.class);
+        Mockito.when(request.getContextPath()).thenReturn("/ctx");
+        Mockito.when(request.getRequestURI()).thenReturn("/ctx/current");
+        Mockito.when(request.getQueryString()).thenReturn("a=b");
+        final Map<String, String> params = new HashMap<>();
+        AuthUtil.sendRedirect(request, response, "relative", params);
+        Mockito.verify(response).sendRedirect(Mockito.startsWith("/ctx?"));
+    }
+
+    // ---- jakarta sendInvalid with reason code ----
+
+    @Test
+    public void test_sendInvalid_withReasonCode_jakarta() throws Exception {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        jakarta.servlet.http.HttpServletResponse response =
+                Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
+        
Mockito.when(request.getAttribute(JakartaAuthenticationHandler.FAILURE_REASON))
+                .thenReturn("bad");
+        
Mockito.when(request.getAttribute(JakartaAuthenticationHandler.FAILURE_REASON_CODE))
+                .thenReturn("code42");
+        Mockito.when(response.getWriter()).thenReturn(new 
java.io.PrintWriter(new java.io.StringWriter()));
+        AuthUtil.sendInvalid(request, response);
+        Mockito.verify(response).setHeader(AuthConstants.X_REASON_CODE, 
"code42");
+    }
+
+    // ---- IOException error paths ----
+
+    @Test
+    public void test_sendValid_ioexception_jakarta() throws Exception {
+        jakarta.servlet.http.HttpServletResponse response =
+                Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
+        Mockito.doThrow(new IOException("boom")).when(response).flushBuffer();
+        AuthUtil.sendValid(response);
+    }
+
+    @Test
+    public void test_sendValid_ioexception_javax() throws Exception {
+        javax.servlet.http.HttpServletResponse response = 
Mockito.mock(javax.servlet.http.HttpServletResponse.class);
+        Mockito.doThrow(new IOException("boom")).when(response).flushBuffer();
+        AuthUtil.sendValid(response);
+    }
+
+    @Test
+    public void test_sendInvalid_ioexception_jakarta() throws Exception {
+        jakarta.servlet.http.HttpServletRequest request = 
Mockito.mock(jakarta.servlet.http.HttpServletRequest.class);
+        jakarta.servlet.http.HttpServletResponse response =
+                Mockito.mock(jakarta.servlet.http.HttpServletResponse.class);
+        Mockito.doThrow(new IOException("boom")).when(response).flushBuffer();
+        AuthUtil.sendInvalid(request, response);
+    }
+
+    @Test
+    public void test_sendInvalid_ioexception_javax() throws Exception {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        javax.servlet.http.HttpServletResponse response = 
Mockito.mock(javax.servlet.http.HttpServletResponse.class);
+        Mockito.doThrow(new IOException("boom")).when(response).flushBuffer();
+        AuthUtil.sendInvalid(request, response);
+    }
+
+    // ---- javax checkReferer malformed URL ----
+
+    @Test
+    public void test_checkReferer_malformedUrl_javax() {
+        javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getMethod()).thenReturn("POST");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Mockito.when(request.getHeader("Referer")).thenReturn("::: not a url 
:::");
+        Assert.assertTrue(AuthUtil.checkReferer(request, "/login"));
+    }
 }
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerHolderTest.java
 
b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerHolderTest.java
new file mode 100644
index 0000000..31bd0aa
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerHolderTest.java
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.impl;
+
+import java.io.IOException;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.sling.auth.core.AuthConstants;
+import org.apache.sling.auth.core.spi.AuthenticationInfo;
+import org.apache.sling.auth.core.spi.JakartaAuthenticationFeedbackHandler;
+import org.apache.sling.auth.core.spi.JakartaAuthenticationHandler;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.osgi.framework.ServiceReference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class AuthenticationHandlerHolderTest {
+
+    private static final String PATH = "/content";
+
+    private interface FeedbackHandler extends JakartaAuthenticationHandler, 
JakartaAuthenticationFeedbackHandler {}
+
+    @SuppressWarnings("deprecation")
+    private interface LegacyFeedbackHandler
+            extends org.apache.sling.auth.core.spi.AuthenticationHandler,
+                    
org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler {}
+
+    @Test
+    public void extractCredentialsSetsAndRestoresExistingPath() {
+        final JakartaAuthenticationHandler handler = 
mock(JakartaAuthenticationHandler.class);
+        final AuthenticationHandlerHolder holder = holder(handler, "form", 
null);
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        final AuthenticationInfo authInfo = new AuthenticationInfo("form");
+        
when(request.getAttribute(JakartaAuthenticationHandler.PATH_PROPERTY)).thenReturn("oldPath");
+        when(handler.extractCredentials(request, 
response)).thenReturn(authInfo);
+
+        assertSame(authInfo, holder.extractCredentials(request, response));
+
+        final InOrder order = inOrder(request, handler);
+        
order.verify(request).getAttribute(JakartaAuthenticationHandler.PATH_PROPERTY);
+        
order.verify(request).setAttribute(JakartaAuthenticationHandler.PATH_PROPERTY, 
PATH);
+        order.verify(handler).extractCredentials(request, response);
+        
order.verify(request).setAttribute(JakartaAuthenticationHandler.PATH_PROPERTY, 
"oldPath");
+    }
+
+    @Test
+    public void dropCredentialsRemovesPathWhenNoPreviousPathExists() throws 
IOException {
+        final JakartaAuthenticationHandler handler = 
mock(JakartaAuthenticationHandler.class);
+        final AuthenticationHandlerHolder holder = holder(handler, "form", 
null);
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+
+        holder.dropCredentials(request, response);
+
+        final InOrder order = inOrder(request, handler);
+        
order.verify(request).setAttribute(JakartaAuthenticationHandler.PATH_PROPERTY, 
PATH);
+        order.verify(handler).dropCredentials(request, response);
+        
order.verify(request).removeAttribute(JakartaAuthenticationHandler.PATH_PROPERTY);
+    }
+
+    @Test
+    public void requestCredentialsDelegatesWhenRequestedLoginMatchesAuthType() 
throws IOException {
+        final JakartaAuthenticationHandler handler = 
mock(JakartaAuthenticationHandler.class);
+        final AuthenticationHandlerHolder holder = holder(handler, "form", 
null);
+        final HttpServletRequest request = requestWithLogin(null, "form");
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        when(handler.requestCredentials(request, response)).thenReturn(true);
+
+        assertTrue(holder.requestCredentials(request, response));
+
+        verify(handler).requestCredentials(request, response);
+        
verify(request).removeAttribute(JakartaAuthenticationHandler.PATH_PROPERTY);
+    }
+
+    @Test
+    public void 
requestCredentialsSkipsWhenRequestedLoginDoesNotMatchAuthType() throws 
IOException {
+        final JakartaAuthenticationHandler handler = 
mock(JakartaAuthenticationHandler.class);
+        final AuthenticationHandlerHolder holder = holder(handler, "form", 
null);
+        final HttpServletRequest request = requestWithLogin(null, "basic");
+
+        assertFalse(holder.requestCredentials(request, 
mock(HttpServletResponse.class)));
+
+        verify(handler, 
never()).requestCredentials(any(HttpServletRequest.class), 
any(HttpServletResponse.class));
+        
verify(request).removeAttribute(JakartaAuthenticationHandler.PATH_PROPERTY);
+    }
+
+    @Test
+    public void requestCredentialsUsesLoginAttributeBeforeParameter() throws 
IOException {
+        final JakartaAuthenticationHandler handler = 
mock(JakartaAuthenticationHandler.class);
+        final AuthenticationHandlerHolder holder = holder(handler, "form", 
null);
+        final HttpServletRequest request = requestWithLogin("form", "basic");
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        when(handler.requestCredentials(request, response)).thenReturn(true);
+
+        assertTrue(holder.requestCredentials(request, response));
+
+        verify(handler).requestCredentials(request, response);
+    }
+
+    @Test
+    public void requestCredentialsWithoutAuthTypeIgnoresRequestedLogin() 
throws IOException {
+        final JakartaAuthenticationHandler handler = 
mock(JakartaAuthenticationHandler.class);
+        final AuthenticationHandlerHolder holder = holder(handler, null, null);
+        final HttpServletRequest request = requestWithLogin(null, "basic");
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        when(handler.requestCredentials(request, response)).thenReturn(true);
+
+        assertTrue(holder.requestCredentials(request, response));
+
+        verify(handler).requestCredentials(request, response);
+    }
+
+    @Test
+    public void browserOnlyRequestCredentialsSkipsNonBrowserRequests() throws 
IOException {
+        final JakartaAuthenticationHandler handler = 
mock(JakartaAuthenticationHandler.class);
+        final AuthenticationHandlerHolder holder = holder(handler, "form", 
"true");
+        final HttpServletRequest request = requestWithLogin(null, null);
+        when(request.getHeader("User-Agent")).thenReturn("curl/8.0");
+
+        assertFalse(holder.requestCredentials(request, 
mock(HttpServletResponse.class)));
+
+        verify(handler, 
never()).requestCredentials(any(HttpServletRequest.class), 
any(HttpServletResponse.class));
+    }
+
+    @Test
+    public void 
browserOnlyRequestCredentialsAllowsBrowserRequestsForYesValue() throws 
IOException {
+        final JakartaAuthenticationHandler handler = 
mock(JakartaAuthenticationHandler.class);
+        final AuthenticationHandlerHolder holder = holder(handler, "form", 
"yes");
+        final HttpServletRequest request = requestWithLogin(null, null);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        when(request.getHeader("User-Agent")).thenReturn("Mozilla/5.0");
+        when(handler.requestCredentials(request, response)).thenReturn(true);
+
+        assertTrue(holder.requestCredentials(request, response));
+
+        verify(handler).requestCredentials(request, response);
+    }
+
+    @Test
+    public void 
getFeedbackHandlerReturnsJakartaFeedbackHandlerOnlyWhenImplemented() {
+        final FeedbackHandler feedbackHandler = mock(FeedbackHandler.class);
+        assertSame(feedbackHandler, holder(feedbackHandler, "form", 
null).getFeedbackHandler());
+        assertNull(
+                holder(mock(JakartaAuthenticationHandler.class), "form", 
null).getFeedbackHandler());
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void legacyConstructorWrapsDeprecatedAuthenticationHandler() {
+        final org.apache.sling.auth.core.spi.AuthenticationHandler handler = 
mock(LegacyFeedbackHandler.class);
+        final AuthenticationHandlerHolder holder =
+                new AuthenticationHandlerHolder(PATH, handler, 
serviceReference("legacy", null));
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        final AuthenticationInfo authInfo = new AuthenticationInfo("legacy");
+        when(handler.extractCredentials(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class)))
+                .thenReturn(authInfo);
+
+        assertSame(authInfo, holder.extractCredentials(request, response));
+    }
+
+    @Test
+    public void equalsHashCodeCompareToAndToStringIncludeHolderState() {
+        final JakartaAuthenticationHandler handler = 
mock(JakartaAuthenticationHandler.class);
+        when(handler.toString()).thenReturn("handlerString");
+        final ServiceReference<?> serviceReference = serviceReference("form", 
null);
+        final AuthenticationHandlerHolder holder = new 
AuthenticationHandlerHolder(PATH, handler, serviceReference);
+        final AuthenticationHandlerHolder same = new 
AuthenticationHandlerHolder(PATH, handler, serviceReference);
+        final AuthenticationHandlerHolder differentHandler =
+                new AuthenticationHandlerHolder(PATH, 
mock(JakartaAuthenticationHandler.class), serviceReference);
+        final AuthenticationHandlerHolder differentType =
+                new AuthenticationHandlerHolder(PATH, handler, 
serviceReference("basic", null));
+        final AuthenticationHandlerHolder differentBrowserOnly =
+                new AuthenticationHandlerHolder(PATH, handler, 
serviceReference("form", "true"));
+
+        assertEquals(holder, holder);
+        assertEquals(holder, same);
+        assertEquals(holder.hashCode(), same.hashCode());
+        assertEquals(0, holder.compareTo(same));
+        assertNotEquals(holder, null);
+        assertNotEquals(holder, differentHandler);
+        assertNotEquals(holder, differentType);
+        assertNotEquals(holder, differentBrowserOnly);
+        assertEquals("handlerString", holder.toString());
+        assertTrue(holder.isPathRequiresHandler("/content/page"));
+    }
+
+    private AuthenticationHandlerHolder holder(
+            final JakartaAuthenticationHandler handler, final String authType, 
final String browserOnly) {
+        return new AuthenticationHandlerHolder(PATH, handler, 
serviceReference(authType, browserOnly));
+    }
+
+    private HttpServletRequest requestWithLogin(final String attributeValue, 
final String parameterValue) {
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        
when(request.getAttribute(JakartaAuthenticationHandler.REQUEST_LOGIN_PARAMETER))
+                .thenReturn(attributeValue);
+        
when(request.getParameter(JakartaAuthenticationHandler.REQUEST_LOGIN_PARAMETER))
+                .thenReturn(parameterValue);
+        return request;
+    }
+
+    private ServiceReference<?> serviceReference(final String authType, final 
String browserOnly) {
+        final ServiceReference<?> serviceReference = 
mock(ServiceReference.class);
+        
when(serviceReference.getProperty(JakartaAuthenticationHandler.TYPE_PROPERTY))
+                .thenReturn(authType);
+        
when(serviceReference.getProperty(AuthConstants.AUTH_HANDLER_BROWSER_ONLY))
+                .thenReturn(browserOnly);
+        return serviceReference;
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerWrapperTest.java
 
b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerWrapperTest.java
new file mode 100644
index 0000000..6fc93ae
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerWrapperTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.impl;
+
+import java.io.IOException;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler;
+import org.apache.sling.auth.core.spi.AuthenticationHandler;
+import org.apache.sling.auth.core.spi.AuthenticationInfo;
+import org.apache.sling.auth.core.spi.JakartaAuthenticationFeedbackHandler;
+import org.apache.sling.auth.core.spi.JakartaAuthenticationHandler;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@SuppressWarnings("deprecation")
+public class AuthenticationHandlerWrapperTest {
+
+    private interface FeedbackHandler extends AuthenticationHandler, 
AuthenticationFeedbackHandler {}
+
+    @Test
+    public void plainWrapperDelegatesAuthenticationHandlerMethods() throws 
IOException {
+        final AuthenticationHandler handler = 
mock(AuthenticationHandler.class);
+        final JakartaAuthenticationHandler wrapper = 
AuthenticationHandlerWrapper.wrap(handler);
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        final AuthenticationInfo authInfo = new AuthenticationInfo("legacy");
+        when(handler.extractCredentials(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class)))
+                .thenReturn(authInfo);
+        when(handler.requestCredentials(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class)))
+                .thenReturn(true);
+
+        assertFalse(wrapper instanceof JakartaAuthenticationFeedbackHandler);
+        assertSame(authInfo, wrapper.extractCredentials(request, response));
+        assertTrue(wrapper.requestCredentials(request, response));
+        wrapper.dropCredentials(request, response);
+
+        final ArgumentCaptor<javax.servlet.http.HttpServletRequest> 
requestCaptor =
+                
ArgumentCaptor.forClass(javax.servlet.http.HttpServletRequest.class);
+        final ArgumentCaptor<javax.servlet.http.HttpServletResponse> 
responseCaptor =
+                
ArgumentCaptor.forClass(javax.servlet.http.HttpServletResponse.class);
+        verify(handler).extractCredentials(requestCaptor.capture(), 
responseCaptor.capture());
+        verify(handler)
+                .requestCredentials(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class));
+        verify(handler)
+                .dropCredentials(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class));
+        assertNotNull(requestCaptor.getValue());
+        assertNotNull(responseCaptor.getValue());
+    }
+
+    @Test
+    public void feedbackWrapperDelegatesFeedbackMethods() {
+        final FeedbackHandler handler = mock(FeedbackHandler.class);
+        final JakartaAuthenticationHandler wrapper = 
AuthenticationHandlerWrapper.wrap(handler);
+        final JakartaAuthenticationFeedbackHandler feedbackWrapper = 
(JakartaAuthenticationFeedbackHandler) wrapper;
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        final AuthenticationInfo authInfo = new AuthenticationInfo("legacy");
+        when(handler.authenticationSucceeded(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class),
+                        any(AuthenticationInfo.class)))
+                .thenReturn(true);
+
+        feedbackWrapper.authenticationFailed(request, response, authInfo);
+        assertTrue(feedbackWrapper.authenticationSucceeded(request, response, 
authInfo));
+
+        verify(handler)
+                .authenticationFailed(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class),
+                        org.mockito.ArgumentMatchers.same(authInfo));
+        verify(handler)
+                .authenticationSucceeded(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class),
+                        org.mockito.ArgumentMatchers.same(authInfo));
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/AuthenticationRequirementHolderTest.java
 
b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationRequirementHolderTest.java
new file mode 100644
index 0000000..c51ca32
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationRequirementHolderTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.impl;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AuthenticationRequirementHolderTest {
+
+    @Test
+    public void test_fromConfig_plus() {
+        AuthenticationRequirementHolder h = 
AuthenticationRequirementHolder.fromConfig("+/secure", null);
+        Assert.assertTrue(h.requiresAuthentication());
+        Assert.assertEquals("/secure", h.fullPath);
+    }
+
+    @Test
+    public void test_fromConfig_minus() {
+        AuthenticationRequirementHolder h = 
AuthenticationRequirementHolder.fromConfig("-/open", null);
+        Assert.assertFalse(h.requiresAuthentication());
+        Assert.assertEquals("/open", h.fullPath);
+    }
+
+    @Test
+    public void test_fromConfig_plain() {
+        AuthenticationRequirementHolder h = 
AuthenticationRequirementHolder.fromConfig("/plain", null);
+        Assert.assertTrue(h.requiresAuthentication());
+        Assert.assertEquals("/plain", h.fullPath);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void test_fromConfig_null() {
+        AuthenticationRequirementHolder.fromConfig(null, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void test_fromConfig_empty() {
+        AuthenticationRequirementHolder.fromConfig("", null);
+    }
+
+    @Test
+    public void test_hashCode_equals() {
+        AuthenticationRequirementHolder a = new 
AuthenticationRequirementHolder("/p", true, null);
+        AuthenticationRequirementHolder b = new 
AuthenticationRequirementHolder("/p", true, null);
+        AuthenticationRequirementHolder c = new 
AuthenticationRequirementHolder("/p", false, null);
+
+        Assert.assertEquals(a, b);
+        Assert.assertEquals(a.hashCode(), b.hashCode());
+        Assert.assertNotEquals(a, c);
+        Assert.assertNotEquals(a.hashCode(), c.hashCode());
+        Assert.assertEquals(a, a);
+        Assert.assertNotEquals(a, null);
+        Assert.assertNotEquals(a, "not a holder");
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/AuthenticatorWebConsolePluginTest.java
 
b/src/test/java/org/apache/sling/auth/core/impl/AuthenticatorWebConsolePluginTest.java
new file mode 100644
index 0000000..c03e1b2
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/impl/AuthenticatorWebConsolePluginTest.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.sling.auth.core.impl.hc.SetField;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class AuthenticatorWebConsolePluginTest {
+
+    private SlingAuthenticator.Config config;
+    private AuthenticationHandlersManager handlersManager;
+
+    @SuppressWarnings("unchecked")
+    private PathBasedHolderCache<AuthenticationRequirementHolder> 
requirementsManager =
+            Mockito.mock(PathBasedHolderCache.class);
+
+    private HttpServletRequest request = 
Mockito.mock(HttpServletRequest.class);
+    private HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+
+    @Before
+    public void setup() throws Exception {
+        config = Mockito.mock(SlingAuthenticator.Config.class);
+        Mockito.when(config.sling_auth_anonymous_user()).thenReturn("");
+        Mockito.when(config.auth_sudo_cookie()).thenReturn("sling.sudo");
+        Mockito.when(config.auth_sudo_parameter()).thenReturn("sudo");
+
+        handlersManager = Mockito.mock(AuthenticationHandlersManager.class);
+        Map<String, List<String>> handlerMap = new LinkedHashMap<>();
+        handlerMap.put("/path/a", Arrays.asList("HandlerA", "HandlerB"));
+        
Mockito.when(handlersManager.getAuthenticationHandlerMap()).thenReturn(handlerMap);
+
+        Mockito.when(requirementsManager.getHolders())
+                .thenReturn(Arrays.asList(
+                        new AuthenticationRequirementHolder("/secure", true, 
null),
+                        new AuthenticationRequirementHolder("/public", false, 
null)));
+    }
+
+    private AuthenticatorWebConsolePlugin newPlugin() throws Exception {
+        AuthenticatorWebConsolePlugin plugin = new 
AuthenticatorWebConsolePlugin(config);
+        SetField.set(plugin, "authenticationHoldersManager", handlersManager);
+        SetField.set(plugin, "authenticationRequirementsManager", 
requirementsManager);
+        return plugin;
+    }
+
+    @Test
+    public void test_doGet_rendersAllSections() throws Exception {
+        AuthenticatorWebConsolePlugin plugin = newPlugin();
+        StringWriter sw = new StringWriter();
+        Mockito.when(response.getWriter()).thenReturn(new PrintWriter(sw));
+
+        plugin.doGet(request, response);
+
+        String out = sw.toString();
+        Assert.assertTrue(out.contains("Registered Authentication Handler"));
+        Assert.assertTrue(out.contains("/path/a"));
+        Assert.assertTrue(out.contains("HandlerA"));
+        Assert.assertTrue(out.contains("Authentication Requirement 
Configuration"));
+        Assert.assertTrue(out.contains("/secure"));
+        Assert.assertTrue(out.contains("Yes"));
+        Assert.assertTrue(out.contains("/public"));
+        Assert.assertTrue(out.contains("No"));
+        Assert.assertTrue(out.contains("Miscellaneous Configuration"));
+        Assert.assertTrue(out.contains("sling.sudo"));
+        Assert.assertTrue(out.contains("sudo"));
+    }
+
+    @Test
+    public void test_config_default_anonymous_user() throws Exception {
+        Mockito.when(config.sling_auth_anonymous_user()).thenReturn(null);
+        AuthenticatorWebConsolePlugin plugin = newPlugin();
+        StringWriter sw = new StringWriter();
+        Mockito.when(response.getWriter()).thenReturn(new PrintWriter(sw));
+
+        plugin.doGet(request, response);
+        Assert.assertTrue(sw.toString().contains("(default)"));
+    }
+
+    @Test
+    public void test_service_get_dispatches() throws Exception {
+        AuthenticatorWebConsolePlugin plugin = newPlugin();
+        StringWriter sw = new StringWriter();
+        Mockito.when(response.getWriter()).thenReturn(new PrintWriter(sw));
+        Mockito.when(request.getMethod()).thenReturn("GET");
+        Mockito.when(request.getProtocol()).thenReturn("HTTP/1.1");
+
+        plugin.service(request, response);
+        Assert.assertTrue(sw.toString().contains("Registered Authentication 
Handler"));
+    }
+
+    @Test
+    public void test_service_post_ignored() throws Exception {
+        AuthenticatorWebConsolePlugin plugin = newPlugin();
+        Mockito.when(request.getMethod()).thenReturn("POST");
+
+        plugin.service(request, response);
+        Mockito.verify(response, Mockito.never()).getWriter();
+    }
+
+    @Test
+    public void test_doGet_ioexception_sends_error() throws Exception {
+        AuthenticatorWebConsolePlugin plugin = newPlugin();
+        jakarta.servlet.ServletConfig scfg = 
Mockito.mock(jakarta.servlet.ServletConfig.class);
+        
Mockito.when(scfg.getServletContext()).thenReturn(Mockito.mock(jakarta.servlet.ServletContext.class));
+        plugin.init(scfg);
+        Mockito.when(response.getWriter()).thenThrow(new IOException("no 
writer"));
+
+        plugin.doGet(request, response);
+        
Mockito.verify(response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    public void test_modified_updates_config() throws Exception {
+        AuthenticatorWebConsolePlugin plugin = newPlugin();
+        SlingAuthenticator.Config newConfig = 
Mockito.mock(SlingAuthenticator.Config.class);
+        Mockito.when(newConfig.sling_auth_anonymous_user()).thenReturn(null);
+        Mockito.when(newConfig.auth_sudo_cookie()).thenReturn("c2");
+        Mockito.when(newConfig.auth_sudo_parameter()).thenReturn("p2");
+        plugin.modified(newConfig);
+
+        StringWriter sw = new StringWriter();
+        Mockito.when(response.getWriter()).thenReturn(new PrintWriter(sw));
+        plugin.doGet(request, response);
+        Assert.assertTrue(sw.toString().contains("c2"));
+        Assert.assertTrue(sw.toString().contains("p2"));
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandlerTest.java
 
b/src/test/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandlerTest.java
new file mode 100644
index 0000000..97db2d9
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandlerTest.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.impl;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.sling.auth.core.spi.AuthenticationInfo;
+import org.apache.sling.auth.core.spi.JakartaAuthenticationHandler;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class HttpBasicAuthenticationHandlerTest {
+
+    private HttpServletRequest request = 
Mockito.mock(HttpServletRequest.class);
+    private HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+
+    private static String basic(String user, String pass) {
+        String raw = user + ":" + pass;
+        return "Basic " + 
Base64.getEncoder().encodeToString(raw.getBytes(StandardCharsets.ISO_8859_1));
+    }
+
+    @Test
+    public void test_extractCredentials_valid() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        
Mockito.when(request.getHeader("Authorization")).thenReturn(basic("admin", 
"secret"));
+        AuthenticationInfo info = handler.extractCredentials(request, 
response);
+        Assert.assertNotNull(info);
+        Assert.assertEquals("admin", info.getUser());
+        Assert.assertArrayEquals("secret".toCharArray(), info.getPassword());
+    }
+
+    @Test
+    public void test_extractCredentials_no_colon() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        String header = "Basic " + 
Base64.getEncoder().encodeToString("justuser".getBytes(StandardCharsets.ISO_8859_1));
+        Mockito.when(request.getHeader("Authorization")).thenReturn(header);
+        AuthenticationInfo info = handler.extractCredentials(request);
+        Assert.assertNotNull(info);
+        Assert.assertEquals("justuser", info.getUser());
+        Assert.assertEquals(0, info.getPassword().length);
+    }
+
+    @Test
+    public void test_extractCredentials_no_header() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Assert.assertNull(handler.extractCredentials(request));
+        Mockito.when(request.getHeader("Authorization")).thenReturn("");
+        Assert.assertNull(handler.extractCredentials(request));
+    }
+
+    @Test
+    public void test_extractCredentials_no_blank() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Mockito.when(request.getHeader("Authorization")).thenReturn("Basic");
+        Assert.assertNull(handler.extractCredentials(request));
+    }
+
+    @Test
+    public void test_extractCredentials_wrong_scheme() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Mockito.when(request.getHeader("Authorization")).thenReturn("Digest 
abc");
+        Assert.assertNull(handler.extractCredentials(request));
+    }
+
+    @Test
+    public void test_extractCredentials_login_requested() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Mockito.when(request.getHeader("Authorization")).thenReturn(null);
+        
Mockito.when(request.getParameter(JakartaAuthenticationHandler.REQUEST_LOGIN_PARAMETER))
+                .thenReturn("BASIC");
+        AuthenticationInfo info = handler.extractCredentials(request, 
response);
+        Assert.assertSame(AuthenticationInfo.DOING_AUTH, info);
+        
Mockito.verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+    }
+
+    @Test
+    public void test_extractCredentials_no_login_requested() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Mockito.when(request.getHeader("Authorization")).thenReturn(null);
+        Assert.assertNull(handler.extractCredentials(request, response));
+    }
+
+    @Test
+    public void test_requestCredentials_fullSupport() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Assert.assertTrue(handler.requestCredentials(request, response));
+        
Mockito.verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+    }
+
+    @Test
+    public void test_requestCredentials_preemptive() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", false);
+        Assert.assertFalse(handler.requestCredentials(request, response));
+        Mockito.verify(response, Mockito.never()).setStatus(Mockito.anyInt());
+    }
+
+    @Test
+    public void test_dropCredentials_fullSupport_withHeader() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Mockito.when(request.getHeader("Authorization")).thenReturn("Basic 
xyz");
+        handler.dropCredentials(request, response);
+        
Mockito.verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+    }
+
+    @Test
+    public void test_dropCredentials_noHeader() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Mockito.when(request.getHeader("Authorization")).thenReturn(null);
+        handler.dropCredentials(request, response);
+        Mockito.verify(response, Mockito.never()).setStatus(Mockito.anyInt());
+    }
+
+    @Test
+    public void test_authenticationFailed_notValidate() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        handler.authenticationFailed(request, response, new 
AuthenticationInfo("BASIC"));
+        
Mockito.verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+    }
+
+    @Test
+    public void test_authenticationFailed_validate() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Mockito.when(request.getParameter("j_validate")).thenReturn("true");
+        handler.authenticationFailed(request, response, new 
AuthenticationInfo("BASIC"));
+        Mockito.verify(response, Mockito.never()).setStatus(Mockito.anyInt());
+    }
+
+    @Test
+    public void test_sendUnauthorized_committed() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Mockito.when(response.isCommitted()).thenReturn(true);
+        Assert.assertFalse(handler.sendUnauthorized(response));
+    }
+
+    @Test
+    public void test_sendUnauthorized_ok() {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Assert.assertTrue(handler.sendUnauthorized(response));
+        Mockito.verify(response).setHeader("WWW-Authenticate", "Basic 
realm=\"realm\"");
+    }
+
+    @Test
+    public void test_sendUnauthorized_ioexception() throws IOException {
+        HttpBasicAuthenticationHandler handler = new 
HttpBasicAuthenticationHandler("realm", true);
+        Mockito.doThrow(new IOException("boom")).when(response).flushBuffer();
+        Assert.assertFalse(handler.sendUnauthorized(response));
+    }
+
+    @Test
+    public void test_toString() {
+        Assert.assertTrue(
+                new HttpBasicAuthenticationHandler("r", 
true).toString().contains("enabled"));
+        Assert.assertTrue(
+                new HttpBasicAuthenticationHandler("r", 
false).toString().contains("preemptive"));
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/LoginServletTest.java 
b/src/test/java/org/apache/sling/auth/core/impl/LoginServletTest.java
new file mode 100644
index 0000000..43773b0
--- /dev/null
+++ b/src/test/java/org/apache/sling/auth/core/impl/LoginServletTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.impl;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.auth.Authenticator;
+import org.apache.sling.api.auth.NoAuthenticationHandlerException;
+import org.apache.sling.auth.core.impl.hc.SetField;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class LoginServletTest {
+
+    private SlingHttpServletRequest request = 
Mockito.mock(SlingHttpServletRequest.class);
+    private SlingHttpServletResponse response = 
Mockito.mock(SlingHttpServletResponse.class);
+
+    @Test
+    public void test_login_success() throws Exception {
+        LoginServlet servlet = new LoginServlet();
+        Authenticator authenticator = Mockito.mock(Authenticator.class);
+        SetField.set(servlet, "authenticator", authenticator);
+
+        Mockito.when(request.getAuthType()).thenReturn(null);
+        servlet.service(request, response);
+
+        Mockito.verify(authenticator)
+                .login((javax.servlet.http.HttpServletRequest) request, 
(javax.servlet.http.HttpServletResponse)
+                        response);
+    }
+
+    @Test
+    public void test_login_redirect_when_authenticated_and_self() throws 
Exception {
+        LoginServlet servlet = new LoginServlet();
+        Authenticator authenticator = Mockito.mock(Authenticator.class);
+        SetField.set(servlet, "authenticator", authenticator);
+
+        Mockito.when(request.getAuthType()).thenReturn("BASIC");
+        Mockito.when(request.getContextPath()).thenReturn("/ctx");
+        // no login resource -> resourcePath null -> isSelf true
+        servlet.service(request, response);
+
+        Mockito.verify(response).sendRedirect("/ctx/");
+        Mockito.verify(authenticator, Mockito.never())
+                .login(
+                        
Mockito.any(javax.servlet.http.HttpServletRequest.class),
+                        
Mockito.any(javax.servlet.http.HttpServletResponse.class));
+    }
+
+    @Test
+    public void test_login_no_authenticator() throws Exception {
+        LoginServlet servlet = new LoginServlet();
+        Mockito.when(request.getAuthType()).thenReturn(null);
+        servlet.service(request, response);
+        
Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_FORBIDDEN),
 Mockito.anyString());
+    }
+
+    @Test
+    public void test_login_no_handler() throws Exception {
+        LoginServlet servlet = new LoginServlet();
+        Authenticator authenticator = Mockito.mock(Authenticator.class);
+        SetField.set(servlet, "authenticator", authenticator);
+        Mockito.when(request.getAuthType()).thenReturn(null);
+        Mockito.doThrow(new NoAuthenticationHandlerException())
+                .when(authenticator)
+                .login(
+                        
Mockito.any(javax.servlet.http.HttpServletRequest.class),
+                        
Mockito.any(javax.servlet.http.HttpServletResponse.class));
+
+        servlet.service(request, response);
+        
Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_FORBIDDEN),
 Mockito.anyString());
+    }
+
+    @Test
+    public void test_login_response_committed() throws Exception {
+        LoginServlet servlet = new LoginServlet();
+        Authenticator authenticator = Mockito.mock(Authenticator.class);
+        SetField.set(servlet, "authenticator", authenticator);
+        Mockito.when(request.getAuthType()).thenReturn(null);
+        Mockito.doThrow(new IllegalStateException("committed"))
+                .when(authenticator)
+                .login(
+                        
Mockito.any(javax.servlet.http.HttpServletRequest.class),
+                        
Mockito.any(javax.servlet.http.HttpServletResponse.class));
+
+        servlet.service(request, response);
+        Mockito.verify(response, Mockito.never()).sendError(Mockito.anyInt(), 
Mockito.anyString());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/LogoutServletTest.java 
b/src/test/java/org/apache/sling/auth/core/impl/LogoutServletTest.java
new file mode 100644
index 0000000..2b98fa8
--- /dev/null
+++ b/src/test/java/org/apache/sling/auth/core/impl/LogoutServletTest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.impl;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.auth.Authenticator;
+import org.apache.sling.auth.core.impl.hc.SetField;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class LogoutServletTest {
+
+    private SlingHttpServletRequest request = 
Mockito.mock(SlingHttpServletRequest.class);
+    private SlingHttpServletResponse response = 
Mockito.mock(SlingHttpServletResponse.class);
+
+    @Test
+    public void test_logout_success() throws Exception {
+        LogoutServlet servlet = new LogoutServlet();
+        Authenticator authenticator = Mockito.mock(Authenticator.class);
+        SetField.set(servlet, "authenticator", authenticator);
+
+        servlet.service(request, response);
+        Mockito.verify(authenticator)
+                .logout((javax.servlet.http.HttpServletRequest) request, 
(javax.servlet.http.HttpServletResponse)
+                        response);
+    }
+
+    @Test
+    public void test_logout_no_authenticator() throws Exception {
+        LogoutServlet servlet = new LogoutServlet();
+        servlet.service(request, response);
+        Mockito.verify(response).setStatus(HttpServletResponse.SC_NO_CONTENT);
+    }
+
+    @Test
+    public void test_logout_response_committed() throws Exception {
+        LogoutServlet servlet = new LogoutServlet();
+        Authenticator authenticator = Mockito.mock(Authenticator.class);
+        SetField.set(servlet, "authenticator", authenticator);
+        Mockito.doThrow(new IllegalStateException("committed"))
+                .when(authenticator)
+                .logout(
+                        
Mockito.any(javax.servlet.http.HttpServletRequest.class),
+                        
Mockito.any(javax.servlet.http.HttpServletResponse.class));
+
+        servlet.service(request, response);
+        Mockito.verify(response, Mockito.never()).setStatus(Mockito.anyInt());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/PathBasedHolderTest.java 
b/src/test/java/org/apache/sling/auth/core/impl/PathBasedHolderTest.java
index a128efa..2548909 100644
--- a/src/test/java/org/apache/sling/auth/core/impl/PathBasedHolderTest.java
+++ b/src/test/java/org/apache/sling/auth/core/impl/PathBasedHolderTest.java
@@ -18,7 +18,11 @@
  */
 package org.apache.sling.auth.core.impl;
 
+import org.junit.Assert;
 import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -26,6 +30,10 @@ import static org.junit.Assert.assertTrue;
 
 public class PathBasedHolderTest {
 
+    private static PathBasedHolder holder(String url, ServiceReference<?> ref) 
{
+        return new PathBasedHolder(url, ref) {};
+    }
+
     @Test
     public void TestIsPathRequiresHandlerRoot() {
         final PathBasedHolder holder = new PathBasedHolder("/", null) {};
@@ -116,4 +124,97 @@ public class PathBasedHolderTest {
         final String handlerPath = "";
         assertPathRequiresHandler(true, requestPath, handlerPath);
     }
+
+    @Test
+    public void test_getProvider_noReference() {
+        assertEquals("Apache Sling Request Authenticator", holder("/x", 
null).getProvider());
+    }
+
+    @Test
+    public void test_getProvider_description() {
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        
Mockito.when(ref.getProperty(Constants.SERVICE_DESCRIPTION)).thenReturn("My 
Service");
+        assertEquals("My Service", holder("/x", ref).getProvider());
+    }
+
+    @Test
+    public void test_getProvider_serviceId() {
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        
Mockito.when(ref.getProperty(Constants.SERVICE_DESCRIPTION)).thenReturn(null);
+        Mockito.when(ref.getProperty(Constants.SERVICE_ID)).thenReturn(42L);
+        assertEquals("Service 42", holder("/x", ref).getProvider());
+    }
+
+    @Test
+    public void test_protocol_and_host_parsing() {
+        PathBasedHolder h = holder("http://example.com/some/path";, null);
+        assertTrue(h.isPathRequiresHandler("/some/path"));
+
+        // host only with trailing content
+        PathBasedHolder h2 = holder("//example.com/foo", null);
+        assertTrue(h2.isPathRequiresHandler("/foo"));
+
+        // host only, no path
+        PathBasedHolder h3 = holder("//example.com", null);
+        assertTrue(h3.isPathRequiresHandler("/anything"));
+
+        // just double slash
+        PathBasedHolder h4 = holder("//", null);
+        assertTrue(h4.isPathRequiresHandler("/anything"));
+    }
+
+    @Test
+    public void test_hashCode_and_equals() {
+        PathBasedHolder a = holder("/x", null);
+        PathBasedHolder b = holder("/x", null);
+        assertEquals(a.hashCode(), b.hashCode());
+        assertEquals(a, b);
+        Assert.assertNotEquals(a, holder("/y", null));
+        Assert.assertNotEquals(a, null);
+        Assert.assertNotEquals(a, "not a holder");
+        assertEquals(a, a);
+    }
+
+    @Test
+    public void test_equals_differentServiceReference() {
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        Assert.assertNotEquals(holder("/x", ref), holder("/x", null));
+        assertEquals(holder("/x", ref), holder("/x", ref));
+    }
+
+    @Test
+    public void test_compareTo_byPath() {
+        PathBasedHolder a = holder("/a", null);
+        PathBasedHolder b = holder("/b", null);
+        assertTrue(a.compareTo(b) > 0);
+        assertTrue(b.compareTo(a) < 0);
+    }
+
+    @Test
+    public void test_compareTo_nullServiceReferences() {
+        PathBasedHolder a = holder("/same", null);
+        PathBasedHolder b = holder("/same", null);
+        // both null service references -> compared by class name (same class) 
-> 0
+        assertEquals(0, a.compareTo(b));
+    }
+
+    @Test
+    public void test_compareTo_oneNullServiceReference() {
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        PathBasedHolder withRef = holder("/same", ref);
+        PathBasedHolder withoutRef = holder("/same", null);
+        assertEquals(-1, withoutRef.compareTo(withRef));
+        assertEquals(1, withRef.compareTo(withoutRef));
+    }
+
+    @Test
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public void test_compareTo_byServiceReference() {
+        ServiceReference refA = Mockito.mock(ServiceReference.class);
+        ServiceReference refB = Mockito.mock(ServiceReference.class);
+        Mockito.when(refB.compareTo(refA)).thenReturn(5);
+        PathBasedHolder a = holder("/same", refA);
+        PathBasedHolder b = holder("/same", refB);
+        assertEquals(5, a.compareTo(b));
+    }
 }
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorTest.java 
b/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorTest.java
index 053a6b9..85cebce 100644
--- a/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorTest.java
+++ b/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorTest.java
@@ -29,6 +29,7 @@ import jakarta.servlet.http.HttpServletResponse;
 import junitx.util.PrivateAccessor;
 import org.apache.sling.api.SlingJakartaHttpServletRequest;
 import org.apache.sling.api.SlingJakartaHttpServletResponse;
+import org.apache.sling.api.auth.NoAuthenticationHandlerException;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.auth.core.AuthenticationSupport;
@@ -493,4 +494,370 @@ public class SlingAuthenticatorTest {
             throw new UnsupportedOperationException("Unimplemented method 
'dropCredentials'");
         }
     }
+
+    private SlingAuthenticator 
createAuthenticator(AbstractAuthenticationHandlerHolder... holders) {
+        final SlingAuthenticator.Config config = 
SlingAuthenticatorTest.createDefaultConfig();
+        final AuthenticationRequirementsManager requirements =
+                new AuthenticationRequirementsManager(createBundleContext(), 
null, config, callable -> callable.run());
+        final AuthenticationHandlersManager handlers = new 
AuthenticationHandlersManager(config);
+        for (AbstractAuthenticationHandlerHolder h : holders) {
+            handlers.addHolder(h);
+        }
+        return new SlingAuthenticator(requirements, handlers, null, 
Mockito.mock(BundleContext.class), config);
+    }
+
+    private SlingAuthenticator createAuthenticator(
+            final ResourceResolverFactory rrf, final 
AbstractAuthenticationHandlerHolder... holders) {
+        final SlingAuthenticator.Config config = 
SlingAuthenticatorTest.createDefaultConfig();
+        final AuthenticationRequirementsManager requirements =
+                new AuthenticationRequirementsManager(createBundleContext(), 
null, config, callable -> callable.run());
+        final AuthenticationHandlersManager handlers = new 
AuthenticationHandlersManager(config);
+        for (AbstractAuthenticationHandlerHolder h : holders) {
+            handlers.addHolder(h);
+        }
+        return new SlingAuthenticator(requirements, handlers, rrf, 
Mockito.mock(BundleContext.class), config);
+    }
+
+    private AbstractAuthenticationHandlerHolder infoHolder(final String path, 
final AuthenticationInfo info) {
+        return new AbstractAuthenticationHandlerHolder(path, null) {
+            @Override
+            protected JakartaAuthenticationFeedbackHandler 
getFeedbackHandler() {
+                return null;
+            }
+
+            @Override
+            protected AuthenticationInfo doExtractCredentials(
+                    HttpServletRequest request, HttpServletResponse response) {
+                return info;
+            }
+
+            @Override
+            protected boolean doRequestCredentials(HttpServletRequest request, 
HttpServletResponse response)
+                    throws IOException {
+                return true;
+            }
+
+            @Override
+            protected void doDropCredentials(HttpServletRequest request, 
HttpServletResponse response)
+                    throws IOException {}
+        };
+    }
+
+    private AbstractAuthenticationHandlerHolder holder(
+            final String path, final boolean requestResult, final boolean 
throwOnRequest) {
+        return new AbstractAuthenticationHandlerHolder(path, null) {
+            @Override
+            protected JakartaAuthenticationFeedbackHandler 
getFeedbackHandler() {
+                return null;
+            }
+
+            @Override
+            protected AuthenticationInfo doExtractCredentials(
+                    HttpServletRequest request, HttpServletResponse response) {
+                return null;
+            }
+
+            @Override
+            protected boolean doRequestCredentials(HttpServletRequest request, 
HttpServletResponse response)
+                    throws IOException {
+                if (throwOnRequest) {
+                    throw new IOException("boom");
+                }
+                return requestResult;
+            }
+
+            @Override
+            protected void doDropCredentials(HttpServletRequest request, 
HttpServletResponse response)
+                    throws IOException {}
+        };
+    }
+
+    private HttpServletRequest requestFor(String path) {
+        final HttpServletRequest request = 
Mockito.mock(HttpServletRequest.class);
+        Mockito.when(request.getServletPath()).thenReturn(path);
+        Mockito.when(request.getServerName()).thenReturn("localhost");
+        Mockito.when(request.getServerPort()).thenReturn(80);
+        Mockito.when(request.getScheme()).thenReturn("http");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Mockito.when(request.getRequestURI()).thenReturn(path);
+        return request;
+    }
+
+    @Test
+    public void test_login_success() {
+        final SlingAuthenticator auth = createAuthenticator(holder("/", true, 
false));
+        final HttpServletRequest request = requestFor("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        auth.login(request, response);
+    }
+
+    @Test
+    public void test_login_handler_throws_is_treated_as_done() {
+        final SlingAuthenticator auth = createAuthenticator(holder("/", false, 
true));
+        final HttpServletRequest request = requestFor("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        auth.login(request, response);
+    }
+
+    @Test(expected = NoAuthenticationHandlerException.class)
+    public void test_login_no_handler() {
+        final SlingAuthenticator auth = createAuthenticator(holder("/", false, 
false));
+        final HttpServletRequest request = requestFor("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        auth.login(request, response);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void test_login_response_committed() {
+        final SlingAuthenticator auth = createAuthenticator(holder("/", true, 
false));
+        final HttpServletRequest request = requestFor("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        Mockito.when(response.isCommitted()).thenReturn(true);
+        auth.login(request, response);
+    }
+
+    @Test
+    public void test_logout_success() {
+        final SlingAuthenticator auth = createAuthenticator(holder("/", true, 
false));
+        final HttpServletRequest request = requestFor("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        auth.logout(request, response);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void test_logout_response_committed() {
+        final SlingAuthenticator auth = createAuthenticator(holder("/", true, 
false));
+        final HttpServletRequest request = requestFor("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        Mockito.when(response.isCommitted()).thenReturn(true);
+        auth.logout(request, response);
+    }
+
+    @Test
+    public void test_javax_login_wrapper() {
+        final SlingAuthenticator auth = createAuthenticator(holder("/", true, 
false));
+        final javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getServletPath()).thenReturn("/content");
+        Mockito.when(request.getServerName()).thenReturn("localhost");
+        Mockito.when(request.getServerPort()).thenReturn(80);
+        Mockito.when(request.getScheme()).thenReturn("http");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        final javax.servlet.http.HttpServletResponse response =
+                Mockito.mock(javax.servlet.http.HttpServletResponse.class);
+        auth.login(request, response);
+    }
+
+    @Test
+    public void test_javax_logout_wrapper() {
+        final SlingAuthenticator auth = createAuthenticator(holder("/", true, 
false));
+        final javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getServletPath()).thenReturn("/content");
+        Mockito.when(request.getServerName()).thenReturn("localhost");
+        Mockito.when(request.getServerPort()).thenReturn(80);
+        Mockito.when(request.getScheme()).thenReturn("http");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        final javax.servlet.http.HttpServletResponse response =
+                Mockito.mock(javax.servlet.http.HttpServletResponse.class);
+        auth.logout(request, response);
+    }
+
+    @Test
+    public void test_handleSecurity_already_authenticated() {
+        final SlingAuthenticator auth = createAuthenticator();
+        final HttpServletRequest request = requestFor("/content");
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        
Mockito.when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER))
+                .thenReturn(resolver);
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        Assert.assertTrue(auth.handleSecurity(request, response));
+    }
+
+    @Test
+    public void test_handleSecurity_valid_credentials() throws Exception {
+        final ResourceResolverFactory rrf = 
Mockito.mock(ResourceResolverFactory.class);
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        
Mockito.when(rrf.getResourceResolver(Mockito.any(java.util.Map.class))).thenReturn(resolver);
+
+        final AuthenticationInfo info = new AuthenticationInfo("basic", 
"user", "pwd".toCharArray());
+        final SlingAuthenticator auth = createAuthenticator(rrf, 
infoHolder("/", info));
+
+        final HttpServletRequest request = requestFor("/content");
+        // a non-resolver attribute should be overwritten
+        
Mockito.when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER))
+                .thenReturn("not-a-resolver");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+
+        auth.handleSecurity(request, response);
+        Mockito.verify(request, Mockito.atLeastOnce())
+                
.setAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER, resolver);
+    }
+
+    @Test
+    public void test_handleSecurity_valid_credentials_with_redirect() throws 
Exception {
+        final ResourceResolverFactory rrf = 
Mockito.mock(ResourceResolverFactory.class);
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        
Mockito.when(rrf.getResourceResolver(Mockito.any(java.util.Map.class))).thenReturn(resolver);
+
+        final AuthenticationInfo info = new AuthenticationInfo("basic", 
"user", "pwd".toCharArray());
+        final SlingAuthenticator auth = createAuthenticator(rrf, 
infoHolder("/", info));
+
+        final HttpServletRequest request = requestFor("/content");
+        
Mockito.when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER))
+                .thenReturn("/content/redirect");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+
+        // redirect requested -> request considered done, resolver closed
+        Assert.assertFalse(auth.handleSecurity(request, response));
+        Mockito.verify(resolver).close();
+    }
+
+    @Test
+    public void test_handleSecurity_doing_auth() {
+        final SlingAuthenticator auth = createAuthenticator(infoHolder("/", 
AuthenticationInfo.DOING_AUTH));
+        final HttpServletRequest request = requestFor("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        Assert.assertFalse(auth.handleSecurity(request, response));
+    }
+
+    @Test
+    public void test_handleSecurity_fail_auth_triggers_login() {
+        final SlingAuthenticator auth = createAuthenticator(infoHolder("/", 
AuthenticationInfo.FAIL_AUTH));
+        final HttpServletRequest request = requestFor("/content");
+        Mockito.when(request.getRequestURI()).thenReturn("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        Assert.assertFalse(auth.handleSecurity(request, response));
+    }
+
+    @Test
+    public void test_finishSecurity_closes_resolver() {
+        final SlingAuthenticator auth = createAuthenticator();
+        final HttpServletRequest request = requestFor("/content");
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        
Mockito.when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER))
+                .thenReturn(resolver);
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        auth.finishSecurity(request, response);
+        Mockito.verify(resolver).close();
+        
Mockito.verify(request).removeAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER);
+    }
+
+    @Test
+    public void test_finishSecurity_no_resolver() {
+        final SlingAuthenticator auth = createAuthenticator();
+        final HttpServletRequest request = requestFor("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        auth.finishSecurity(request, response);
+    }
+
+    @Test
+    public void test_javax_handleSecurity_wrapper() {
+        final ResourceResolverFactory rrf = 
Mockito.mock(ResourceResolverFactory.class);
+        final ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        try {
+            
Mockito.when(rrf.getResourceResolver(Mockito.any(java.util.Map.class)))
+                    .thenReturn(resolver);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        final AuthenticationInfo info = new AuthenticationInfo("basic", 
"user", "pwd".toCharArray());
+        final SlingAuthenticator auth = createAuthenticator(rrf, 
infoHolder("/", info));
+
+        final javax.servlet.http.HttpServletRequest request = 
Mockito.mock(javax.servlet.http.HttpServletRequest.class);
+        Mockito.when(request.getServletPath()).thenReturn("/content");
+        Mockito.when(request.getServerName()).thenReturn("localhost");
+        Mockito.when(request.getServerPort()).thenReturn(80);
+        Mockito.when(request.getScheme()).thenReturn("http");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Mockito.when(request.getRequestURI()).thenReturn("/content");
+        final javax.servlet.http.HttpServletResponse response =
+                Mockito.mock(javax.servlet.http.HttpServletResponse.class);
+        auth.handleSecurity(request, response);
+    }
+
+    @Test
+    public void test_logout_dropCredentials_ioexception() {
+        final AbstractAuthenticationHandlerHolder dropThrows = new 
AbstractAuthenticationHandlerHolder("/", null) {
+            @Override
+            protected JakartaAuthenticationFeedbackHandler 
getFeedbackHandler() {
+                return null;
+            }
+
+            @Override
+            protected AuthenticationInfo doExtractCredentials(
+                    HttpServletRequest request, HttpServletResponse response) {
+                return null;
+            }
+
+            @Override
+            protected boolean doRequestCredentials(HttpServletRequest request, 
HttpServletResponse response)
+                    throws IOException {
+                return false;
+            }
+
+            @Override
+            protected void doDropCredentials(HttpServletRequest request, 
HttpServletResponse response)
+                    throws IOException {
+                throw new IOException("boom");
+            }
+        };
+        final SlingAuthenticator auth = createAuthenticator(dropThrows);
+        final HttpServletRequest request = requestFor("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        auth.logout(request, response);
+    }
+
+    private Object invokeHandleLoginFailure(
+            SlingAuthenticator auth,
+            HttpServletRequest request,
+            HttpServletResponse response,
+            AuthenticationInfo info,
+            Exception reason)
+            throws Throwable {
+        return junitx.util.PrivateAccessor.invoke(
+                auth,
+                "handleLoginFailure",
+                new Class[] {
+                    HttpServletRequest.class, HttpServletResponse.class, 
AuthenticationInfo.class, Exception.class
+                },
+                new Object[] {request, response, info, reason});
+    }
+
+    @Test
+    public void test_handleLoginFailure_loginException_doLogin() throws 
Throwable {
+        final SlingAuthenticator auth = createAuthenticator(holder("/", true, 
false));
+        final HttpServletRequest request = requestFor("/content");
+        
Mockito.when(request.getParameter(org.apache.sling.auth.core.AuthConstants.PAR_J_VALIDATE))
+                .thenReturn("true");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        final AuthenticationInfo info = new AuthenticationInfo("basic", 
"user");
+        final Object result = invokeHandleLoginFailure(
+                auth, request, response, info, new 
org.apache.sling.api.resource.LoginException("bad"));
+        Assert.assertEquals(Boolean.FALSE, result);
+    }
+
+    @Test
+    public void test_handleLoginFailure_tooManySessions() throws Throwable {
+        final SlingAuthenticator auth = createAuthenticator();
+        final HttpServletRequest request = requestFor("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        final AuthenticationInfo info = new AuthenticationInfo("basic", 
"user");
+        invokeHandleLoginFailure(auth, request, response, info, new 
TooManySessionsException("too many"));
+        Mockito.verify(response).sendError(Mockito.eq(503), 
Mockito.anyString());
+    }
+
+    @Test
+    public void test_handleLoginFailure_genericError() throws Throwable {
+        final SlingAuthenticator auth = createAuthenticator();
+        final HttpServletRequest request = requestFor("/content");
+        final HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+        final AuthenticationInfo info = new AuthenticationInfo("basic", 
"user");
+        invokeHandleLoginFailure(auth, request, response, info, new 
RuntimeException("kaboom"));
+        Mockito.verify(response).sendError(Mockito.eq(500), 
Mockito.anyString());
+    }
+
+    static class TooManySessionsException extends RuntimeException {
+        TooManySessionsException(String msg) {
+            super(msg);
+        }
+    }
 }
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/engine/EngineAuthenticationHandlerHolderTest.java
 
b/src/test/java/org/apache/sling/auth/core/impl/engine/EngineAuthenticationHandlerHolderTest.java
new file mode 100644
index 0000000..a3fc99b
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/impl/engine/EngineAuthenticationHandlerHolderTest.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.impl.engine;
+
+import javax.jcr.Credentials;
+
+import java.io.IOException;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import junitx.util.PrivateAccessor;
+import org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler;
+import org.apache.sling.auth.core.spi.AuthenticationInfo;
+import org.apache.sling.auth.core.spi.JakartaAuthenticationFeedbackHandler;
+import org.apache.sling.engine.auth.AuthenticationHandler;
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@SuppressWarnings("deprecation")
+public class EngineAuthenticationHandlerHolderTest {
+
+    private static final String PATH = "/content";
+
+    private interface FeedbackEngineHandler extends AuthenticationHandler, 
AuthenticationFeedbackHandler {}
+
+    @Test
+    public void extractCredentialsReturnsNullWhenEngineHandlerReturnsNull() {
+        final AuthenticationHandler handler = 
mock(AuthenticationHandler.class);
+        final EngineAuthenticationHandlerHolder holder = holder(handler);
+
+        assertNull(holder.doExtractCredentials(mock(HttpServletRequest.class), 
mock(HttpServletResponse.class)));
+    }
+
+    @Test
+    public void extractCredentialsConvertsDoingAuthSingleton() {
+        final AuthenticationHandler handler = 
mock(AuthenticationHandler.class);
+        final EngineAuthenticationHandlerHolder holder = holder(handler);
+        when(handler.authenticate(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class)))
+                
.thenReturn(org.apache.sling.engine.auth.AuthenticationInfo.DOING_AUTH);
+
+        assertSame(
+                AuthenticationInfo.DOING_AUTH,
+                holder.doExtractCredentials(mock(HttpServletRequest.class), 
mock(HttpServletResponse.class)));
+    }
+
+    @Test
+    public void extractCredentialsConvertsLegacyAuthenticationInfo() {
+        final AuthenticationHandler handler = 
mock(AuthenticationHandler.class);
+        final EngineAuthenticationHandlerHolder holder = holder(handler);
+        final Credentials credentials = mock(Credentials.class);
+        final org.apache.sling.engine.auth.AuthenticationInfo engineInfo =
+                new org.apache.sling.engine.auth.AuthenticationInfo("engine", 
credentials, "workspace");
+        when(handler.authenticate(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class)))
+                .thenReturn(engineInfo);
+
+        final AuthenticationInfo info =
+                holder.doExtractCredentials(mock(HttpServletRequest.class), 
mock(HttpServletResponse.class));
+
+        assertEquals("engine", info.getAuthType());
+        assertSame(credentials, info.get("user.jcr.credentials"));
+        assertEquals("workspace", info.get("user.jcr.workspace"));
+    }
+
+    @Test
+    public void requestCredentialsDelegatesToLegacyHandler() throws 
IOException {
+        final AuthenticationHandler handler = 
mock(AuthenticationHandler.class);
+        final EngineAuthenticationHandlerHolder holder = holder(handler);
+        when(handler.requestAuthentication(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class)))
+                .thenReturn(true);
+
+        assertTrue(holder.doRequestCredentials(mock(HttpServletRequest.class), 
mock(HttpServletResponse.class)));
+
+        verify(handler)
+                .requestAuthentication(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class));
+    }
+
+    @Test
+    public void dropCredentialsDoesNotCallLegacyHandler() throws Throwable {
+        final AuthenticationHandler handler = 
mock(AuthenticationHandler.class);
+
+        holder(handler).doDropCredentials(mock(HttpServletRequest.class), 
mock(HttpServletResponse.class));
+
+        verify(handler, never())
+                .authenticate(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class));
+        verify(handler, never())
+                .requestAuthentication(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class));
+    }
+
+    @Test
+    public void getFeedbackHandlerReturnsNullForPlainEngineHandler() throws 
Throwable {
+        
assertNull(getFeedbackHandler(holder(mock(AuthenticationHandler.class))));
+    }
+
+    @Test
+    public void feedbackHandlerAdaptsJakartaCallsToLegacyFeedbackHandler() 
throws Throwable {
+        final FeedbackEngineHandler handler = 
mock(FeedbackEngineHandler.class);
+        final JakartaAuthenticationFeedbackHandler feedbackHandler = 
getFeedbackHandler(holder(handler));
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        final AuthenticationInfo authInfo = new AuthenticationInfo("engine");
+        when(handler.authenticationSucceeded(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class),
+                        same(authInfo)))
+                .thenReturn(true);
+
+        assertNotNull(feedbackHandler);
+        feedbackHandler.authenticationFailed(request, response, authInfo);
+        assertTrue(feedbackHandler.authenticationSucceeded(request, response, 
authInfo));
+
+        verify(handler)
+                .authenticationFailed(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class),
+                        same(authInfo));
+        verify(handler)
+                .authenticationSucceeded(
+                        any(javax.servlet.http.HttpServletRequest.class),
+                        any(javax.servlet.http.HttpServletResponse.class),
+                        same(authInfo));
+    }
+
+    @Test
+    public void finalMethodsSetAndResetPathAroundEngineDelegation() {
+        final AuthenticationHandler handler = 
mock(AuthenticationHandler.class);
+        final EngineAuthenticationHandlerHolder holder = holder(handler);
+        final HttpServletRequest request = mock(HttpServletRequest.class);
+        final HttpServletResponse response = mock(HttpServletResponse.class);
+        
when(request.getAttribute(org.apache.sling.auth.core.spi.JakartaAuthenticationHandler.PATH_PROPERTY))
+                .thenReturn("oldPath");
+
+        holder.extractCredentials(request, response);
+
+        
verify(request).setAttribute(org.apache.sling.auth.core.spi.JakartaAuthenticationHandler.PATH_PROPERTY,
 PATH);
+        verify(request)
+                
.setAttribute(org.apache.sling.auth.core.spi.JakartaAuthenticationHandler.PATH_PROPERTY,
 "oldPath");
+    }
+
+    @Test
+    public void equalsHashCodeCompareToAndToStringIncludeEngineHandler() {
+        final AuthenticationHandler handler = 
mock(AuthenticationHandler.class);
+        when(handler.toString()).thenReturn("engineHandler");
+        final ServiceReference<?> reference = mock(ServiceReference.class);
+        final EngineAuthenticationHandlerHolder holder =
+                new EngineAuthenticationHandlerHolder(PATH, handler, 
reference);
+        final EngineAuthenticationHandlerHolder same = new 
EngineAuthenticationHandlerHolder(PATH, handler, reference);
+        final EngineAuthenticationHandlerHolder differentHandler =
+                new EngineAuthenticationHandlerHolder(PATH, 
mock(AuthenticationHandler.class), reference);
+
+        assertEquals(holder, holder);
+        assertEquals(holder, same);
+        assertEquals(holder.hashCode(), same.hashCode());
+        assertEquals(0, holder.compareTo(same));
+        assertNotEquals(holder, null);
+        assertNotEquals(holder, differentHandler);
+        assertFalse(holder.equals(new Object()));
+        assertEquals("engineHandler (Legacy API Handler)", holder.toString());
+    }
+
+    private EngineAuthenticationHandlerHolder holder(final 
AuthenticationHandler handler) {
+        return new EngineAuthenticationHandlerHolder(PATH, handler, 
mock(ServiceReference.class));
+    }
+
+    private JakartaAuthenticationFeedbackHandler getFeedbackHandler(final 
EngineAuthenticationHandlerHolder holder)
+            throws Throwable {
+        return (JakartaAuthenticationFeedbackHandler)
+                PrivateAccessor.invoke(holder, "getFeedbackHandler", new 
Class[0], new Object[0]);
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/engine/EngineSlingAuthenticatorTest.java
 
b/src/test/java/org/apache/sling/auth/core/impl/engine/EngineSlingAuthenticatorTest.java
new file mode 100644
index 0000000..a56241d
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/impl/engine/EngineSlingAuthenticatorTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.impl.engine;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.auth.core.impl.hc.SetField;
+import org.apache.sling.engine.auth.NoAuthenticationHandlerException;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+@SuppressWarnings("deprecation")
+public class EngineSlingAuthenticatorTest {
+
+    private HttpServletRequest request = 
Mockito.mock(HttpServletRequest.class);
+    private HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+
+    @Test
+    public void test_login_delegates() throws Exception {
+        EngineSlingAuthenticator bridge = new EngineSlingAuthenticator();
+        org.apache.sling.api.auth.Authenticator delegate = 
Mockito.mock(org.apache.sling.api.auth.Authenticator.class);
+        SetField.set(bridge, "slingAuthenticator", delegate);
+
+        bridge.login(request, response);
+        Mockito.verify(delegate)
+                .login((javax.servlet.http.HttpServletRequest) request, 
(javax.servlet.http.HttpServletResponse)
+                        response);
+    }
+
+    @Test
+    public void test_login_wraps_exception() throws Exception {
+        EngineSlingAuthenticator bridge = new EngineSlingAuthenticator();
+        org.apache.sling.api.auth.Authenticator delegate = 
Mockito.mock(org.apache.sling.api.auth.Authenticator.class);
+        SetField.set(bridge, "slingAuthenticator", delegate);
+        Mockito.doThrow(new 
org.apache.sling.api.auth.NoAuthenticationHandlerException())
+                .when(delegate)
+                .login(
+                        
Mockito.any(javax.servlet.http.HttpServletRequest.class),
+                        
Mockito.any(javax.servlet.http.HttpServletResponse.class));
+
+        try {
+            bridge.login(request, response);
+            Assert.fail("Expected NoAuthenticationHandlerException");
+        } catch (NoAuthenticationHandlerException expected) {
+            Assert.assertNotNull(expected.getCause());
+        }
+    }
+
+    @Test
+    public void test_logout_delegates() throws Exception {
+        EngineSlingAuthenticator bridge = new EngineSlingAuthenticator();
+        org.apache.sling.api.auth.Authenticator delegate = 
Mockito.mock(org.apache.sling.api.auth.Authenticator.class);
+        SetField.set(bridge, "slingAuthenticator", delegate);
+
+        bridge.logout(request, response);
+        Mockito.verify(delegate)
+                .logout((javax.servlet.http.HttpServletRequest) request, 
(javax.servlet.http.HttpServletResponse)
+                        response);
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationFormServletTest.java
 
b/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationFormServletTest.java
new file mode 100644
index 0000000..d778d18
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationFormServletTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.spi;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.apache.sling.api.auth.Authenticator;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+@SuppressWarnings("deprecation")
+public class AbstractAuthenticationFormServletTest {
+
+    private static class TestFormServlet extends 
AbstractAuthenticationFormServlet {
+        private final String reason;
+
+        TestFormServlet(String reason) {
+            this.reason = reason;
+        }
+
+        @Override
+        protected String getReason(HttpServletRequest request) {
+            return reason;
+        }
+
+        @Override
+        protected String getDefaultFormPath() {
+            return "test-login-form.html";
+        }
+
+        @Override
+        protected String getCustomFormPath() {
+            return "does-not-exist.html";
+        }
+    }
+
+    private HttpServletRequest request = 
Mockito.mock(HttpServletRequest.class);
+    private HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+
+    @Test
+    public void test_doGet_rendersForm() throws Exception {
+        TestFormServlet servlet = new TestFormServlet("Reason!");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        StringWriter sw = new StringWriter();
+        Mockito.when(response.getWriter()).thenReturn(new PrintWriter(sw));
+
+        servlet.doGet(request, response);
+
+        String out = sw.toString();
+        Assert.assertTrue(out.contains("reason=[Reason!]"));
+        Mockito.verify(response).setContentType("text/html");
+        Mockito.verify(response).flushBuffer();
+    }
+
+    @Test
+    public void test_doPost_rendersForm() throws Exception {
+        TestFormServlet servlet = new TestFormServlet("");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        StringWriter sw = new StringWriter();
+        Mockito.when(response.getWriter()).thenReturn(new PrintWriter(sw));
+
+        servlet.doPost(request, response);
+        Assert.assertTrue(sw.toString().contains("<html>"));
+    }
+
+    @Test
+    public void test_getForm_substitutes_and_escapes() throws Exception {
+        TestFormServlet servlet = new TestFormServlet("<b>&\"'");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        
Mockito.when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/valid/path");
+
+        String form = servlet.getForm(request);
+        Assert.assertTrue(form.contains("resource=[/valid/path]"));
+        Assert.assertTrue(form.contains("&lt;b&gt;&amp;%22%27"));
+    }
+
+    @Test
+    public void test_getForm_invalid_resource_cleansed() throws Exception {
+        TestFormServlet servlet = new TestFormServlet("");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        
Mockito.when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/invalid//path");
+
+        String form = servlet.getForm(request);
+        Assert.assertTrue(form.contains("resource=[]"));
+    }
+
+    @Test
+    public void test_getContextPath_from_resource() {
+        TestFormServlet servlet = new TestFormServlet("");
+        
Mockito.when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/foo/bar/?x=1");
+        Assert.assertEquals("/foo/bar", servlet.getContextPath(request));
+    }
+
+    @Test
+    public void test_getContextPath_fallback_to_servlet_context() {
+        TestFormServlet servlet = new TestFormServlet("");
+        Mockito.when(request.getContextPath()).thenReturn("/ctx");
+        Assert.assertEquals("/ctx", servlet.getContextPath(request));
+    }
+
+    @Test
+    public void test_handle_ioexception_sends_error() throws Exception {
+        TestFormServlet servlet = new TestFormServlet("");
+        javax.servlet.ServletConfig config = 
Mockito.mock(javax.servlet.ServletConfig.class);
+        
Mockito.when(config.getServletContext()).thenReturn(Mockito.mock(javax.servlet.ServletContext.class));
+        servlet.init(config);
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Mockito.when(response.getWriter()).thenThrow(new IOException("no 
writer"));
+
+        servlet.doGet(request, response);
+        
Mockito.verify(response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    public void test_default_paths() {
+        AbstractAuthenticationFormServlet servlet = new 
AbstractAuthenticationFormServlet() {
+            @Override
+            protected String getReason(HttpServletRequest request) {
+                return "";
+            }
+        };
+        Assert.assertEquals("login.html", servlet.getDefaultFormPath());
+        Assert.assertEquals("custom_login.html", servlet.getCustomFormPath());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationHandlerTest.java
 
b/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationHandlerTest.java
new file mode 100644
index 0000000..272ac45
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationHandlerTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.spi;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.auth.Authenticator;
+import org.apache.sling.auth.core.AuthConstants;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Verifies that the deprecated {@link AbstractAuthenticationHandler} helper
+ * methods delegate to {@link org.apache.sling.auth.core.AuthUtil}.
+ */
+@SuppressWarnings("deprecation")
+public class AbstractAuthenticationHandlerTest {
+
+    private HttpServletRequest request = 
Mockito.mock(HttpServletRequest.class);
+    private HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+
+    @Test
+    public void test_getAttributeOrParameter() {
+        Mockito.when(request.getParameter("p")).thenReturn("v");
+        Assert.assertEquals("v", 
AbstractAuthenticationHandler.getAttributeOrParameter(request, "p", "def"));
+    }
+
+    @Test
+    public void test_getLoginResource() {
+        
Mockito.when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/res");
+        Assert.assertEquals("/res", 
AbstractAuthenticationHandler.getLoginResource(request, "/def"));
+    }
+
+    @Test
+    public void test_setLoginResourceAttribute() {
+        Assert.assertEquals("/def", 
AbstractAuthenticationHandler.setLoginResourceAttribute(request, "/def"));
+    }
+
+    @Test
+    public void test_sendRedirect() throws Exception {
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Mockito.when(request.getRequestURI()).thenReturn("/current");
+        Map<String, String> params = new HashMap<>();
+        AbstractAuthenticationHandler.sendRedirect(request, response, 
"/target", params);
+        Mockito.verify(response).sendRedirect(Mockito.contains("/target?"));
+    }
+
+    @Test
+    public void test_isRedirectValid() {
+        Assert.assertTrue(AbstractAuthenticationHandler.isRedirectValid(null, 
"/absolute/path"));
+        Assert.assertFalse(AbstractAuthenticationHandler.isRedirectValid(null, 
"http://host";));
+    }
+
+    @Test
+    public void test_isValidateRequest() {
+        
Mockito.when(request.getParameter(AuthConstants.PAR_J_VALIDATE)).thenReturn("true");
+        
Assert.assertTrue(AbstractAuthenticationHandler.isValidateRequest(request));
+    }
+
+    @Test
+    public void test_sendValid() {
+        AbstractAuthenticationHandler.sendValid(response);
+        Mockito.verify(response).setStatus(HttpServletResponse.SC_OK);
+    }
+
+    @Test
+    public void test_sendInvalid() {
+        AbstractAuthenticationHandler.sendInvalid(request, response);
+        Mockito.verify(response).setStatus(HttpServletResponse.SC_FORBIDDEN);
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/spi/AbstractJakartaAuthenticationFormServletTest.java
 
b/src/test/java/org/apache/sling/auth/core/spi/AbstractJakartaAuthenticationFormServletTest.java
new file mode 100644
index 0000000..0d88a06
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/spi/AbstractJakartaAuthenticationFormServletTest.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.spi;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.sling.api.auth.Authenticator;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class AbstractJakartaAuthenticationFormServletTest {
+
+    private static class TestFormServlet extends 
AbstractJakartaAuthenticationFormServlet {
+        private final String reason;
+
+        TestFormServlet(String reason) {
+            this.reason = reason;
+        }
+
+        @Override
+        protected String getReason(HttpServletRequest request) {
+            return reason;
+        }
+
+        @Override
+        protected String getDefaultFormPath() {
+            return "test-login-form.html";
+        }
+
+        @Override
+        protected String getCustomFormPath() {
+            return "does-not-exist.html";
+        }
+    }
+
+    private HttpServletRequest request = 
Mockito.mock(HttpServletRequest.class);
+    private HttpServletResponse response = 
Mockito.mock(HttpServletResponse.class);
+
+    @Test
+    public void test_doGet_rendersForm() throws Exception {
+        TestFormServlet servlet = new TestFormServlet("Reason!");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        StringWriter sw = new StringWriter();
+        Mockito.when(response.getWriter()).thenReturn(new PrintWriter(sw));
+
+        servlet.doGet(request, response);
+
+        String out = sw.toString();
+        Assert.assertTrue(out.contains("reason=[Reason!]"));
+        Mockito.verify(response).setContentType("text/html");
+        Mockito.verify(response).flushBuffer();
+    }
+
+    @Test
+    public void test_doPost_rendersForm() throws Exception {
+        TestFormServlet servlet = new TestFormServlet("");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        StringWriter sw = new StringWriter();
+        Mockito.when(response.getWriter()).thenReturn(new PrintWriter(sw));
+
+        servlet.doPost(request, response);
+        Assert.assertTrue(sw.toString().contains("<html>"));
+    }
+
+    @Test
+    public void test_getForm_substitutes_and_escapes() throws Exception {
+        TestFormServlet servlet = new TestFormServlet("<b>&\"'");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        
Mockito.when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/valid/path");
+
+        String form = servlet.getForm(request);
+        Assert.assertTrue(form.contains("resource=[/valid/path]"));
+        // reason escaped
+        Assert.assertTrue(form.contains("&lt;b&gt;&amp;%22%27"));
+    }
+
+    @Test
+    public void test_getForm_invalid_resource_cleansed() throws Exception {
+        TestFormServlet servlet = new TestFormServlet("");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        // an invalid (non-normalized) redirect target must be cleansed to 
empty
+        
Mockito.when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/invalid//path");
+
+        String form = servlet.getForm(request);
+        Assert.assertTrue(form.contains("resource=[]"));
+    }
+
+    @Test
+    public void test_getResource_default_empty() {
+        TestFormServlet servlet = new TestFormServlet("");
+        Assert.assertEquals("", servlet.getResource(request));
+    }
+
+    @Test
+    public void test_getContextPath_from_resource() {
+        TestFormServlet servlet = new TestFormServlet("");
+        
Mockito.when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/foo/bar/?x=1");
+        Assert.assertEquals("/foo/bar", servlet.getContextPath(request));
+    }
+
+    @Test
+    public void test_getContextPath_fallback_to_servlet_context() {
+        TestFormServlet servlet = new TestFormServlet("");
+        Mockito.when(request.getContextPath()).thenReturn("/ctx");
+        Assert.assertEquals("/ctx", servlet.getContextPath(request));
+    }
+
+    @Test
+    public void test_handle_ioexception_sends_error() throws Exception {
+        TestFormServlet servlet = new TestFormServlet("");
+        jakarta.servlet.ServletConfig config = 
Mockito.mock(jakarta.servlet.ServletConfig.class);
+        
Mockito.when(config.getServletContext()).thenReturn(Mockito.mock(jakarta.servlet.ServletContext.class));
+        servlet.init(config);
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Mockito.when(response.getWriter()).thenThrow(new IOException("no 
writer"));
+
+        servlet.doGet(request, response);
+        
Mockito.verify(response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    public void test_default_paths() {
+        AbstractJakartaAuthenticationFormServlet servlet = new 
AbstractJakartaAuthenticationFormServlet() {
+            @Override
+            protected String getReason(HttpServletRequest request) {
+                return "";
+            }
+        };
+        Assert.assertEquals("login.html", servlet.getDefaultFormPath());
+        Assert.assertEquals("custom_login.html", servlet.getCustomFormPath());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/spi/DefaultAuthenticationFeedbackHandlerTest.java
 
b/src/test/java/org/apache/sling/auth/core/spi/DefaultAuthenticationFeedbackHandlerTest.java
new file mode 100644
index 0000000..bd5d905
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/spi/DefaultAuthenticationFeedbackHandlerTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.spi;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.auth.core.AuthenticationSupport;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+@SuppressWarnings("deprecation")
+public class DefaultAuthenticationFeedbackHandlerTest {
+
+    @Test
+    public void test_no_redirect() {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        
Assert.assertFalse(DefaultAuthenticationFeedbackHandler.handleRedirect(request, 
response));
+    }
+
+    @Test
+    public void test_redirect_true_uses_requestUri() throws Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        
Mockito.when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER))
+                .thenReturn("true");
+        Mockito.when(request.getRequestURI()).thenReturn("/same/uri");
+        
Assert.assertTrue(DefaultAuthenticationFeedbackHandler.handleRedirect(request, 
response));
+        Mockito.verify(response).sendRedirect("/same/uri");
+    }
+
+    @Test
+    public void test_redirect_absolute_valid() throws Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        
Mockito.when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER))
+                .thenReturn("/valid/path");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        
Assert.assertTrue(DefaultAuthenticationFeedbackHandler.handleRedirect(request, 
response));
+        Mockito.verify(response).sendRedirect("/valid/path");
+    }
+
+    @Test
+    public void test_redirect_relative_made_absolute() throws Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        
Mockito.when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER))
+                .thenReturn("rel");
+        Mockito.when(request.getRequestURI()).thenReturn("/base/page");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        
Assert.assertTrue(DefaultAuthenticationFeedbackHandler.handleRedirect(request, 
response));
+        Mockito.verify(response).sendRedirect("/base/rel");
+    }
+
+    @Test
+    public void test_redirect_invalid_falls_back_to_root() throws Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        
Mockito.when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER))
+                .thenReturn("/invalid//path");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        
Assert.assertTrue(DefaultAuthenticationFeedbackHandler.handleRedirect(request, 
response));
+        Mockito.verify(response).sendRedirect("/");
+    }
+
+    @Test
+    public void test_authenticationFailed_noop() {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        DefaultAuthenticationFeedbackHandler handler = new 
DefaultAuthenticationFeedbackHandler();
+        handler.authenticationFailed(request, response, new 
AuthenticationInfo("test"));
+        Mockito.verifyNoInteractions(response);
+    }
+
+    @Test
+    public void test_authenticationSucceeded_delegates() {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        DefaultAuthenticationFeedbackHandler handler = new 
DefaultAuthenticationFeedbackHandler();
+        Assert.assertFalse(handler.authenticationSucceeded(request, response, 
new AuthenticationInfo("test")));
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/auth/core/spi/DefaultJakartaAuthenticationFeedbackHandlerTest.java
 
b/src/test/java/org/apache/sling/auth/core/spi/DefaultJakartaAuthenticationFeedbackHandlerTest.java
new file mode 100644
index 0000000..1480070
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/auth/core/spi/DefaultJakartaAuthenticationFeedbackHandlerTest.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.auth.core.spi;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.sling.auth.core.AuthenticationSupport;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class DefaultJakartaAuthenticationFeedbackHandlerTest {
+
+    @Test
+    public void test_no_redirect() {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        // no redirect parameter and no login resource -> false
+        
Assert.assertFalse(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request,
 response));
+    }
+
+    @Test
+    public void test_redirect_true_uses_requestUri() throws Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        
Mockito.when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER))
+                .thenReturn("true");
+        Mockito.when(request.getRequestURI()).thenReturn("/same/uri");
+        
Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request,
 response));
+        Mockito.verify(response).sendRedirect("/same/uri");
+    }
+
+    @Test
+    public void test_redirect_empty_uses_requestUri() throws Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        
Mockito.when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER))
+                .thenReturn("");
+        Mockito.when(request.getRequestURI()).thenReturn("/same/uri");
+        
Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request,
 response));
+        Mockito.verify(response).sendRedirect("/same/uri");
+    }
+
+    @Test
+    public void test_redirect_absolute_valid() throws Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        
Mockito.when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER))
+                .thenReturn("/valid/path");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        
Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request,
 response));
+        Mockito.verify(response).sendRedirect("/valid/path");
+    }
+
+    @Test
+    public void test_redirect_relative_made_absolute() throws Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        
Mockito.when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER))
+                .thenReturn("rel");
+        Mockito.when(request.getRequestURI()).thenReturn("/base/page");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        
Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request,
 response));
+        Mockito.verify(response).sendRedirect("/base/rel");
+    }
+
+    @Test
+    public void test_redirect_invalid_falls_back_to_root() throws Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        
Mockito.when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER))
+                .thenReturn("/invalid//path");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        
Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request,
 response));
+        Mockito.verify(response).sendRedirect("/");
+    }
+
+    @Test
+    public void test_redirect_send_failure_is_swallowed() throws Exception {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        
Mockito.when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER))
+                .thenReturn("/valid");
+        Mockito.when(request.getContextPath()).thenReturn("");
+        Mockito.doThrow(new 
java.io.IOException("boom")).when(response).sendRedirect(Mockito.anyString());
+        
Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request,
 response));
+    }
+
+    @Test
+    public void test_authenticationFailed_noop() {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        DefaultJakartaAuthenticationFeedbackHandler handler = new 
DefaultJakartaAuthenticationFeedbackHandler();
+        handler.authenticationFailed(request, response, new 
AuthenticationInfo("test"));
+        Mockito.verifyNoInteractions(response);
+    }
+
+    @Test
+    public void test_authenticationSucceeded_delegates() {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        DefaultJakartaAuthenticationFeedbackHandler handler = new 
DefaultJakartaAuthenticationFeedbackHandler();
+        Assert.assertFalse(handler.authenticationSucceeded(request, response, 
new AuthenticationInfo("test")));
+    }
+}
diff --git 
a/src/test/resources/org/apache/sling/auth/core/spi/test-login-form.html 
b/src/test/resources/org/apache/sling/auth/core/spi/test-login-form.html
new file mode 100644
index 0000000..cdea831
--- /dev/null
+++ b/src/test/resources/org/apache/sling/auth/core/spi/test-login-form.html
@@ -0,0 +1 @@
+<html>resource=[${resource}] reason=[${j_reason}] rcp=[${requestContextPath}] 
cp=[${contextPath}]</html>
\ No newline at end of file


Reply via email to