test_client.py 21.9 KB
Newer Older
1
2
import pytest

3
4
import urllib.request
from urllib.error import HTTPError, URLError
5
import os
6
import json
7
import parfive
8

9
import astropy.units as u
10
from astropy.io import fits
11
from astropy.time import Time
12
from sunpy.net import Fido
13
from sunpy.net import attrs as a
14
from sunpy.net.base_client import QueryResponseTable
15

16
from sdc.client import KISClient
17
18
19


_BASE_URL = "http://dockertest:8083/sdc/"
20
21
22
23
24
25
_QUERY_BASE = "gris_observations?filter="
_EXAMPLE_QUERY = "{'description.OBS_NAME':'gris_20140426_000'}"
_EXAMPLE_RANGE = "{'description.THETA':{'$gt':70.0,'$lt':80}}"
_EXAMPLE_DATES = ("{'$and':[{'description.INSTRUMENT':'gris'},"
                  "{'description.DATE_BEG':{'$gte':{'$date':'2014-04-26T00:00:00'},"
                  "'$lte':{'$date':'2014-04-27T00:00:00'}}}]}")
26
27

try:
28
    response = urllib.request.urlopen(f"{_BASE_URL}{_QUERY_BASE}{_EXAMPLE_QUERY}")
29
30
31
32
33
34
35
    HAS_DOCKERTEST = True
except(HTTPError, URLError):
    HAS_DOCKERTEST = False


@pytest.fixture
def client():
36
    return KISClient()
37
38


39
def _dockerexc(instr):
40
41
    return (rf"Unable to execute search .http://dockertest:8083/sdc/{instr.lower()}_observations."
            rf"filter={{'.and':.{{'description.INSTRUMENT':'{instr.lower()}'}},")
42
43


44
45
46
47
48
49
50
51
52
53
54
55
56
def test_docker(client):
    """Test example queries on dockertest."""
    if not HAS_DOCKERTEST:
        pytest.xfail("No dockertest running")

    response = urllib.request.urlopen(f"{_BASE_URL}{_QUERY_BASE}{_EXAMPLE_QUERY}")
    data = json.loads(response.read())
    assert '_embedded' in data.keys()
    assert 'description' in data['_embedded'][0]

    response = urllib.request.urlopen(f"{_BASE_URL}{_QUERY_BASE}{_EXAMPLE_DATES}")
    data = json.loads(response.read())
    assert 'description' in data['_embedded'][0]
57
    assert len(data['_embedded']) > 4
58
59
60
61
62
63

    response = urllib.request.urlopen(f"{_BASE_URL}{_QUERY_BASE}{_EXAMPLE_RANGE}")
    data = json.loads(response.read())
    assert 'description' in data['_embedded'][0]

    res = client.search(a.Instrument("GRIS") & a.sdc.ObsName('gris_20140426_000'))
64
    assert isinstance(res, QueryResponseTable)
65
    assert len(res) == 1
66
67
    description = res[0].get('description')
    assert len(description) == 34
68
69
70
    assert description['INSTRUMENT'] == 'gris'
    assert description['TELESCOPE'] == 'GREGOR'
    assert description['BTYPE'] == 'phot.count'
71
72
    assert description['DATE_BEG']['$date'] == 1398505619000
    assert description['DATE_END']['$date'] == 1398506021300
73

74
    file_ids = [ld['$oid'] for ld in res[0]['links']['l1_data']]
75
76
77
78
79
80
81
82
    assert len(file_ids) == 105
    for oid in file_ids[0], file_ids[104]:
        meta = json.loads(urllib.request.urlopen(f"{_BASE_URL}gris_l1_data.files/{oid}").read())
        assert meta['_id']['$oid'] == oid
        hdulist = fits.open(f"{_BASE_URL}gris_l1_data.files/{oid}/binary")
        assert hdulist[0].header.get('TELESCOP') == 'GREGOR'
        assert '2014-04-26T' in hdulist[0].header.get('DATE-OBS')
        hdulist.close()
