Repository: astropy/astroquery

Issue:
SDSS.query_crossid doesn't work for DR<10
Run into this with the test suite, didn't investigate in detail. It likely just needs the URLs fixed. Could you maybe have a look @weaverba137? 

```
In [1]: from astroquery import sdss

In [2]: from astropy.coordinates import SkyCoord
   ...: from astropy.table import Table

In [3]: coords = SkyCoord('0h8m05.63s +14d50m23.3s')

In [4]: sdss.SDSS.query_crossid(coords,data_release=9)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-4-6418e02a3b0b> in <module>
----> 1 sdss.SDSS.query_crossid(coords,data_release=9)

~/munka/devel/worktrees/astroquery/testings/astroquery/utils/class_or_instance.py in f(*args, **kwds)
     23         def f(*args, **kwds):
     24             if obj is not None:
---> 25                 return self.fn(obj, *args, **kwds)
     26             else:
     27                 return self.fn(cls, *args, **kwds)

~/munka/devel/worktrees/astroquery/testings/astroquery/utils/process_asyncs.py in newmethod(self, *args, **kwargs)
     27             if kwargs.get('get_query_payload') or kwargs.get('field_help'):
     28                 return response
---> 29             result = self._parse_result(response, verbose=verbose)
     30             self.table = result
     31             return result

~/munka/devel/worktrees/astroquery/testings/astroquery/sdss/core.py in _parse_result(self, response, verbose)
    871                                           names=True, dtype=None,
    872                                           delimiter=',', skip_header=skip_header,
--> 873                                           comments='#'))
    874 
    875         if len(arr) == 0:

~/.pyenv/versions/3.7.9/lib/python3.7/site-packages/numpy/lib/npyio.py in genfromtxt(fname, dtype, comments, delimiter, skip_header, skip_footer, converters, missing_values, filling_values, usecols, names, excludelist, deletechars, replace_space, autostrip, case_sensitive, defaultfmt, unpack, usemask, loose, invalid_raise, max_rows, encoding)
   2078             # Raise an exception ?
   2079             if invalid_raise:
-> 2080                 raise ValueError(errmsg)
   2081             # Issue a warning ?
   2082             else:

ValueError: Some errors were detected !
    Line #12 (got 3 columns instead of 1)
    Line #32 (got 5 columns instead of 1)
    Line #34 (got 3 columns instead of 1)
```

Response: 

```
<!DOCTYPE html>
<html>
    <head>
        <title>The resource cannot be found.</title>
        <meta name="viewport" content="width=device-width" />
        <style>
         body {font-family:"Verdana";font-weight:normal;font-size: .7em;color:black;} 
         p {font-family:"Verdana";font-weight:normal;color:black;margin-top: -5px}
         b {font-family:"Verdana";font-weight:bold;color:black;margin-top: -5px}
         H1 { font-family:"Verdana";font-weight:normal;font-size:18pt;color:red }
         H2 { font-family:"Verdana";font-weight:normal;font-size:14pt;color:maroon }
         pre {font-family:"Consolas","Lucida Console",Monospace;font-size:11pt;margin:0;padding:0.5em;line-height:14pt}
         .marker {font-weight: bold; color: black;text-decoration: none;}
         .version {color: gray;}
         .error {margin-bottom: 10px;}
         .expandable { text-decoration:underline; font-weight:bold; color:navy; cursor:pointer; }
         @media screen and (max-width: 639px) {
          pre { width: 440px; overflow: auto; white-space: pre-wrap; word-wrap: break-word; }
         }
         @media screen and (max-width: 479px) {
          pre { width: 280px; }
         }
        </style>
    </head>

    <body bgcolor="white">

            <span><H1>Server Error in '/dr9' Application.<hr width=100% size=1 color=silver></H1>

            <h2> <i>The resource cannot be found.</i> </h2></span>

            <font face="Arial, Helvetica, Geneva, SunSans-Regular, sans-serif ">

            <b> Description: </b>HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. &nbsp;Please review the following URL and make sure that it is spelled correctly.
            <br><br>

            <b> Requested URL: </b>/dr9/en/tools/crossid/x_crossid.aspx<br><br>

            </font>

    </body>
</html>
```

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index c82d3a62..571a1ef7 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -3,17 +3,10 @@
 
 New Tools and Services
 ----------------------
-esa.hubble
-^^^^^^^^^^
 
