SOLR-17153: Fix IndexOutOfBoundsException, add bats test (#2393)

Resolve IndexOutOfBoundsException, a regression from the first change in this issue.

* Call resolveDocCollection inside getCoreByCollection
* New BATS test to check URLs that the Solr Admin UI (or a user trying to navigate to such) might use
diff --git a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
index 20cec8f..6912af3 100644
--- a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
+++ b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
@@ -40,7 +40,6 @@
 import net.jcip.annotations.ThreadSafe;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.util.JsonSchemaValidator;
 import org.apache.solr.common.util.PathTrie;
@@ -141,7 +140,12 @@
         String collectionStr = queryParams.get(COLLECTION_PROP, origCorename);
         collectionsList =
             resolveCollectionListOrAlias(collectionStr); // &collection= takes precedence
-        if (collectionsList.size() > 1) {
+        if (collectionsList.isEmpty()) {
+          if (!path.endsWith(CommonParams.INTROSPECT)) {
+            throw new SolrException(
+                SolrException.ErrorCode.BAD_REQUEST, "Resolved list of collections is empty");
+          }
+        } else if (collectionsList.size() > 1) {
           throw new SolrException(
               SolrException.ErrorCode.BAD_REQUEST,
               "Request must be sent to a single collection "
@@ -150,29 +154,22 @@
                   + collectionStr
                   + "' resolves to "
                   + this.collectionsList);
-        }
-        DocCollection collection = resolveDocCollection(collectionsList);
-        if (collection == null) {
-          if (!path.endsWith(CommonParams.INTROSPECT)) {
-            throw new SolrException(
-                SolrException.ErrorCode.BAD_REQUEST, "no such collection or alias");
-          }
         } else {
+          String collectionName = collectionsList.get(0);
           // Certain HTTP methods are only used for admin APIs, check for those and short-circuit
           if (List.of("delete").contains(req.getMethod().toLowerCase(Locale.ROOT))) {
             initAdminRequest(path);
             return;
           }
           boolean isPreferLeader = (path.endsWith("/update") || path.contains("/update/"));
-          core = getCoreByCollection(collection.getName(), isPreferLeader);
+          core = getCoreByCollection(collectionName, isPreferLeader);
           if (core == null) {
             // this collection exists , but this node does not have a replica for that collection
-            extractRemotePath(collection.getName(), collection.getName());
+            extractRemotePath(collectionName, collectionName);
             if (action == REMOTEQUERY) {
               action = ADMIN_OR_REMOTEQUERY;
               coreUrl = coreUrl.replace("/solr/", "/solr/____v2/c/");
-              this.path =
-                  path = path.substring(prefix.length() + collection.getName().length() + 2);
+              this.path = path = path.substring(prefix.length() + collectionName.length() + 2);
               return;
             }
           }
diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
index 2e5856f..72633c7 100644
--- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
+++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
@@ -282,8 +282,6 @@
               queryParams.get(COLLECTION_PROP, def)); // &collection= takes precedence
 
       if (core == null) {
-        // force update collection only if local clusterstate is outdated
-        resolveDocCollection(collectionsList);
         // lookup core from collection, or route away if need to
         // route to 1st
         String collectionName = collectionsList.isEmpty() ? null : collectionsList.get(0);
@@ -349,18 +347,21 @@
   }
 
   /**
-   * Lookup the collection from the collection string (maybe comma delimited). Also sets {@link
-   * #collectionsList} by side-effect. if {@code secondTry} is false then we'll potentially
-   * recursively try this all one more time while ensuring the alias and collection info is sync'ed
-   * from ZK.
+   * Resolves the specified collection name to a {@link DocCollection} object. If Solr is not in
+   * cloud mode, a {@link SolrException} is thrown. Returns null if the collection name is null or
+   * empty. Retrieves the {@link DocCollection} using the {@link ZkStateReader} from {@link
+   * CoreContainer}. If the collection is null, updates the state by refreshing aliases and forcing
+   * a collection update.
    */
-  protected DocCollection resolveDocCollection(List<String> collectionsList) {
+  protected DocCollection resolveDocCollection(String collectionName) {
     if (!cores.isZooKeeperAware()) {
       throw new SolrException(
           SolrException.ErrorCode.BAD_REQUEST, "Solr not running in cloud mode ");
     }
+    if (collectionName == null || collectionName.trim().isEmpty()) {
+      return null;
+    }
     ZkStateReader zkStateReader = cores.getZkController().getZkStateReader();
-    String collectionName = collectionsList.get(0);
     Supplier<DocCollection> logic =
         () -> zkStateReader.getClusterState().getCollectionOrNull(collectionName);
 
@@ -1064,15 +1065,21 @@
     return result;
   }
 
+  /**
+   * Retrieves a SolrCore instance associated with the specified collection name, with an option to
+   * prefer leader replicas. Makes a call to {@link #resolveDocCollection} which make an attempt to
+   * force update collection if it is not found in local cluster state
+   */
   protected SolrCore getCoreByCollection(String collectionName, boolean isPreferLeader) {
     ZkStateReader zkStateReader = cores.getZkController().getZkStateReader();
-
     ClusterState clusterState = zkStateReader.getClusterState();
-    DocCollection collection = clusterState.getCollectionOrNull(collectionName, true);
+    DocCollection collection = resolveDocCollection(collectionName);
+    // the usage of getCoreByCollection assumes that if null is returned, collection is found, but
+    // replicas might not
+    // have been created. Hence returning null here would be misleading...
     if (collection == null) {
       return null;
     }
-
     Set<String> liveNodes = clusterState.getLiveNodes();
 
     if (isPreferLeader) {
diff --git a/solr/packaging/test/test_adminconsole_urls.bats b/solr/packaging/test/test_adminconsole_urls.bats
new file mode 100644
index 0000000..68fb79d
--- /dev/null
+++ b/solr/packaging/test/test_adminconsole_urls.bats
@@ -0,0 +1,38 @@
+#!/usr/bin/env bats
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+load bats_helper
+
+setup() {
+  common_clean_setup
+}
+
+teardown() {
+  # save a snapshot of SOLR_HOME for failed tests
+  save_home_on_failure
+
+  solr stop -all >/dev/null 2>&1
+}
+
+@test "assert able to launch solr admin console" {
+  run solr start -c
+
+  run curl -s -o /dev/null -w "%{http_code}" http://localhost:${SOLR_PORT}/solr/
+
+  # Check if the HTTP response code is 200 (OK)
+  assert_output "200"
+}