83

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    date = a.Time("2014/04/26 01:00", "2014/04/26 22:00")
    downloader = parfive.Downloader()
    inst = res[0]['description']['INSTRUMENT']
    rowpath = f"{res[0]['_id']['$oid']}"
    binfile = ''
    ext = 'json'
    for i, l1_data in enumerate(res[0]['links']['l1_data'][:10]):
        oid = l1_data['$oid']
        filename = f"{oid}.{ext}"
        url = f"{_BASE_URL}{inst}_l1_data.files/{oid}{binfile}"
        assert url == f"{_BASE_URL}gris_l1_data.files/{file_ids[i]}"
        downloader.enqueue_file(url, filename=os.path.join(rowpath, filename), max_splits=1)

    binfile = '/binary'
    ext = 'fits'
    for l1_data in res[0]['links']['l1_data'][:2]:
        oid = l1_data['$oid']
        filename = f"{oid}.{ext}"
        url = f"{_BASE_URL}{inst}_l1_data.files/{oid}{binfile}"
        downloader.enqueue_file(url, filename=os.path.join(rowpath, filename), max_splits=1)

    assert downloader.queued_downloads == 12
    assert downloader.http_queue[0].keywords['url'].startswith(_BASE_URL)
    assert res[0]['links']['l1_data'][0]['$oid'] in downloader.http_queue[0].keywords['url']
    assert downloader.http_queue[10].keywords['url'].endswith(binfile)

    files = downloader.download()
    assert len(files) == 12
    assert os.path.dirname(files[0]) == '5ee0feb97a92554c6de920ab'

    for filepath in files:
        if filepath.endswith('.fits'):
            hdulist = fits.open(filepath)
            assert hdulist[0].header.get('TELESCOP') == 'GREGOR'
            assert '2014-04-26T' in hdulist[0].header.get('DATE-OBS')
            assert date.start < Time(hdulist[0].header['DATE-OBS']) < date.end
            hdulist.close()
        else:
            assert filepath.endswith('.json')
            meta = json.load(open(filepath))
            assert date.start < Time(meta['metadata']['header']['DATE-BEG']) < date.end
            assert date.start < Time(meta['metadata']['header']['DATE-OBS']) < date.end
            assert meta['_id']['$oid'] == os.path.splitext(os.path.basename(filepath))[0]

128
129
130
131
132
133
134
135
136
137
138
139
140

def test_gridfs(client):
    """Test gridfs access on dockertest."""
    pytest.xfail("No GridFS on gitlab-runner")
    import gridfs
    from kis_tools.generic import get_sdc_connection

    res = client.search(a.Instrument("GRIS") & a.sdc.ObsName('gris_20140426_000'))
    links = res[0]['_embedded'][0].get('links')
    file_ids = [ld['$oid'] for ld in links['l1_data']]
    sdc = get_sdc_connection()
    gfs = gridfs.GridFS(sdc.sdc_test, "gris_l1_data")
    found = gfs.find({"_id": {"$in": file_ids}})
141
    assert(len(found)) == 105
142

143

144
def test_search(client):
145
    """Test conversion of (supported) Attrs to query string."""
146

147
    assert not client._can_handle_query(a.Time("2019/01/01", "2021/01/01"))
148
149
150
151
    with pytest.raises(AttributeError, match=r"Query not possible: "
                       r"No 'Instrument' found in Attributes"):
        client.search(a.Time("2019/01/01", "2021/01/01"))

152
    assert not client._can_handle_query(a.Instrument("UVES"), a.Time("2019/01/01", "2021/01/01"))
153
154
155
156
    with pytest.raises(AttributeError, match=r"Query not possible: "
                       r"Instrument UVES not in registered list"):
        client.search(a.Instrument("UVES") & a.Time("2019/01/01", "2021/01/01"))

