test_cli.py 21 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
27
from nomad import utils, processing as proc, files
from nomad.search import v0 as search
28
import nomad.search.v1
29
from nomad.cli import cli
30
from nomad.cli.cli import POPO
31
from nomad.processing import Upload, Calc, ProcessStatus
32

Markus Scheidgen's avatar
Markus Scheidgen committed
33
34
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
35
    test_user_bravado_client, client, session_client, admin_user_bravado_client)  # pylint: disable=unused-import
Markus Scheidgen's avatar
Markus Scheidgen committed
36
from tests.app.conftest import test_user_auth, admin_user_auth  # pylint: disable=unused-import
37

38
39
# TODO there is much more to test

40

41
42
43
44
45
46
47
48
49
50
51
@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


52
@pytest.mark.usefixtures('reset_config', 'nomad_logging')
Markus Scheidgen's avatar
Markus Scheidgen committed
53
54
55
56
57
58
59
60
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


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

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

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

78
79
80
    #     Upload.objects(upload_id=upload_id).delete()
    #     assert published.upload_files.exists()
    #     assert Calc.objects(upload_id=upload_id).first() is not None
81
    #     search.refresh()
82
    #     assert search.SearchRequest().search_parameter('upload_id', upload_id).execute()['total'] > 0
83
84
85
    #     # 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
86

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

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

98
99
100
101
102
103
104
105
106
107
108
109
110
    @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
111
        with pytest.raises(Exception):
112
113
            with files.UploadFiles.get(upload_id=upload_id).read_archive(calc_id=calc.calc_id):
                pass
114
115
116

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

        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:
123
124
            with files.UploadFiles.get(upload_id=upload_id).read_archive(calc_id=calc.calc_id) as archive:
                assert calc.calc_id in archive
125

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

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

        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

138

139
140
141
142
143
def transform_for_index_test(calc):
    calc.comment = 'specific'
    return calc


144
@pytest.mark.usefixtures('reset_config', 'no_warn')
145
146
class TestAdminUploads:

147
148
149
150
151
152
153
154
155
156
    @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(
157
            cli, ['admin', 'uploads'] + codes_args + ['ls'], catch_exceptions=False)
158
159
160
161

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

Markus Scheidgen's avatar
Markus Scheidgen committed
162
163
164
165
166
167
    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)],
168
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
169
170
171
172

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

173
    def test_ls(self, published):
174
175
176
        upload_id = published.upload_id

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

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

182
183
184
185
    def test_ls_query(self, published):
        upload_id = published.upload_id

        result = click.testing.CliRunner().invoke(
186
            cli, ['admin', 'uploads', 'ls', '{"match":{"upload_id":"%s"}}' % upload_id], catch_exceptions=False)
187
188
189
        assert result.exit_code == 0
        assert '1 uploads selected' in result.stdout

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

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

        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

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

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

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

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

    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
230

231
    def test_re_process(self, published, monkeypatch):
232
        monkeypatch.setattr('nomad.config.meta.version', 'test_version')
233
234
235
236
237
        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(
238
            cli, ['admin', 'uploads', 're-process', '--parallel', '2', upload_id], catch_exceptions=False)
239
240

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

245
246
247
248
249
250
251
252
    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(
253
            cli, ['admin', 'uploads', 're-pack', '--parallel', '2', upload_id], catch_exceptions=False)
254
255
256
257
258

        assert result.exit_code == 0
        assert 're-pack' in result.stdout
        calc.reload()
        upload_files = files.PublicUploadFiles(upload_id)
259
260
        for path_info in upload_files.raw_directory_list(recursive=True, files_only=True):
            with upload_files.raw_file(path_info.path) as f:
261
262
                f.read()
        for calc in Calc.objects(upload_id=upload_id):
263
264
            with upload_files.read_archive(calc.calc_id) as archive:
                assert calc.calc_id in archive
265

266
        published.reload()
267
        assert published.process_status == ProcessStatus.SUCCESS
268

269
270
271
    def test_chown(self, published, test_user, other_test_user):
        upload_id = published.upload_id
        calc = Calc.objects(upload_id=upload_id).first()
272
        assert calc.metadata['uploader'] == test_user.user_id
273
274

        result = click.testing.CliRunner().invoke(
275
            cli, ['admin', 'uploads', 'chown', other_test_user.username, upload_id], catch_exceptions=False)
276
277
278
279
280
281
282

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

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

283
284
        assert upload.user_id == other_test_user.user_id
        assert calc.metadata['uploader'] == other_test_user.user_id
285

286
287
288
289
290
291
292
293
294
    def test_edit(self, published):
        upload_id = published.upload_id

        def assert_calcs(publish, with_embargo):
            calcs = Calc.objects(upload_id=upload_id)
            for calc in calcs:
                assert calc.metadata['published'] == publish
                assert calc.metadata['with_embargo'] == with_embargo

295
            for calc in nomad.search.v1.search(owner=None, query=dict(upload_id=upload_id)).data:
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
                assert calc['published'] == publish
                assert calc['with_embargo'] == with_embargo

        assert_calcs(True, True)

        def perform_test(publish, with_embargo):
            if publish:
                params = ['--publish', 'with-embargo' if with_embargo else 'no-embargo']
            else:
                assert not with_embargo
                params = ['--unpublish']

            result = click.testing.CliRunner().invoke(
                cli, ['admin', 'uploads', 'edit'] + params, catch_exceptions=False)

            assert result.exit_code == 0
            assert 'editing' in result.stdout
            assert_calcs(publish, with_embargo)

        perform_test(False, False)
        perform_test(True, False)
        perform_test(True, True)

