test_cli.py 18.8 KB
Newer Older
1

Markus Scheidgen's avatar
Markus Scheidgen committed
2
3
4
5
#
# Copyright The NOMAD Authors.
#
# This file is part of NOMAD. See https://nomad-lab.eu for further info.
6
7
8
9
10
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
Markus Scheidgen's avatar
Markus Scheidgen committed
11
#     http://www.apache.org/licenses/LICENSE-2.0
12
13
#
# Unless required by applicable law or agreed to in writing, software
Markus Scheidgen's avatar
Markus Scheidgen committed
14
# distributed under the License is distributed on an "AS IS" BASIS,
15
16
17
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Markus Scheidgen's avatar
Markus Scheidgen committed
18
#
19

20
import pytest
21
import click.testing
22
import json
23
import datetime
24
import time
25

26
from nomad import search, utils, processing as proc, files
27
from nomad.cli import cli
28
from nomad.cli.cli import POPO
29
30
from nomad.processing import Upload, Calc

Markus Scheidgen's avatar
Markus Scheidgen committed
31
32
from tests.app.flask.test_app import BlueprintClient
from tests.app.flask.conftest import (  # pylint: disable=unused-import
Markus Scheidgen's avatar
Markus Scheidgen committed
33
    test_user_bravado_client, client, session_client, admin_user_bravado_client)  # pylint: disable=unused-import
Markus Scheidgen's avatar
Markus Scheidgen committed
34
from tests.app.conftest import test_user_auth, admin_user_auth  # pylint: disable=unused-import
35

36
37
# TODO there is much more to test

38

39
40
41
42
43
44
45
46
47
48
49
@pytest.mark.usefixtures('reset_config', 'nomad_logging')
class TestCli:
    def test_help(self, example_mainfile):

        start = time.time()
        result = click.testing.CliRunner().invoke(
            cli, ['--help'], catch_exceptions=False)
        assert result.exit_code == 0
        assert time.time() - start < 1


50
@pytest.mark.usefixtures('reset_config', 'nomad_logging')
Markus Scheidgen's avatar
Markus Scheidgen committed
51
52
53
54
55
56
57
58
class TestParse:
    def test_parser(self, example_mainfile):
        _, mainfile_path = example_mainfile
        result = click.testing.CliRunner().invoke(
            cli, ['parse', mainfile_path], catch_exceptions=False)
        assert result.exit_code == 0


59
@pytest.mark.usefixtures('reset_config', 'no_warn', 'mongo_infra', 'elastic_infra', 'raw_files_infra')
60
class TestAdmin:
61
    def test_reset(self, reset_infra):
62
        result = click.testing.CliRunner().invoke(
63
            cli, ['admin', 'reset', '--i-am-really-sure'], catch_exceptions=False)
64
65
66
67
        assert result.exit_code == 0

    def test_reset_not_sure(self):
        result = click.testing.CliRunner().invoke(
68
            cli, ['admin', 'reset'], catch_exceptions=False)
69
70
        assert result.exit_code == 1

71
72
    # TODO this has somekind of raise condition in it and the test fails every other time
    # on the CI/CD
73
74
    # def test_clean(self, published):
    #     upload_id = published.upload_id
75

76
77
78
    #     Upload.objects(upload_id=upload_id).delete()
    #     assert published.upload_files.exists()
    #     assert Calc.objects(upload_id=upload_id).first() is not None
79
    #     search.refresh()
80
    #     assert search.SearchRequest().search_parameter('upload_id', upload_id).execute()['total'] > 0
81
82
83
    #     # TODO test new index pair
    #     # assert es_search(owner=None, query=dict(upload_id=upload_id)).pagination.total == 0
    #     # assert es_search(owner=None, query=dict(upload_id=upload_id)).pagination.total == 0
84

85
86
    #     result = click.testing.CliRunner().invoke(
    #         cli, ['admin', 'clean', '--force', '--skip-es'], catch_exceptions=False)
87

88
89
90
    #     assert result.exit_code == 0
    #     assert not published.upload_files.exists()
    #     assert Calc.objects(upload_id=upload_id).first() is None
91
    #     search.refresh()