157
    query = a.Instrument("BBI") & a.Time("2017/05/21", "2017/05/22 22:00")
158
    assert client._can_handle_query(query)
159
    if HAS_DOCKERTEST:
160
        res = client.search(query)
161
        assert isinstance(res, QueryResponseTable)
162
163
        assert len(res) > 50
        assert 'description' in res.colnames
164
    else:
165
        with pytest.raises(URLError, match=rf"{_dockerexc('bbi')}"
166
167
                           r"{'description.DATE_BEG':{'.lte':{'.date':'2017-05-22T22:00:00.000'}}},"
                           r"{'description.DATE_END':{'.gte':{'.date':'2017-05-21T00:00:00.000'}}}"
168
                           rf".*Confirm that RESTHeart is running on {_BASE_URL} and connected"):
169
170
            client.search(query)

171
    # Maximum numer of returned observation records defaults to 100 per query.
172
173
174
175
    query = a.Instrument("LARS") & a.sdc.HelioProjLat(-10*u.arcsec, 0.2*u.arcmin)
    assert client._can_handle_query(query)
    if HAS_DOCKERTEST:
        res = client.search(query)
176
177
        assert len(res) == 100
        assert 'description' in res.colnames
178
    else:
179
        with pytest.raises(URLError, match=rf"{_dockerexc('lars')}"
180
181
                           r"{'description.HPLT_TAN_MIN':{'.lte':12}},"
                           r"{'description.HPLT_TAN_MAX':{'.gte':-10}}"):
182
183
            client.search(query)

184
185
186
187
    query = a.Instrument("LARS"), a.sdc.HelioProjLat(-10*u.arcsec, 0.2*u.arcmin)
    assert client._can_handle_query(*query)
    if HAS_DOCKERTEST:
        res = client.search(*query)
188
189
        assert len(res) == 100
        assert 'description' in res.colnames
190
191
192
193
194
195
196
    else:
        with pytest.raises(URLError, match=rf"{_dockerexc('lars')}"
                           r"{'description.HPLT_TAN_MIN':{'.lte':12}},"
                           r"{'description.HPLT_TAN_MAX':{'.gte':-10}}"):
            client.search(*query)

    query = a.Instrument("GRIS") & (a.sdc.Theta(85*u.deg, 3000*u.arcmin) | a.sdc.PolStates('iquv'))
197
198
199
    assert client._can_handle_query(query)
    if HAS_DOCKERTEST:
        res = client.search(query)
200
201
202
203
204
205
        assert len(res) == 200
        assert 'description' in res.colnames
        assert 'THETA' in res[0]['description']
        theta = [obs['description']['THETA'] for obs in res]
        assert (min(theta[:100]) >= 50) & (max(theta[:100]) <= 85)
        assert res[100]['description']['POL_STATES'] == 'IQUV'
206
    else:
207
208
        # Will raise on first of multi-part OR queries; somehow switches INSTRUMENT and THETA.
        with pytest.raises(URLError, match=rf"{_dockerexc('gris')[:80]}") as exc:
209
            client.search(query)
210
        assert "{'description.THETA':{'$gte':50,'$lte':85}}" in str(exc.value)
211
212
        assert "{'description.POL_STATES':" not in str(exc.value)

213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
    query = a.Instrument("LARS") | a.Instrument("GRIS"), a.sdc.Theta(85*u.deg, 3000*u.arcmin)
    if HAS_DOCKERTEST:
        res = client.search(*query)
        assert len(res) == 200
        assert 'description' in res.colnames
        assert 'THETA' in res[0]['description']
        theta = [obs['description']['THETA'] for obs in res]
        assert (min(theta) >= 50) & (max(theta) <= 85)
    else:
        # Will raise on first of multi-part OR queries; somehow switches INSTRUMENT and THETA.
        with pytest.raises(URLError, match=rf"{_dockerexc('LARS')[:80]}") as exc:
            client.search(*query)
        assert "{'description.THETA':{'$gte':50,'$lte':85}}" in str(exc.value)
        assert "{'description.INSTRUMENT':'gris'" not in str(exc.value)

