test_client.py 22.9 KB
Newer Older
1
2
import pytest

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

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

18
from sdc.client import KISClient
19
20
21


_BASE_URL = "http://dockertest:8083/sdc/"
22
23
24
25
26
27
_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'}}}]}")
28
29

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


36
37
38
39
def dirnames(path):
    return os.path.dirname(path).split(os.path.sep)


40
41
@pytest.fixture
def client():
42
    return KISClient()
43
44


45
def _dockerexc(instr):
46
47
    return (rf"Unable to execute search .http://dockertest:8083/sdc/{instr.lower()}_observations."
            rf"filter={{'.and':.{{'description.INSTRUMENT':'{instr.lower()}'}},")
48
49


50
51
52
53
54
55
56
57
58
59
60
61
62
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]
63
    assert len(data['_embedded']) > 4
64
65
66
67
68
69

    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'))
70
    assert isinstance(res, QueryResponseTable)
71
    assert len(res) == 105
72
73
    description = res[0].get('description')
    assert len(description) == 34
74
75
76
    assert description['INSTRUMENT'] == 'gris'
    assert description['TELESCOPE'] == 'GREGOR'
    assert description['BTYPE'] == 'phot.count'
77
78
    assert description['DATE_BEG']['$date'] == 1398505619000
    assert description['DATE_END']['$date'] == 1398506021300
79

80
    file_ids = [ld['links']['l1_data']['$oid'] for ld in res]
81
82
83
84
85
86
87
88
    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()
89

90
91
92
93
94
95
    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'
96
97
    for i, ld in enumerate(res[:10]):
        oid = ld['links']['l1_data']['$oid']
98
99
100
101
102
103
104
        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'
105
106
    for ld in res[:2]:
        oid = ld['links']['l1_data']['$oid']
107
108
109
110
111
112
        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)
113
    assert res[0]['links']['l1_data']['$oid'] in downloader.http_queue[0].keywords['url']
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
    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]

134
135
136
137
138
139
140
141
142
143
144
145
146

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}})
147
    assert(len(found)) == 105
148

149

150
def test_search(client):
151
    """Test conversion of (supported) Attrs to query string."""
152

153
    assert not client._can_handle_query(a.Time("2019/01/01", "2021/01/01"))
154
155
156
157
    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"))

158
    assert not client._can_handle_query(a.Instrument("UVES"), a.Time("2019/01/01", "2021/01/01"))
159
160
161
162
    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"))

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

177
    # Maximum numer of returned observation records defaults to 100 per query.
178
179
180
181
    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)
182
        assert len(res) == 1
183
        assert 'description' in res.colnames
184
    else:
185
        with pytest.raises(URLError, match=rf"{_dockerexc('lars')}"
186
187
                           r"{'description.HPLT_TAN_MIN':{'.lte':12}},"
                           r"{'description.HPLT_TAN_MAX':{'.gte':-10}}"):
188
189
            client.search(query)

190
191
192
193
    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)
194
        assert len(res) == 1
195
        assert 'description' in res.colnames
196
197
198
199
200
201
202
    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'))
203
204
205
    assert client._can_handle_query(query)
    if HAS_DOCKERTEST:
        res = client.search(query)
206
        assert len(res) == 362
207
208
209
210
211
        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'
212
    else:
213
214
        # 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:
215
            client.search(query)
216
        assert "{'description.THETA':{'$gte':50,'$lte':85}}" in str(exc.value)
217
218
        assert "{'description.POL_STATES':" not in str(exc.value)

219
220
221
    query = a.Instrument("LARS") | a.Instrument("GRIS"), a.sdc.Theta(85*u.deg, 3000*u.arcmin)
    if HAS_DOCKERTEST:
        res = client.search(*query)
222
        assert len(res) == 163
223
224
225
226
227
228
229
230
231
232
233
        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)

234

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

        res = Fido.search(a.Instrument("GRIS"), a.sdc.Theta(50*u.deg, 80*u.deg))
245
        assert len(res['kis']) == 162
246
        theta = [obs['description']['THETA'] for obs in res['kis']]
247
248
        assert (min(theta) >= 50) & (max(theta) <= 80)

