diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 8b2b364d..16320988 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -27,7 +27,7 @@ jobs: Java_Version=$(mvn help:evaluate "-Dexpression=maven.compiler.release" -q -DforceStdout | sed -e 's/^1\./1.0./') echo "Java_Version=$Java_Version" >> $GITHUB_ENV - name: Set up JDK 19 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: ${{ env.Java_Version }} distribution: 'temurin' diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 1525b796..11f3a074 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: release-drafter/release-drafter@v5 + - uses: release-drafter/release-drafter@v6 with: config-name: release-drafter-config.yml env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b0963335..c8292211 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: Java_Version=$(mvn help:evaluate "-Dexpression=maven.compiler.release" -q -DforceStdout | sed -e 's/^1\./1.0./') echo "Java_Version=$Java_Version" >> $GITHUB_ENV - name: Set up JDK 19 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: ${{ env.Java_Version }} distribution: 'temurin' @@ -35,13 +35,13 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -49,7 +49,7 @@ jobs: - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: | fungover/haze @@ -62,10 +62,10 @@ jobs: type=sha - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . - platforms: linux/amd64,linux/arm64,linux/arm/v7 + platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index ddcae766..3794c1f0 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -18,13 +18,13 @@ jobs: Java_Version=$(mvn help:evaluate "-Dexpression=maven.compiler.release" -q -DforceStdout | sed -e 's/^1\./1.0./') echo "Java_Version=$Java_Version" >> $GITHUB_ENV - name: Set up JDK 19 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: ${{ env.Java_Version }} distribution: 'temurin' cache: maven - name: Cache SonarCloud packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar diff --git a/pom.xml b/pom.xml index 6ff3f4dd..c9a3b292 100644 --- a/pom.xml +++ b/pom.xml @@ -17,19 +17,13 @@ org.junit.jupiter junit-jupiter - 5.9.2 - test - - - org.junit.jupiter - junit-jupiter-engine - 5.9.2 + 5.10.2 test org.mockito mockito-core - 5.3.1 + 5.10.0 test @@ -63,13 +57,13 @@ org.testcontainers junit-jupiter - 1.17.6 + 1.19.5 test org.slf4j slf4j-nop - 1.7.36 + 2.0.12 test @@ -143,7 +137,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.2.1 + 3.2.5 diff --git a/src/main/java/org/fungover/haze/Command.java b/src/main/java/org/fungover/haze/Command.java index 43e3e166..8fffbe13 100644 --- a/src/main/java/org/fungover/haze/Command.java +++ b/src/main/java/org/fungover/haze/Command.java @@ -1,5 +1,6 @@ package org.fungover.haze; public enum Command { - SET, GET, DEL, PING, SETNX, EXISTS, SAVE, RPUSH, LPUSH, LPOP, RPOP, LLEN, LMOVE, LTRIM, AUTH + SET, GET, DEL, PING, SETNX, EXISTS, SAVE, RPUSH, LPUSH, LPOP, RPOP, LLEN, LMOVE, LTRIM, AUTH, LINDEX, INCR, DECR + } diff --git a/src/main/java/org/fungover/haze/HazeDatabase.java b/src/main/java/org/fungover/haze/HazeDatabase.java index 75f6bcbf..230b474a 100644 --- a/src/main/java/org/fungover/haze/HazeDatabase.java +++ b/src/main/java/org/fungover/haze/HazeDatabase.java @@ -1,5 +1,4 @@ package org.fungover.haze; - import java.util.HashMap; import java.util.List; import java.util.Map; @@ -46,6 +45,9 @@ public String get(List inputList) { } public String delete(List keys) { + if (keys.isEmpty()) + throw new IllegalArgumentException("No keys provided"); + var counter = new AtomicInteger(0); lock.lock(); try { @@ -62,6 +64,9 @@ public String delete(List keys) { } public String exists(List keys) { + if (keys.isEmpty()) + return ":0\r\n"; + lock.lock(); int numberOfKeys = 0; try { @@ -109,6 +114,12 @@ public Map copy() { } public String ping(List messageList) { + if (messageList == null || messageList.isEmpty()) { + throw new IllegalArgumentException("No message provided"); + } else if (messageList.size() > 2) { + throw new IllegalArgumentException("Too many arguments for PING command"); + } + if (messageList.size() == 1) return "+PONG\r\n"; else return "$" + (messageList.get(1)).length() + "\r\n" + messageList.get(1) + "\r\n"; @@ -143,4 +154,47 @@ public void addValue(String key, String value) { lock.unlock(); } } + + public String increaseValue(List inputList) { + lock.lock(); + String key = inputList.get(1); + try { + if (!database.containsKey(key)) { + return "-ERR no such key\r\n"; + } + String value = database.get(key); + try { + long longValue = Long.parseLong(value); + longValue++; + database.put(key, String.valueOf(longValue)); + return ":" + longValue + "\r\n"; + } catch (NumberFormatException e) { + return "-WRONGTYPE value is not an integer or out of range\r\n"; + } + } finally { + lock.unlock(); + } + } + + public String decreaseValue(List inputList) { + lock.lock(); + String key = inputList.get(1); + try { + if (!database.containsKey(key)) { + return "-ERR no such key\r\n"; + } + String value = database.get(key); + try { + long longValue = Long.parseLong(value); + longValue--; + database.put(key, String.valueOf(longValue)); + return ":" + longValue + "\r\n"; + } catch (NumberFormatException e) { + return "-WRONGTYPE value is not an integer or out of range\r\n"; + } + } finally { + lock.unlock(); + } + } + } diff --git a/src/main/java/org/fungover/haze/HazeList.java b/src/main/java/org/fungover/haze/HazeList.java index 00470985..41ded3ac 100644 --- a/src/main/java/org/fungover/haze/HazeList.java +++ b/src/main/java/org/fungover/haze/HazeList.java @@ -264,6 +264,33 @@ public static String listValueAsString(List list) { return String.join("\r\n", list); } + public String lIndex(List inputList){ + String key = getKey(inputList); + int index; + if (!hazeDatabase.containsKey(key)) + return "Could not find list"; + String indexStr = inputList.get(2); + + try { + index = Integer.parseInt(indexStr); + } catch (NumberFormatException e) { + return "-ERR invalid index\r\n"; + } + + List data = getValueAsList(hazeDatabase.getValue(key)); + if(data.size()-1 inputList) { String key = null; if (inputList.size() > 1) diff --git a/src/main/java/org/fungover/haze/Main.java b/src/main/java/org/fungover/haze/Main.java index 38a8aca1..b91c8ec8 100644 --- a/src/main/java/org/fungover/haze/Main.java +++ b/src/main/java/org/fungover/haze/Main.java @@ -119,8 +119,10 @@ public static String executeCommand(HazeDatabase hazeDatabase, List inpu case LLEN -> hazeList.lLen(inputList); case LMOVE -> hazeList.lMove(inputList); case LTRIM -> hazeList.callLtrim(inputList); + case LINDEX -> hazeList.lIndex(inputList); case AUTH -> "+OK\r\n"; - + case INCR -> hazeDatabase.increaseValue(inputList); + case DECR -> hazeDatabase.decreaseValue(inputList); }; } @@ -143,4 +145,16 @@ private static void readInputStream(BufferedReader input, List inputList } + + private static void shutdownClientIfNotAuthenticated(Socket client, boolean clientAuthenticated, boolean isPasswordSet) throws IOException { + if (!clientAuthenticated && isPasswordSet) { + client.getOutputStream().write(Auth.printAuthError()); + client.shutdownOutput(); + } + } + + static boolean authCommandReceived(boolean isPasswordSet, List inputList, boolean clientAuthenticated) { + return isPasswordSet && !clientAuthenticated && inputList.size() == 2 && inputList.getFirst().equals("AUTH"); + } + } diff --git a/src/test/java/org/fungover/haze/AuthTest.java b/src/test/java/org/fungover/haze/AuthTest.java index f854c798..3101f17c 100644 --- a/src/test/java/org/fungover/haze/AuthTest.java +++ b/src/test/java/org/fungover/haze/AuthTest.java @@ -2,15 +2,23 @@ +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mock; +import org.mockito.Mockito; import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Method; import java.net.Socket; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; class AuthTest { @@ -70,7 +78,72 @@ void authCommandReceivedPasswordNotAuth() { } -} + @Test + void authenticateShouldShutdownOutputForInvalidPassword() throws IOException { + + Auth auth = new Auth(); + auth.setPassword("12345"); + + Socket client = Mockito.mock(Socket.class); + OutputStream outputStream = Mockito.mock(OutputStream.class); + when(client.getOutputStream()).thenReturn(outputStream); + auth.authenticate("wrongPassword", client); + + verify(client).shutdownOutput(); + } + + @Test + void authenticateClientShouldReturnTrueForValidPassword() throws IOException { + + Auth auth = Mockito.mock(Auth.class); + Socket client = Mockito.mock(Socket.class); + List inputList = new ArrayList<>(); + inputList.add("AUTH"); + inputList.add("password"); + + when(auth.authenticate(inputList.get(1), client)).thenReturn(true); + + boolean result = Auth.authenticateClient(auth, true, client, inputList, false); + + assertTrue(result); + } + + @Test + void shutdownClientIfNotAuthenticatedWhenClientNotAuthenticatedAndPasswordIsSet() throws Exception { + + Socket client = Mockito.mock(Socket.class); + OutputStream outputStream = Mockito.mock(OutputStream.class); + when(client.getOutputStream()).thenReturn(outputStream); + + Method method = Auth.class.getDeclaredMethod("shutdownClientIfNotAuthenticated", Socket.class, boolean.class, boolean.class); + method.setAccessible(true); + + method.invoke(null, client, false, true); + + verify(outputStream).write(Auth.printAuthError()); + verify(client).shutdownOutput(); + + } + + @Test + void clientShouldNotBeShutdownWhenAuthenticatedOrPasswordIsNotSet() throws Exception { + + Socket client = Mockito.mock(Socket.class); + OutputStream outputStream = Mockito.mock(OutputStream.class); + when(client.getOutputStream()).thenReturn(outputStream); + + Method method = Auth.class.getDeclaredMethod("shutdownClientIfNotAuthenticated", Socket.class, boolean.class, boolean.class); + method.setAccessible(true); + + method.invoke(null, client, true, true); + verify(outputStream, Mockito.never()).write(Auth.printAuthError()); + verify(client, Mockito.never()).shutdownOutput(); + + method.invoke(null, client, false, false); + verify(outputStream, Mockito.never()).write(Auth.printAuthError()); + verify(client, Mockito.never()).shutdownOutput(); + } +} diff --git a/src/test/java/org/fungover/haze/HazeDatabaseTest.java b/src/test/java/org/fungover/haze/HazeDatabaseTest.java index 89e1122a..54ed9928 100644 --- a/src/test/java/org/fungover/haze/HazeDatabaseTest.java +++ b/src/test/java/org/fungover/haze/HazeDatabaseTest.java @@ -1,5 +1,6 @@ package org.fungover.haze; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.Arrays; @@ -8,6 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; class HazeDatabaseTest { @@ -33,6 +35,14 @@ void callingDeleteRemovesTheSpecifiedKey() { assertThat(testDatabase.get(List.of("", "1"))).isEqualTo("$-1\r\n"); } + @Test + @DisplayName("Calling delete throw IllegalArgumentException for empty key") + void callingDeleteThrowIllegalArgumentExceptionForEmptyKey() { + assertThrows(IllegalArgumentException.class, () -> { + testDatabase.delete(Collections.emptyList()); + }, "No keys provided"); + } + @Test void callingGetReturnsTheCorrectValueIfItExists() { testDatabase.setNX(List.of("SETNX", "someKey", "someValue")); @@ -92,6 +102,33 @@ void testPingResponseShouldBeSameAsValue() { assertThat(testDatabase.ping(List.of("PING", "test message"))).isEqualTo("$12\r\ntest message\r\n"); } + @Test + @DisplayName("testPing throw Exception for null message list") + void testPingThrowExceptionForNullMessageList() { + + assertThrows(IllegalArgumentException.class, () -> { + testDatabase.ping(null); + }, "No message provided"); + } + + @Test + @DisplayName("testPing throw Exception for Empty message list") + void testPingThrowExceptionForEmptyMessageList() { + + assertThrows(IllegalArgumentException.class, () -> { + testDatabase.ping(List.of()); + }, "No message provided"); + } + + @Test + @DisplayName("testPing throw exception for too many arguments") + void testPingThrowExceptionForTooManyArguments() { + + assertThrows(IllegalArgumentException.class, () -> { + testDatabase.ping(List.of("arg1", "arg2", "arg3")); + }, "Too many arguments for PING command"); + } + @Test void testSetWithValidKeyValuePair() { String result = testDatabase.set(List.of("", "key", "value")); @@ -159,5 +196,60 @@ void shouldShouldReturnTrue(){ assertThat(testDatabase.containsKey("key1")).isTrue(); } + @Test + void callingIncreaseWithKeyWithIntegerShouldIncreaseValueBy1(){ + testDatabase.addValue("key1", "1"); + + String increaseResult = testDatabase.increaseValue(List.of("INCR","key1")); + assertThat(increaseResult).isEqualTo(":2\r\n"); + assertThat(testDatabase.getValue("key1")).isEqualTo("2"); + + } + + @Test + void callingIncreaseWithKeyThatDoesNotContainIntegerShouldReturnErrorMessage() { + testDatabase.addValue("key1", "Gunnar"); + String increaseResult = testDatabase.increaseValue(List.of("INCR","key1")); + assertThat(increaseResult).isEqualTo("-WRONGTYPE value is not an integer or out of range\r\n"); + } + @Test + void callingDecreaseWithKeyWithIntegerShouldDecreaseValueBy1(){ + testDatabase.addValue("key1", "1"); + + String increaseResult = testDatabase.decreaseValue(List.of("DECR","key1")); + assertThat(increaseResult).isEqualTo(":0\r\n"); + assertThat(testDatabase.getValue("key1")).isEqualTo("0"); + } + + @Test + @DisplayName("increaseValue should return ERR message when key does not exist") + void increaseValueShouldReturnErrMessageWhenKeyDoesNotExist() { + + String nonExistentKey = "nonExistentKey"; + List inputList = List.of("INCR", nonExistentKey); + + assertThat(testDatabase.increaseValue(inputList)).isEqualTo("-ERR no such key\r\n"); + } + + @Test + @DisplayName("decreaseValue should return ERR message when key does not exist") + void decreaseValueShouldReturnErrMessageWhenKeyDoesNotExist() { + + String nonExistentKey = "nonExistentKey"; + List inputList = List.of("DECR", nonExistentKey); + + assertThat(testDatabase.decreaseValue(inputList)).isEqualTo("-ERR no such key\r\n"); + } + + @Test + @DisplayName("decreaseValue should return WRONGTYPE message when value is not Integer") + void decreaseValueShouldReturnWrongtypeMessageWhenValueIsNotInteger() { + String key = "key"; + testDatabase.addValue(key, "notInteger"); + + assertThat(testDatabase.decreaseValue(List.of("DECR", key))) + .isEqualTo("-WRONGTYPE value is not an integer or out of range\r\n"); + + } } diff --git a/src/test/java/org/fungover/haze/HazeListTest.java b/src/test/java/org/fungover/haze/HazeListTest.java index 50f0190f..99f958fd 100644 --- a/src/test/java/org/fungover/haze/HazeListTest.java +++ b/src/test/java/org/fungover/haze/HazeListTest.java @@ -1,6 +1,8 @@ package org.fungover.haze; import org.junit.jupiter.api.Test; import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; class HazeListTest { @@ -287,4 +289,29 @@ void CallLReturnCorrectErrorMessageWhenNotGivenNumbersAsArguments () { void parserWithBadInputShouldReturnZero(){ assertEquals(0, HazeList.parser("This is not a number")); } + + + @Test + void callingLindexWithValidPositiveIndexReturnValue(){ + hazeList.rPush(List.of("", "key2", "val1", "val2", "val3")); + assertThat(hazeList.lIndex(List.of("", "key2", "2"))).isEqualTo("$4\r\nval3\r\n"); + } + + @Test + void callingLindexWithIndexOutOfBoundsReturnNil(){ + hazeList.rPush(List.of("", "key2", "val1", "val2", "val3")); + assertThat(hazeList.lIndex(List.of("", "key2", "3"))).isEqualTo("$5\r\n(nil)\r\n"); + } + + @Test + void callingLindexWithValidNegativeIndexReturnValue(){ + hazeList.rPush(List.of("", "key2", "val1", "val2", "val3")); + assertThat(hazeList.lIndex(List.of("", "key2", "-1"))).isEqualTo("$4\r\nval3\r\n"); + } + + @Test + void callingLindexWithValidIndexZeroReturnFirstValue(){ + hazeList.rPush(List.of("", "key2", "val1", "val2", "val3")); + assertThat(hazeList.lIndex(List.of("", "key2", "0"))).isEqualTo("$4\r\nval1\r\n"); + } } diff --git a/src/test/java/org/fungover/haze/InitializeTest.java b/src/test/java/org/fungover/haze/InitializeTest.java index e15804ce..eaa3bfa1 100644 --- a/src/test/java/org/fungover/haze/InitializeTest.java +++ b/src/test/java/org/fungover/haze/InitializeTest.java @@ -2,9 +2,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.verify; class InitializeTest { @@ -58,7 +60,16 @@ void passwordSetByCLIWithDashPortShouldReturn1233() { assertThat(initialize.getPassword()).isEqualTo("1233"); } + @Test + void shouldImportCliOptionsWhenInitializingServer() { + String[] args = {"--password", "1234"}; + Initialize initialize = Mockito.mock(Initialize.class); + Auth auth = Mockito.mock(Auth.class); + + Initialize.initializeServer(args, initialize, auth); + verify(initialize).importCliOptions(args); + } diff --git a/src/test/java/org/fungover/haze/MainTest.java b/src/test/java/org/fungover/haze/MainTest.java index 3a2a2179..04f3f3f0 100644 --- a/src/test/java/org/fungover/haze/MainTest.java +++ b/src/test/java/org/fungover/haze/MainTest.java @@ -7,11 +7,13 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import java.util.LinkedList; import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.fungover.haze.Main.printThreadDebug; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -109,6 +111,18 @@ void callExecuteCommandWithLTRIMShouldReturnErrorMessageWhenKeyDoesNotExist() { assertThat(Main.executeCommand(database, List.of("LTRIM", "key", "2", "3"), hazeList)).isEqualTo("-The key is not present in the database.\r\n"); } + @Test + void callExecuteCommandWithIncrShouldIncreaseTheValueOfTheKeyBy1() { + Main.executeCommand(database, List.of("SET", "key1", "1"), hazeList); + assertThat(Main.executeCommand(database, List.of("INCR", "key1"), hazeList)).isEqualTo(":2\r\n"); + } + + @Test + void callExecuteCommandWithDecrShouldDecreaseTheValueOfTheKeyBy1(){ + Main.executeCommand(database, List.of("SET", "key1", "1"), hazeList); + assertThat(Main.executeCommand(database, List.of("DECR", "key1"), hazeList)).isEqualTo(":0\r\n"); + } + @Test void testPrintThreadDebug() { ByteArrayOutputStream outContent = new ByteArrayOutputStream(); @@ -120,6 +134,22 @@ void testPrintThreadDebug() { assertFalse(outContent.toString().contains("Is virtual Thread")); } + @ParameterizedTest + @CsvSource({ + "true, AUTH, password, false, true", + "false, AUTH, password, false, false", + "true, SET, password, false, false", + "true, AUTH, password, true, false", + "false, AUTH, password, true, false" + }) + void authCommandReceivedTest(boolean isPasswordSet, String command, String password, boolean clientAuthenticated, boolean expected) { + List inputList = new LinkedList<>(List.of(command, password)); + + boolean result = Auth.authCommandReceived(isPasswordSet, inputList, clientAuthenticated); + + assertEquals(expected, result); + } + @Test void testExecuteCommandNoCommandProvided() { diff --git a/src/test/java/org/fungover/haze/SaveFileTest.java b/src/test/java/org/fungover/haze/SaveFileTest.java index a118eeb2..8d0a2382 100644 --- a/src/test/java/org/fungover/haze/SaveFileTest.java +++ b/src/test/java/org/fungover/haze/SaveFileTest.java @@ -71,4 +71,16 @@ private int compareLastModified(Path p1, Path p2) { throw new RuntimeException(e); } } + + @Test + @DisplayName("File already exists returns +OK\\r\\n") + void fileAlreadyExistsReturnsOK(@TempDir Path tempDir) { + System.setProperty("user.home", tempDir.toString()); + var map = Map.of("key", "value"); + + SaveFile.createFile(); + + assertThat(SaveFile.writeOnFile(map)).isEqualTo("+OK\r\n"); + } } + diff --git a/src/test/java/org/fungover/haze/integration/HazeIT.java b/src/test/java/org/fungover/haze/integration/HazeIT.java index c5bae8b0..b27a893e 100644 --- a/src/test/java/org/fungover/haze/integration/HazeIT.java +++ b/src/test/java/org/fungover/haze/integration/HazeIT.java @@ -6,6 +6,8 @@ import redis.clients.jedis.Protocol; import redis.clients.jedis.util.SafeEncoder; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(HazeExtension.class) @@ -84,6 +86,20 @@ void listKeyWithMultipleValues() { assertThat(pool.rpush("test", "fifth", "sixth")).isEqualTo(6); assertThat(pool.llen("test")).isEqualTo(6); + pool.del("test"); + assertThat(pool.exists("right")).isFalse(); + } + + @Test + void lindexReturnCorrectIndex() { + assertThat(pool.rpush("test", "hello")).isEqualTo(1); + assertThat(pool.rpush("test", "hey")).isEqualTo(2); + assertThat(pool.rpush("test", "bonjour")).isEqualTo(3); + assertThat(pool.rpush("test", "hej")).isEqualTo(4); + assertThat(pool.lindex("test", 0)).isEqualTo("hello"); + assertThat(pool.lindex("test", -1)).isEqualTo("hej"); + + pool.del("test"); assertThat(pool.exists("right")).isFalse(); }