StaticCommandFlagsRegistryTest.java

package redis.clients.jedis;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.EnumSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.CommandFlagsRegistry.CommandFlag;
import redis.clients.jedis.CommandFlagsRegistry.RequestPolicy;
import redis.clients.jedis.CommandFlagsRegistry.ResponsePolicy;
import redis.clients.jedis.commands.ProtocolCommand;
import redis.clients.jedis.util.SafeEncoder;

/**
 * Unit tests for StaticCommandFlagsRegistry. Tests the retrieval of command flags for various Redis
 * commands, including commands with subcommands.
 */
public class StaticCommandFlagsRegistryTest {

  private StaticCommandFlagsRegistry registry;

  @BeforeEach
  public void setUp() {
    registry = StaticCommandFlagsRegistry.registry();
  }

  /**
   * Test that FUNCTION LOAD command returns the correct flags. FUNCTION LOAD should have: DENYOOM,
   * NOSCRIPT, WRITE flags.
   */
  @Test
  public void testFunctionLoadCommandFlags() {
    // Create a CommandArguments for "FUNCTION LOAD"
    CommandArguments functionLoadArgs = new CommandArguments(Protocol.Command.FUNCTION).add("LOAD");

    EnumSet<CommandFlag> flags = registry.getFlags(functionLoadArgs);

    assertNotNull(flags, "Flags should not be null");
    assertFalse(flags.isEmpty(), "FUNCTION LOAD should have flags");
    assertEquals(3, flags.size(), "FUNCTION LOAD should have exactly 3 flags");
    assertTrue(flags.contains(CommandFlag.DENYOOM), "FUNCTION LOAD should have DENYOOM flag");
    assertTrue(flags.contains(CommandFlag.NOSCRIPT), "FUNCTION LOAD should have NOSCRIPT flag");
    assertTrue(flags.contains(CommandFlag.WRITE), "FUNCTION LOAD should have WRITE flag");
  }

  /**
   * Test that FUNCTION DELETE command returns the correct flags. FUNCTION DELETE should have:
   * NOSCRIPT, WRITE flags.
   */
  @Test
  public void testFunctionDeleteCommandFlags() {
    CommandArguments functionDeleteArgs = new CommandArguments(Protocol.Command.FUNCTION)
        .add("DELETE");

    EnumSet<CommandFlag> flags = registry.getFlags(functionDeleteArgs);

    assertNotNull(flags, "Flags should not be null");
    assertFalse(flags.isEmpty(), "FUNCTION DELETE should have flags");
    assertEquals(2, flags.size(), "FUNCTION DELETE should have exactly 2 flags");
    assertTrue(flags.contains(CommandFlag.NOSCRIPT), "FUNCTION DELETE should have NOSCRIPT flag");
    assertTrue(flags.contains(CommandFlag.WRITE), "FUNCTION DELETE should have WRITE flag");
  }

  /**
   * Test other subcommand examples: ACL SETUSER
   */
  @Test
  public void testAclSetUserCommandFlags() {
    CommandArguments aclSetUserArgs = new CommandArguments(Protocol.Command.ACL).add("SETUSER");

    EnumSet<CommandFlag> flags = registry.getFlags(aclSetUserArgs);

    assertNotNull(flags, "Flags should not be null");
    assertFalse(flags.isEmpty(), "ACL SETUSER should have flags");
    assertTrue(flags.contains(CommandFlag.ADMIN), "ACL SETUSER should have ADMIN flag");
    assertTrue(flags.contains(CommandFlag.NOSCRIPT), "ACL SETUSER should have NOSCRIPT flag");
  }

  /**
   * Test other subcommand examples: CONFIG GET
   */
  @Test
  public void testConfigGetCommandFlags() {
    CommandArguments configGetArgs = new CommandArguments(Protocol.Command.CONFIG).add("GET");

    EnumSet<CommandFlag> flags = registry.getFlags(configGetArgs);

    assertNotNull(flags, "Flags should not be null");
    assertFalse(flags.isEmpty(), "CONFIG GET should have flags");
    assertTrue(flags.contains(CommandFlag.ADMIN), "CONFIG GET should have ADMIN flag");
    assertTrue(flags.contains(CommandFlag.LOADING), "CONFIG GET should have LOADING flag");
    assertTrue(flags.contains(CommandFlag.STALE), "CONFIG GET should have STALE flag");
  }