249
250
251
252
253
254
255
256
        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

257
        res = Fido.search(two_inst, a.sdc.Theta(50*u.deg, 80*u.deg))
258
        assert len(res['kis']) == 2
259
260
        assert len(res['kis'][0]) == 1
        assert len(res['kis'][1]) == 162
261
262
263
264
265
        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]]
266
        assert (min(theta) >= 50) & (max(theta) <= 80)
267
268
269

        date = a.Time("2016/08/26 16:25", "2016/08/26 16:45")
        res = Fido.search(a.Instrument("GRIS"), a.sdc.PolStates('iquv'), date)
270
        assert len(res['kis']) == 400
271
        assert res['kis'][0]['description']['POL_STATES'] == 'IQUV'
272
273
274
275
276
277
278
279
280
    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))

281
        with pytest.raises(URLError, match=rf"{_dockerexc('LARS')}") as exc:
282
283
            Fido.search(two_inst, a.sdc.Theta(50*u.deg, 80*u.deg))
        assert "{'description.THETA':{'$gte':50,'$lte':80}}" in str(exc.value)
284
        assert "{'description.INSTRUMENT':'gris'" not in str(exc.value)
285
286


287
288
289
290
291
292
293
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)
294
295
296
297
298
    assert len(res['kis']) == 10
    desc = [r['description'] for r in res['kis']]

    assert max([d['DATE_BEG']['$date'] for d in desc]) < date.end.unix * 1000
    assert min([d['DATE_END']['$date'] for d in desc]) > date.start.unix * 1000
299
300

    files = Fido.fetch(res['kis'])
301
302
303
    assert len(files) == 10
    for i, filepath in enumerate(files):
        assert dirnames(filepath)[-1] == desc[::-1][i]['OBS_NAME']
304
305
306
307
        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
308
        assert desc[i]['OBS_NAME'] in meta['metadata']['header']['FILENAME']
309

310
311
312
313
    files = Fido.fetch(res['kis'][:3], binary=True)
    assert len(files) == 3
    for i, filepath in enumerate(files):
        assert dirnames(filepath)[-1] == desc[::-1][i]['OBS_NAME']
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
        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
336
        assert len(res['kis'][0]) == 400
337
338
339
340

        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

341
342
        files = Fido.fetch(res['kis'][0, :100], binary=False)
        assert len(files) == 100
343
344
345
346
347
348
349
        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'])

350
351
352
353
354
355
356
357
358
359
360
361
        files = Fido.fetch(res['kis'][0, :5], binary=True)
        assert len(files) == 5
        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()

362
363
    date = a.Time("2016/05/13 10:55", "2016/05/13 11:00")
    if not HAS_DOCKERTEST:
364
        with pytest.raises(URLError, match=rf"{_dockerexc('gris')}"
365
366
367
368
369
370
371
                           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
372
    assert len(res['kis'][0]) == 300
373
    assert len(res['kis'][1]) == 1
374
375
376
    desc = [r['description'] for r in res['kis'][0]] + [r['description'] for r in res['kis'][1]]
    dirs = [d['OBS_NAME'] for d in desc]
    assert len(desc) == 301
377

378
379
    assert max([d['DATE_BEG']['$date'] for d in desc]) < date.end.unix * 1000
    assert min([d['DATE_END']['$date'] for d in desc]) > date.start.unix * 1000
380

381
    files = Fido.fetch(res['kis'][:, :100], binary=False)
382
    assert len(files.errors) == 0
383
    assert len(files) == 101
384
385
    assert files[0].endswith('.json')
    for filepath in files:
386
        assert dirnames(filepath)[-1] in dirs
387
388
389
390
391
        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

392
    files = Fido.fetch(res['kis'][:, :10], binary=True)
393
394
395
396
397
    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')
398
        assert hdulist[0].header['EXTNAME'] in dirnames(filepath)[-1]
399
400
401
402
403
        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()

404
    assert len(files) == 11
405
406


407
408
409
410
411
412
413
414
415
416
417
418
419
@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),
420
                                   a.sdc.DataProduct('cube'), a.sdc.ObsName('gris_20140426_000'),