228

229
230
def test_fido_search():
    """Test search using the Fido base class with AttrAnd, AttrOr and lists of *args."""
231
    two_inst = (a.Instrument("LARS") | a.Instrument("GRIS"))
232
233
    if HAS_DOCKERTEST:
        res = Fido.search(a.Instrument("GRIS") & a.sdc.Theta(50*u.deg, 80*u.deg))
234
235
        assert len(res['kis']) == 100
        theta = [obs['description']['THETA'] for obs in res['kis']]
236
237
238
        assert (min(theta) >= 50) & (max(theta) <= 80)

        res = Fido.search(a.Instrument("GRIS"), a.sdc.Theta(50*u.deg, 80*u.deg))
239
240
        assert len(res['kis']) == 100
        theta = [obs['description']['THETA'] for obs in res['kis']]
241
242
        assert (min(theta) >= 50) & (max(theta) <= 80)

243
244
245
246
247
248
249
250
        date = a.Time("2017/05/12 01:40", "2017/05/16 19:00")
        res = Fido.search(a.Instrument("LARS") & date)
        assert len(res['kis']) > 0
        date_beg = [obs['description']['DATE_BEG']['$date'] for obs in res['kis']]
        date_end = [obs['description']['DATE_END']['$date'] for obs in res['kis']]
        assert max(date_beg) < date.end.unix * 1000
        assert min(date_end) > date.start.unix * 1000

251
        res = Fido.search(two_inst, a.sdc.Theta(50*u.deg, 80*u.deg))
252
253
254
255
256
257
258
        assert len(res['kis']) == 2
        assert len(res['kis'][0]) == 100
        assert res['kis'][0][0]['description']['INSTRUMENT'] == 'lars'
        theta = [obs['description']['THETA'] for obs in res['kis'][0]]
        assert (min(theta) >= 50) & (max(theta) <= 80)
        assert res['kis'][1, 0]['description']['INSTRUMENT'] == 'gris'
        theta = [obs['description']['THETA'] for obs in res['kis'][1]]
259
        assert (min(theta) >= 50) & (max(theta) <= 80)
260
261
262
263
264

        date = a.Time("2016/08/26 16:25", "2016/08/26 16:45")
        res = Fido.search(a.Instrument("GRIS"), a.sdc.PolStates('iquv'), date)
        assert len(res['kis']) == 1
        assert res['kis'][0]['description']['POL_STATES'] == 'IQUV'
265
266
267
268
269
270
271
272
273
    else:
        with pytest.raises(URLError, match=rf"{_dockerexc('gris')}"
                           r"{'description.THETA':{'.gte':50,'.lte':80}}"):
            Fido.search(a.Instrument("GRIS") & a.sdc.Theta(50*u.deg, 80*u.deg))

        with pytest.raises(URLError, match=rf"{_dockerexc('gris')}"
                           r"{'description.THETA':{'.gte':50,'.lte':80}}"):
            Fido.search(a.Instrument("GRIS"), a.sdc.Theta(50*u.deg, 80*u.deg))

274
        with pytest.raises(URLError, match=rf"{_dockerexc('LARS')}") as exc:
275
276
            Fido.search(two_inst, a.sdc.Theta(50*u.deg, 80*u.deg))
        assert "{'description.THETA':{'$gte':50,'$lte':80}}" in str(exc.value)
277
        assert "{'description.INSTRUMENT':'gris'" not in str(exc.value)
278
279


