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

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

37
38
# TODO there is much more to test

39

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


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


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

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

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

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

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

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

97
    @pytest.mark.parametrize('publish_time,dry,lifted', [
98
99
100
        (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)])
101
    def test_lift_embargo(self, published, publish_time, dry, lifted):
102
        upload_id = published.upload_id
103
        published.publish_time = publish_time
104
105
106
107
        published.save()
        calc = Calc.objects(upload_id=upload_id).first()

        assert published.upload_files.exists()
108
        assert published.with_embargo
109
110
111
112
        assert search.SearchRequest().owner('public').search_parameter('upload_id', upload_id).execute()['total'] == 0

        result = click.testing.CliRunner().invoke(
            cli, ['admin', 'lift-embargo'] + (['--dry'] if dry else []),
113
            catch_exceptions=False)
114
115

        assert result.exit_code == 0
David Sikter's avatar
David Sikter committed
116
        published.block_until_complete()
117
        assert not published.with_embargo == lifted
118
119
        assert (search.SearchRequest().owner('public').search_parameter('upload_id', upload_id).execute()['total'] > 0) == lifted
        if lifted:
120
121
            with files.UploadFiles.get(upload_id=upload_id).read_archive(calc_id=calc.calc_id) as archive:
                assert calc.calc_id in archive
122

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

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

        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

135

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


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

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

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

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

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

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

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

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

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

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

187
    def test_rm(self, published):
188
189
190
        upload_id = published.upload_id

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

        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

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

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

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

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

    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
227

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

        assert result.exit_code == 0
238
        assert 'processing' in result.stdout
239
240
        calc.reload()
        assert calc.metadata['nomad_version'] == 'test_version'
241

242
243
244
    def test_re_pack(self, published, monkeypatch):
        upload_id = published.upload_id
        calc = Calc.objects(upload_id=upload_id).first()
245
        assert published.with_embargo
246
247
        published.embargo_length = 0
        published.save()
248
249

        result = click.testing.CliRunner().invoke(
250
            cli, ['admin', 'uploads', 're-pack', '--parallel', '2', upload_id], catch_exceptions=False)
251
252
253
254
255

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

263
        published.reload()
264
        assert published.process_status == ProcessStatus.SUCCESS
265

266
    def test_chown(self, published: Upload, test_user, other_test_user):
267
        upload_id = published.upload_id
268
269
270
        with published.entries_metadata() as entries_metadata:
            for entry_metadata in entries_metadata:
                assert entry_metadata.uploader.user_id == test_user.user_id
271
272

        result = click.testing.CliRunner().invoke(
273
            cli, ['admin', 'uploads', 'chown', other_test_user.username, upload_id], catch_exceptions=False)
274
275
276
277

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

278
        published.block_until_complete()
279

280
281
282
283
        assert published.user_id == other_test_user.user_id
        with published.entries_metadata() as entries_metadata:
            for entry_metadata in entries_metadata:
                assert entry_metadata.uploader.user_id == other_test_user.user_id
284

285
286
287
288
289
290
    @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):
291
292
        upload_id = non_empty_processed.upload_id

293
294
        upload = Upload.objects(upload_id=upload_id).first()
        calc = Calc.objects(upload_id=upload_id).first()
295
296
        assert upload.process_status == ProcessStatus.SUCCESS
        assert calc.process_status == ProcessStatus.SUCCESS
297
298
299
300
301
302
303

        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)
304
305
306
307
308
309

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

310
311
312
313
        expected_state = ProcessStatus.READY
        if success: expected_state = ProcessStatus.SUCCESS
        if failure: expected_state = ProcessStatus.FAILURE
        assert upload.process_status == expected_state
314
        if not with_calcs:
315
            assert calc.process_status == ProcessStatus.SUCCESS
316
        else:
317
            assert calc.process_status == expected_state
318

319

320
@pytest.mark.usefixtures('reset_config')
321
322
class TestClient:

Markus Scheidgen's avatar
Markus Scheidgen committed
323
324
325
326
    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],
327
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
328
329
330
331
332
333
334
335

        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
