Repository: sunpy/sunpy

Issue:
Add support to Fido to download SUVI data
<!-- This comments are hidden when you submit the issue so you do not need to remove them!
Please be sure to check out our contributing guidelines: https://github.com/sunpy/sunpy/blob/master/CONTRIBUTING.rst
Please be sure to check out our code of conduct:
https://github.com/sunpy/sunpy/blob/master/CODE_OF_CONDUCT.rst -->

<!-- Please have a search on our GitHub repository to see if a similar issue has already been posted.
If a similar issue is closed, have a quick look to see if you are satisfied by the resolution.
If not please go ahead and open an issue! -->

### Description
<!-- Provide a general description of the feature you would like. -->
<!-- "If you want to, you can suggest a draft design or API. -->
<!-- This way we have a deeper discussion on the feature. -->
The title says it all.

### Additional context
<!-- Add any other context or screenshots about the feature request here. -->
<!-- This part is optional. -->



Discussion:

Resolving patch:
diff --git a/sunpy/net/dataretriever/__init__.py b/sunpy/net/dataretriever/__init__.py
--- a/sunpy/net/dataretriever/__init__.py
+++ b/sunpy/net/dataretriever/__init__.py
@@ -11,12 +11,12 @@
 from .client import QueryResponseBlock, QueryResponse, GenericClient
 from .sources.eve import EVEClient
 from .sources.lyra import LYRAClient
-from .sources.goes import XRSClient
+from .sources.goes import XRSClient, SUVIClient
 from .sources.norh import NoRHClient
 from .sources.rhessi import RHESSIClient
 from .sources.noaa import NOAAIndicesClient, NOAAPredictClient, SRSClient
 from .sources.fermi_gbm import GBMClient
 
 __all__ = ['QueryResponseBlock', 'QueryResponse', 'GenericClient',
-           'EVEClient', 'XRSClient', 'LYRAClient', 'NOAAIndicesClient',
+           'EVEClient', 'XRSClient', 'SUVIClient', 'LYRAClient', 'NOAAIndicesClient',
            'NOAAPredictClient', 'NoRHClient', 'RHESSIClient', 'SRSClient', 'GBMClient']
diff --git a/sunpy/net/dataretriever/sources/goes.py b/sunpy/net/dataretriever/sources/goes.py
--- a/sunpy/net/dataretriever/sources/goes.py
+++ b/sunpy/net/dataretriever/sources/goes.py
@@ -3,21 +3,25 @@
 # Google Summer of Code 2014
 
 import os
+from datetime import timedelta, datetime
+from itertools import compress
 from urllib.parse import urlsplit
 
-from astropy.time import TimeDelta
-from astropy.time import Time
 import astropy.units as u
+from astropy.time import Time, TimeDelta
 
+from sunpy import config, log
+from sunpy.net import attrs as a
+from sunpy.time import TimeRange, parse_time
+from sunpy.util.scraper import Scraper
+from sunpy.time.time import _variables_for_parse_time_docstring
+from sunpy.util.decorators import add_common_docstring
 
-from datetime import timedelta
-from sunpy.time import parse_time, TimeRange
 from ..client import GenericClient
-from sunpy import config
 
 TIME_FORMAT = config.get("general", "time_format")
 
-__all__ = ['XRSClient']
+__all__ = ["XRSClient", "SUVIClient"]
 
 
 class XRSClient(GenericClient):
@@ -46,6 +50,7 @@ class XRSClient(GenericClient):
     <BLANKLINE>
 
     """
+
     def _get_goes_sat_num(self, date):
         """
         Determines the satellite number for a given date.