280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
def test_fido_fetch():
    """Test search and fetch using the Fido interface."""
    if not HAS_DOCKERTEST:
        pytest.xfail("No dockertest running")

    date = a.Time("2017/05/22 08:45", "2017/05/22 08:55")
    res = Fido.search(a.Instrument("BBI"), date)
    assert len(res['kis']) == 8

    files = Fido.fetch(res['kis'])
    assert len(files) == 45
    for filepath in files:
        meta = json.load(open(filepath))
        assert meta['_id']['$oid'] == os.path.splitext(os.path.basename(filepath))[0]
        assert date.start.isot[:12] in meta['metadata']['header']['DATE-BEG']
        assert date.start < Time(meta['metadata']['header']['DATE-BEG']) < date.end

    files = Fido.fetch(res['kis'][:1], binary=True)
    assert os.path.dirname(files[0]).split(os.path.sep)[-1] == res['kis'][0]['_id']['$oid']
    assert len(files) == 10
    for filepath in files:
        hdulist = fits.open(filepath)
        assert hdulist[0].header.get('TELESCOP') == 'GREGOR'
        assert hdulist[0].header.get('INSTRUME') == 'BBI'
        assert date.start.iso[:12] in hdulist[0].header['DATE-OBS']
        assert date.start < Time(hdulist[0].header['DATE-OBS']) < date.end


def test_fido_fetch_2():
    """
    Test search and fetch from 2 instruments in time interval using the Fido interface.
    Assert observations are within some exposure times (10 min) of range.
    """

    date = a.Time("2016/08/26 16:25", "2016/08/26 16:26")
    if not HAS_DOCKERTEST:
        with pytest.raises(URLError, match=rf"{_dockerexc('gris')}"
                           rf"{{'description.DATE_BEG':{{'.lte':{{'.date':'{date.end.isot}'}}}}}},"
                           rf"{{'description.DATE_END':{{'.gte':{{'.date':'{date.start.isot}'}}"):
            res = Fido.search((a.Instrument("GRIS") | a.Instrument("LARS")) & date)
    else:
        res = Fido.search((a.Instrument("GRIS") | a.Instrument("LARS")) & date)
        assert len(res['kis']) == 2
        assert len(res['kis'][0]) == 1

        assert res['kis'][0][0]['description']['DATE_BEG']['$date'] < date.end.unix * 1000
        assert res['kis'][0][0]['description']['DATE_END']['$date'] > date.start.unix * 1000

        files = Fido.fetch(res['kis'], binary=False)
        assert len(files) == 400
        assert files[0].endswith('.json')
        for filepath in files:
            meta = json.load(open(filepath))
            assert meta['_id']['$oid'] == os.path.splitext(os.path.basename(filepath))[0]
            assert date.start.isot[:12] in meta['metadata']['header']['DATE-OBS']
            assert date.start < Time(meta['metadata']['header']['DATE-OBS'])

    date = a.Time("2016/05/13 10:55", "2016/05/13 11:00")
    if not HAS_DOCKERTEST:
        with pytest.raises(URLError, match=rf"{_dockerexc('bbi')}"
                           rf"{{'description.DATE_BEG':{{'.lte':{{'.date':'{date.end.isot}'}}}}}},"
                           rf"{{'description.DATE_END':{{'.gte':{{'.date':'{date.start.isot}'}}"):
            res = Fido.search((a.Instrument("GRIS") | a.Instrument("LARS")) & date)
        return

    res = Fido.search((a.Instrument("GRIS") | a.Instrument("LARS")) & date)
    assert len(res['kis']) == 2
    assert len(res['kis'][1]) == 1

    date_beg = [obs['description']['DATE_BEG']['$date'] for obs in res['kis'][0]]
    date_end = [obs['description']['DATE_END']['$date'] for obs in res['kis'][0]]
    assert max(date_beg) < date.end.unix * 1000
    assert min(date_end) > date.start.unix * 1000
    date_beg = [obs['description']['DATE_BEG']['$date'] for obs in res['kis'][1]]
    date_end = [obs['description']['DATE_END']['$date'] for obs in res['kis'][1]]
    assert max(date_beg) < date.end.unix * 1000
    assert min(date_end) > date.start.unix * 1000

    files = Fido.fetch(res['kis'], binary=False)
    assert len(files.errors) == 0
    assert len(files) >= 1
    assert files[0].endswith('.json')
    for filepath in files:
        meta = json.load(open(filepath))
        assert meta['_id']['$oid'] == os.path.splitext(os.path.basename(filepath))[0]
        assert date.start.iso[:10] in meta['metadata']['header']['DATE-OBS']
        assert Time(meta['metadata']['header']['DATE-OBS']).mjd < date.end.mjd + 600

    files = Fido.fetch(res['kis'], binary=True)
    assert files[0].endswith('.fits')
    for filepath in files:
        hdulist = fits.open(filepath)
        assert hdulist[0].header['TELESCOP'] in ('GREGOR', 'VTT')
        assert hdulist[0].header['INSTRUME'] in ('GRIS', 'LARS')
        assert date.start.iso[:10] in hdulist[0].header['DATE-OBS']
        assert Time(hdulist[0].header['DATE-OBS']).mjd < date.end.mjd + 600
        assert Time(hdulist[0].header['DATE-OBS']).mjd > date.start.mjd - 600
        hdulist.close()

    assert len(files) == 301