92
    #     assert search.SearchRequest().search_parameter('upload_id', upload_id).execute()['total'] > 0
93
94
    #     # TODO test new index pair
    #     # assert es_search(owner=None, query=dict(upload_id=upload_id)).pagination.total == 0
95

96
97
98
99
100
101
102
103
104
105
106
107
108
    @pytest.mark.parametrize('upload_time,dry,lifted', [
        (datetime.datetime.now(), False, False),
        (datetime.datetime(year=2012, month=1, day=1), True, False),
        (datetime.datetime(year=2012, month=1, day=1), False, True)])
    def test_lift_embargo(self, published, upload_time, dry, lifted):
        upload_id = published.upload_id
        published.upload_time = upload_time
        published.save()
        calc = Calc.objects(upload_id=upload_id).first()

        assert published.upload_files.exists()
        assert calc.metadata['with_embargo']
        assert search.SearchRequest().owner('public').search_parameter('upload_id', upload_id).execute()['total'] == 0
109
        with pytest.raises(Exception):
110
111
            with files.UploadFiles.get(upload_id=upload_id).read_archive(calc_id=calc.calc_id):
                pass
112
113
114

        result = click.testing.CliRunner().invoke(
            cli, ['admin', 'lift-embargo'] + (['--dry'] if dry else []),
115
            catch_exceptions=False)
116
117
118
119
120

        assert result.exit_code == 0
        assert not Calc.objects(upload_id=upload_id).first().metadata['with_embargo'] == lifted
        assert (search.SearchRequest().owner('public').search_parameter('upload_id', upload_id).execute()['total'] > 0) == lifted
        if lifted:
121
122
            with files.UploadFiles.get(upload_id=upload_id).read_archive(calc_id=calc.calc_id) as archive:
                assert calc.calc_id in archive
123

124
125
126
127
128
    def test_delete_entry(self, published):
        upload_id = published.upload_id
        calc = Calc.objects(upload_id=upload_id).first()

        result = click.testing.CliRunner().invoke(
129
            cli, ['admin', 'entries', 'rm', calc.calc_id], catch_exceptions=False)
130
131
132
133
134
135

        assert result.exit_code == 0
        assert 'deleting' in result.stdout
        assert Upload.objects(upload_id=upload_id).first() is not None
        assert Calc.objects(calc_id=calc.calc_id).first() is None

136

137
138
139
140
141
def transform_for_index_test(calc):
    calc.comment = 'specific'
    return calc


142
@pytest.mark.usefixtures('reset_config', 'no_warn')
143
144
class TestAdminUploads:

145
146
147
148
149
150
151
152
153
154
    @pytest.mark.parametrize('codes, count', [
        (['VASP'], 1),
        (['doesNotExist'], 0),
        (['VASP', 'doesNotExist'], 1)])
    def test_uploads_code(self, published, codes, count):
        codes_args = []
        for code in codes:
            codes_args.append('--code')
            codes_args.append(code)
        result = click.testing.CliRunner().invoke(
155
            cli, ['admin', 'uploads'] + codes_args + ['ls'], catch_exceptions=False)
156
157
158
159

        assert result.exit_code == 0
        assert '%d uploads selected' % count in result.stdout