421
                                   a.sdc.Date('2021/01/31'), a.sdc.Filter('LOT%233802'),
422
423
                                   a.sdc.PolStates('IQUV'), a.sdc.Telescope('VTT'),
                                   a.sdc.Target('Sunspot_22'), a.sdc.AtmosR0(*([1, 20000]*u.mm)),
424
425
426
427
                                   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),
428
                                   a.sdc.SpectralResolution(6000, 200000),
429
                                   a.sdc.TemporalResolution(2*u.s, 30*u.s),
430
                                   a.sdc.NDimensions(1, 2), a.sdc.PolXel(2, 4),
431
432
433
                                   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):
434
    """Test an example of all supported query attributes with automatic field names."""
435
436
437
    assert client._can_handle_query(a.Instrument("GRIS"), query)
    if HAS_DOCKERTEST:
        res = client.search(a.Instrument("GRIS") & query)
438
439
        if len(res) > 0:
            assert 'description' in res.colnames
440
    else:
441
        with pytest.raises(URLError, match=rf"{_dockerexc('gris')}"
442
443
                           rf"{{'description.*{query.type_name.upper()}"):
            client.search(a.Instrument("GRIS") & query)
444
445


446
447
448
449
450
451
452
453
454
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)
455
456
        wave_min = [obs['WAVELENGTH_MIN'] for obs in res['description']]
        wave_max = [obs['WAVELENGTH_MAX'] for obs in res['description']]
457
458
459
        assert max(wave_min) <= 1250
        assert min(wave_max) >= 1080
    else:
460
        with pytest.raises(URLError, match=rf"{_dockerexc('gris')}"
461
462
463
464
465
                           r"{'description.WAVELENGTH_MIN':{'.lte':1250}},"
                           r"{'description.WAVELENGTH_MAX':{'.gte':1080}}"):
            client.search(query)


466
467
468
469
470
def test_full_range(client):
    """
    Test 'fullrange' option - 'FIELD_MIN,_MAX' shall completely include `[Attr.min, Attr.max]`.
    """

471
    t = a.Time("2014/04/26 09:50", "2014/04/26 09:52")
472
    t.fullrange = True
473
    query = a.Instrument("GRIS") & t
474
475
476
    assert client._can_handle_query(query)
    if HAS_DOCKERTEST:
        res = client.search(query)
477
        assert len(res) == 105
478
        assert 'description' in res.colnames
479
    else:
480
481
482
        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'}"):
483
484
485
            client.search(query)

    # Test with inverted `min`, `max` inputs.
486
487
488
489
    hplt = a.sdc.HelioProjLat(0.1*u.arcmin, -2*u.arcsec)
    query = a.Instrument("LARS") & hplt
    if HAS_DOCKERTEST:
        res = client.search(query)
490
491
        hplt_tan_min = [obs['HPLT_TAN_MIN'] for obs in res['description']]
        hplt_tan_max = [obs['HPLT_TAN_MAX'] for obs in res['description']]
492
493
494
        assert max(hplt_tan_min) <= 6
        assert min(hplt_tan_max) >= -2
    else:
495
        with pytest.raises(URLError, match=rf"{_dockerexc('LARS')}"
496
497
498
499
                           r"{'description.HPLT_TAN_MIN':{'.lte':6}},"
                           r"{'description.HPLT_TAN_MAX':{'.gte':-2}}"):
            client.search(query)

500
501
502
503
504
    hplt.fullrange = True
    query = a.Instrument("LARS") & hplt
    assert client._can_handle_query(query)
    if HAS_DOCKERTEST:
        res = client.search(query)
505
506
        hplt_tan_min = [obs['HPLT_TAN_MIN'] for obs in res['description']]
        hplt_tan_max = [obs['HPLT_TAN_MAX'] for obs in res['description']]
507
508
        assert max(hplt_tan_min) <= -2
        assert min(hplt_tan_max) >= 6
509
    else:
510
        with pytest.raises(URLError, match=rf"{_dockerexc('LARS')}"
511
512
                           r"{'description.HPLT_TAN_MIN':{'.lte':-2}},"
                           r"{'description.HPLT_TAN_MAX':{'.gte':6}}"):
513
            client.search(query)