382
383
384
385
386
387
388
389
390
391
392
393
394
@pytest.mark.parametrize("query", ((a.Instrument("GRIS") & a.Level(3)),
                                   (a.Instrument("ChroTel") & a.Physobs("perspective.vortex")),
                                   (a.Level(0) & a.Instrument("Bob")),
                                   (a.Instrument("LARS") & a.sdc.Telescope("Leviathan"))))
def test_cant_handle_query(client, query):
    """Some examples of invalid queries with exceptions."""
    assert not client._can_handle_query(*query.attrs)
    with pytest.raises(AttributeError, match=r"Query not possible: "
                       rf"[ILPT][a-z]* {query.attrs[1].value} not in [rs]"):
        client.search(query)


@pytest.mark.parametrize("query", (a.Level(1), a.Wavelength(3200*u.AA, 1.6*u.micron),
395
                                   a.sdc.DataProduct('cube'), a.sdc.ObsName('gris_20140426_000'),
396
                                   a.sdc.Date('2021/01/31'), a.sdc.Filter('LOT%233802'),
397
398
                                   a.sdc.PolStates('IQUV'), a.sdc.Telescope('VTT'),
                                   a.sdc.Target('Sunspot_22'), a.sdc.AtmosR0(*([1, 20000]*u.mm)),
399
400
401
402
                                   a.sdc.Theta(20*u.arcmin, 89*u.deg), a.sdc.Mu(0.1, 1),
                                   a.sdc.ExposureTime(*([5, 60]*u.min)),
                                   a.sdc.HelioProjLon(5*u.arcsec), a.sdc.HelioProjLat(9*u.arcsec),
                                   a.sdc.SpatialResolution(0.1*u.arcsec, 0.8*u.arcsec),
403
                                   a.sdc.SpectralResolution(6000, 200000),
404
                                   a.sdc.TemporalResolution(2*u.s, 30*u.s),
405
                                   a.sdc.NDimensions(1, 2), a.sdc.PolXel(2, 4),
406
407
408
                                   a.sdc.SpatialXel1(200, 3000), a.sdc.SpatialXel2(100, 4000),
                                   a.sdc.SpectralXel(320, 4096), a.sdc.TimeXel(60, 86400)))
def test_all_queries(client, query):
409
    """Test an example of all supported query attributes with automatic field names."""
410
411
412
    assert client._can_handle_query(a.Instrument("GRIS"), query)
    if HAS_DOCKERTEST:
        res = client.search(a.Instrument("GRIS") & query)
413
414
        if len(res) > 0:
            assert 'description' in res.colnames
415
    else:
416
        with pytest.raises(URLError, match=rf"{_dockerexc('gris')}"
417
418
                           rf"{{'description.*{query.type_name.upper()}"):
            client.search(a.Instrument("GRIS") & query)
419
420


421
422
423
424
425
426
427
428
429
def test_range(client):
    """
    Test range filter - 'FIELD_MIN,_MAX' shall include at least one of `Attr.min`, `Attr.max`.
    """
    wl = a.Wavelength(10800*u.AA, 1.25*u.micron)
    query = a.Instrument("GRIS") & wl
    assert client._can_handle_query(query)
    if HAS_DOCKERTEST:
        res = client.search(query)
430
431
        wave_min = [obs['WAVELENGTH_MIN'] for obs in res['description']]
        wave_max = [obs['WAVELENGTH_MAX'] for obs in res['description']]
432
433
434
        assert max(wave_min) <= 1250
        assert min(wave_max) >= 1080
    else:
435
        with pytest.raises(URLError, match=rf"{_dockerexc('gris')}"
436
437
438
439
440
                           r"{'description.WAVELENGTH_MIN':{'.lte':1250}},"
                           r"{'description.WAVELENGTH_MAX':{'.gte':1080}}"):
            client.search(query)


441
442
443
444
445
def test_full_range(client):
    """
    Test 'fullrange' option - 'FIELD_MIN,_MAX' shall completely include `[Attr.min, Attr.max]`.
    """

446
    t = a.Time("2014/04/26 09:50", "2014/04/26 09:52")
447
    t.fullrange = True
448
    query = a.Instrument("GRIS") & t
449
450
451
    assert client._can_handle_query(query)
    if HAS_DOCKERTEST:
        res = client.search(query)
452
453
        assert len(res) == 1
        assert 'description' in res.colnames
454
    else:
455
456
457
        with pytest.raises(URLError, match=rf"{_dockerexc('GRIS')}"
                           r"{'description.DATE_BEG':{'.lte':{'.date':'2014-04-26T09:50:00.000'}}},"
                           r"{'description.DATE_END':{'.gte':{'.date':'2014-04-26T09:52:00.000'}"):
458
459
460
            client.search(query)

    # Test with inverted `min`, `max` inputs.
461
462
463
464
    hplt = a.sdc.HelioProjLat(0.1*u.arcmin, -2*u.arcsec)
    query = a.Instrument("LARS") & hplt
    if HAS_DOCKERTEST:
        res = client.search(query)
465
466
        hplt_tan_min = [obs['HPLT_TAN_MIN'] for obs in res['description']]
        hplt_tan_max = [obs['HPLT_TAN_MAX'] for obs in res['description']]
467
468
469
        assert max(hplt_tan_min) <= 6
        assert min(hplt_tan_max) >= -2
    else:
470
        with pytest.raises(URLError, match=rf"{_dockerexc('LARS')}"
471
472
473
474
                           r"{'description.HPLT_TAN_MIN':{'.lte':6}},"
                           r"{'description.HPLT_TAN_MAX':{'.gte':-2}}"):
            client.search(query)

475
476
477
478
479
    hplt.fullrange = True
    query = a.Instrument("LARS") & hplt
    assert client._can_handle_query(query)
    if HAS_DOCKERTEST:
        res = client.search(query)
480
481
        hplt_tan_min = [obs['HPLT_TAN_MIN'] for obs in res['description']]
        hplt_tan_max = [obs['HPLT_TAN_MAX'] for obs in res['description']]
482
483
        assert max(hplt_tan_min) <= -2
        assert min(hplt_tan_max) >= 6
484
    else:
485
        with pytest.raises(URLError, match=rf"{_dockerexc('LARS')}"
486
487
                           r"{'description.HPLT_TAN_MIN':{'.lte':-2}},"
                           r"{'description.HPLT_TAN_MAX':{'.gte':6}}"):
488
            client.search(query)