mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
yesterday 2c443ee5664c0307d7b7f50491fa6130a83faef5
opendj-server-legacy/src/test/java/org/opends/server/backends/jdbc/TestCase.java
@@ -11,23 +11,43 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2024-2025 3A Systems, LLC.
 * Copyright 2024-2026 3A Systems, LLC.
 */
package org.opends.server.backends.jdbc;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.server.config.server.JDBCBackendCfg;
import org.opends.server.backends.pluggable.PluggableBackendImplTestCase;
import org.opends.server.backends.pluggable.spi.AccessMode;
import org.opends.server.backends.pluggable.spi.Cursor;
import org.opends.server.backends.pluggable.spi.ReadOperation;
import org.opends.server.backends.pluggable.spi.ReadableTransaction;
import org.opends.server.backends.pluggable.spi.TreeName;
import org.opends.server.backends.pluggable.spi.WriteOperation;
import org.opends.server.backends.pluggable.spi.WriteableTransaction;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testng.SkipException;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import static org.forgerock.opendj.config.ConfigurationMock.mockCfg;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@@ -50,14 +70,35 @@
            throw new SkipException(getContainerDockerCommand());
         }
      }
      try(Connection ignored = DriverManager.getConnection(createBackendCfg().getDBDirectory())){
      try(Connection con = DriverManager.getConnection(createBackendCfg().getDBDirectory())){
         dropStaleTrees(con);
      } catch (Exception e) {
         throw new SkipException(getContainerDockerCommand());
      }
      super.setUp();
   }
   /**
    * Backend test classes sharing one database map the same tree names to the same tables,
    * so a previous run may leave trees behind — including entries encrypted with a lost cipher key.
    */
   static void dropStaleTrees(Connection con) throws SQLException {
      final List<String> stale = new ArrayList<>();
      try (final ResultSet rs = con.getMetaData().getTables(null, null, null, new String[]{"TABLE"})) {
         while (rs.next()) {
            final String name = rs.getString("TABLE_NAME");
            if (name.toLowerCase().startsWith("opendj_")) {
               stale.add(name);
            }
         }
      }
      try (final Statement st = con.createStatement()) {
         for (final String name : stale) {
            st.execute("drop table " + name);
         }
      }
   }
   @Override
   protected Backend createBackend() {
      return new Backend();
@@ -87,4 +128,113 @@
   protected abstract String getBackendId();
   protected abstract String getJdbcUrl();
   private static ByteString key(int i) {
      return ByteString.valueOfUtf8(String.format("key%02d", i));
   }
   private static ByteString value(int i) {
      return ByteString.valueOfUtf8("value" + i);
   }
   /** Cursor operations must keep working when the tree spans several "fetchsize" batches. */
   @Test
   public void testCursorCrossesFetchSizeBatches() throws Exception {
      System.setProperty("org.openidentityplatform.opendj.jdbc.fetchsize", "2");
      final Storage storage = new Storage(createBackendCfg(), null);
      final TreeName tree = new TreeName("testCursorBatch", "tree");
      try {
         storage.open(AccessMode.READ_WRITE);
         storage.write(new WriteOperation() {
            @Override
            public void run(WriteableTransaction txn) throws Exception {
               txn.openTree(tree, true);
               for (int i = 0; i < 7; i++) {
                  txn.put(tree, key(i), value(i));
               }
            }
         });
         storage.read(new ReadOperation<Void>() {
            @Override
            public Void run(ReadableTransaction txn) throws Exception {
               try (final Cursor<ByteString, ByteString> cursor = txn.openCursor(tree)) {
                  for (int i = 0; i < 7; i++) {
                     assertTrue(cursor.next(), "next() at " + i);
                     assertEquals(cursor.getKey(), key(i));
                     assertEquals(cursor.getValue(), value(i));
                  }
                  assertFalse(cursor.next());
                  assertFalse(cursor.isDefined());
                  try {
                     cursor.getKey();
                     fail("getKey() on undefined cursor must fail");
                  } catch (NoSuchElementException expected) {}
                  assertTrue(cursor.positionToKeyOrNext(key(3)));
                  assertEquals(cursor.getKey(), key(3));
                  assertTrue(cursor.positionToKeyOrNext(ByteString.valueOfUtf8("key031")));
                  assertEquals(cursor.getKey(), key(4));
                  assertTrue(cursor.next());
                  assertEquals(cursor.getKey(), key(5));
                  assertTrue(cursor.next());
                  assertEquals(cursor.getKey(), key(6));
                  assertFalse(cursor.next());
                  assertFalse(cursor.positionToKeyOrNext(ByteString.valueOfUtf8("z")));
                  assertFalse(cursor.isDefined());
                  assertTrue(cursor.positionToKey(key(5)));
                  assertEquals(cursor.getValue(), value(5));
                  assertTrue(cursor.next());
                  assertEquals(cursor.getKey(), key(6));
                  assertFalse(cursor.positionToKey(ByteString.valueOfUtf8("key99")));
                  assertFalse(cursor.isDefined());
                  assertTrue(cursor.positionToIndex(0));
                  assertEquals(cursor.getKey(), key(0));
                  assertTrue(cursor.positionToIndex(5));
                  assertEquals(cursor.getKey(), key(5));
                  assertTrue(cursor.next());
                  assertEquals(cursor.getKey(), key(6));
                  assertFalse(cursor.positionToIndex(7));
                  assertFalse(cursor.positionToIndex(-1));
                  assertTrue(cursor.positionToLastKey());
                  assertEquals(cursor.getKey(), key(6));
                  assertFalse(cursor.next());
                  assertTrue(cursor.positionToKey(key(0)));
                  try {
                     cursor.delete();
                     fail("delete() on read-only cursor must fail");
                  } catch (UnsupportedOperationException expected) {}
               }
               return null;
            }
         });
         storage.write(new WriteOperation() {
            @Override
            public void run(WriteableTransaction txn) throws Exception {
               try (final Cursor<ByteString, ByteString> cursor = txn.openCursor(tree)) {
                  assertTrue(cursor.positionToKey(key(3)));
                  cursor.delete();
                  assertTrue(cursor.next());
                  assertEquals(cursor.getKey(), key(4));
               }
               assertNull(txn.read(tree, key(3)));
               assertEquals(txn.getRecordCount(tree), 6);
            }
         });
      } finally {
         System.clearProperty("org.openidentityplatform.opendj.jdbc.fetchsize");
         try {
            storage.write(new WriteOperation() {
               @Override
               public void run(WriteableTransaction txn) throws Exception {
                  txn.deleteTree(tree);
               }
            });
         } catch (Exception ignored) {}
         storage.close();
      }
   }
}