-- Added new method ``get_hap_hst_link`` and ``get_member_observations`` to get related observations [#2268]
 
 Service fixes and enhancements
 ------------------------------
-esa.hubble
-^^^^^^^^^^
-
-- Changed query_target method to use TAP instead of AIO [#2268]
 
 casda
 ^^^^^
@@ -58,6 +51,7 @@ sdss
 
 - Fix ``query_crossid`` to be able to query larger list of coordinates. [#2305]
 
+- Fix ``query_crossid`` for very old data releases (< DR10). [#2318]
 
 
 Infrastructure, Utility and Other Changes and Additions
@@ -1162,4 +1156,4 @@ Infrastructure, Utility and Other Changes and Additions
 0.1 (2013-09-19)
 ================
 
-- Initial release.  Includes features!
\ No newline at end of file
+- Initial release.  Includes features!
diff --git a/astroquery/esa/hubble/core.py b/astroquery/esa/hubble/core.py
index 15307747..7b413d92 100644
--- a/astroquery/esa/hubble/core.py
+++ b/astroquery/esa/hubble/core.py
@@ -16,12 +16,19 @@ Created on 13 Aug. 2018
 
 
 """
+from astroquery.utils import commons
 from astropy import units
 from astropy.coordinates import SkyCoord
 from astropy.coordinates import Angle
+from astropy.units import Quantity
 from astroquery.utils.tap.core import TapPlus
+from astroquery.utils.tap.model import modelutils
 from astroquery.query import BaseQuery
+from astropy.table import Table
+from io import BytesIO
 import shutil
+import os
+import json
 
 from . import conf
 from astroquery import log
@@ -112,105 +119,14 @@ class ESAHubbleClass(BaseQuery):
 
         shutil.move(response, filename)
 
-    def get_member_observations(self, observation_id):
-        """
-        Returns the related members of simple and composite observations
-
-        Parameters
-        ----------
-        observation_id : str
-            Observation identifier.
-
-        Returns
-        -------
-        A list of strings with the observation_id of the associated
-        observations
-        """
-        observation_type = self.get_observation_type(observation_id)
-
-        if 'Composite' in observation_type:
-            oids = self._select_related_members(observation_id)
-        elif 'Simple' in observation_type:
-            oids = self._select_related_composite(observation_id)
-        else:
-            raise ValueError("Invalid observation id")
-        return oids
-
-    def get_hap_hst_link(self, observation_id):
-        """
-        Returns the related members of hap and hst observations
-
-        Parameters
-        ----------
-        observation_id : string
-           id of the observation to be downloaded, mandatory
-           The identifier of the observation we want to retrieve, regardless
-           of whether it is simple or composite.
-
-        Returns
-        -------
-        A list of strings with the observation_id of the associated
-        observations
-        """
-        observation_type = self.get_observation_type(observation_id)
-        if 'Composite' in observation_type:
-            raise ValueError("HAP-HST link is only available for simple observations. Input observation is Composite.")
-        elif 'HAP' in observation_type:
-            oids = self._select_related_members(observation_id)
-        elif 'HST' in observation_type:
-            query = f"select observation_id from ehst.observation where obs_type='HAP Simple' and members like '%{observation_id}%'"
-            job = self.query_hst_tap(query=query)
-            oids = job["observation_id"].pformat(show_name=False)
-        else:
-            raise ValueError("Invalid observation id")
-        return oids
-
-    def get_observation_type(self, observation_id):
-        """
-        Returns the type of an observation
-
-        Parameters
-        ----------
-        observation_id : string
-           id of the observation to be downloaded, mandatory
-           The identifier of the observation we want to retrieve, regardless
-           of whether it is simple or composite.
-
-        Returns
-        -------
-        String with the observation type
-        """
-        if observation_id is None:
-            raise ValueError("Please input an observation id")
-
-        query = f"select obs_type from ehst.observation where observation_id='{observation_id}'"
-        job = self.query_hst_tap(query=query)
-        if any(job["obs_type"]):
-            obs_type = self._get_decoded_string(string=job["obs_type"][0])
-        else:
-            raise ValueError("Invalid Observation ID")
-        return obs_type
-
-    def _select_related_members(self, observation_id):
-        query = f"select members from ehst.observation where observation_id='{observation_id}'"
-        job = self.query_hst_tap(query=query)
-        oids = self._get_decoded_string(string=job["members"][0]).replace("caom:HST/", "").split(" ")
-        return oids
-
-    def _select_related_composite(self, observation_id):
-        query = f"select observation_id from ehst.observation where members like '%{observation_id}%'"
-        job = self.query_hst_tap(query=query)
-        oids = job["observation_id"].pformat(show_name=False)
-        return oids
-
     def __validate_product_type(self, product_type):
-        if (product_type not in self.product_types):
+        if(product_type not in self.product_types):
             raise ValueError("This product_type is not allowed")
 
     def _get_product_filename(self, product_type, filename):
-        if (product_type == "PRODUCT"):
+        if(product_type == "PRODUCT"):
             return filename
-        elif (product_type == "SCIENCE_PRODUCT"):
+        elif(product_type == "SCIENCE_PRODUCT"):
             log.info("This is a SCIENCE_PRODUCT, the filename will be "
                      "renamed to " + filename + ".fits.gz")
             return filename + ".fits.gz"
@@ -347,27 +263,27 @@ class ESAHubbleClass(BaseQuery):
             radius_in_grades = radius.to(units.deg).value
         ra = coord.ra.deg
         dec = coord.dec.deg
-        query = ("select o.observation_id, "
-                 "o.start_time, o.end_time, o.start_time_mjd, "
-                 "o.end_time_mjd, o.exposure_duration, o.release_date, "
-                 "o.run_id, o.program_id, o.set_id, o.collection, "
-                 "o.members_number, o.instrument_configuration, "
-                 "o.instrument_name, o.obs_type, o.target_moving, "
-                 "o.target_name, o.target_description, o.proposal_id, "
-                 "o.pi_name, prop.title, pl.metadata_provenance, "
-                 "pl.data_product_type, pl.software_version, pos.ra, "
-                 "pos.dec, pos.gal_lat, pos.gal_lon, pos.ecl_lat, "
-                 "pos.ecl_lon, pos.fov_size, en.wave_central, "
-                 "en.wave_bandwidth, en.wave_max, en.wave_min, "
-                 "en.filter from ehst.observation o join ehst.proposal "
-                 "prop on o.proposal_id=prop.proposal_id join ehst.plane "
-                 "pl on pl.observation_id=o.observation_id join "
-                 "ehst.position pos on pos.plane_id = pl.plane_id join "
-                 "ehst.energy en on en.plane_id=pl.plane_id where "
-                 "pl.main_science_plane='true' and 1=CONTAINS(POINT('ICRS', "
-                 f"pos.ra, pos.dec),CIRCLE('ICRS', {str(ra)}, {str(dec)}, {str(radius_in_grades)})) order "
-                 "by prop.proposal_id desc")
-        print("type: " + str(type(query)))
+        query = "select o.observation_id, "\
+                "o.start_time, o.end_time, o.start_time_mjd, "\
+                "o.end_time_mjd, o.exposure_duration, o.release_date, "\
+                "o.run_id, o.program_id, o.set_id, o.collection, "\
+                "o.members_number, o.instrument_configuration, "\
+                "o.instrument_name, o.obs_type, o.target_moving, "\
+                "o.target_name, o.target_description, o.proposal_id, "\
+                "o.pi_name, prop.title, pl.metadata_provenance, "\
+                "pl.data_product_type, pl.software_version, pos.ra, "\
+                "pos.dec, pos.gal_lat, pos.gal_lon, pos.ecl_lat, "\
+                "pos.ecl_lon, pos.fov_size, en.wave_central, "\
+                "en.wave_bandwidth, en.wave_max, en.wave_min, "\
+                "en.filter from ehst.observation o join ehst.proposal "\
+                "prop on o.proposal_id=prop.proposal_id join ehst.plane "\
+                "pl on pl.observation_id=o.observation_id join "\
+                "ehst.position pos on pos.plane_id = pl.plane_id join "\
+                "ehst.energy en on en.plane_id=pl.plane_id where "\
+                "pl.main_science_plane='true' and 1=CONTAINS(POINT('ICRS', "\
+                "pos.ra, pos.dec),CIRCLE('ICRS', {0}, {1}, {2})) order "\
+                "by prop.proposal_id desc".format(str(ra), str(dec),
+                                                  str(radius_in_grades))
         if verbose:
             log.info(query)
         table = self.query_hst_tap(query=query, async_job=async_job,
@@ -464,20 +380,19 @@ class ESAHubbleClass(BaseQuery):
             raise TypeError("Please use only target or coordinates as"
                             "parameter.")
         if target:
-            coord = self._query_tap_target(target)
+            ra, dec = self._query_tap_target(target)
         else:
             coord = self._getCoordInput(coordinates)
-
-        ra = coord.ra.deg
-        dec = coord.dec.deg
+            ra = coord.ra.deg
+            dec = coord.dec.deg
 
         if type(radius) == int or type(radius) == float:
             radius_in_grades = Angle(radius, units.arcmin).deg
         else:
             radius_in_grades = radius.to(units.deg).value
-        cone_query = "1=CONTAINS(POINT('ICRS', pos.ra, pos.dec)," \
-                     "CIRCLE('ICRS', {0}, {1}, {2}))". \
-            format(str(ra), str(dec), str(radius_in_grades))
+        cone_query = "1=CONTAINS(POINT('ICRS', pos.ra, pos.dec),"\
+                     "CIRCLE('ICRS', {0}, {1}, {2}))".\
+                     format(str(ra), str(dec), str(radius_in_grades))
         query = "{}{})".format(crit_query, cone_query)
         if verbose:
             log.info(query)
@@ -500,15 +415,15 @@ class ESAHubbleClass(BaseQuery):
             target_result = target_response.json()['data'][0]
             ra = target_result['RA_DEGREES']
             dec = target_result['DEC_DEGREES']
-            return SkyCoord(ra=ra, dec=dec, unit="deg")
+            return ra, dec
         except KeyError as e:
             raise ValueError("This target cannot be resolved")
 
     def query_metadata(self, output_format='votable', verbose=False):
         return
 
-    def query_target(self, name, *, filename=None, output_format='votable',
-                     verbose=False, async_job=False, radius=7):
+    def query_target(self, name, filename=None, output_format='votable',
+                     verbose=False):
         """
         It executes a query over EHST and download the xml with the results.
 
@@ -524,22 +439,34 @@ class ESAHubbleClass(BaseQuery):
         verbose : bool
             optional, default 'False'
             Flag to display information about the process
-        async_job : bool, optional, default 'False'
-            executes the query (job) in asynchronous/synchronous mode (default
-            synchronous)
-        radius : int
-            optional, default 7
-            radius in arcmin (int, float) or quantity of the cone_search
 
         Returns
         -------
         Table with the result of the query. It downloads metadata as a file.
         """
-        coordinates = self._query_tap_target(name)
-        table = self.cone_search(coordinates, radius, filename=filename, output_format=output_format,
-                                 verbose=verbose, async_job=async_job)
 
-        return table
+        params = {"RESOURCE_CLASS": "OBSERVATION",
+                  "USERNAME": "ehst-astroquery",
+                  "SELECTED_FIELDS": "OBSERVATION",
+                  "QUERY": "(TARGET.TARGET_NAME=='" + name + "')",
+                  "RETURN_TYPE": str(output_format)}
+        response = self._request('GET', self.metadata_url, save=True,
+                                 cache=True,
+                                 params=params)
+
+        if verbose:
+            log.info(self.metadata_url + "?RESOURCE_CLASS=OBSERVATION&"
+                     "SELECTED_FIELDS=OBSERVATION&QUERY=(TARGET.TARGET_NAME"
+                     "=='" + name + "')&USERNAME=ehst-astroquery&"
+                     "RETURN_TYPE=" + str(output_format))
+            log.info(self.copying_string.format(filename))
+        if filename is None:
+            filename = "target.xml"
+
+        shutil.move(response, filename)
+
+        return modelutils.read_results_table_from_file(filename,
+                                                       str(output_format))
 
     def query_hst_tap(self, query, async_job=False, output_file=None,
                       output_format="votable", verbose=False):
@@ -568,12 +495,13 @@ class ESAHubbleClass(BaseQuery):
             job = self._tap.launch_job_async(query=query,
                                              output_file=output_file,
                                              output_format=output_format,
-                                             verbose=verbose,
-                                             dump_to_file=output_file is not None)
+                                             verbose=False,
+                                             dump_to_file=output_file
+                                             is not None)
         else:
             job = self._tap.launch_job(query=query, output_file=output_file,
                                        output_format=output_format,
-                                       verbose=verbose,
+                                       verbose=False,
                                        dump_to_file=output_file is not None)
         table = job.get_results()
         return table
@@ -654,9 +582,9 @@ class ESAHubbleClass(BaseQuery):
             parameters.append("(o.instrument_configuration LIKE '%{}%')"
                               .format("%' OR o.instrument_configuration "
                                       "LIKE '%".join(filters)))
-        query = "select o.*, p.calibration_level, p.data_product_type, " \
-                "pos.ra, pos.dec from ehst.observation AS o JOIN " \
-                "ehst.plane as p on o.observation_uuid=p.observation_uuid " \
+        query = "select o.*, p.calibration_level, p.data_product_type, "\
+                "pos.ra, pos.dec from ehst.observation AS o JOIN "\
+                "ehst.plane as p on o.observation_uuid=p.observation_uuid "\
                 "JOIN ehst.position as pos on p.plane_id = pos.plane_id"
         if parameters:
             query += " where({})".format(" AND ".join(parameters))
@@ -672,7 +600,7 @@ class ESAHubbleClass(BaseQuery):
 
     def __get_calibration_level(self, calibration_level):
         condition = ""
-        if (calibration_level is not None):
+        if(calibration_level is not None):
             if isinstance(calibration_level, str):
                 condition = calibration_level
             elif isinstance(calibration_level, int):
@@ -768,11 +696,5 @@ class ESAHubbleClass(BaseQuery):
         else:
             return value
 
-    def _get_decoded_string(self, string):
-        try:
-            return string.decode('utf-8')
-        except (UnicodeDecodeError, AttributeError):
-            return string
-
 
 ESAHubble = ESAHubbleClass()
diff --git a/astroquery/sdss/core.py b/astroquery/sdss/core.py
index 293a7ae4..975e919e 100644
--- a/astroquery/sdss/core.py
+++ b/astroquery/sdss/core.py
@@ -32,7 +32,8 @@ class SDSSClass(BaseQuery):
     QUERY_URL_SUFFIX_DR_OLD = '/dr{dr}/en/tools/search/x_sql.asp'
     QUERY_URL_SUFFIX_DR_10 = '/dr{dr}/en/tools/search/x_sql.aspx'
     QUERY_URL_SUFFIX_DR_NEW = '/dr{dr}/en/tools/search/x_results.aspx'
-    XID_URL_SUFFIX_OLD = '/dr{dr}/en/tools/crossid/x_crossid.aspx'
+    XID_URL_SUFFIX_OLD = '/dr{dr}/en/tools/crossid/x_crossid.asp'
+    XID_URL_SUFFIX_DR_10 = '/dr{dr}/en/tools/crossid/x_crossid.aspx'
     XID_URL_SUFFIX_NEW = '/dr{dr}/en/tools/search/X_Results.aspx'
     IMAGING_URL_SUFFIX = ('{base}/dr{dr}/{instrument}/photoObj/frames/'
                           '{rerun}/{run}/{camcol}/'
@@ -123,7 +124,7 @@ class SDSSClass(BaseQuery):
                 raise TypeError("radius should be either Quantity or "
                                 "convertible to float.")
 
-        sql_query = 'SELECT '
+        sql_query = 'SELECT\r\n'  # Older versions expect the CRLF to be there.
 
         if specobj_fields is None:
             if photoobj_fields is None:
@@ -1078,8 +1079,10 @@ class SDSSClass(BaseQuery):
         return url
 
     def _get_crossid_url(self, data_release):
-        if data_release < 11:
+        if data_release < 10:
             suffix = self.XID_URL_SUFFIX_OLD
+        elif data_release == 10:
+            suffix = self.XID_URL_SUFFIX_DR_10
         else:
             suffix = self.XID_URL_SUFFIX_NEW
 
diff --git a/docs/esa/hubble.rst b/docs/esa/hubble.rst
index c29ba328..00cedcc3 100644
--- a/docs/esa/hubble.rst
+++ b/docs/esa/hubble.rst
@@ -339,39 +339,6 @@ votable). The result of the query will be stored in the file
 result.get_results() or printing it by doing print(result).
 
 
-------------------------------------------------------
-9. Getting related members of HAP and HST observations
-------------------------------------------------------
-
-This function takes in an observation id of a Composite or Simple observation.
-If the observation is Simple the method returns the Composite observation that
-is built on this simple observation. If the observation is Composite then the
-method returns the simple observations that make it up.
-
-.. code-block:: python
-
-  >>> from astroquery.esa.hubble import ESAHubble
-  >>> esahubble = ESAHubble()
-  >>> result = esahubble.get_member_observations("jdrz0c010")
-  >>> print(result)
-
-
---------------------------------------------------------
-10. Getting link between Simple HAP and HST observations
---------------------------------------------------------
-
-This function takes in an observation id of a Simple HAP or HST observation and
-returns the corresponding HAP or HST observation
-
-.. code-block:: python
-
-  >>> from astroquery.esa.hubble import ESAHubble
-  >>> esahubble = ESAHubble()
-  >>> result = esahubble.get_hap_hst_link("hst_16316_71_acs_sbc_f150lp_jec071i9")
-  >>> print(result)
-
-
-
 Reference/API
 =============
 


Test patch:
diff --git a/astroquery/esa/hubble/tests/test_esa_hubble.py b/astroquery/esa/hubble/tests/test_esa_hubble.py
index 107bff87..d00c4444 100644
--- a/astroquery/esa/hubble/tests/test_esa_hubble.py
+++ b/astroquery/esa/hubble/tests/test_esa_hubble.py
@@ -11,10 +11,8 @@ Created on 13 Aug. 2018
 
 
 """
-import numpy as np
 import pytest
 import os
-from unittest.mock import patch
 
 from requests.models import Response
 from astroquery.esa.hubble import ESAHubbleClass
@@ -66,14 +64,6 @@ def ehst_cone_search(request):
     return mp
 
 
-class MockResponse:
-    observation_id = 'test'
-
-    @staticmethod
-    def pformat():
-        return True
-
-
 class TestESAHubble:
 
     def get_dummy_tap_handler(self):
@@ -132,14 +122,12 @@ class TestESAHubble:
                           filename="X0MC5101T.vot",
                           verbose=True)
 
-    @patch.object(ESAHubbleClass, 'cone_search')
-    @patch.object(ESAHubbleClass, '_query_tap_target')
-    def test_query_target(self, mock_query_tap_target, mock_cone_search):
-        mock_query_tap_target.return_value = 10, 10
-        mock_cone_search.return_value = "test"
+    def test_query_target(self):
+        parameters = {'name': "m31",
+                      'verbose': True}
         ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        table = ehst.query_target(name="test")
-        assert table == "test"
+        ehst.query_target(name=parameters['name'],
+                          verbose=parameters['verbose'])
 
     def test_cone_search(self):
         coords = coordinates.SkyCoord("00h42m44.51s +41d16m08.45s",
@@ -190,7 +178,7 @@ class TestESAHubble:
                          parameters['verbose'])
         with pytest.raises(ValueError) as err:
             ehst._getCoordInput(1234)
-        assert "Coordinates must be either a string or " \
+        assert "Coordinates must be either a string or "\
                "astropy.coordinates" in err.value.args[0]
 
     def test_query_hst_tap(self):
@@ -361,32 +349,32 @@ class TestESAHubble:
                        'filename': "output_test_query_by_criteria.vot.gz",
                        'output_format': "votable",
                        'verbose': True}
-        test_query = "select o.*, p.calibration_level, p.data_product_type, " \
-                     "pos.ra, pos.dec from ehst.observation AS o JOIN " \
-                     "ehst.plane as p on o.observation_uuid=p.observation_" \
-                     "uuid JOIN ehst.position as pos on p.plane_id = " \
-                     "pos.plane_id where((o.collection LIKE '%HST%') AND " \
-                     "(o.instrument_name LIKE '%WFPC2%') AND " \
-                     "(o.instrument_configuration LIKE '%F606W%') AND " \
-                     "1=CONTAINS(POINT('ICRS', pos.ra, pos.dec)," \
-                     "CIRCLE('ICRS', 10.6847083, 41.26875, " \
+        test_query = "select o.*, p.calibration_level, p.data_product_type, "\
+                     "pos.ra, pos.dec from ehst.observation AS o JOIN "\
+                     "ehst.plane as p on o.observation_uuid=p.observation_"\
+                     "uuid JOIN ehst.position as pos on p.plane_id = "\
+                     "pos.plane_id where((o.collection LIKE '%HST%') AND "\
+                     "(o.instrument_name LIKE '%WFPC2%') AND "\
+                     "(o.instrument_configuration LIKE '%F606W%') AND "\
+                     "1=CONTAINS(POINT('ICRS', pos.ra, pos.dec),"\
+                     "CIRCLE('ICRS', 10.6847083, 41.26875, "\
                      "0.11666666666666667)))"
         parameters3 = {'query': test_query,
                        'output_file': "output_test_query_by_criteria.vot.gz",
                        'output_format': "votable",
                        'verbose': False}
         ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        query_criteria_query = "select o.*, p.calibration_level, " \
-                               "p.data_product_type, pos.ra, pos.dec from " \
-                               "ehst.observation AS o JOIN ehst.plane as p " \
-                               "on o.observation_uuid=p.observation_uuid " \
-                               "JOIN ehst.position as pos on p.plane_id = " \
-                               "pos.plane_id where((o.collection LIKE " \
-                               "'%HST%') AND (o.instrument_name LIKE " \
-                               "'%WFPC2%') AND (o.instrument_configuration " \
+        query_criteria_query = "select o.*, p.calibration_level, "\
+                               "p.data_product_type, pos.ra, pos.dec from "\
+                               "ehst.observation AS o JOIN ehst.plane as p "\
+                               "on o.observation_uuid=p.observation_uuid "\
+                               "JOIN ehst.position as pos on p.plane_id = "\
+                               "pos.plane_id where((o.collection LIKE "\
+                               "'%HST%') AND (o.instrument_name LIKE "\
+                               "'%WFPC2%') AND (o.instrument_configuration "\
                                "LIKE '%F606W%'))"
         ehst.query_criteria = MagicMock(return_value=query_criteria_query)
-        target = coordinates.SkyCoord("00h42m44.51s +41d16m08.45s", frame='icrs')
+        target = {'RA_DEGREES': '10.6847083', 'DEC_DEGREES': '41.26875'}
         ehst._query_tap_target = MagicMock(return_value=target)
         ehst.cone_search_criteria(target=parameters1['target'],
                                   radius=parameters1['radius'],
@@ -431,14 +419,14 @@ class TestESAHubble:
                                       output_format=parameters1
                                       ['output_format'],
                                       verbose=parameters1['verbose'])
-        assert "Please use only target or coordinates as" \
-               "parameter." in err.value.args[0]
+        assert "Please use only target or coordinates as"\
+            "parameter." in err.value.args[0]
 
     def test_query_criteria_no_params(self):
         ehst = ESAHubbleClass(self.get_dummy_tap_handler())
         ehst.query_criteria(async_job=False,
                             output_file="output_test_query_"
-                                        "by_criteria.vot.gz",
+                            "by_criteria.vot.gz",
                             output_format="votable",
                             verbose=True)
         parameters = {'query': "select o.*, p.calibration_level, "
@@ -457,175 +445,8 @@ class TestESAHubble:
             ehst.query_criteria(instrument_name=[1],
                                 async_job=False,
                                 output_file="output_test_query_"
-                                            "by_criteria.vot.gz",
+                                "by_criteria.vot.gz",
                                 output_format="votable",
                                 verbose=True)
-        assert "One of the lists is empty or there are " \
+        assert "One of the lists is empty or there are "\
                "elements that are not strings" in err.value.args[0]
-
-    def test__get_decoded_string(self):
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        dummy = '\x74\x65\x73\x74'
-        decoded_string = ehst._get_decoded_string(dummy)
-        assert decoded_string == 'test'
-
-    def test__get_decoded_string_unicodedecodeerror(self):
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        dummy = '\xd0\x91'
-        decoded_string = ehst._get_decoded_string(dummy)
-        assert decoded_string == dummy
-
-    def test__get_decoded_string_attributeerror(self):
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        dummy = True
-        decoded_string = ehst._get_decoded_string(dummy)
-        assert decoded_string == dummy
-
-    @patch.object(ESAHubbleClass, 'query_hst_tap')
-    def test__select_related_composite(self, mock_query):
-        arr = {'a': np.array([1, 4], dtype=np.int32),
-               'b': [2.0, 5.0],
-               'observation_id': ['x', 'y']}
-        data_table = Table(arr)
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        mock_query.return_value = data_table
-        dummy_obs_id = "1234"
-        oids = ehst._select_related_composite(dummy_obs_id)
-        assert oids == ['x', 'y']
-
-    @patch.object(ESAHubbleClass, 'query_hst_tap')
-    def test__select_related_members(self, mock_query):
-        arr = {'a': np.array([1, 4], dtype=np.int32),
-               'b': [2.0, 5.0],
-               'members': ['caom:HST/test', 'y']}
-        data_table = Table(arr)
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        mock_query.return_value = data_table
-        dummy_obs_id = "1234"
-        oids = ehst._select_related_members(dummy_obs_id)
-        assert oids == ['test']
-
-    @patch.object(ESAHubbleClass, 'query_hst_tap')
-    def test_get_observation_type(self, mock_query):
-        arr = {'a': np.array([1, 4], dtype=np.int32),
-               'b': [2.0, 5.0],
-               'obs_type': ['HST Test', 'y']}
-        data_table = Table(arr)
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        mock_query.return_value = data_table
-        dummy_obs_id = "1234"
-        oids = ehst.get_observation_type(dummy_obs_id)
-        assert oids == 'HST Test'
-
-    def test_get_observation_type_obs_id_none_valueerror(self):
-        with pytest.raises(ValueError):
-            ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-            dummy_obs_id = None
-            ehst.get_observation_type(dummy_obs_id)
-
-    @patch.object(ESAHubbleClass, 'query_hst_tap')
-    def test_get_observation_type_invalid_obs_id_valueerror(self, mock_query):
-        with pytest.raises(ValueError):
-            arr = {'a': np.array([], dtype=np.int32),
-                   'b': [],
-                   'obs_type': []}
-            data_table = Table(arr)
-            ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-            mock_query.return_value = data_table
-            dummy_obs_id = '1234'
-            ehst.get_observation_type(dummy_obs_id)
-
-    @patch.object(ESAHubbleClass, 'query_hst_tap')
-    @patch.object(ESAHubbleClass, 'get_observation_type')
-    def test_get_hst_link(self, mock_observation_type, mock_query):
-        mock_observation_type.return_value = "HST"
-        arr = {'a': np.array([1], dtype=np.int32),
-               'b': [2.0],
-               'observation_id': ['1234']}
-        data_table = Table(arr)
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        mock_query.return_value = data_table
-        dummy_obs_id = "1234"
-        oids = ehst.get_hap_hst_link(dummy_obs_id)
-        assert oids == ['1234']
-
-    @patch.object(ESAHubbleClass, 'get_observation_type')
-    @patch.object(ESAHubbleClass, '_select_related_members')
-    def test_get_hap_link(self, mock_select_related_members, mock_observation_type):
-        mock_select_related_members.return_value = 'test'
-        mock_observation_type.return_value = "HAP"
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        dummy_obs_id = "1234"
-        oids = ehst.get_hap_hst_link(dummy_obs_id)
-        assert oids == 'test'
-
-    @patch.object(ESAHubbleClass, 'get_observation_type')
-    def test_get_hap_hst_link_invalid_id_valueerror(self, mock_observation_type):
-        with pytest.raises(ValueError):
-            mock_observation_type.return_value = "valueerror"
-            ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-            dummy_obs_id = "1234"
-            ehst.get_hap_hst_link(dummy_obs_id)
-
-    @patch.object(ESAHubbleClass, 'get_observation_type')
-    def test_get_hap_hst_link_compositeerror(self, mock_observation_type):
-        with pytest.raises(ValueError):
-            mock_observation_type.return_value = "HAP Composite"
-            ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-            dummy_obs_id = "1234"
-            ehst.get_hap_hst_link(dummy_obs_id)
-
-    @patch.object(ESAHubbleClass, '_select_related_members')
-    @patch.object(ESAHubbleClass, 'get_observation_type')
-    def test_get_member_observations_composite(self, mock_observation_type, mock_select_related_members):
-        mock_observation_type.return_value = "Composite"
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        mock_select_related_members.return_value = 'test'
-        dummy_obs_id = "1234"
-        oids = ehst.get_member_observations(dummy_obs_id)
-        assert oids == 'test'
-
-    @patch.object(ESAHubbleClass, '_select_related_composite')
-    @patch.object(ESAHubbleClass, 'get_observation_type')
-    def test_get_member_observations_simple(self, mock_observation_type, mock_select_related_composite):
-        mock_observation_type.return_value = "Simple"
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        mock_select_related_composite.return_value = 'test'
-        dummy_obs_id = "1234"
-        oids = ehst.get_member_observations(dummy_obs_id)
-        assert oids == 'test'
-
-    @patch.object(ESAHubbleClass, 'get_observation_type')
-    def test_get_member_observations_invalid_id_valueerror(self, mock_observation_type):
-        with pytest.raises(ValueError):
-            mock_observation_type.return_value = "valueerror"
-            ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-            dummy_obs_id = "1234"
-            ehst.get_member_observations(dummy_obs_id)
-
-    @patch.object(ESAHubbleClass, 'query_criteria')
-    @patch.object(ESAHubbleClass, '_query_tap_target')
-    @patch.object(ESAHubbleClass, 'query_hst_tap')
-    def test_cone_search_criteria_only_target(self, mock_query_hst_tap, mock__query_tap_target, mock_query_criteria):
-        mock_query_criteria.return_value = "Simple query"
-        mock__query_tap_target.return_value = coordinates.SkyCoord("00h42m44.51s +41d16m08.45s", frame='icrs')
-        mock_query_hst_tap.return_value = "table"
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        oids = ehst.cone_search_criteria(target="m11", radius=1)
-        assert oids == 'table'
-
-    @patch.object(ESAHubbleClass, 'query_criteria')
-    @patch.object(ESAHubbleClass, 'query_hst_tap')
-    def test_cone_search_criteria_only_coordinates(self, mock_query_hst_tap, mock_query_criteria):
-        mock_query_criteria.return_value = "Simple query"
-        mock_query_hst_tap.return_value = "table"
-        ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-        oids = ehst.cone_search_criteria(coordinates="00h42m44.51s +41d16m08.45s", radius=1)
-        assert oids == 'table'
-
-    @patch.object(ESAHubbleClass, 'query_criteria')
-    def test_cone_search_criteria_typeerror(self, mock_query_criteria):
-        mock_query_criteria.return_value = "Simple query"
-        with pytest.raises(TypeError):
-            ehst = ESAHubbleClass(self.get_dummy_tap_handler())
-            ehst.cone_search_criteria(coordinates="00h42m44.51s +41d16m08.45s", target="m11", radius=1)
diff --git a/astroquery/esa/hubble/tests/test_esa_hubble_remote.py b/astroquery/esa/hubble/tests/test_esa_hubble_remote.py
index 79c385c5..eda1438c 100644
--- a/astroquery/esa/hubble/tests/test_esa_hubble_remote.py
+++ b/astroquery/esa/hubble/tests/test_esa_hubble_remote.py
@@ -40,14 +40,15 @@ def remove_last_job():
 
 @pytest.mark.remote_data
 class TestEsaHubbleRemoteData:
+
     obs_query = "select top 2050 o.observation_id from ehst.observation o"
 
     top_obs_query = "select top 100 o.observation_id from ehst.observation o"
 
-    hst_query = "select top 50 o.observation_id from ehst.observation " \
+    hst_query = "select top 50 o.observation_id from ehst.observation "\
                 "o where o.collection='HST'"
 
-    top_artifact_query = "select top 50 a.artifact_id, a.observation_id " \
+    top_artifact_query = "select top 50 a.artifact_id, a.observation_id "\
                          " from ehst.artifact a"
 
     temp_folder = create_temp_folder()
@@ -74,6 +75,12 @@ class TestEsaHubbleRemoteData:
         esa_hubble.get_artifact(artifact_id, temp_file)
         assert os.path.exists(temp_file)
 
+    def test_query_target(self):
+        temp_file = self.temp_folder.name + "/" + "m31_query.xml"
+        table = esa_hubble.query_target("m31", temp_file)
+        assert os.path.exists(temp_file)
+        assert 'OBSERVATION_ID' in table.columns
+
     def test_cone_search(self):
         esa_hubble = ESAHubble()
         c = coordinates.SkyCoord("00h42m44.51s +41d16m08.45s", frame='icrs')
@@ -83,40 +90,3 @@ class TestEsaHubbleRemoteData:
         assert 'observation_id' in table.columns
         assert len(table) > 0
         remove_last_job()
-
-    # tests for get_related_members
-    def test_hst_composite_to_hst_simple(self):
-        esa_hubble = ESAHubble()
-        result = esa_hubble.get_member_observations('jdrz0c010')
-        assert result == ['jdrz0cjxq', 'jdrz0cjyq']
-
-    def test_hst_simple_to_hst_composite(self):
-        esa_hubble = ESAHubble()
-        result = esa_hubble.get_member_observations('jdrz0cjxq')
-        assert result == ['jdrz0c010']
-
-    def test_hap_composite_to_hap_simple(self):
-        esa_hubble = ESAHubble()
-        result = esa_hubble.get_member_observations('hst_15446_4v_acs_wfc_f606w_jdrz4v')
-        assert result == ['hst_15446_4v_acs_wfc_f606w_jdrz4vkv', 'hst_15446_4v_acs_wfc_f606w_jdrz4vkw']
-
-    def test_hap_simple_to_hap_composite(self):
-        esa_hubble = ESAHubble()
-        result = esa_hubble.get_member_observations('hst_16316_71_acs_sbc_f150lp_jec071i9')
-        assert result == ['hst_16316_71_acs_sbc_f150lp_jec071']
-
-    def test_hap_simple_to_hst_simple(self):
-        esa_hubble = ESAHubble()
-        result = esa_hubble.get_hap_hst_link('hst_16316_71_acs_sbc_f150lp_jec071i9')
-        assert result == ['jec071i9q']
-
-    def test_hst_simple_to_hap_simple(self):
-        esa_hubble = ESAHubble()
-        result = esa_hubble.get_hap_hst_link('jec071i9q')
-        assert result == ['hst_16316_71_acs_sbc_f150lp_jec071i9']
-
-    def test_query_target(self):
-        temp_file = self.temp_folder.name + "/" + "m31_query.xml"
-        table = esa_hubble.query_target(name="m3", filename=temp_file)
-        assert os.path.exists(temp_file)
-        assert 'observation_id' in table.columns
diff --git a/astroquery/sdss/tests/test_sdss.py b/astroquery/sdss/tests/test_sdss.py
index bb92c01b..38b7d1d4 100644
--- a/astroquery/sdss/tests/test_sdss.py
+++ b/astroquery/sdss/tests/test_sdss.py
@@ -136,7 +136,9 @@ def url_tester(data_release):
 
 
 def url_tester_crossid(data_release):
-    if data_release < 11:
+    if data_release < 10:
+        baseurl = 'http://skyserver.sdss.org/dr{}/en/tools/crossid/x_crossid.asp'
+    if data_release == 10:
         baseurl = 'http://skyserver.sdss.org/dr{}/en/tools/crossid/x_crossid.aspx'
     if data_release == 11:
         return
diff --git a/astroquery/sdss/tests/test_sdss_remote.py b/astroquery/sdss/tests/test_sdss_remote.py
index 723195f2..26251eaf 100644
--- a/astroquery/sdss/tests/test_sdss_remote.py
+++ b/astroquery/sdss/tests/test_sdss_remote.py
@@ -174,8 +174,7 @@ class TestSDSSRemote:
         assert query1.colnames == ['r', 'psfMag_r']
         assert query2.colnames == ['ra', 'dec', 'r']
 
-    # crossid doesn't work for DR<10, remove limitation once #2303 is fixed
-    @pytest.mark.parametrize("dr", dr_list[2:])
+    @pytest.mark.parametrize("dr", dr_list)
     def test_query_crossid(self, dr):
         query1 = sdss.SDSS.query_crossid(self.coords, data_release=dr)
         query2 = sdss.SDSS.query_crossid([self.coords, self.coords])
@@ -185,8 +184,7 @@ class TestSDSSRemote:
         assert isinstance(query2, Table)
         assert query2['objID'][0] == query1['objID'][0] == query2['objID'][1]
 
-    # crossid doesn't work for DR<10, remove limitation once #2303 is fixed
-    @pytest.mark.parametrize("dr", dr_list[2:])
+    @pytest.mark.parametrize("dr", dr_list)
     def test_spectro_query_crossid(self, dr):
         query1 = sdss.SDSS.query_crossid(self.coords,
                                          specobj_fields=['specObjID', 'z'],