HimportLifecycleMockTest.java
package redis.clients.jedis;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.args.Rawable;
import redis.clients.jedis.util.SafeEncoder;
import redis.clients.jedis.util.server.RespResponse;
import redis.clients.jedis.util.server.TcpMockServer;
/**
* HIMPORT lifecycle at the wire level, against a mock server recording every command: PREPARE is
* injected once per connection ahead of the first SET, and queued DISCARDs of closed templates go
* out right before the connection's next command. Command formats accepted by a real server are
* covered by the command ITs and {@code RedisClientHashImportLifecycleIT}.
*/
public class HimportLifecycleMockTest {
private TcpMockServer mockServer;
private Connection connection;
private CommandObjects commandObjects;
private final List<String> received = Collections.synchronizedList(new ArrayList<>());
@BeforeEach
public void setUp() throws IOException {
mockServer = new TcpMockServer();
mockServer.setCommandHandler((args, clientId) -> {
StringBuilder command = new StringBuilder();
for (Rawable arg : args) {
if (command.length() > 0) {
command.append(' ');
}
command.append(SafeEncoder.encode(arg.getRaw()));
}
received.add(command.toString());
return RespResponse.simpleString("OK");
});
mockServer.start();
connection = new Connection("localhost", mockServer.getPort());
commandObjects = new CommandObjects(RedisProtocol.RESP2);
}
@AfterEach
public void tearDown() throws IOException {
connection.close();
mockServer.stop();
}
@Test
public void preparesOncePerConnectionAheadOfFirstSet() {
try (HashImport fs = HashImport.of("f")) {
connection.executeCommand(commandObjects.himportSet("k1", fs, "v1"));
connection.executeCommand(commandObjects.himportSet("k2", fs, "v2"));
assertEquals(Arrays.asList( //
"HIMPORT PREPARE " + fs.name() + " f", //
"HIMPORT SET k1 " + fs.name() + " v1", //
"HIMPORT SET k2 " + fs.name() + " v2"), received);
}
}
@Test
public void discardsClosedTemplateBeforeNextCommand() {
HashImport fs = HashImport.of("f");
connection.executeCommand(commandObjects.himportSet("k1", fs, "v1"));
fs.close();
// nothing is sent by close() itself
assertEquals(2, received.size());
connection.executeCommand(Protocol.Command.PING);
assertEquals(Arrays.asList( //
"HIMPORT PREPARE " + fs.name() + " f", //
"HIMPORT SET k1 " + fs.name() + " v1", //
"HIMPORT DISCARD " + fs.name(), //
"PING"), received);
}
@Test
public void resetClearsPreparedStateSoNextSetRePrepares() {
try (HashImport fs = HashImport.of("f"); Jedis jedis = new Jedis(connection)) {
connection.executeCommand(commandObjects.himportSet("k1", fs, "v1"));
// RESET drops connection-scoped fieldsets server-side; the client note must follow
jedis.reset();
connection.executeCommand(commandObjects.himportSet("k2", fs, "v2"));
assertEquals(Arrays.asList( //
"HIMPORT PREPARE " + fs.name() + " f", //
"HIMPORT SET k1 " + fs.name() + " v1", //
"RESET", //
"HIMPORT PREPARE " + fs.name() + " f", //
"HIMPORT SET k2 " + fs.name() + " v2"), received);
}
}
@Test
public void batchesAllQueuedDiscardsBeforeOneCommand() {
HashImport fs1 = HashImport.of("a");
HashImport fs2 = HashImport.of("b");
connection.executeCommand(commandObjects.himportSet("k1", fs1, "1"));
connection.executeCommand(commandObjects.himportSet("k2", fs2, "2"));
fs1.close();
fs2.close();
received.clear();
connection.executeCommand(Protocol.Command.PING);
// both DISCARDs drain ahead of the one command
assertEquals("PING", received.get(received.size() - 1));
assertEquals(3, received.size());
assertEquals(
new java.util.HashSet<>(
Arrays.asList("HIMPORT DISCARD " + fs1.name(), "HIMPORT DISCARD " + fs2.name())),
new java.util.HashSet<>(received.subList(0, 2)));
}
}