// Path: src/main/java/com/github/tonivade/resp/protocol/RedisToken.java
// static RedisToken array(RedisToken... redisTokens) {
//   return new ArrayRedisToken(ImmutableList.of(redisTokens));
// }
// 
// Path: src/main/java/com/github/tonivade/resp/protocol/RedisToken.java
// static RedisToken string(SafeString str) {
//   return new StringRedisToken(str);
// }
// 
// Path: src/main/java/com/github/tonivade/resp/command/CommandSuite.java
// public class CommandSuite {
// 
//   private static final Logger LOGGER = LoggerFactory.getLogger(CommandSuite.class);
// 
//   private final Map<String, Class<?>> metadata = new HashMap<>();
//   private final Map<String, RespCommand> commands = new HashMap<>();
// 
//   private final NullCommand nullCommand = new NullCommand();
// 
//   private final CommandWrapperFactory factory;
// 
//   public CommandSuite() {
//     this(new DefaultCommandWrapperFactory());
//   }
// 
//   public CommandSuite(CommandWrapperFactory factory) {
//     this.factory = checkNonNull(factory);
//     addCommand(PingCommand::new);
//     addCommand(EchoCommand::new);
//     addCommand(QuitCommand::new);
//     addCommand(TimeCommand::new);
//   }
// 
//   public RespCommand getCommand(String name) {
//     return commands.getOrDefault(name.toLowerCase(), nullCommand);
//   }
// 
//   public boolean isPresent(String name, Class<? extends Annotation> annotationClass) {
//     return getMetadata(name).isAnnotationPresent(annotationClass);
//   }
// 
//   public boolean contains(String name) {
//     return commands.get(name) != null;
//   }
// 
//   protected void addCommand(Producer<?> newInstance) {
//     Try.of(newInstance)
//        .onSuccess(this::processCommand)
//        .onFailure(e -> LOGGER.error("error loading command", e));
//   }
// 
//   protected void addCommand(String name, RespCommand command) {
//     commands.put(name.toLowerCase(), factory.wrap(command));
//   }
// 
//   private void processCommand(Object command) {
//     Class<?> clazz = command.getClass();
//     Command annotation = clazz.getAnnotation(Command.class);
//     if (annotation != null) {
//       commands.put(annotation.value().toLowerCase(), factory.wrap(command));
//       metadata.put(annotation.value().toLowerCase(), clazz);
//     } else {
//       LOGGER.warn("annotation not present at {}", clazz.getName());
//     }
//   }
// 
//   private Class<?> getMetadata(String name) {
//     return metadata.getOrDefault(name.toLowerCase(), Void.class);
//   }
// }
// 
// Path: src/main/java/com/github/tonivade/resp/protocol/RedisToken.java
// public interface RedisToken {
//   RedisToken NULL_STRING = string((SafeString) null);
//   RedisToken RESPONSE_OK = status("OK");
// 
//   RedisTokenType getType();
// 
//   <T> T accept(RedisTokenVisitor<T> visitor);
// 
//   static RedisToken nullString() {
//     return NULL_STRING;
//   }
// 
//   static RedisToken responseOk() {
//     return RESPONSE_OK;
//   }
// 
//   static RedisToken string(SafeString str) {
//     return new StringRedisToken(str);
//   }
// 
//   static RedisToken string(String str) {
//     return new StringRedisToken(safeString(str));
//   }
// 
//   static RedisToken status(String str) {
//     return new StatusRedisToken(str);
//   }
// 
//   static RedisToken integer(boolean b) {
//     return new IntegerRedisToken(b ? 1 : 0);
//   }
// 
//   static RedisToken integer(int i) {
//     return new IntegerRedisToken(i);
//   }
// 
//   static RedisToken error(String str) {
//     return new ErrorRedisToken(str);
//   }
// 
//   static RedisToken array(RedisToken... redisTokens) {
//     return new ArrayRedisToken(ImmutableList.of(redisTokens));
//   }
// 
//   static RedisToken array(Collection<RedisToken> redisTokens) {
//     return new ArrayRedisToken(ImmutableList.from(redisTokens));
//   }
// 
//   static RedisToken array(Sequence<RedisToken> redisTokens) {
//     return new ArrayRedisToken(redisTokens.asArray());
//   }
// 
//   static <T> Stream<T> visit(Stream<RedisToken> tokens, RedisTokenVisitor<T> visitor) {
//     return tokens.map(token -> token.accept(visitor));
//   }
// }


