AuthXManagerHookRegistrationTest.java
package redis.clients.jedis.authentication;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import redis.clients.authentication.core.SimpleToken;
import redis.clients.authentication.core.Token;
import redis.clients.authentication.core.TokenManager;
import redis.clients.jedis.ConnectionPool;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.util.ReflectionTestUtil;
/**
* A pool registers a post-authentication hook on its {@link AuthXManager} and must unregister it
* when torn down, without touching the lifecycle of the manager itself.
*/
public class AuthXManagerHookRegistrationTest {
// no connection is ever borrowed, so no Redis is needed
private static final HostAndPort HNP = new HostAndPort("localhost", 6379);
private static JedisClientConfig configWith(AuthXManager authXManager) {
return DefaultJedisClientConfig.builder().authXManager(authXManager).build();
}
private static List<?> hooksOf(AuthXManager authXManager) {
return ReflectionTestUtil.getField(authXManager, "postAuthenticateHooks");
}
@Test
public void destroyUnregistersHookAndLeavesManagerRunning() {
TokenManager tokenManager = mock(TokenManager.class);
AuthXManager authXManager = new AuthXManager(tokenManager);
ConnectionPool pool = new ConnectionPool(HNP, configWith(authXManager));
assertEquals(1, hooksOf(authXManager).size());
pool.destroy();
assertEquals(0, hooksOf(authXManager).size(), "hook of a destroyed pool must be removed");
// repeated teardown is harmless
pool.destroy();
pool.close();
assertEquals(0, hooksOf(authXManager).size());
verify(tokenManager, never()).stop();
}
@Test
public void closeUnregistersOnlyTheClosedPool() {
TokenManager tokenManager = mock(TokenManager.class);
AuthXManager authXManager = new AuthXManager(tokenManager);
JedisClientConfig config = configWith(authXManager);
ConnectionPool first = new ConnectionPool(HNP, config);
ConnectionPool second = new ConnectionPool(new HostAndPort("localhost", 6380), config);
assertEquals(2, hooksOf(authXManager).size());
first.close();
assertEquals(1, hooksOf(authXManager).size());
second.close();
assertEquals(0, hooksOf(authXManager).size());
verify(tokenManager, never()).stop();
}
@Test
public void poolCreatedAfterAnotherWasClosedRegistersOnce() {
AuthXManager authXManager = new AuthXManager(mock(TokenManager.class));
JedisClientConfig config = configWith(authXManager);
new ConnectionPool(HNP, config).close();
ConnectionPool recreated = new ConnectionPool(HNP, config);
assertEquals(1, hooksOf(authXManager).size());
recreated.close();
assertEquals(0, hooksOf(authXManager).size());
}
/**
* destroy() may land between the hook's closed-check and evict(); commons-pool2 then throws "Pool
* not open". That must not escape, or the renewal aborts for every other pool on the manager.
*/
@Test
public void destroyRacingWithRenewalDoesNotAbortOtherHooks() {
AuthXManager authXManager = new AuthXManager(mock(TokenManager.class));
JedisClientConfig config = configWith(authXManager);
// models the race deterministically: the pool gets closed right before the real evict() runs
ConnectionPool racing = new ConnectionPool(HNP, config) {
@Override
public void evict() throws Exception {
destroy();
super.evict();
}
};
AtomicInteger laterHookCalls = new AtomicInteger();
authXManager.addPostAuthenticationHook(t -> laterHookCalls.incrementAndGet());
long now = System.currentTimeMillis();
Token token = new SimpleToken("default", "password", now + 60_000, now,
Collections.singletonMap("oid", "default"));
assertDoesNotThrow(() -> authXManager.authenticateConnections(token));
assertTrue(racing.isClosed());
assertEquals(1, laterHookCalls.get(), "hooks after the closed pool must still run");
assertEquals(1, hooksOf(authXManager).size(), "closed pool must have unregistered itself");
}
}