  /**
   * Test simple command without subcommands: GET
   */
  @Test
  public void testGetCommandFlags() {
    CommandArguments getArgs = new CommandArguments(Protocol.Command.GET).add("key");

    EnumSet<CommandFlag> flags = registry.getFlags(getArgs);

    assertNotNull(flags, "Flags should not be null");
    assertFalse(flags.isEmpty(), "GET should have flags");
    assertTrue(flags.contains(CommandFlag.READONLY), "GET should have READONLY flag");
    assertTrue(flags.contains(CommandFlag.FAST), "GET should have FAST flag");
  }

  /**
   * Test simple command without subcommands: SET
   */
  @Test
  public void testSetCommandFlags() {
    CommandArguments setArgs = new CommandArguments(Protocol.Command.SET).add("key").add("value");

    EnumSet<CommandFlag> flags = registry.getFlags(setArgs);

    assertNotNull(flags, "Flags should not be null");
    assertFalse(flags.isEmpty(), "SET should have flags");
    assertTrue(flags.contains(CommandFlag.WRITE), "SET should have WRITE flag");
    assertTrue(flags.contains(CommandFlag.DENYOOM), "SET should have DENYOOM flag");
  }

  /**
   * Test that unknown commands return empty flags
   */
  @Test
  public void testUnknownCommandReturnsEmptyFlags() {
    ProtocolCommand unknownCommand = () -> SafeEncoder.encode("UNKNOWN_COMMAND_XYZ");
    CommandArguments unknownArgs = new CommandArguments(unknownCommand);

    EnumSet<CommandFlag> flags = registry.getFlags(unknownArgs);

    assertNotNull(flags, "Flags should not be null");
    assertTrue(flags.isEmpty(), "Unknown command should return empty flags");
  }

  /**
   * Test case insensitivity - command names should be normalized to uppercase
   */
  @Test
  public void testCaseInsensitivity() {
    ProtocolCommand functionCommand = () -> SafeEncoder.encode("function");
    CommandArguments functionLoadArgs = new CommandArguments(functionCommand).add("load");

    EnumSet<CommandFlag> flags = registry.getFlags(functionLoadArgs);

    assertNotNull(flags, "Flags should not be null");
    assertFalse(flags.isEmpty(), "function load (lowercase) should have flags");
    assertEquals(3, flags.size(), "function load should have exactly 3 flags");
    assertTrue(flags.contains(CommandFlag.DENYOOM), "function load should have DENYOOM flag");
    assertTrue(flags.contains(CommandFlag.NOSCRIPT), "function load should have NOSCRIPT flag");
    assertTrue(flags.contains(CommandFlag.WRITE), "function load should have WRITE flag");
  }

  /**
   * Test that unknown subcommands of parent commands fall back to parent command flags. If the
   * parent command also doesn't exist, it should return empty flags.
   */
  @Test
  public void testUnknownSubcommandFallback() {
    // Create a CommandArguments for "FUNCTION UNKNOWN_SUBCOMMAND"
    // This subcommand doesn't exist, so it should fall back to "FUNCTION" parent flags
    // Since "FUNCTION" parent has empty flags, it should return empty flags
    CommandArguments unknownSubcommandArgs = new CommandArguments(Protocol.Command.FUNCTION)
        .add("UNKNOWN_SUBCOMMAND");

    EnumSet<CommandFlag> flags = registry.getFlags(unknownSubcommandArgs);

    assertNotNull(flags, "Flags should not be null");
    assertTrue(flags.isEmpty(),
      "Unknown FUNCTION subcommand should return empty flags (parent flags)");
  }

  // ==================== Request Policy Tests ====================

  /**
   * Test that getRequestPolicy returns DEFAULT for commands without a specific policy. Since the
   * current registry doesn't populate request policies, all commands should return DEFAULT.
   */
  @Test
  public void testGetRequestPolicyReturnsDefault() {
    CommandArguments getArgs = new CommandArguments(Protocol.Command.GET).add("key");

    RequestPolicy policy = registry.getRequestPolicy(getArgs);

    assertNotNull(policy, "Request policy should not be null");
    assertEquals(RequestPolicy.DEFAULT, policy, "GET should have DEFAULT request policy");
  }

  /**
   * Test that getRequestPolicy returns DEFAULT for unknown commands.
   */
  @Test
  public void testGetRequestPolicyForUnknownCommand() {
    ProtocolCommand unknownCommand = () -> SafeEncoder.encode("UNKNOWN_COMMAND_XYZ");
    CommandArguments unknownArgs = new CommandArguments(unknownCommand);

    RequestPolicy policy = registry.getRequestPolicy(unknownArgs);

    assertNotNull(policy, "Request policy should not be null");
    assertEquals(RequestPolicy.DEFAULT, policy,
      "Unknown command should have DEFAULT request policy");
  }