@@ -57,18 +62,18 @@ def _get_goes_sat_num(self, date):
             The date to determine which satellite is active.
         """
         goes_operational = {
-            2: TimeRange('1981-01-01', '1983-04-30'),
-            5: TimeRange('1983-05-02', '1984-07-31'),
-            6: TimeRange('1983-06-01', '1994-08-18'),
-            7: TimeRange('1994-01-01', '1996-08-13'),
-            8: TimeRange('1996-03-21', '2003-06-18'),
-            9: TimeRange('1997-01-01', '1998-09-08'),
-            10: TimeRange('1998-07-10', '2009-12-01'),
-            11: TimeRange('2006-06-20', '2008-02-15'),
-            12: TimeRange('2002-12-13', '2007-05-08'),
-            13: TimeRange('2006-08-01', '2006-08-01'),
-            14: TimeRange('2009-12-02', '2010-10-04'),
-            15: TimeRange('2010-09-01', parse_time('now'))
+            2: TimeRange("1981-01-01", "1983-04-30"),
+            5: TimeRange("1983-05-02", "1984-07-31"),
+            6: TimeRange("1983-06-01", "1994-08-18"),
+            7: TimeRange("1994-01-01", "1996-08-13"),
+            8: TimeRange("1996-03-21", "2003-06-18"),
+            9: TimeRange("1997-01-01", "1998-09-08"),
+            10: TimeRange("1998-07-10", "2009-12-01"),
+            11: TimeRange("2006-06-20", "2008-02-15"),
+            12: TimeRange("2002-12-13", "2007-05-08"),
+            13: TimeRange("2006-08-01", "2006-08-01"),
+            14: TimeRange("2009-12-02", "2010-10-04"),
+            15: TimeRange("2010-09-01", parse_time("now")),
         }
 
         results = []
@@ -82,8 +87,11 @@ def _get_goes_sat_num(self, date):
             return max(results)
         else:
             # if no satellites were found then raise an exception
-            raise ValueError('No operational GOES satellites on {}'.format(
-                date.strftime(TIME_FORMAT)))
+            raise ValueError(
+                "No operational GOES satellites on {}".format(
+                    date.strftime(TIME_FORMAT)
+                )
+            )
 
     def _get_time_for_url(self, urls):
         times = []
@@ -99,7 +107,7 @@ def _get_time_for_url(self, urls):
             else:
                 start = Time.strptime(datestamp, "%Y%m%d")
 
-            almost_day = TimeDelta(1*u.day - 1*u.millisecond)
+            almost_day = TimeDelta(1 * u.day - 1 * u.millisecond)
             times.append(TimeRange(start, start + almost_day))
 
         return times
@@ -119,11 +127,12 @@ def _get_url_for_timerange(self, timerange, **kwargs):
             types depend on the satellite number specified. (default = xrs_2s)
         """
         # find out which satellite and datatype to query from the query times
-        base_url = 'https://umbra.nascom.nasa.gov/goes/fits/'
-        start_time = Time(timerange.start.strftime('%Y-%m-%d'))
+        base_url = "https://umbra.nascom.nasa.gov/goes/fits/"
+        start_time = Time(timerange.start.strftime("%Y-%m-%d"))
         # make sure we are counting a day even if only a part of it is in the query range.
-        day_range = TimeRange(timerange.start.strftime('%Y-%m-%d'),
-                              timerange.end.strftime('%Y-%m-%d'))
+        day_range = TimeRange(
+            timerange.start.strftime("%Y-%m-%d"), timerange.end.strftime("%Y-%m-%d")
+        )
         total_days = int(day_range.days.value) + 1
         result = list()
 
@@ -135,12 +144,14 @@ def _get_url_for_timerange(self, timerange, **kwargs):
             # This is necessary because when adding a day to a Time, we may
             # end up with the same day if the day is a leap second day
             date = start_time.datetime + timedelta(days=day)
-            regex = date.strftime('%Y') + "/go{sat:02d}"
-            if (date < parse_time('1999/01/15')):
-                regex += date.strftime('%y%m%d') + '.fits'
+            regex = date.strftime("%Y") + "/go{sat:02d}"
+            if date < parse_time("1999/01/15"):
+                regex += date.strftime("%y%m%d") + ".fits"
             else:
-                regex += date.strftime('%Y%m%d') + '.fits'
-            satellitenumber = kwargs.get('satellitenumber', self._get_goes_sat_num(date))
+                regex += date.strftime("%Y%m%d") + ".fits"
+            satellitenumber = kwargs.get(
+                "satellitenumber", self._get_goes_sat_num(date)
+            )
             url = base_url + regex.format(sat=satellitenumber)
             result.append(url)
         return result
@@ -149,10 +160,10 @@ def _makeimap(self):
         """
         Helper function used to hold information about source.
         """
-        self.map_['source'] = 'nasa'
-        self.map_['instrument'] = 'goes'
-        self.map_['physobs'] = 'irradiance'
-        self.map_['provider'] = 'sdac'
+        self.map_["source"] = "nasa"
+        self.map_["instrument"] = "goes"
+        self.map_["physobs"] = "irradiance"
+        self.map_["provider"] = "sdac"
 
     @classmethod
     def _can_handle_query(cls, *query):
@@ -168,9 +179,201 @@ def _can_handle_query(cls, *query):
         boolean
             answer as to whether client can service the query
         """
-        chkattr = ['Time', 'Instrument', 'SatelliteNumber']
+        chkattr = ["Time", "Instrument", "SatelliteNumber"]
         chklist = [x.__class__.__name__ in chkattr for x in query]
         for x in query:
-            if x.__class__.__name__ == 'Instrument' and x.value.lower() in ('xrs', 'goes'):
+            if x.__class__.__name__ == "Instrument" and x.value.lower() in (
+                "xrs",
+                "goes",
+            ):
                 return all(chklist)
         return False
+
+
+class SUVIClient(GenericClient):
+    """
+    Provides access to data from the GOES Solar Ultraviolet Imager (SUVI).
+
+    SUVI data are provided by NOAA at the following url
+    https://data.ngdc.noaa.gov/platforms/solar-space-observing-satellites/
+    The SUVI instrument was first included on GOES-16. It produces level-1b as
+    well as level-2 data products. Level-2 data products are a weighted average
+    of level-1b product files and therefore provide higher imaging dynamic
+    range than individual images. The exposure time of level 1b images range
+    from 1 s to 0.005 s. SUVI supports the following wavelengths;
+    94, 131, 171, 195, 284, 304 angstrom. If no wavelength is specified, images
+    from all wavelengths are returned.
+
+    Note
+    ----
+    GOES-16 began providing regular level-1b data on 2018-06-01.  At the time
+    of writing, SUVI on GOES-17 is operational but currently does not provide
+    Level-2 data.
+    """
+
+    @add_common_docstring(**_variables_for_parse_time_docstring())
+    def _get_goes_sat_num(self, date):
+        """
+        Determines the best satellite number for a given date.
+
+        Parameters
+        ----------
+        date : {parse_time_types}
+            The date to determine which satellite is active.
+
+        Note
+        ----
+        At the time this function was written.
+        GOES-17 is operational but currently does not provide Level 2 data therefore it is never returned.
+        The GOES-16 start date is based on the availability of regular level 1b data.
+        """
+
+        # GOES-17 is operational but currently does not provide Level 2 data
+        # GOES-16 start date is based on the availability of regular level 1b data
+        suvi_operational = {
+            16: TimeRange("2018-06-01", parse_time("now")),
+        }
+
+        results = []
+        for sat_num in suvi_operational:
+            if date in suvi_operational[sat_num]:
+                # if true then the satellite with sat_num is available
+                results.append(sat_num)
+
+        if results:
+            # Return the newest satellite
+            return max(results)
+        else:
+            # if no satellites were found then raise an exception
+            raise ValueError(f"No operational SUVI instrument on {date.strftime(TIME_FORMAT)}")
+
+    def _get_time_for_url(self, urls):
+        these_timeranges = []
+
+        for this_url in urls:
+            if this_url.count('/l2/') > 0:  # this is a level 2 data file
+                start_time = parse_time(os.path.basename(this_url).split('_s')[2].split('Z')[0])
+                end_time = parse_time(os.path.basename(this_url).split('_e')[1].split('Z')[0])
+                these_timeranges.append(TimeRange(start_time, end_time))
+            if this_url.count('/l1b/') > 0:  # this is a level 1b data file
+                start_time = datetime.strptime(os.path.basename(this_url).split('_s')[1].split('_e')[0][:-1], '%Y%j%H%M%S')
+                end_time = datetime.strptime(os.path.basename(this_url).split('_e')[1].split('_c')[0][:-1], '%Y%j%H%M%S')
+                these_timeranges.append(TimeRange(start_time, end_time))
+        return these_timeranges
+
+
+    def _get_url_for_timerange(self, timerange, **kwargs):
+        """
+        Returns urls to the SUVI data for the specified time range.
+
+        Parameters
+        ----------
+        timerange: `sunpy.time.TimeRange`
+            Time range for which data is to be downloaded.
+        level : `str`, optional
+            The level of the data. Possible values are 1b and 2 (default).
+        wavelength : `astropy.units.Quantity` or `tuple`, optional
+            Wavelength band. If not given, all wavelengths are returned.
+        satellitenumber : `int`, optional
+            GOES satellite number. Must be >= 16. Default is 16.
+        """
+        base_url = "https://data.ngdc.noaa.gov/platforms/solar-space-observing-satellites/goes/goes{goes_number}/"
+        supported_waves = [94, 131, 171, 195, 284, 304]
+        supported_levels = ("2", "1b")
+
+        # these are optional requirements so if not provided assume defaults
+        # if wavelength is not provided assuming all of them
+        if "wavelength" in kwargs.keys():
+            wavelength_input = kwargs.get("wavelength")
+            if isinstance(wavelength_input, u.Quantity):  # not a range
+                if int(wavelength_input.to_value('Angstrom')) not in supported_waves:
+                    raise ValueError(f"Wavelength {kwargs.get('wavelength')} not supported.")
+                else:
+                    wavelength = [kwargs.get("wavelength")]
+            else:  #  _Range was provided
+                compress_index = [wavelength_input.wavemin <= this_wave <= wavelength_input.wavemax for this_wave in (supported_waves * u.Angstrom)]
+                if not any(compress_index):
+                    raise ValueError(
+                        f"Wavelength {wavelength_input} not supported.")
+                else:
+                    wavelength = list(compress(supported_waves, compress_index)) * u.Angstrom
+        else:  # no wavelength provided return all of them
+            wavelength = supported_waves * u.Angstrom
+        # check that the input wavelength can be converted to angstrom
+        waves = [int(this_wave.to_value('angstrom', equivalencies=u.spectral())) for this_wave in wavelength]
+        # use the given satellite number or choose the best one
+        satellitenumber = int(kwargs.get("satellitenumber", self._get_goes_sat_num(timerange.start)))
+        if satellitenumber < 16:
+            raise ValueError(f"Satellite number {satellitenumber} not supported.")
+        # default to the highest level of data
+        level = str(kwargs.get("level", "2"))  # make string in case the input is a number
+
+        if level not in supported_levels:
+            raise ValueError(f"Level {level} is not supported.")
+
+        results = []
+        for this_wave in waves:
+            if level == "2":
+                search_pattern = base_url + 'l{level}/data/suvi-l{level}-ci{wave:03}/%Y/%m/%d/dr_suvi-l{level}-ci{wave:03}_g{goes_number}_s%Y%m%dT%H%M%SZ_.*\.fits'
+            elif level == "1b":
+                if this_wave in [131, 171, 195, 284]:
+                    search_pattern = base_url + 'l{level}/suvi-l{level}-fe{wave:03}/%Y/%m/%d/OR_SUVI-L{level}-Fe{wave:03}_G{goes_number}_s%Y%j%H%M%S.*\.fits.gz'
+                elif this_wave == 304:
+                    search_pattern = base_url + 'l{level}/suvi-l{level}-he{wave:03}/%Y/%m/%d/OR_SUVI-L{level}-He{wave_minus1:03}_G{goes_number}_s%Y%j%H%M%S.*\.fits.gz'
+                elif this_wave == 94:
+                    search_pattern = base_url + 'l{level}/suvi-l{level}-fe{wave:03}/%Y/%m/%d/OR_SUVI-L{level}-Fe{wave_minus1:03}_G{goes_number}_s%Y%j%H%M%S.*\.fits.gz'
+
+            if search_pattern.count('wave_minus1'):
+                scraper = Scraper(search_pattern, level=level, wave=this_wave,
+                                  goes_number=satellitenumber, wave_minus1=this_wave-1)
+            else:
+                scraper = Scraper(search_pattern, level=level, wave=this_wave,
+                                  goes_number=satellitenumber)
+            results.extend(scraper.filelist(timerange))
+        return results
+
+    def _makeimap(self):
+        """
+        Helper Function used to hold information about source.
+        """
+        self.map_['source'] = 'GOES'
+        self.map_['provider'] = 'NOAA'
+        self.map_['instrument'] = 'SUVI'
+        self.map_['physobs'] = 'flux'
+
+    @classmethod
+    def _can_handle_query(cls, *query):
+        """
+        Answers whether client can service the query.
+
+        Parameters
+        ----------
+        query : `tuple`
+            All specified query objects.
+
+        Returns
+        -------
+        `bool`
+            answer as to whether client can service the query.
+        """
+
+        required = {a.Time, a.Instrument}
+        optional = {a.Wavelength, a.Level, a.goes.SatelliteNumber}
+        all_attrs = {type(x) for x in query}
+
+        ops = all_attrs - required
+        # check to ensure that all optional requirements are in approved list
+        if ops and not all(elem in optional for elem in ops):
+            return False
+
+        # if we get this far we have either Instrument and Time
+        # or Instrument, Time and Wavelength
+        check_var_count = 0
+        for x in query:
+            if isinstance(x, a.Instrument) and x.value.lower() == 'suvi':
+                check_var_count += 1
+
+        if check_var_count == 1:
+            return True
+        else:
+            return False
diff --git a/sunpy/util/scraper.py b/sunpy/util/scraper.py
--- a/sunpy/util/scraper.py
+++ b/sunpy/util/scraper.py
@@ -116,7 +116,7 @@ def _URL_followsPattern(self, url):
             pattern = pattern.replace(k, v)
         matches = re.match(pattern, url)
         if matches:
-            return matches.end() == matches.endpos == len(self.now)
+            return matches.end() == matches.endpos
         return False
 
     def _extractDateURL(self, url):


Test patch:
diff --git a/sunpy/net/dataretriever/tests/test_goes_suvi.py b/sunpy/net/dataretriever/tests/test_goes_suvi.py
new file mode 100644
--- /dev/null
+++ b/sunpy/net/dataretriever/tests/test_goes_suvi.py
@@ -0,0 +1,202 @@
+import tempfile
+from unittest import mock
+
+import pytest
+from hypothesis import given
+
+import astropy.units as u
+
+import sunpy.net.dataretriever.sources.goes as goes
+from sunpy.net import Fido
+from sunpy.net import attrs as a
+from sunpy.net.dataretriever.client import QueryResponse
+from sunpy.net.tests.strategies import time_attr
+from sunpy.time import parse_time, TimeRange
+
+
+@pytest.fixture
+def suvi_client():
+    return goes.SUVIClient()
+
+
+@given(time_attr())
+def test_can_handle_query(suvi_client, time):
+    ans1 = suvi_client._can_handle_query(time, a.Instrument('suvi'))
+    assert ans1 is True
+    ans2 = suvi_client._can_handle_query(time, a.Instrument('suvi'),
+                                         a.Wavelength(131 * u.Angstrom))
+    assert ans2 is True
+    ans3 = suvi_client._can_handle_query(time, a.Instrument('suvi'),
+                                         a.Wavelength(131 * u.Angstrom),
+                                         a.Level(2))
+    assert ans3 is True
+    ans4 = suvi_client._can_handle_query(time)
+    assert ans4 is False
+    ans5 = suvi_client._can_handle_query(time, a.Instrument('aia'))
+    assert ans5 is False
+    ans6 = suvi_client._can_handle_query(time, a.Instrument('suvi'),
+                                         a.goes.SatelliteNumber(16))
+    assert ans6 is True
+
+
+def test_get_goes_sat_num(suvi_client):
+    date = parse_time('2019/06/11 00:00')
+    min_satellite_number = 16  # when SUVI was first included
+    assert suvi_client._get_goes_sat_num(date) >= min_satellite_number
+    assert type(suvi_client._get_goes_sat_num(date)) is int
+
+
+def test_get_goes_sat_num_error(suvi_client):
+    date = parse_time('1800/06/11 00:00')
+    with pytest.raises(ValueError):
+        suvi_client._get_goes_sat_num(date)
+
+
+def test_get_url_for_timerange_errors(suvi_client):
+    """Check that unsupported values raise errors."""
+    tr = TimeRange('2019/06/11 00:00', '2019/06/11 00:10')
+    with pytest.raises(ValueError):
+        suvi_client._get_url_for_timerange(tr, level=0)
+    with pytest.raises(ValueError):
+        suvi_client._get_url_for_timerange(tr, wavelength=100 * u.Angstrom)
+    with pytest.raises(ValueError):
+        suvi_client._get_url_for_timerange(tr, satellitenumber=1)
+
+
+def mock_querry_object(suvi_client, start, end):
+    """
+    Creating a Query Response object and prefilling it with some information
+    """
+    # Creating a Query Response Object
+    obj = {
+        'TimeRange': TimeRange(parse_time(start), parse_time(end)),
+        'Time_start': parse_time(start),
+        'Time_end':  parse_time(end),
+        'source': 'GOES',
+        'instrument': 'SUVI',
+        'physobs': 'flux',
+        'provider': 'NOAA'
+    }
+    results = QueryResponse.create(obj, suvi_client._get_url_for_timerange(TimeRange(start, end)))
+    results.client = suvi_client
+    return results
+
+
+@pytest.mark.remote_data
+def test_fetch_working(suvi_client):
+    """
+    Tests if the online server for fermi_gbm is working.
+    This also checks if the mock is working well.
+    """
+    start = '2019/05/25 00:50'
+    end = '2019/05/25 00:52'
+    qr1 = suvi_client.search(a.Time(start, end), a.Instrument('suvi'))
+
+    # Mock QueryResponse object
+    mock_qr = mock_querry_object(suvi_client, start, end)
+
+    # Compare if two objects have the same attribute
+
+    mock_qr = mock_qr[0]
+    qr = qr1[0]
+
+    assert mock_qr.source == qr.source
+    assert mock_qr.provider == qr.provider
+    assert mock_qr.physobs == qr.physobs
+    assert mock_qr.instrument == qr.instrument
+    assert mock_qr.url == qr.url
+
+    assert qr1.time_range() == TimeRange(start, end)
+
+    with tempfile.TemporaryDirectory() as tmpdirname:
+        download_list = suvi_client.fetch(qr1, path=tmpdirname)
+    assert len(download_list) == len(qr1)
+
+
+@pytest.mark.remote_data
+@pytest.mark.parametrize("start, end, wave, expected_num_files",
+                         [('2019/05/25 00:50', '2019/05/25 00:52', 94, 1),
+                          ('2019/05/25 00:50', '2019/05/25 00:52', 131, 1),
+                          ('2019/05/25 00:50', '2019/05/25 00:52', 171, 1),
+                          ('2019/05/25 00:50', '2019/05/25 00:52', 195, 1),
+                          ('2019/05/25 00:50', '2019/05/25 00:52', 284, 1),
+                          ('2019/05/25 00:50', '2019/05/25 00:52', 304, 1)]
+                         )
+def test_get_url_for_time_range_level2(suvi_client, start, end, wave, expected_num_files):
+    urls = suvi_client._get_url_for_timerange(TimeRange(start, end),
+                                              wavelength=wave * u.Angstrom,
+                                              level=2)
+    assert isinstance(urls, list)
+    assert len(urls) == expected_num_files
+
+@pytest.mark.remote_data
+@pytest.mark.parametrize("start, end, expected_num_files",
+                         [('2019/05/25 00:50', '2019/05/25 00:52', 6)]
+                         )
+def test_get_url_for_time_range_level2_allwave(suvi_client, start, end, expected_num_files):
+    """check that we get all wavelengths if no wavelength is given"""
+    urls = suvi_client._get_url_for_timerange(TimeRange(start, end), level=2)
+    assert isinstance(urls, list)
+    assert len(urls) == expected_num_files
+
+
+@pytest.mark.remote_data
+@pytest.mark.parametrize("start, end ,wave, expected_num_files",
+                         [('2019/05/25 00:50', '2019/05/25 00:54', 94, 6),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 131, 3),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 171, 2),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 195, 7),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 284, 2),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 304, 4)]
+                         )
+def test_get_url_for_time_range_level1b(suvi_client, start, end, wave, expected_num_files):
+    """check that we get all wavelengths if no wavelength is given"""
+    urls = suvi_client._get_url_for_timerange(TimeRange(start, end),
+                                              wavelength=wave * u.Angstrom,
+                                              level='1b')
+    assert isinstance(urls, list)
+    assert len(urls) == expected_num_files
+
+
+@pytest.mark.remote_data
+@pytest.mark.parametrize("start, end ,wave, expected_num_files",
+                         [('2019/05/25 00:50', '2019/05/25 00:54', 94, 6),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 131, 3),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 171, 2),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 195, 7),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 284, 2),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 304, 4)]
+                         )
+def test_fido_onewave_level1b(start, end, wave, expected_num_files):
+    result = Fido.search(a.Time(start, end), a.Instrument('suvi'),
+                         a.Wavelength(wave * u.Angstrom), a.Level('1b'))
+    assert result.file_num == expected_num_files
+
+
+@pytest.mark.remote_data
+@pytest.mark.parametrize("start, end, wave1, wave2, expected_num_files",
+                         [('2019/05/25 00:50', '2019/05/25 00:54', 1, 100, 6),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 1, 150, 9),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 1, 180, 11),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 1, 200, 18),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 1, 300, 20),
+                          ('2019/05/25 00:50', '2019/05/25 00:54', 1, 310, 24)]
+                         )
+def test_fido_waverange_level1b(start, end, wave1, wave2, expected_num_files):
+    """check that we get all wavelengths if no wavelength is given"""
+    result = Fido.search(a.Time(start, end), a.Instrument('suvi'),
+                         a.Wavelength(wave1 * u.Angstrom, wave2 * u.Angstrom),
+                         a.Level('1b'))
+    assert result.file_num == expected_num_files
+
+
+@pytest.mark.remote_data
+@pytest.mark.parametrize("start, end, expected_num_files",
+                         [('2019/05/25 00:50', '2019/05/25 00:52', 6)]
+                         )
+def test_query(suvi_client, start, end, expected_num_files):
+    qr1 = suvi_client.search(a.Time(start, end), a.Instrument('suvi'))
+    assert isinstance(qr1, QueryResponse)
+    assert len(qr1) == expected_num_files
+    assert qr1.time_range().start == parse_time(start)
+    assert qr1.time_range().end == parse_time(end)
diff --git a/sunpy/net/dataretriever/tests/test_goes_ud.py b/sunpy/net/dataretriever/tests/test_goes_ud.py
--- a/sunpy/net/dataretriever/tests/test_goes_ud.py
+++ b/sunpy/net/dataretriever/tests/test_goes_ud.py
@@ -129,3 +129,4 @@ def test_time_for_url(LCClient, time):
     times = LCClient._get_time_for_url(url)
 
     assert all([tr == t2 for t2 in times])
+