Markus Scheidgen's avatar
Markus Scheidgen committed
160
161
162
163
164
165
    def test_query_mongo(self, published):
        upload_id = published.upload_id

        query = dict(upload_id=upload_id)
        result = click.testing.CliRunner().invoke(
            cli, ['admin', 'uploads', '--query-mongo', 'ls', json.dumps(query)],
166
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
167
168
169
170

        assert result.exit_code == 0
        assert '1 uploads selected' in result.stdout

171
    def test_ls(self, published):
172
173
174
        upload_id = published.upload_id

        result = click.testing.CliRunner().invoke(
175
            cli, ['admin', 'uploads', 'ls', upload_id], catch_exceptions=False)
176
177
178
179

        assert result.exit_code == 0
        assert '1 uploads selected' in result.stdout

180
181
182
183
    def test_ls_query(self, published):
        upload_id = published.upload_id

        result = click.testing.CliRunner().invoke(
184
            cli, ['admin', 'uploads', 'ls', '{"match":{"upload_id":"%s"}}' % upload_id], catch_exceptions=False)
185
186
187
188

        assert result.exit_code == 0
        assert '1 uploads selected' in result.stdout

189
    def test_rm(self, published):
190
191
192
        upload_id = published.upload_id

        result = click.testing.CliRunner().invoke(
193
            cli, ['admin', 'uploads', 'rm', upload_id], catch_exceptions=False)
194
195
196
197
198
199

        assert result.exit_code == 0
        assert 'deleting' in result.stdout
        assert Upload.objects(upload_id=upload_id).first() is None
        assert Calc.objects(upload_id=upload_id).first() is None

200
201
202
203
204
205
    def test_index(self, published):
        upload_id = published.upload_id
        calc = Calc.objects(upload_id=upload_id).first()
        calc.metadata['comment'] = 'specific'
        calc.save()

206
        assert search.SearchRequest().search_parameters(comment='specific').execute()['total'] == 0
207
208

        result = click.testing.CliRunner().invoke(
209
            cli, ['admin', 'uploads', 'index', upload_id], catch_exceptions=False)
210
211
212
        assert result.exit_code == 0
        assert 'index' in result.stdout

213
        assert search.SearchRequest().search_parameters(comment='specific').execute()['total'] == 1
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228

    def test_index_with_transform(self, published):
        upload_id = published.upload_id
        assert search.SearchRequest().search_parameters(comment='specific').execute()['total'] == 0

        result = click.testing.CliRunner().invoke(
            cli, [
                'admin', 'uploads', 'index',
                '--transformer', 'tests.test_cli.transform_for_index_test',
                upload_id],
            catch_exceptions=False)
        assert result.exit_code == 0
        assert 'index' in result.stdout

        assert search.SearchRequest().search_parameters(comment='specific').execute()['total'] == 1
229

230
    def test_re_process(self, published, monkeypatch):
231
        monkeypatch.setattr('nomad.config.meta.version', 'test_version')
232
233
234
235
236
        upload_id = published.upload_id
        calc = Calc.objects(upload_id=upload_id).first()
        assert calc.metadata['nomad_version'] != 'test_version'

        result = click.testing.CliRunner().invoke(
237
            cli, ['admin', 'uploads', 're-process', '--parallel', '2', upload_id], catch_exceptions=False)
238
239
240
241
242

        assert result.exit_code == 0
        assert 're-processing' in result.stdout
        calc.reload()
        assert calc.metadata['nomad_version'] == 'test_version'
243

244
245
246
247
248
249
250
251
    def test_re_pack(self, published, monkeypatch):
        upload_id = published.upload_id
        calc = Calc.objects(upload_id=upload_id).first()
        assert calc.metadata['with_embargo']
        calc.metadata['with_embargo'] = False
        calc.save()

        result = click.testing.CliRunner().invoke(
252
            cli, ['admin', 'uploads', 're-pack', '--parallel', '2', upload_id], catch_exceptions=False)
253
254
255
256
257
258
259
260
261

        assert result.exit_code == 0
        assert 're-pack' in result.stdout
        calc.reload()
        upload_files = files.PublicUploadFiles(upload_id)
        for raw_file in upload_files.raw_file_manifest():
            with upload_files.raw_file(raw_file) as f:
                f.read()
        for calc in Calc.objects(upload_id=upload_id):
262
263
            with upload_files.read_archive(calc.calc_id) as archive:
                assert calc.calc_id in archive
264

265
266
267
268
269
270
    def test_chown(self, published, test_user, other_test_user):
        upload_id = published.upload_id
        calc = Calc.objects(upload_id=upload_id).first()
        assert calc.metadata['uploader'] == other_test_user.user_id

        result = click.testing.CliRunner().invoke(
271
            cli, ['admin', 'uploads', 'chown', test_user.username, upload_id], catch_exceptions=False)
272
273
274
275
276
277
278
279
280
281
282
283
284
285

        assert result.exit_code == 0
        assert 'changing' in result.stdout

        upload = Upload.objects(upload_id=upload_id).first()
        calc.reload()

        assert upload.user_id == test_user.user_id
        assert calc.metadata['uploader'] == test_user.user_id

    def test_reset(self, non_empty_processed):
        upload_id = non_empty_processed.upload_id

        result = click.testing.CliRunner().invoke(
286
            cli, ['admin', 'uploads', 'reset', '--with-calcs', upload_id], catch_exceptions=False)
287
288
289
290
291
292
293
294
295

        assert result.exit_code == 0
        assert 'reset' in result.stdout
        upload = Upload.objects(upload_id=upload_id).first()
        calc = Calc.objects(upload_id=upload_id).first()

        assert upload.tasks_status == proc.PENDING
        assert calc.tasks_status == proc.PENDING

296

297
@pytest.mark.usefixtures('reset_config')
298
299
class TestClient:

Markus Scheidgen's avatar
Markus Scheidgen committed
300
301
302
303
    def test_upload(self, test_user_bravado_client, non_empty_example_upload, proc_infra):
        result = click.testing.CliRunner().invoke(
            cli,
            ['client', 'upload', '--offline', '--name', 'test_upload', non_empty_example_upload],
304
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
305
306
307
308
309
310
311
312

        assert result.exit_code == 0
        assert '1/0/1' in result.output
        assert proc.Upload.objects(name='test_upload').first() is not None

    def test_local(self, client, published, admin_user_bravado_client, monkeypatch):
        def requests_get(url, stream, headers):
            assert stream
Markus Scheidgen's avatar
Markus Scheidgen committed
313
            rv = client.get(url[url.index('/api/raw'):], headers=headers)
Markus Scheidgen's avatar
Markus Scheidgen committed
314
            assert rv.status_code == 200
315
            return POPO(iter_content=lambda *args, **kwargs: [bytes(rv.data)])
Markus Scheidgen's avatar
Markus Scheidgen committed
316
317
318
319
320

        monkeypatch.setattr('requests.get', requests_get)
        result = click.testing.CliRunner().invoke(
            cli,
            ['client', 'local', '%s/%s' % (published.upload_id, list(published.calcs)[0].calc_id)],
321
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
322
323
324

        assert result.exit_code == 0

325
326
327
    def test_mirror_dry(self, published, admin_user_bravado_client, monkeypatch):
        monkeypatch.setattr('nomad.cli.client.mirror.__in_test', True)

328
        result = click.testing.CliRunner().invoke(
329
            cli, ['client', 'mirror', '--dry'], catch_exceptions=False)
330
331
332
333
334

        assert result.exit_code == 0
        assert published.upload_id in result.output
        assert published.upload_files.os_path in result.output

335
336
    @pytest.mark.parametrize('move, link', [(True, False), (False, True), (False, False)])
    def test_mirror(self, published, admin_user_bravado_client, monkeypatch, move, link):
337
        ref_search_results = utils.flat(
338
339
            search.SearchRequest().search_parameters(
                upload_id=published.upload_id).execute_paginated()['results'][0])
340
341
342

        monkeypatch.setattr('nomad.cli.client.mirror.__in_test', True)

343
344
        if move:
            result = click.testing.CliRunner().invoke(
345
                cli, ['client', 'mirror', '--move'], catch_exceptions=False)
346
347
        elif link:
            result = click.testing.CliRunner().invoke(
348
                cli, ['client', 'mirror', '--link'], catch_exceptions=False)
349
350
        else:
            result = click.testing.CliRunner().invoke(
351
                cli, ['client', 'mirror', '--source-mapping', '.volumes/test_fs:.volumes/test_fs'], catch_exceptions=False)
352
353

        assert result.exit_code == 0
354
355
        assert published.upload_id in result.output
        assert published.upload_files.os_path in result.output
356
357
        assert proc.Upload.objects(upload_id=published.upload_id).count() == 1
        assert proc.Calc.objects(upload_id=published.upload_id).count() == 1
358
        new_search = search.SearchRequest().search_parameters(upload_id=published.upload_id).execute_paginated()
359
360
361
        calcs_in_search = new_search['pagination']['total']
        assert calcs_in_search == 1

362
        new_search_results = utils.flat(new_search['results'][0])
363
        for key in new_search_results.keys():
364
            if key not in ['upload_time', 'last_processing', 'dft.labels.label', 'owners', 'authors', 'uploader', 'coauthors', 'shared_with']:
Markus Scheidgen's avatar
Markus Scheidgen committed
365
366
                # There is a sub second change due to date conversions (?).
                # Labels have arbitrary order.
367
368
369
                assert json.dumps(new_search_results[key]) == json.dumps(ref_search_results[key])

        published.upload_files.exists
370
371
372
        proc.Upload.objects(upload_id=published.upload_id).first().upload_files.exists

    def test_mirror_staging(self, non_empty_processed, admin_user_bravado_client, monkeypatch):
373
        ref_search_results = utils.flat(
374
375
            search.SearchRequest().search_parameters(
                upload_id=non_empty_processed.upload_id).execute_paginated()['results'][0])
376
377
378
379

        monkeypatch.setattr('nomad.cli.client.mirror.__in_test', True)

        result = click.testing.CliRunner().invoke(
380
            cli, ['client', 'mirror', '--staging', '--link'], catch_exceptions=False)
381
382
383
384
385
386
387
388
389
390

        assert result.exit_code == 0
        assert non_empty_processed.upload_id in result.output
        assert non_empty_processed.upload_files.os_path in result.output
        assert proc.Upload.objects(upload_id=non_empty_processed.upload_id).count() == 1
        assert proc.Calc.objects(upload_id=non_empty_processed.upload_id).count() == 1
        new_search = search.SearchRequest().search_parameters(upload_id=non_empty_processed.upload_id).execute_paginated()
        calcs_in_search = new_search['pagination']['total']
        assert calcs_in_search == 1

391
        new_search_results = utils.flat(new_search['results'][0])
392
393
394
395
396
397
        for key in new_search_results.keys():
            if key not in ['upload_time', 'last_processing']:  # There is a sub second change due to date conversions (?)
                assert json.dumps(new_search_results[key]) == json.dumps(ref_search_results[key])

        non_empty_processed.upload_files.exists
        proc.Upload.objects(upload_id=non_empty_processed.upload_id).first().upload_files.exists
398
399
400
401
402

    def test_mirror_files_only(self, published, admin_user_bravado_client, monkeypatch):
        monkeypatch.setattr('nomad.cli.client.mirror.__in_test', True)

        result = click.testing.CliRunner().invoke(
403
            cli, ['client', 'mirror', '--files-only'], catch_exceptions=False)
404
405
406
407
408
409

        assert result.exit_code == 0, result.output
        assert published.upload_id in result.output
        assert published.upload_files.os_path in result.output

        published.upload_files.exists
410

411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
    def test_mirror_datasets(self, client, published_wo_user_metadata, test_user_auth, admin_user_bravado_client, monkeypatch):
        # use the API to create dataset and DOI
        api = BlueprintClient(client, '/api')
        rv = api.post(
            '/repo/edit', headers=test_user_auth, content_type='application/json',
            data=json.dumps({
                'actions': {
                    'datasets': [{
                        'value': 'test_dataset'
                    }]
                }
            }))
        assert rv.status_code == 200

        rv = api.post('/datasets/test_dataset', headers=test_user_auth)
        assert rv.status_code == 200

        # perform the mirror
        monkeypatch.setattr('nomad.cli.client.mirror.__in_test', True)

        result = click.testing.CliRunner().invoke(
432
            cli, ['client', 'mirror'], catch_exceptions=False)
433
434
435
436
437
438
439

        assert result.exit_code == 0, result.output
        assert published_wo_user_metadata.upload_id in result.output
        assert published_wo_user_metadata.upload_files.os_path in result.output

        published_wo_user_metadata.upload_files.exists

440
441
442
    def test_statistics(self, client, proc_infra, admin_user_bravado_client):

        result = click.testing.CliRunner().invoke(
443
            cli, ['client', 'statistics-table'], catch_exceptions=True)
444
445
446

        assert result.exit_code == 0, result.output
        assert 'Calculations, e.g. total energies' in result.output
447
        assert 'Geometries' in result.output
448
449
450
451
452
        assert 'Bulk crystals' in result.output
        assert '2D / Surfaces' in result.output
        assert 'Atoms / Molecules' in result.output
        assert 'DOS' in result.output
        assert 'Band structures' in result.output