  /**
   * Test that getRequestPolicy works for commands with subcommands.
   */
  @Test
  public void testGetRequestPolicyForSubcommand() {
    CommandArguments functionLoadArgs = new CommandArguments(Protocol.Command.FUNCTION).add("LOAD");

    RequestPolicy policy = registry.getRequestPolicy(functionLoadArgs);

    assertNotNull(policy, "Request policy should not be null");
    // Currently all commands return DEFAULT since policies aren't populated
    assertEquals(RequestPolicy.ALL_SHARDS, policy,
      "FUNCTION LOAD should have DEFAULT request policy");
  }

  // ==================== Response Policy Tests ====================

  /**
   * Test that getResponsePolicy returns DEFAULT for commands without a specific policy. Since the
   * current registry doesn't populate response policies, all commands should return DEFAULT.
   */
  @Test
  public void testGetResponsePolicyReturnsDefault() {
    CommandArguments getArgs = new CommandArguments(Protocol.Command.GET).add("key");

    ResponsePolicy policy = registry.getResponsePolicy(getArgs);

    assertNotNull(policy, "Response policy should not be null");
    assertEquals(ResponsePolicy.DEFAULT, policy, "GET should have DEFAULT response policy");
  }

  /**
   * Test that getResponsePolicy returns DEFAULT for unknown commands.
   */
  @Test
  public void testGetResponsePolicyForUnknownCommand() {
    ProtocolCommand unknownCommand = () -> SafeEncoder.encode("UNKNOWN_COMMAND_XYZ");
    CommandArguments unknownArgs = new CommandArguments(unknownCommand);

    ResponsePolicy policy = registry.getResponsePolicy(unknownArgs);

    assertNotNull(policy, "Response policy should not be null");
    assertEquals(ResponsePolicy.DEFAULT, policy,
      "Unknown command should have DEFAULT response policy");
  }

  /**
   * Test that getResponsePolicy works for commands with subcommands.
   */
  @Test
  public void testGetResponsePolicyForSubcommand() {
    CommandArguments functionLoadArgs = new CommandArguments(Protocol.Command.FUNCTION).add("LOAD");

    ResponsePolicy policy = registry.getResponsePolicy(functionLoadArgs);

    assertNotNull(policy, "Response policy should not be null");
    // Currently all commands return DEFAULT since policies aren't populated
    assertEquals(ResponsePolicy.ALL_SUCCEEDED, policy,
      "FUNCTION LOAD should have DEFAULT response policy");
  }

  /**
   * Test that RequestPolicy enum contains all expected values.
   */
  @Test
  public void testRequestPolicyEnumValues() {
    RequestPolicy[] values = RequestPolicy.values();
    assertEquals(5, values.length, "RequestPolicy should have 5 values");

    // Verify all expected values exist
    assertNotNull(RequestPolicy.valueOf("DEFAULT"));
    assertNotNull(RequestPolicy.valueOf("ALL_NODES"));
    assertNotNull(RequestPolicy.valueOf("ALL_SHARDS"));
    assertNotNull(RequestPolicy.valueOf("MULTI_SHARD"));
    assertNotNull(RequestPolicy.valueOf("SPECIAL"));
  }

  /**
   * Test that ResponsePolicy enum contains all expected values.
   */
  @Test
  public void testResponsePolicyEnumValues() {
    ResponsePolicy[] values = ResponsePolicy.values();
    assertEquals(9, values.length, "ResponsePolicy should have 9 values");

    // Verify all expected values exist
    assertNotNull(ResponsePolicy.valueOf("DEFAULT"));
    assertNotNull(ResponsePolicy.valueOf("ONE_SUCCEEDED"));
    assertNotNull(ResponsePolicy.valueOf("ALL_SUCCEEDED"));
    assertNotNull(ResponsePolicy.valueOf("AGG_LOGICAL_AND"));
    assertNotNull(ResponsePolicy.valueOf("AGG_LOGICAL_OR"));
    assertNotNull(ResponsePolicy.valueOf("AGG_MIN"));
    assertNotNull(ResponsePolicy.valueOf("AGG_MAX"));
    assertNotNull(ResponsePolicy.valueOf("AGG_SUM"));
    assertNotNull(ResponsePolicy.valueOf("SPECIAL"));
  }