336
            rv = client.get(url[url.index('/api/raw'):], headers=headers)
Markus Scheidgen's avatar
Markus Scheidgen committed
337
            assert rv.status_code == 200
338
            return POPO(iter_content=lambda *args, **kwargs: [bytes(rv.data)])
Markus Scheidgen's avatar
Markus Scheidgen committed
339
340
341
342
343

        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)],
344
            catch_exceptions=False)
Markus Scheidgen's avatar
Markus Scheidgen committed
345
346
347

        assert result.exit_code == 0

348
349
350
    def test_mirror_dry(self, published, admin_user_bravado_client, monkeypatch):
        monkeypatch.setattr('nomad.cli.client.mirror.__in_test', True)

351
        result = click.testing.CliRunner().invoke(
352
            cli, ['client', 'mirror', '--dry'], catch_exceptions=False)
353
354
355
356
357

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

358
359
    @pytest.mark.parametrize('move, link', [(True, False), (False, True), (False, False)])
    def test_mirror(self, published, admin_user_bravado_client, monkeypatch, move, link):
360
        ref_search_results = utils.flat(
361
362
            search.SearchRequest().search_parameters(
                upload_id=published.upload_id).execute_paginated()['results'][0])
363
364
365

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

366
367
        if move:
            result = click.testing.CliRunner().invoke(
368
                cli, ['client', 'mirror', '--move'], catch_exceptions=False)
369
370
        elif link:
            result = click.testing.CliRunner().invoke(
371
                cli, ['client', 'mirror', '--link'], catch_exceptions=False)
372
373
        else:
            result = click.testing.CliRunner().invoke(
374
                cli, ['client', 'mirror', '--source-mapping', '.volumes/test_fs:.volumes/test_fs'], catch_exceptions=False)
375
376

        assert result.exit_code == 0
377
378
        assert published.upload_id in result.output
        assert published.upload_files.os_path in result.output
379
380
        assert proc.Upload.objects(upload_id=published.upload_id).count() == 1
        assert proc.Calc.objects(upload_id=published.upload_id).count() == 1
381
        new_search = search.SearchRequest().search_parameters(upload_id=published.upload_id).execute_paginated()
382
383
384
        calcs_in_search = new_search['pagination']['total']
        assert calcs_in_search == 1

385
        new_search_results = utils.flat(new_search['results'][0])
386
        for key in new_search_results.keys():
387
            if key not in ['upload_time', 'last_processing', 'dft.labels', 'owners', 'authors', 'uploader', 'coauthors', 'shared_with']:
Markus Scheidgen's avatar
Markus Scheidgen committed
388
                # There is a sub second change due to date conversions (?).
389
                assert json.dumps(new_search_results[key]) == json.dumps(ref_search_results[key]), key
390
391

        published.upload_files.exists
392
393
394
        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):
395
        ref_search_results = utils.flat(
396
397
            search.SearchRequest().search_parameters(
                upload_id=non_empty_processed.upload_id).execute_paginated()['results'][0])
398
399
400
401

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

        result = click.testing.CliRunner().invoke(
402
            cli, ['client', 'mirror', '--staging', '--link'], catch_exceptions=False)
403
404
405
406
407
408
409
410
411
412

        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

413
        new_search_results = utils.flat(new_search['results'][0])
414
415
416
417
418
419
        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
420
421
422
423
424

    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(
425
            cli, ['client', 'mirror', '--files-only'], catch_exceptions=False)
426
427
428
429
430
431

        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
432

433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
    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(
454
            cli, ['client', 'mirror'], catch_exceptions=False)
455
456
457
458
459
460
461

        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

462
463
464
    def test_statistics(self, client, proc_infra, admin_user_bravado_client):

        result = click.testing.CliRunner().invoke(
465
            cli, ['client', 'statistics-table'], catch_exceptions=True)
466
467
468

        assert result.exit_code == 0, result.output
        assert 'Calculations, e.g. total energies' in result.output
469
        assert 'Geometries' in result.output
470
471
472
473
474
        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