import static com.github.tonivade.resp.protocol.RedisToken.array;
import static com.github.tonivade.resp.protocol.RedisToken.string;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import com.github.tonivade.resp.command.CommandSuite;
import com.github.tonivade.resp.protocol.RedisToken;
/*
 * Copyright (c) 2015-2022, Antonio Gabriel Muñoz Conejo <antoniogmc at gmail dot com>
 * Distributed under the terms of the MIT License
 */
package com.github.tonivade.resp;




public class RespServerTest {

  private static final String HOST = "localhost";
  private static final int PORT = 12345;
  private static final int TIMEOUT = 3000;

  private final RespServer respServer = 
      new RespServer(new RespServerContext(HOST, PORT, new CommandSuite()));

  private RespCallback callback = mock(RespCallback.class);

  @BeforeEach
  public void setUp() {
    respServer.start();
  }

  @AfterEach
  public void tearDown() {
    respServer.stop();
  }

  @Test
  public void serverRespond() {
    RespClient redisClient = createClient();

    redisClient.send(array(string("PING")));

    verifyResponse("PONG");
  }

  @Test
  public void clientDisconects() {
    RespClient redisClient = createClient();

    redisClient.stop();

    verify(callback, timeout(TIMEOUT)).onDisconnect();
  }

  @Test
  public void serverDisconects() {
// Path: src/main/java/com/github/tonivade/resp/protocol/RedisToken.java
// static RedisToken array(RedisToken... redisTokens) {
//   return new ArrayRedisToken(ImmutableList.of(redisTokens));
// }
// 
// Path: src/main/java/com/github/tonivade/resp/protocol/RedisToken.java
// static RedisToken string(SafeString str) {
//   return new StringRedisToken(str);
// }
// 
// Path: src/main/java/com/github/tonivade/resp/command/CommandSuite.java
// public class CommandSuite {
// 
//   private static final Logger LOGGER = LoggerFactory.getLogger(CommandSuite.class);
// 
//   private final Map<String, Class<?>> metadata = new HashMap<>();
//   private final Map<String, RespCommand> commands = new HashMap<>();
// 
//   private final NullCommand nullCommand = new NullCommand();
// 
//   private final CommandWrapperFactory factory;
// 
//   public CommandSuite() {
//     this(new DefaultCommandWrapperFactory());
//   }
// 
//   public CommandSuite(CommandWrapperFactory factory) {
//     this.factory = checkNonNull(factory);
//     addCommand(PingCommand::new);
//     addCommand(EchoCommand::new);
//     addCommand(QuitCommand::new);
//     addCommand(TimeCommand::new);
//   }
// 
//   public RespCommand getCommand(String name) {
//     return commands.getOrDefault(name.toLowerCase(), nullCommand);
//   }
// 
//   public boolean isPresent(String name, Class<? extends Annotation> annotationClass) {
//     return getMetadata(name).isAnnotationPresent(annotationClass);
//   }
// 
//   public boolean contains(String name) {
//     return commands.get(name) != null;
//   }
// 
//   protected void addCommand(Producer<?> newInstance) {
//     Try.of(newInstance)
//        .onSuccess(this::processCommand)
//        .onFailure(e -> LOGGER.error("error loading command", e));
//   }
// 
//   protected void addCommand(String name, RespCommand command) {
//     commands.put(name.toLowerCase(), factory.wrap(command));
//   }
// 
//   private void processCommand(Object command) {
//     Class<?> clazz = command.getClass();
//     Command annotation = clazz.getAnnotation(Command.class);
//     if (annotation != null) {
//       commands.put(annotation.value().toLowerCase(), factory.wrap(command));
//       metadata.put(annotation.value().toLowerCase(), clazz);
//     } else {
//       LOGGER.warn("annotation not present at {}", clazz.getName());
//     }
//   }
// 
//   private Class<?> getMetadata(String name) {
//     return metadata.getOrDefault(name.toLowerCase(), Void.class);
//   }
// }
// 
// Path: src/main/java/com/github/tonivade/resp/protocol/RedisToken.java
// public interface RedisToken {
//   RedisToken NULL_STRING = string((SafeString) null);
//   RedisToken RESPONSE_OK = status("OK");
// 
//   RedisTokenType getType();
// 
//   <T> T accept(RedisTokenVisitor<T> visitor);
// 
//   static RedisToken nullString() {
//     return NULL_STRING;
//   }
// 
//   static RedisToken responseOk() {
//     return RESPONSE_OK;
//   }
// 
//   static RedisToken string(SafeString str) {
//     return new StringRedisToken(str);
//   }
// 
//   static RedisToken string(String str) {
//     return new StringRedisToken(safeString(str));
//   }
// 
//   static RedisToken status(String str) {
//     return new StatusRedisToken(str);
//   }
// 
//   static RedisToken integer(boolean b) {
//     return new IntegerRedisToken(b ? 1 : 0);
//   }
// 
//   static RedisToken integer(int i) {
//     return new IntegerRedisToken(i);
//   }
// 
//   static RedisToken error(String str) {
//     return new ErrorRedisToken(str);
//   }
// 
//   static RedisToken array(RedisToken... redisTokens) {
//     return new ArrayRedisToken(ImmutableList.of(redisTokens));
//   }
// 
//   static RedisToken array(Collection<RedisToken> redisTokens) {
//     return new ArrayRedisToken(ImmutableList.from(redisTokens));
//   }
// 
//   static RedisToken array(Sequence<RedisToken> redisTokens) {
//     return new ArrayRedisToken(redisTokens.asArray());
//   }
// 
//   static <T> Stream<T> visit(Stream<RedisToken> tokens, RedisTokenVisitor<T> visitor) {
//     return tokens.map(token -> token.accept(visitor));
//   }
// }


// Path: src/test/java/com/github/tonivade/resp/RespServerTest.java
import static com.github.tonivade.resp.protocol.RedisToken.array;
import static com.github.tonivade.resp.protocol.RedisToken.string;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import com.github.tonivade.resp.command.CommandSuite;
import com.github.tonivade.resp.protocol.RedisToken;

/*
 * Copyright (c) 2015-2022, Antonio Gabriel Muñoz Conejo <antoniogmc at gmail dot com>
 * Distributed under the terms of the MIT License
 */
package com.github.tonivade.resp;




public class RespServerTest {

  private static final String HOST = "localhost";
  private static final int PORT = 12345;
  private static final int TIMEOUT = 3000;

  private final RespServer respServer = 
      new RespServer(new RespServerContext(HOST, PORT, new CommandSuite()));

  private RespCallback callback = mock(RespCallback.class);

  @BeforeEach
  public void setUp() {
    respServer.start();
  }

  @AfterEach
  public void tearDown() {
    respServer.stop();
  }

  @Test
  public void serverRespond() {
    RespClient redisClient = createClient();

    redisClient.send(array(string("PING")));

    verifyResponse("PONG");
  }

  @Test
  public void clientDisconects() {
    RespClient redisClient = createClient();

    redisClient.stop();

    verify(callback, timeout(TIMEOUT)).onDisconnect();
  }

  @Test
  public void serverDisconects() {
    RespClient redisClient = createClient();