  /**
   * Test that correct flags are stored for commands with subcommands.
   */
  @Test
  public void testFlagsForCommandWithSubcommands() {
    // verify flags for COMMAND (top level)
    CommandArguments commandArgs = new CommandArguments(Protocol.Command.COMMAND);
    EnumSet<CommandFlag> commandFlags = registry.getFlags(commandArgs);
    EnumSet<CommandFlag> expectedCommandFlags = EnumSet.of(CommandFlag.LOADING, CommandFlag.STALE);
    assertEquals(expectedCommandFlags, commandFlags, "COMMAND should have expected flags");

    // verify flags for COMMAND INFO (subcommand)
    CommandArguments commandInfoArgs = new CommandArguments(Protocol.Command.COMMAND).add("INFO");
    EnumSet<CommandFlag> commandInfoflags = registry.getFlags(commandInfoArgs);
    EnumSet<CommandFlag> expectedCommandInfoFlags = EnumSet.of(CommandFlag.LOADING,
      CommandFlag.STALE);
    assertEquals(expectedCommandInfoFlags, commandInfoflags,
      "COMMAND INFO should have expected flags");
  }

  /**
   * Test that HOTKEYS subcommands (START, STOP, GET, RESET) return the correct flags. All HOTKEYS
   * subcommands should have ADMIN and NOSCRIPT flags.
   */
  @Test
  public void testHotkeysSubcommandFlags() {
    // Expected flags for all HOTKEYS subcommands: ADMIN, NOSCRIPT
    EnumSet<CommandFlag> expectedFlags = EnumSet.of(CommandFlag.ADMIN, CommandFlag.NOSCRIPT);

    // Test HOTKEYS START
    CommandArguments hotkeysStartArgs = new CommandArguments(Protocol.Command.HOTKEYS).add("START");
    EnumSet<CommandFlag> startFlags = registry.getFlags(hotkeysStartArgs);
    assertNotNull(startFlags, "HOTKEYS START flags should not be null");
    assertFalse(startFlags.isEmpty(), "HOTKEYS START should have flags");
    assertEquals(expectedFlags, startFlags, "HOTKEYS START should have ADMIN and NOSCRIPT flags");

    // Test HOTKEYS STOP
    CommandArguments hotkeysStopArgs = new CommandArguments(Protocol.Command.HOTKEYS).add("STOP");
    EnumSet<CommandFlag> stopFlags = registry.getFlags(hotkeysStopArgs);
    assertNotNull(stopFlags, "HOTKEYS STOP flags should not be null");
    assertFalse(stopFlags.isEmpty(), "HOTKEYS STOP should have flags");
    assertEquals(expectedFlags, stopFlags, "HOTKEYS STOP should have ADMIN and NOSCRIPT flags");

    // Test HOTKEYS GET
    CommandArguments hotkeysGetArgs = new CommandArguments(Protocol.Command.HOTKEYS).add("GET");
    EnumSet<CommandFlag> getFlags = registry.getFlags(hotkeysGetArgs);
    assertNotNull(getFlags, "HOTKEYS GET flags should not be null");
    assertFalse(getFlags.isEmpty(), "HOTKEYS GET should have flags");
    assertEquals(expectedFlags, getFlags, "HOTKEYS GET should have ADMIN and NOSCRIPT flags");

    // Test HOTKEYS RESET
    CommandArguments hotkeysResetArgs = new CommandArguments(Protocol.Command.HOTKEYS).add("RESET");
    EnumSet<CommandFlag> resetFlags = registry.getFlags(hotkeysResetArgs);
    assertNotNull(resetFlags, "HOTKEYS RESET flags should not be null");
    assertFalse(resetFlags.isEmpty(), "HOTKEYS RESET should have flags");
    assertEquals(expectedFlags, resetFlags, "HOTKEYS RESET should have ADMIN and NOSCRIPT flags");

    // Verify request policy for HOTKEYS GET (has SPECIAL request and response policy)
    RequestPolicy getRequestPolicy = registry.getRequestPolicy(hotkeysGetArgs);
    assertEquals(RequestPolicy.SPECIAL, getRequestPolicy,
      "HOTKEYS GET should have SPECIAL request policy");

    ResponsePolicy getResponsePolicy = registry.getResponsePolicy(hotkeysGetArgs);
    assertEquals(ResponsePolicy.SPECIAL, getResponsePolicy,
      "HOTKEYS GET should have SPECIAL response policy");

    // Verify request policy for HOTKEYS START (has SPECIAL request policy)
    RequestPolicy startRequestPolicy = registry.getRequestPolicy(hotkeysStartArgs);
    assertEquals(RequestPolicy.SPECIAL, startRequestPolicy,
      "HOTKEYS START should have SPECIAL request policy");
  }
}