319
320
321
322
323
324
    @pytest.mark.parametrize('with_calcs,success,failure', [
        (True, False, False),
        (False, False, False),
        (True, True, False),
        (False, False, True)])
    def test_reset(self, non_empty_processed, with_calcs, success, failure):
325
326
        upload_id = non_empty_processed.upload_id

327
328
        upload = Upload.objects(upload_id=upload_id).first()
        calc = Calc.objects(upload_id=upload_id).first()
329
330
        assert upload.process_status == ProcessStatus.SUCCESS
        assert calc.process_status == ProcessStatus.SUCCESS
331
332
333
334
335
336
337

        args = ['admin', 'uploads', 'reset']
        if with_calcs: args.append('--with-calcs')
        if success: args.append('--success')
        if failure: args.append('--failure')
        args.append(upload_id)
        result = click.testing.CliRunner().invoke(cli, args, catch_exceptions=False)
338
339
340
341
342
343

        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()

344
345
346
347
        expected_state = ProcessStatus.READY
        if success: expected_state = ProcessStatus.SUCCESS
        if failure: expected_state = ProcessStatus.FAILURE
        assert upload.process_status == expected_state
348
        if not with_calcs:
349
            assert calc.process_status == ProcessStatus.SUCCESS
350
        else:
351
            assert calc.process_status == expected_state
352

353

354
@pytest.mark.usefixtures('reset_config')
355
356
class TestClient:

Markus Scheidgen's avatar
Markus Scheidgen committed
357
358
359
360
    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],
361
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
362
363
364
365
366
367
368
369

        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
370
            rv = client.get(url[url.index('/api/raw'):], headers=headers)
Markus Scheidgen's avatar
Markus Scheidgen committed
371
            assert rv.status_code == 200
372
            return POPO(iter_content=lambda *args, **kwargs: [bytes(rv.data)])
Markus Scheidgen's avatar
Markus Scheidgen committed
373
374
375
376
377

        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)],
378
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
379
380
381

        assert result.exit_code == 0

382
383
384
    def test_mirror_dry(self, published, admin_user_bravado_client, monkeypatch):
        monkeypatch.setattr('nomad.cli.client.mirror.__in_test', True)

385
        result = click.testing.CliRunner().invoke(
386
            cli, ['client', 'mirror', '--dry'], catch_exceptions=False)
387
388
389
390
391

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

392
393
    @pytest.mark.parametrize('move, link', [(True, False), (False, True), (False, False)])
    def test_mirror(self, published, admin_user_bravado_client, monkeypatch, move, link):
394
        ref_search_results = utils.flat(
395
396
            search.SearchRequest().search_parameters(
                upload_id=published.upload_id).execute_paginated()['results'][0])
397
398
399

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

400
401
        if move:
            result = click.testing.CliRunner().invoke(
402
                cli, ['client', 'mirror', '--move'], catch_exceptions=False)
403
404
        elif link:
            result = click.testing.CliRunner().invoke(
405
                cli, ['client', 'mirror', '--link'], catch_exceptions=False)
406
407
        else:
            result = click.testing.CliRunner().invoke(
408
                cli, ['client', 'mirror', '--source-mapping', '.volumes/test_fs:.volumes/test_fs'], catch_exceptions=False)
409
410

        assert result.exit_code == 0
411
412
        assert published.upload_id in result.output
        assert published.upload_files.os_path in result.output
413
414
        assert proc.Upload.objects(upload_id=published.upload_id).count() == 1
        assert proc.Calc.objects(upload_id=published.upload_id).count() == 1
415
        new_search = search.SearchRequest().search_parameters(upload_id=published.upload_id).execute_paginated()
416
417
418
        calcs_in_search = new_search['pagination']['total']
        assert calcs_in_search == 1

419
        new_search_results = utils.flat(new_search['results'][0])
420
        for key in new_search_results.keys():
421
            if key not in ['upload_time', 'last_processing', 'dft.labels', 'owners', 'authors', 'uploader', 'coauthors', 'shared_with']:
Markus Scheidgen's avatar
Markus Scheidgen committed
422
                # There is a sub second change due to date conversions (?).
423
                assert json.dumps(new_search_results[key]) == json.dumps(ref_search_results[key]), key
424
425

        published.upload_files.exists
426
427
428
        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):
429
        ref_search_results = utils.flat(
430
431
            search.SearchRequest().search_parameters(
                upload_id=non_empty_processed.upload_id).execute_paginated()['results'][0])
432
433
434
435

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

        result = click.testing.CliRunner().invoke(
436
            cli, ['client', 'mirror', '--staging', '--link'], catch_exceptions=False)
437
438
439
440
441
442
443
444
445
446

        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

447
        new_search_results = utils.flat(new_search['results'][0])
448
449
450
451
452
453
        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
454
455
456
457
458

    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(
459
            cli, ['client', 'mirror', '--files-only'], catch_exceptions=False)
460
461
462
463
464
465

        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
466

467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
    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(
488
            cli, ['client', 'mirror'], catch_exceptions=False)
489
490
491
492
493
494
495

        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

496
497
498
    def test_statistics(self, client, proc_infra, admin_user_bravado_client):

        result = click.testing.CliRunner().invoke(
499
            cli, ['client', 'statistics-table'], catch_exceptions=True)
500
501
502

        assert result.exit_code == 0, result.output
        assert 'Calculations, e.g. total energies' in result.output
503
        assert 'Geometries' in result.output
504
505
506
507
508
        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